From 64f255de05e2edd35a8be15abe45d78fe9616aa0 Mon Sep 17 00:00:00 2001 From: Mark Davison <6168730+markdavison@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:39:59 +0100 Subject: [PATCH] feat: add SayGM provider --- package.json | 1 + packages/core/src/sync/index.ts | 5 +- packages/core/src/sync/providers/saygm.ts | 157 ++++++++++++++++++ packages/core/test/sync.test.ts | 110 ++++++++++++ providers/saygm/logo.svg | 6 + providers/saygm/models/claude-fable-5.toml | 15 ++ providers/saygm/models/claude-haiku-4-5.toml | 15 ++ providers/saygm/models/claude-opus-4-7.toml | 15 ++ providers/saygm/models/claude-opus-4-8.toml | 15 ++ providers/saygm/models/claude-opus-5.toml | 15 ++ providers/saygm/models/claude-sonnet-4-6.toml | 19 +++ providers/saygm/models/claude-sonnet-5.toml | 20 +++ providers/saygm/models/gpt-5.4-mini.toml | 10 ++ providers/saygm/models/gpt-5.4-nano.toml | 10 ++ providers/saygm/models/gpt-5.4.toml | 15 ++ providers/saygm/models/gpt-5.5-pro.toml | 16 ++ providers/saygm/models/gpt-5.5.toml | 15 ++ providers/saygm/models/gpt-5.6-luna.toml | 15 ++ providers/saygm/models/gpt-5.6-sol.toml | 15 ++ providers/saygm/models/gpt-5.6-terra.toml | 15 ++ providers/saygm/models/o3.toml | 10 ++ providers/saygm/models/o4-mini.toml | 10 ++ providers/saygm/provider.toml | 5 + 23 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/sync/providers/saygm.ts create mode 100644 providers/saygm/logo.svg create mode 100644 providers/saygm/models/claude-fable-5.toml create mode 100644 providers/saygm/models/claude-haiku-4-5.toml create mode 100644 providers/saygm/models/claude-opus-4-7.toml create mode 100644 providers/saygm/models/claude-opus-4-8.toml create mode 100644 providers/saygm/models/claude-opus-5.toml create mode 100644 providers/saygm/models/claude-sonnet-4-6.toml create mode 100644 providers/saygm/models/claude-sonnet-5.toml create mode 100644 providers/saygm/models/gpt-5.4-mini.toml create mode 100644 providers/saygm/models/gpt-5.4-nano.toml create mode 100644 providers/saygm/models/gpt-5.4.toml create mode 100644 providers/saygm/models/gpt-5.5-pro.toml create mode 100644 providers/saygm/models/gpt-5.5.toml create mode 100644 providers/saygm/models/gpt-5.6-luna.toml create mode 100644 providers/saygm/models/gpt-5.6-sol.toml create mode 100644 providers/saygm/models/gpt-5.6-terra.toml create mode 100644 providers/saygm/models/o3.toml create mode 100644 providers/saygm/models/o4-mini.toml create mode 100644 providers/saygm/provider.toml diff --git a/package.json b/package.json index cb013bfc0b2..0f497320e25 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "kilo:sync": "bun ./packages/core/script/sync-models.ts kilo", "llmgateway:sync": "bun ./packages/core/script/sync-models.ts llmgateway", "requesty:sync": "bun ./packages/core/script/sync-models.ts requesty", + "saygm:sync": "bun ./packages/core/script/sync-models.ts saygm", "merge-gateway:sync": "bun ./packages/core/script/sync-models.ts merge-gateway", "nano-gpt:sync": "bun ./packages/core/script/sync-models.ts nano-gpt", "venice:sync": "bun ./packages/core/script/sync-models.ts venice", diff --git a/packages/core/src/sync/index.ts b/packages/core/src/sync/index.ts index 34691c06c5f..bcea9831783 100644 --- a/packages/core/src/sync/index.ts +++ b/packages/core/src/sync/index.ts @@ -30,6 +30,7 @@ import { openrouter } from "./providers/openrouter.js"; import { ovhcloud } from "./providers/ovhcloud.js"; import { pioneer } from "./providers/pioneer.js"; import { requesty } from "./providers/requesty.js"; +import { saygm } from "./providers/saygm.js"; import { tinfoil } from "./providers/tinfoil.js"; import { vercel } from "./providers/vercel.js"; import { venice } from "./providers/venice.js"; @@ -139,6 +140,7 @@ export const providers: { ovhcloud: SyncProvider; pioneer: SyncProvider; requesty: SyncProvider; + saygm: SyncProvider; tinfoil: SyncProvider; vercel: SyncProvider; venice: SyncProvider; @@ -170,6 +172,7 @@ export const providers: { ovhcloud, pioneer, requesty, + saygm, tinfoil, vercel, venice, @@ -194,7 +197,7 @@ export const groups = { "vercel", ], cloudflare: ["cloudflare-workers-ai"], - direct: ["ambient", "anthropic", "baseten", "chutes", "cortecs", "deepinfra", "digitalocean", "google", "hyper", "openai", "ovhcloud", "pioneer", "tinfoil", "venice", "wandb", "xai"], + direct: ["ambient", "anthropic", "baseten", "chutes", "cortecs", "deepinfra", "digitalocean", "google", "hyper", "openai", "ovhcloud", "pioneer", "saygm", "tinfoil", "venice", "wandb", "xai"], } as const; type ProviderID = keyof typeof providers; diff --git a/packages/core/src/sync/providers/saygm.ts b/packages/core/src/sync/providers/saygm.ts new file mode 100644 index 00000000000..6e81b49d49d --- /dev/null +++ b/packages/core/src/sync/providers/saygm.ts @@ -0,0 +1,157 @@ +import { z } from "zod"; + +import type { ExistingModel, SyncProvider, SyncedModel } from "../index.js"; + +const API_ENDPOINT = "https://api.saygm.com/v1/models"; +const NDOLLARS_PER_DOLLAR = 1_000_000_000; + +const NanoDollars = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER); + +const PriceDimensions = z.object({ + input_per_mtok_ndollars: NanoDollars, + output_per_mtok_ndollars: NanoDollars, + cache_read_per_mtok_ndollars: NanoDollars.optional(), + cache_write_5m_per_mtok_ndollars: NanoDollars.optional(), + cache_write_1h_per_mtok_ndollars: NanoDollars.optional(), + audio_input_per_mtok_ndollars: NanoDollars.optional(), + audio_output_per_mtok_ndollars: NanoDollars.optional(), + long_context_threshold_tokens: z.number().int().positive().max(Number.MAX_SAFE_INTEGER).optional(), + long_context_input_per_mtok_ndollars: NanoDollars.optional(), + long_context_output_per_mtok_ndollars: NanoDollars.optional(), +}).passthrough().superRefine((dimensions, context) => { + const longContext = [ + dimensions.long_context_threshold_tokens, + dimensions.long_context_input_per_mtok_ndollars, + dimensions.long_context_output_per_mtok_ndollars, + ]; + const present = longContext.filter((value) => value !== undefined).length; + if (present !== 0 && present !== longContext.length) { + context.addIssue({ + code: z.ZodIssueCode.custom, + message: "Long-context pricing requires a threshold plus input and output prices", + }); + } +}); + +const RetailCeiling = z.object({ + basis: z.literal("published_retail"), + dimensions: PriceDimensions, +}).passthrough(); + +export const SaygmModel = z.object({ + id: z.string().min(1), + object: z.literal("model"), + available: z.boolean(), + api_shapes: z.array(z.string().min(1)), + price_range: z.object({ + unit: z.literal("ndollars_per_mtok"), + currency: z.literal("USD"), + ceiling: RetailCeiling, + }).passthrough().optional(), +}).passthrough(); + +export const SaygmResponse = z.object({ + object: z.literal("list"), + data: z.array(SaygmModel), +}).passthrough(); + +export type SaygmModel = z.infer; + +export const saygm = { + id: "saygm", + name: "SayGM", + modelsDir: "providers/saygm/models", + // The live inventory can change with miner supply. Until the API publishes + // durable supply depth, only update models that were reviewed into the + // catalog and retain them through temporary outages. + skipCreates: true, + deleteMissing: false, + trackMissingModels: false, + sourceID(model) { + return model.id; + }, + skippedNotice(ids) { + if (ids.length === 0) return []; + return [ + `${ids.length} SayGM models were not created because this sync is update-only until the public API exposes durable miner depth.`, + `Skipped remote IDs: ${ids.map((id) => `\`${id}\``).join(", ")}`, + ]; + }, + missingNotice(paths) { + if (paths.length === 0) return []; + return [ + `${paths.length} reviewed SayGM models were retained despite being absent from the current live response.`, + `Retained local models: ${paths.map((item) => `\`${item.replace(/\.toml$/, "")}\``).join(", ")}`, + ]; + }, + async fetchModels() { + return fetchSaygmModels(); + }, + parseModels(raw) { + return SaygmResponse.parse(raw).data; + }, + translateModel(model, context) { + if (context.existing(model.id) === undefined) return undefined; + const authored = context.authored(model.id); + if (authored === undefined) { + throw new Error(`SayGM model ${model.id} has no authored catalog entry`); + } + return { + id: model.id, + model: buildSaygmModel(model, authored), + }; + }, +} satisfies SyncProvider; + +export async function fetchSaygmModels(fetcher: typeof fetch = fetch) { + const response = await fetcher(API_ENDPOINT); + if (!response.ok) { + throw new Error(`SayGM models request failed: ${response.status} ${response.statusText}`); + } + return SaygmResponse.parse(await response.json()); +} + +export function buildSaygmModel(model: SaygmModel, existing: ExistingModel): SyncedModel { + const ceiling = model.price_range?.ceiling; + if (ceiling === undefined) { + // A partial catalogue response must not make the hourly update discard or + // block a reviewed model. Keep its last reviewed price until SayGM + // publishes a complete retail ceiling again. + return existing as SyncedModel; + } + + const dimensions = ceiling.dimensions; + const cost: NonNullable = { + input: usdPerMtok(dimensions.input_per_mtok_ndollars), + output: usdPerMtok(dimensions.output_per_mtok_ndollars), + cache_read: optionalUsdPerMtok(dimensions.cache_read_per_mtok_ndollars), + // models.dev has one cache-write field; its provider catalogs conventionally + // use Anthropic's standard five-minute write rate. + cache_write: optionalUsdPerMtok(dimensions.cache_write_5m_per_mtok_ndollars), + input_audio: optionalUsdPerMtok(dimensions.audio_input_per_mtok_ndollars), + output_audio: optionalUsdPerMtok(dimensions.audio_output_per_mtok_ndollars), + }; + + if (dimensions.long_context_threshold_tokens !== undefined) { + const longInput = dimensions.long_context_input_per_mtok_ndollars; + const longOutput = dimensions.long_context_output_per_mtok_ndollars; + if (longInput === undefined || longOutput === undefined) { + throw new Error(`SayGM model ${model.id} has incomplete long-context pricing`); + } + cost.tiers = [{ + tier: { type: "context", size: dimensions.long_context_threshold_tokens }, + input: usdPerMtok(longInput), + output: usdPerMtok(longOutput), + }]; + } + + return { ...existing, cost } as SyncedModel; +} + +export function usdPerMtok(ndollars: number) { + return ndollars / NDOLLARS_PER_DOLLAR; +} + +function optionalUsdPerMtok(ndollars: number | undefined) { + return ndollars === undefined ? undefined : usdPerMtok(ndollars); +} diff --git a/packages/core/test/sync.test.ts b/packages/core/test/sync.test.ts index 454a2036580..0fffac106cb 100644 --- a/packages/core/test/sync.test.ts +++ b/packages/core/test/sync.test.ts @@ -75,6 +75,13 @@ import { import { openai, parseOpenAIModels } from "../src/sync/providers/openai.js"; import { ofox } from "../src/sync/providers/ofox.js"; import { pioneer } from "../src/sync/providers/pioneer.js"; +import { + buildSaygmModel, + saygm, + SaygmResponse, + type SaygmModel, + usdPerMtok, +} from "../src/sync/providers/saygm.js"; import { google, shouldTrackGoogleModel } from "../src/sync/providers/google.js"; import { buildTinfoilModel, tinfoil, type TinfoilModel } from "../src/sync/providers/tinfoil.js"; import { resolveVeniceBaseModel } from "../src/sync/providers/venice.js"; @@ -198,6 +205,109 @@ function readyInceptronModel(overrides: Partial = {}): ReadyInce })[0]!; } +function saygmModel(overrides: Partial = {}): SaygmModel { + return { + id: "gpt-5.4", + object: "model", + available: true, + api_shapes: ["chat.completions", "responses"], + price_range: { + unit: "ndollars_per_mtok", + currency: "USD", + ceiling: { + basis: "published_retail", + dimensions: { + input_per_mtok_ndollars: 2_500_000_000, + output_per_mtok_ndollars: 15_000_000_000, + cache_read_per_mtok_ndollars: 250_000_000, + long_context_threshold_tokens: 272_000, + long_context_input_per_mtok_ndollars: 5_000_000_000, + long_context_output_per_mtok_ndollars: 22_500_000_000, + }, + }, + }, + ...overrides, + }; +} + +test("converts SayGM retail nanodollars into models.dev USD per Mtok costs", () => { + const built = buildSaygmModel(saygmModel(), { + base_model: "openai/gpt-5.4", + reasoning_options: [{ type: "effort", values: ["none", "low", "high"] }], + }); + + expect(built).toMatchObject({ + base_model: "openai/gpt-5.4", + cost: { + input: 2.5, + output: 15, + cache_read: 0.25, + tiers: [{ tier: { type: "context", size: 272_000 }, input: 5, output: 22.5 }], + }, + }); + expect(usdPerMtok(1)).toBe(0.000000001); +}); + +test("uses SayGM's five-minute cache-write rate for models.dev", () => { + const built = buildSaygmModel(saygmModel({ + id: "claude-haiku-4-5", + api_shapes: ["messages"], + price_range: { + unit: "ndollars_per_mtok", + currency: "USD", + ceiling: { + basis: "published_retail", + dimensions: { + input_per_mtok_ndollars: 1_000_000_000, + output_per_mtok_ndollars: 5_000_000_000, + cache_read_per_mtok_ndollars: 100_000_000, + cache_write_5m_per_mtok_ndollars: 1_250_000_000, + cache_write_1h_per_mtok_ndollars: 2_000_000_000, + }, + }, + }, + }), { base_model: "anthropic/claude-haiku-4-5" }); + + expect(built).toMatchObject({ + cost: { input: 1, output: 5, cache_read: 0.1, cache_write: 1.25 }, + }); +}); + +test("retains reviewed SayGM pricing when the live ceiling is unavailable", () => { + const existing = { + base_model: "openai/gpt-5.4", + cost: { input: 2.5, output: 15, cache_read: 0.25 }, + }; + + const built = buildSaygmModel(saygmModel({ price_range: undefined }), existing); + + expect(built).toEqual(existing); +}); + +test("rejects incomplete or non-retail SayGM pricing", () => { + const valid = saygmModel(); + const missingLongOutput = structuredClone(valid); + delete missingLongOutput.price_range!.ceiling.dimensions.long_context_output_per_mtok_ndollars; + expect(() => SaygmResponse.parse({ object: "list", data: [missingLongOutput] })).toThrow( + "Long-context pricing requires a threshold plus input and output prices", + ); + + const floorOnly = { + ...valid, + price_range: { + ...valid.price_range!, + ceiling: { ...valid.price_range!.ceiling, basis: "cheapest_eligible_offer" }, + }, + }; + expect(() => SaygmResponse.parse({ object: "list", data: [floorOnly] })).toThrow(); +}); + +test("keeps SayGM model lifecycle changes review-only", () => { + expect(saygm.skipCreates).toBe(true); + expect(saygm.deleteMissing).toBe(false); + expect(saygm.trackMissingModels).toBe(false); +}); + test("builds current Inceptron models from explicit base metadata", () => { const models = parseInceptronModels({ object: "list", diff --git a/providers/saygm/logo.svg b/providers/saygm/logo.svg new file mode 100644 index 00000000000..64b29067084 --- /dev/null +++ b/providers/saygm/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/providers/saygm/models/claude-fable-5.toml b/providers/saygm/models/claude-fable-5.toml new file mode 100644 index 00000000000..3f2aa5d48dc --- /dev/null +++ b/providers/saygm/models/claude-fable-5.toml @@ -0,0 +1,15 @@ +base_model = "anthropic/claude-fable-5" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "xhigh", "max"] + +[cost] +input = 10 +output = 50 +cache_read = 1 +cache_write = 12.5 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/claude-haiku-4-5.toml b/providers/saygm/models/claude-haiku-4-5.toml new file mode 100644 index 00000000000..cc3760232b5 --- /dev/null +++ b/providers/saygm/models/claude-haiku-4-5.toml @@ -0,0 +1,15 @@ +base_model = "anthropic/claude-haiku-4-5" + +[[reasoning_options]] +type = "budget_tokens" +min = 1_024 + +[cost] +input = 1 +output = 5 +cache_read = 0.1 +cache_write = 1.25 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/claude-opus-4-7.toml b/providers/saygm/models/claude-opus-4-7.toml new file mode 100644 index 00000000000..a9780cb2673 --- /dev/null +++ b/providers/saygm/models/claude-opus-4-7.toml @@ -0,0 +1,15 @@ +base_model = "anthropic/claude-opus-4-7" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "xhigh", "max"] + +[cost] +input = 5 +output = 25 +cache_read = 0.5 +cache_write = 6.25 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/claude-opus-4-8.toml b/providers/saygm/models/claude-opus-4-8.toml new file mode 100644 index 00000000000..b0110c1cb8c --- /dev/null +++ b/providers/saygm/models/claude-opus-4-8.toml @@ -0,0 +1,15 @@ +base_model = "anthropic/claude-opus-4-8" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "xhigh", "max"] + +[cost] +input = 5 +output = 25 +cache_read = 0.5 +cache_write = 6.25 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/claude-opus-5.toml b/providers/saygm/models/claude-opus-5.toml new file mode 100644 index 00000000000..66637ab80a9 --- /dev/null +++ b/providers/saygm/models/claude-opus-5.toml @@ -0,0 +1,15 @@ +base_model = "anthropic/claude-opus-5" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "xhigh", "max"] + +[cost] +input = 5 +output = 25 +cache_read = 0.5 +cache_write = 6.25 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/claude-sonnet-4-6.toml b/providers/saygm/models/claude-sonnet-4-6.toml new file mode 100644 index 00000000000..5d7c4e5c1c0 --- /dev/null +++ b/providers/saygm/models/claude-sonnet-4-6.toml @@ -0,0 +1,19 @@ +base_model = "anthropic/claude-sonnet-4-6" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "max"] + +[[reasoning_options]] +type = "budget_tokens" +min = 1_024 + +[cost] +input = 3 +output = 10 +cache_read = 0.3 +cache_write = 3.75 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/claude-sonnet-5.toml b/providers/saygm/models/claude-sonnet-5.toml new file mode 100644 index 00000000000..944f699e5aa --- /dev/null +++ b/providers/saygm/models/claude-sonnet-5.toml @@ -0,0 +1,20 @@ +# Toggle: thinking.type = enabled|disabled (Anthropic Messages wire; SayGM mirrors upstream) +# Effort: output_config.effort = low|medium|high|xhigh|max +base_model = "anthropic/claude-sonnet-5" + +[[reasoning_options]] +type = "toggle" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high", "xhigh", "max"] + +[cost] +input = 3 +output = 10 +cache_read = 0.3 +cache_write = 3.75 + +[provider] +npm = "@ai-sdk/anthropic" +api = "https://api.saygm.com/v1" diff --git a/providers/saygm/models/gpt-5.4-mini.toml b/providers/saygm/models/gpt-5.4-mini.toml new file mode 100644 index 00000000000..0744ddc0397 --- /dev/null +++ b/providers/saygm/models/gpt-5.4-mini.toml @@ -0,0 +1,10 @@ +base_model = "openai/gpt-5.4-mini" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh"] + +[cost] +input = 0.75 +output = 4.5 +cache_read = 0.075 diff --git a/providers/saygm/models/gpt-5.4-nano.toml b/providers/saygm/models/gpt-5.4-nano.toml new file mode 100644 index 00000000000..8d6d5218295 --- /dev/null +++ b/providers/saygm/models/gpt-5.4-nano.toml @@ -0,0 +1,10 @@ +base_model = "openai/gpt-5.4-nano" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh"] + +[cost] +input = 0.2 +output = 1.25 +cache_read = 0.02 diff --git a/providers/saygm/models/gpt-5.4.toml b/providers/saygm/models/gpt-5.4.toml new file mode 100644 index 00000000000..bda43f57d60 --- /dev/null +++ b/providers/saygm/models/gpt-5.4.toml @@ -0,0 +1,15 @@ +base_model = "openai/gpt-5.4" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh"] + +[cost] +input = 2.5 +output = 15 +cache_read = 0.25 + +[[cost.tiers]] +tier = { type = "context", size = 272_000 } +input = 5 +output = 22.5 diff --git a/providers/saygm/models/gpt-5.5-pro.toml b/providers/saygm/models/gpt-5.5-pro.toml new file mode 100644 index 00000000000..ccd3238408b --- /dev/null +++ b/providers/saygm/models/gpt-5.5-pro.toml @@ -0,0 +1,16 @@ +# Served only on the Responses API (SayGM /v1/models api_shapes = ["responses"]). +base_model = "openai/gpt-5.5-pro" + +[[reasoning_options]] +type = "effort" +values = ["medium", "high", "xhigh"] + +[cost] +input = 30 +output = 180 +cache_read = 3 + +[provider] +npm = "@ai-sdk/openai" +api = "https://api.saygm.com/v1" +shape = "responses" diff --git a/providers/saygm/models/gpt-5.5.toml b/providers/saygm/models/gpt-5.5.toml new file mode 100644 index 00000000000..197bb4ccc5f --- /dev/null +++ b/providers/saygm/models/gpt-5.5.toml @@ -0,0 +1,15 @@ +base_model = "openai/gpt-5.5" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh"] + +[cost] +input = 5 +output = 30 +cache_read = 0.5 + +[[cost.tiers]] +tier = { type = "context", size = 272_000 } +input = 10 +output = 45 diff --git a/providers/saygm/models/gpt-5.6-luna.toml b/providers/saygm/models/gpt-5.6-luna.toml new file mode 100644 index 00000000000..628385cddcb --- /dev/null +++ b/providers/saygm/models/gpt-5.6-luna.toml @@ -0,0 +1,15 @@ +base_model = "openai/gpt-5.6-luna" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh", "max"] + +[cost] +input = 0.2 +output = 1.2 +cache_read = 0.02 + +[[cost.tiers]] +tier = { type = "context", size = 272_000 } +input = 0.4 +output = 1.8 diff --git a/providers/saygm/models/gpt-5.6-sol.toml b/providers/saygm/models/gpt-5.6-sol.toml new file mode 100644 index 00000000000..16e77d90fd9 --- /dev/null +++ b/providers/saygm/models/gpt-5.6-sol.toml @@ -0,0 +1,15 @@ +base_model = "openai/gpt-5.6-sol" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh", "max"] + +[cost] +input = 5 +output = 30 +cache_read = 0.5 + +[[cost.tiers]] +tier = { type = "context", size = 272_000 } +input = 10 +output = 45 diff --git a/providers/saygm/models/gpt-5.6-terra.toml b/providers/saygm/models/gpt-5.6-terra.toml new file mode 100644 index 00000000000..2ae2aa742e2 --- /dev/null +++ b/providers/saygm/models/gpt-5.6-terra.toml @@ -0,0 +1,15 @@ +base_model = "openai/gpt-5.6-terra" + +[[reasoning_options]] +type = "effort" +values = ["none", "low", "medium", "high", "xhigh", "max"] + +[cost] +input = 2 +output = 12 +cache_read = 0.2 + +[[cost.tiers]] +tier = { type = "context", size = 272_000 } +input = 4 +output = 18 diff --git a/providers/saygm/models/o3.toml b/providers/saygm/models/o3.toml new file mode 100644 index 00000000000..977bf918256 --- /dev/null +++ b/providers/saygm/models/o3.toml @@ -0,0 +1,10 @@ +base_model = "openai/o3" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + +[cost] +input = 2 +output = 8 +cache_read = 0.5 diff --git a/providers/saygm/models/o4-mini.toml b/providers/saygm/models/o4-mini.toml new file mode 100644 index 00000000000..75807e417c1 --- /dev/null +++ b/providers/saygm/models/o4-mini.toml @@ -0,0 +1,10 @@ +base_model = "openai/o4-mini" + +[[reasoning_options]] +type = "effort" +values = ["low", "medium", "high"] + +[cost] +input = 1.1 +output = 4.4 +cache_read = 0.275 diff --git a/providers/saygm/provider.toml b/providers/saygm/provider.toml new file mode 100644 index 00000000000..4b5786a9bc1 --- /dev/null +++ b/providers/saygm/provider.toml @@ -0,0 +1,5 @@ +name = "SayGM" +npm = "@ai-sdk/openai-compatible" +env = ["SAYGM_API_KEY"] +api = "https://api.saygm.com/v1" +doc = "https://saygm.com"