fix(core): copy models.dev snapshot without structuredClone (#46710)

This commit is contained in:
Kit Langton 2026-09-01 23:07:15 -04:00 committed by GitHub
parent a978a1e010
commit e327f93711
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 75 additions and 2 deletions

View file

@ -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
}

View file

@ -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