mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-09 04:44:34 +00:00
fix(core): copy models.dev snapshot without structuredClone (#46710)
This commit is contained in:
parent
a978a1e010
commit
e327f93711
2 changed files with 75 additions and 2 deletions
|
|
@ -37,7 +37,7 @@ export const ModelsDevPlugin = define({
|
|||
})
|
||||
for (const model of provider.models) {
|
||||
if (model.status === "deprecated") continue
|
||||
catalog.model.update(provider.info.id, model.id, (draft) => Object.assign(draft, structuredClone(model)))
|
||||
catalog.model.update(provider.info.id, model.id, (draft) => Object.assign(draft, copy(model)))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -65,8 +65,27 @@ function environmentNames(provider: ModelsDev.Snapshot) {
|
|||
}
|
||||
|
||||
function snapshots(data: readonly ModelsDev.Snapshot[]) {
|
||||
return structuredClone(data).filter(
|
||||
return copy(data).filter(
|
||||
// These deprecated aliases are replaced by the canonical Azure and Google Vertex providers.
|
||||
(provider) => provider.info.id !== "azure-cognitive-services" && provider.info.id !== "google-vertex-anthropic",
|
||||
)
|
||||
}
|
||||
|
||||
// The catalog owns and mutates its model records, so every rebuild needs fresh copies of the
|
||||
// thousands of snapshot models. Snapshot data is plain JSON, and a direct copy is an order of
|
||||
// magnitude faster than structuredClone's general graph walk on the startup path.
|
||||
function copy<T>(value: T): T {
|
||||
if (Array.isArray(value)) return value.map(copy) as T
|
||||
if (value !== null && typeof value === "object") {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const key of Object.keys(value)) {
|
||||
const copied = copy((value as Record<string, unknown>)[key])
|
||||
// Assigning this key would set the prototype rather than an own property, unlike structuredClone.
|
||||
if (key === "__proto__")
|
||||
Object.defineProperty(result, key, { value: copied, enumerable: true, writable: true, configurable: true })
|
||||
else result[key] = copied
|
||||
}
|
||||
return result as T
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,6 +411,60 @@ describe("ModelsDevPlugin", () => {
|
|||
),
|
||||
)
|
||||
|
||||
it.effect("copies model request bodies without reinterpreting literal __proto__ keys", () =>
|
||||
Effect.gen(function* () {
|
||||
const integrations = yield* Integration.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const providerID = Provider.ID.make("acme")
|
||||
const modelID = Model.ID.make("gpt-5.4")
|
||||
// A JSON body may legitimately contain a "__proto__" key; both copy stages must keep it as an own property.
|
||||
const body = JSON.parse('{"__proto__":{"service_tier":"priority"},"keep":true}') as Record<string, unknown>
|
||||
const snapshot = {
|
||||
info: {
|
||||
id: providerID,
|
||||
name: "Acme",
|
||||
activation: "auto",
|
||||
package: Provider.aisdk("@ai-sdk/openai-compatible"),
|
||||
},
|
||||
environment: [],
|
||||
models: [
|
||||
{
|
||||
id: modelID,
|
||||
modelID,
|
||||
providerID,
|
||||
name: "GPT-5.4",
|
||||
capabilities: { tools: true, input: [], output: [] },
|
||||
variants: [],
|
||||
time: { released: Date.parse("2026-01-01") },
|
||||
cost: [],
|
||||
status: "active",
|
||||
enabled: true,
|
||||
limit: { context: 1_050_000, output: 128_000 },
|
||||
body,
|
||||
},
|
||||
],
|
||||
} satisfies ModelsDev.Snapshot
|
||||
yield* ModelsDevPlugin.effect(
|
||||
host({
|
||||
catalog: catalogHost(catalog),
|
||||
integration: integrationHost(integrations),
|
||||
}),
|
||||
).pipe(
|
||||
Effect.provideService(
|
||||
ModelsDev.Service,
|
||||
ModelsDev.Service.of({ get: () => Effect.succeed([snapshot]), refresh: () => Effect.void }),
|
||||
),
|
||||
)
|
||||
|
||||
const copied = (yield* catalog.model.get(providerID, modelID))?.body
|
||||
expect(copied).not.toBe(body)
|
||||
expect(Object.hasOwn(copied ?? {}, "__proto__")).toBe(true)
|
||||
expect(Object.keys(copied ?? {})).toEqual(["__proto__", "keep"])
|
||||
expect(JSON.stringify(copied)).toBe(JSON.stringify(body))
|
||||
expect(copied).not.toHaveProperty("service_tier")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("omits legacy provider aliases", () =>
|
||||
Effect.gen(function* () {
|
||||
const integrations = yield* Integration.Service
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue