mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 06:33:01 -04:00
fix(opencode): close tier precedence gaps
This commit is contained in:
@@ -91,7 +91,8 @@ export const Plugin = define({
|
||||
}
|
||||
}
|
||||
if (config.cost !== undefined) {
|
||||
model.cost = (Array.isArray(config.cost) ? config.cost : [config.cost]).map((cost) => ({
|
||||
const input = Array.isArray(config.cost) ? config.cost : [config.cost]
|
||||
const next = input.map((cost) => ({
|
||||
tier: cost.tier && { ...cost.tier },
|
||||
input: cost.input,
|
||||
output: cost.output,
|
||||
@@ -100,6 +101,23 @@ export const Plugin = define({
|
||||
write: cost.cache?.write ?? 0,
|
||||
},
|
||||
}))
|
||||
const explicit = next.filter((cost) => cost.tier?.type === "context")
|
||||
const existing = model.cost.filter((cost) => cost.tier?.type === "context")
|
||||
const legacy = input.find((cost) => cost.tier === undefined)?.context_over_200k
|
||||
model.cost = [
|
||||
...next.filter((cost) => cost.tier === undefined),
|
||||
...(explicit.length > 0 ? explicit : existing),
|
||||
...(legacy && explicit.length === 0 && existing.length === 0
|
||||
? [
|
||||
{
|
||||
tier: { type: "context" as const, size: 200_000 },
|
||||
input: legacy.input,
|
||||
output: legacy.output,
|
||||
cache: { read: legacy.cache?.read ?? 0, write: legacy.cache?.write ?? 0 },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
}
|
||||
if (config.disabled !== undefined) model.enabled = !config.disabled
|
||||
if (config.limit !== undefined) model.limit = { ...model.limit, ...config.limit }
|
||||
|
||||
@@ -22,6 +22,11 @@ class Cost extends Schema.Class<Cost>("ConfigV2.Model.Cost")({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
cache: Cache.pipe(Schema.optional),
|
||||
context_over_200k: Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
output: Schema.Finite,
|
||||
cache: Cache.pipe(Schema.optional),
|
||||
}).pipe(Schema.optional),
|
||||
}) {}
|
||||
|
||||
class Limit extends Schema.Class<Limit>("ConfigV2.Model.Limit")({
|
||||
|
||||
@@ -51,8 +51,10 @@ function cost(input: ModelsDev.Model["cost"]): ModelV2Info["cost"] {
|
||||
|
||||
function mergeCost(base: ModelV2Info["cost"], override: ModelsDev.Model["cost"] | undefined) {
|
||||
if (!override) return base
|
||||
const next = cost(override)
|
||||
const [baseDefault, ...baseTiers] = base
|
||||
const next = cost(
|
||||
baseTiers.length > 0 && !override.tiers?.length ? { ...override, context_over_200k: undefined } : override,
|
||||
)
|
||||
const [nextDefault, ...nextTiers] = next
|
||||
const tierKey = (item: ModelV2Info["cost"][number]) => `${item.tier?.type ?? "base"}:${item.tier?.size ?? 0}`
|
||||
const merge = (left: ModelV2Info["cost"][number], right: ModelV2Info["cost"][number]) => ({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Duration, Effect, Schema, Semaphore, Stream } from "effect"
|
||||
import type { Scope } from "effect"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { CredentialValue } from "@opencode-ai/sdk/v2/types"
|
||||
import type { CredentialValue, ModelV2Info } from "@opencode-ai/sdk/v2/types"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { EventV2 } from "../../event"
|
||||
import { Credential } from "../../credential"
|
||||
@@ -162,7 +162,10 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
||||
model.time.released = Number.isFinite(released) ? released : 0
|
||||
}
|
||||
if (config.cost !== undefined) {
|
||||
model.cost = remoteCost(config.cost)
|
||||
model.cost = remoteCost(
|
||||
config.cost,
|
||||
model.cost.filter((cost) => cost.tier?.type === "context"),
|
||||
)
|
||||
}
|
||||
model.status = config.status ?? "active"
|
||||
model.enabled = config.status !== "deprecated"
|
||||
@@ -224,7 +227,7 @@ function withoutCredentials(body: Readonly<Record<string, unknown>> | undefined)
|
||||
return Object.fromEntries(Object.entries(body ?? {}).filter(([key]) => key !== "apiKey" && key !== "headers"))
|
||||
}
|
||||
|
||||
function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>) {
|
||||
function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cost"]>, tiers: ModelV2Info["cost"] = []) {
|
||||
const legacy = input.context_over_200k
|
||||
return [
|
||||
{
|
||||
@@ -232,7 +235,8 @@ function remoteCost(input: NonNullable<(typeof ConfigProviderV1.Model.Type)["cos
|
||||
output: input.output,
|
||||
cache: { read: input.cache_read ?? 0, write: input.cache_write ?? 0 },
|
||||
},
|
||||
...(legacy
|
||||
...tiers,
|
||||
...(legacy && tiers.length === 0
|
||||
? [
|
||||
{
|
||||
tier: { type: "context" as const, size: 200_000 },
|
||||
|
||||
@@ -194,23 +194,20 @@ function migrateModel(info: typeof ConfigProviderV1.Model.Type, packageName?: st
|
||||
const lowerer = ConfigProviderOptionsV1.get(packageID)
|
||||
const request = info.options && lowerer.request(info.options)
|
||||
const legacy = info.cost?.context_over_200k
|
||||
const costs = info.cost && [
|
||||
{
|
||||
input: info.cost.input,
|
||||
output: info.cost.output,
|
||||
cache: { read: info.cost.cache_read, write: info.cost.cache_write },
|
||||
},
|
||||
const costs = info.cost && {
|
||||
input: info.cost.input,
|
||||
output: info.cost.output,
|
||||
cache: { read: info.cost.cache_read, write: info.cost.cache_write },
|
||||
...(legacy
|
||||
? [
|
||||
{
|
||||
tier: { type: "context" as const, size: 200_000 },
|
||||
? {
|
||||
context_over_200k: {
|
||||
input: legacy.input,
|
||||
output: legacy.output,
|
||||
cache: { read: legacy.cache_read, write: legacy.cache_write },
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
const capabilities =
|
||||
info.tool_call !== undefined || info.modalities?.input !== undefined || info.modalities?.output !== undefined
|
||||
? { tools: info.tool_call ?? false, input: info.modalities?.input ?? [], output: info.modalities?.output ?? [] }
|
||||
|
||||
@@ -55,6 +55,71 @@ function request(headers: Record<string, string>, variant?: string) {
|
||||
const decode = Schema.decodeUnknownSync(Config.Info)
|
||||
|
||||
describe("ConfigProviderPlugin.Plugin", () => {
|
||||
it.effect("prefers catalog tiers over legacy config pricing", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = ProviderV2.ID.opencode
|
||||
const modelID = ModelV2.ID.make("alpha-gpt-next")
|
||||
const config = Config.Service.of({
|
||||
entries: () =>
|
||||
Effect.succeed([
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode({
|
||||
providers: {
|
||||
opencode: {
|
||||
models: {
|
||||
"alpha-gpt-next": {
|
||||
cost: [
|
||||
{ input: 1, output: 2 },
|
||||
{
|
||||
tier: { type: "context", size: 272_000 },
|
||||
input: 3,
|
||||
output: 4,
|
||||
cache: { read: 0.3 },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode({
|
||||
providers: {
|
||||
opencode: {
|
||||
models: {
|
||||
"alpha-gpt-next": {
|
||||
cost: {
|
||||
input: 5,
|
||||
output: 6,
|
||||
context_over_200k: { input: 100, output: 100 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
||||
yield* addPlugin(config)
|
||||
|
||||
expect(required(yield* catalog.model.get(providerID, modelID)).cost).toEqual([
|
||||
{ input: 5, output: 6, cache: { read: 0, write: 0 }, tier: undefined },
|
||||
{
|
||||
tier: { type: "context", size: 272_000 },
|
||||
input: 3,
|
||||
output: 4,
|
||||
cache: { read: 0.3, write: 0 },
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("keeps configured model variant bodies unchanged", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
|
||||
@@ -67,7 +67,12 @@ describe("ModelsDevPlugin", () => {
|
||||
experimental: {
|
||||
modes: {
|
||||
fast: {
|
||||
cost: { input: 5, output: 30, cache_read: 0.5 },
|
||||
cost: {
|
||||
input: 5,
|
||||
output: 30,
|
||||
cache_read: 0.5,
|
||||
context_over_200k: { input: 100, output: 100 },
|
||||
},
|
||||
provider: {
|
||||
headers: { "x-mode": "fast" },
|
||||
body: { service_tier: "priority" },
|
||||
|
||||
@@ -1489,14 +1489,18 @@ export type Model = {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
experimentalOver200K?: {
|
||||
tiers?: Array<{
|
||||
input: number
|
||||
output: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
tier: {
|
||||
type: "context"
|
||||
size: number
|
||||
}
|
||||
}>
|
||||
}
|
||||
limit: {
|
||||
context: number
|
||||
|
||||
Reference in New Issue
Block a user