Compare commits

...
4 changed files with 177 additions and 38 deletions
+44 -13
View File
@@ -1094,6 +1094,21 @@ export const ConfigProvidersResult = Schema.Struct({
export type ConfigProvidersResult = Types.DeepMutable<Schema.Schema.Type<typeof ConfigProvidersResult>>
export function toPublicInfo(provider: Info): Info {
const result = cloneInfo(provider)
for (const model of Object.values(result.models)) {
if (model.cost.experimentalOver200K) continue
if (model.cost.tiers?.length !== 1) continue
const [tier] = model.cost.tiers
model.cost.experimentalOver200K = {
input: tier.input,
output: tier.output,
cache: { ...tier.cache },
}
}
return result
}
function cloneInfo(provider: Info): Info {
return JSON.parse(
JSON.stringify(
{
@@ -1202,8 +1217,8 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
write: c?.cache_write ?? 0,
},
}
if (c?.tiers) {
result.tiers = c.tiers.map((item) => ({
const tiers =
c?.tiers?.map((item) => ({
input: item.input,
output: item.output,
cache: {
@@ -1211,18 +1226,19 @@ function cost(c: ModelsDev.Model["cost"]): Model["cost"] {
write: item.cache_write ?? 0,
},
tier: item.tier,
}))
}
if (c?.context_over_200k) {
result.experimentalOver200K = {
})) ?? []
const legacy = c?.context_over_200k
if (legacy && tiers.length === 0)
tiers.push({
input: legacy.input,
output: legacy.output,
cache: {
read: c.context_over_200k.cache_read ?? 0,
write: c.context_over_200k.cache_write ?? 0,
read: legacy.cache_read ?? 0,
write: legacy.cache_write ?? 0,
},
input: c.context_over_200k.input,
output: c.context_over_200k.output,
}
}
tier: { type: "context", size: 200_000 },
})
if (tiers.length > 0) result.tiers = tiers
return result
}
@@ -1378,7 +1394,7 @@ const layer = Layer.effect(
const cfg = yield* config.get()
const modelsDev = yield* modelsDevSvc.get()
const catalog = mapValues(modelsDev, fromModelsDevProvider)
const database = mapValues(catalog, toPublicInfo)
const database = mapValues(catalog, cloneInfo)
const providers: Record<ProviderV2.ID, Info> = {} as Record<ProviderV2.ID, Info>
const languages = new Map<string, LanguageModelV3>()
@@ -1482,6 +1498,20 @@ const layer = Layer.effect(
if (model.id && model.id !== modelID) return modelID
return existingModel?.name ?? modelID
})
const legacy = existingModel?.cost.tiers?.length ? undefined : model.cost?.context_over_200k
const tiers = legacy
? [
...(existingModel?.cost.tiers?.filter(
(item) => item.tier.type !== "context" || item.tier.size !== 200_000,
) ?? []),
{
input: legacy.input,
output: legacy.output,
cache: { read: legacy.cache_read ?? 0, write: legacy.cache_write ?? 0 },
tier: { type: "context" as const, size: 200_000 },
},
]
: existingModel?.cost.tiers
const parsedModel: Model = {
id: ModelV2.ID.make(modelID),
api: {
@@ -1528,6 +1558,7 @@ const layer = Layer.effect(
read: model?.cost?.cache_read ?? existingModel?.cost?.cache.read ?? 0,
write: model?.cost?.cache_write ?? existingModel?.cost?.cache.write ?? 0,
},
tiers,
},
options: mergeDeep(existingModel?.options ?? {}, model.options ?? {}),
limit: {
+1 -4
View File
@@ -380,10 +380,7 @@ export const getUsage = (input: { model: Provider.Model; usage: Usage; metadata?
const costInfo =
input.model.cost?.tiers
?.filter((item) => item.tier.type === "context" && contextTokens > item.tier.size)
.sort((a, b) => b.tier.size - a.tier.size)[0] ??
(input.model.cost?.experimentalOver200K && contextTokens > 200_000
? input.model.cost.experimentalOver200K
: input.model.cost)
.sort((a, b) => b.tier.size - a.tier.size)[0] ?? input.model.cost
const totalNanoAiu = input.metadata?.["copilot"]?.["totalNanoAiu"]
return {
cost:
@@ -566,6 +566,81 @@ it.instance(
},
)
it.instance(
"model config preserves catalog cost tiers",
Effect.gen(function* () {
yield* set("REQUESTY_API_KEY", "test-api-key")
const providers = yield* list
const provider = providers[ProviderV2.ID.make("requesty")]
const model = provider.models["google/gemini-2.5-pro"]
expect(model.cost.tiers).toEqual([
{
input: 2.5,
output: 15,
cache: { read: 0.25, write: 0 },
tier: { type: "context", size: 200_000 },
},
])
expect(model.cost.experimentalOver200K).toBeUndefined()
expect(Provider.toPublicInfo(provider).models["google/gemini-2.5-pro"].cost.experimentalOver200K).toEqual({
input: 2.5,
output: 15,
cache: { read: 0.25, write: 0 },
})
}),
{
config: {
provider: {
requesty: {
models: {
"google/gemini-2.5-pro": {
cost: {
input: 1.25,
output: 10,
context_over_200k: { input: 4, output: 16, cache_read: 0.4 },
},
},
},
},
},
},
},
)
it.instance(
"legacy model config pricing is normalized into a context tier",
Effect.gen(function* () {
const providers = yield* list
expect(providers[ProviderV2.ID.make("legacy")].models.legacy.cost.tiers).toEqual([
{
input: 4,
output: 16,
cache: { read: 0.4, write: 0 },
tier: { type: "context", size: 200_000 },
},
])
}),
{
config: {
provider: {
legacy: {
npm: "@ai-sdk/openai-compatible",
options: { apiKey: "test-api-key" },
models: {
legacy: {
cost: {
input: 1.25,
output: 10,
context_over_200k: { input: 4, output: 16, cache_read: 0.4 },
},
},
},
},
},
},
},
)
it.instance(
"model config preserves explicitly empty models.dev variants",
Effect.gen(function* () {
@@ -1401,10 +1476,18 @@ test("mode options and cost are derived from the base model", () => {
input: 2.5,
output: 15,
cache_read: 0.25,
tiers: [
{
input: 5,
output: 22.5,
cache_read: 0.5,
tier: { type: "context", size: 272_000 },
},
],
context_over_200k: {
input: 5,
output: 22.5,
cache_read: 0.5,
input: 4,
output: 20,
cache_read: 0.4,
},
},
limit: {
@@ -1449,11 +1532,49 @@ test("mode options and cost are derived from the base model", () => {
const pro = Provider.fromModelsDevProvider(provider).models["gpt-5.6-sol-pro"]
expect(pro.api.id).toEqual("gpt-5.6-sol")
expect(pro.options).toEqual({ reasoningMode: "pro", serviceTier: "priority" })
expect(model.cost.experimentalOver200K).toEqual({
input: 5,
output: 22.5,
cache: { read: 0.5, write: 0 },
})
expect(model.cost.tiers).toEqual([
{
input: 5,
output: 22.5,
cache: { read: 0.5, write: 0 },
tier: { type: "context", size: 272_000 },
},
])
})
test("legacy models.dev pricing is normalized into a context tier", () => {
const provider = {
id: "legacy",
name: "Legacy",
env: [],
npm: "@ai-sdk/openai-compatible",
models: {
legacy: {
id: "legacy",
name: "Legacy",
release_date: "2026-01-01",
attachment: false,
reasoning: false,
temperature: true,
tool_call: true,
cost: {
input: 1,
output: 2,
context_over_200k: { input: 3, output: 4, cache_read: 0.3 },
},
limit: { context: 1_000_000, output: 32_000 },
},
},
} as unknown as ModelsDev.Provider
expect(Provider.fromModelsDevProvider(provider).models.legacy.cost.tiers).toEqual([
{
input: 3,
output: 4,
cache: { read: 0.3, write: 0 },
tier: { type: "context", size: 200_000 },
},
])
})
test("models.dev normalization fills required response fields", () => {
@@ -1830,7 +1830,7 @@ describe("SessionNs.getUsage", () => {
expect(result.cost).toBe(0.04473525)
})
test("uses matching context cost tier before over-200k fallback", () => {
test("uses the matching context cost tier", () => {
const model = createModel({
context: 1_000_000,
output: 32_000,
@@ -1852,11 +1852,6 @@ describe("SessionNs.getUsage", () => {
tier: { type: "context", size: 500_000 },
},
],
experimentalOver200K: {
input: 100,
output: 100,
cache: { read: 100, write: 100 },
},
},
})
const result = SessionNs.getUsage({
@@ -1873,7 +1868,7 @@ describe("SessionNs.getUsage", () => {
expect(result.cost).toBe(2.75 + 0.6 + 0.05)
})
test("falls back to over-200k pricing when no cost tier matches", () => {
test("uses base pricing when no context cost tier matches", () => {
const model = createModel({
context: 1_000_000,
output: 32_000,
@@ -1889,11 +1884,6 @@ describe("SessionNs.getUsage", () => {
tier: { type: "context", size: 500_000 },
},
],
experimentalOver200K: {
input: 3,
output: 4,
cache: { read: 0.3, write: 1.5 },
},
},
})
const result = SessionNs.getUsage({
@@ -1901,7 +1891,7 @@ describe("SessionNs.getUsage", () => {
usage: usage({ inputTokens: 300_000, outputTokens: 100_000, totalTokens: 400_000 }),
})
expect(result.cost).toBe(0.9 + 0.4)
expect(result.cost).toBe(0.3 + 0.2)
})
test.each(["@ai-sdk/anthropic", "@ai-sdk/amazon-bedrock", "@ai-sdk/google-vertex/anthropic"])(