fix(core): stop sending API key metadata as request body (#53546)

Co-authored-by: neriousy <filip@anoma.ly>
This commit is contained in:
opencode-agent[bot]
2026-10-07 10:36:22 +02:00
committed by GitHub
Unverified
parent 34cf183c4c
commit 6273bd7924
4 changed files with 35 additions and 16 deletions
+4 -10
View File
@@ -198,7 +198,7 @@ const resolveCatalogModel = Effect.fn("ModelResolver.resolveCatalogModel")(funct
credential?: Credential.Value,
dependencies?: Dependencies,
) {
const resolved = prepareRuntimeModel(model, credential)
const resolved = prepareRuntimeModel(model)
const configuration = credential?.type === "key" ? credential.configuration : undefined
const configured = { ...resolved.settings, ...credential?.metadata, ...configuration }
if (Provider.isAISDK(resolved.package)) {
@@ -256,15 +256,9 @@ const resolveCatalogModel = Effect.fn("ModelResolver.resolveCatalogModel")(funct
})
})
function prepareRuntimeModel(model: RuntimeInfo, credential: Credential.Value | undefined) {
if (model.settings?.apiKey !== "" && (credential?.type !== "key" || credential.metadata === undefined)) return model
return {
...model,
...(model.settings?.apiKey === "" ? { settings: Struct.omit(model.settings, ["apiKey"]) } : {}),
...(credential?.type === "key" && credential.metadata !== undefined
? { body: Provider.mergeOverlay(model.body, credential.metadata) }
: {}),
}
function prepareRuntimeModel(model: RuntimeInfo) {
if (model.settings?.apiKey !== "") return model
return { ...model, settings: Struct.omit(model.settings, ["apiKey"]) }
}
function validateProviderVariables(
+3 -2
View File
@@ -487,11 +487,12 @@ function expandResourceName(baseURL: string, resourceName: string) {
.replaceAll("${AZURE_COGNITIVE_SERVICES_RESOURCE_NAME}", resourceName)
}
// The Azure CLI method stores the resource as credential metadata, the API key method as its form answer.
// The Azure CLI method stores the resource as credential metadata, the API key method as its form answer. API keys
// imported from V1 keep their connect-form answer as metadata.
function credentialResource(credential: Credential.Value | undefined) {
const resource =
credential?.type === "key"
? credential.configuration?.resourceName
? (credential.configuration?.resourceName ?? credential.metadata?.resourceName)
: credential?.methodID === methodID
? credential.metadata?.resourceName
: undefined
+20 -2
View File
@@ -710,7 +710,7 @@ describe("ModelResolver", () => {
it.effect("prefers stored credentials over configured auth", () =>
Effect.gen(function* () {
const credential = Credential.Key.make({ type: "key", key: "stored-secret", metadata: { tenant: "work" } })
const credential = Credential.Key.make({ type: "key", key: "stored-secret" })
const resolved = yield* ModelResolver.fromCatalogModel(
model(Provider.aisdk("@ai-sdk/openai"), {
settings: { apiKey: "configured-secret", baseURL: "https://openai.example/v1" },
@@ -728,7 +728,25 @@ describe("ModelResolver", () => {
})
expect(headers.authorization).toBe("Bearer stored-secret")
expect(resolved.route.defaults.http?.body).toEqual({ tenant: "work" })
}),
)
it.effect("does not project API key metadata into the request body", () =>
Effect.gen(function* () {
// V1 auth.json stored connect-form answers as API key metadata, and the legacy import preserves them there.
const resolved = yield* ModelResolver.fromCatalogModel(
model(Provider.aisdk("@ai-sdk/azure"), {
providerID: Provider.ID.azure,
modelID: "responses-deployment",
settings: { apiVersion: "2025-01-01-preview" },
headers: {},
body: {},
}),
Credential.Key.make({ type: "key", key: "secret", metadata: { resourceName: "migrated-resource" } }),
)
expect(resolved.route.endpoint.baseURL).toBe("https://migrated-resource.openai.azure.com/openai/v1")
expect(resolved.route.defaults.http?.body).toEqual({})
}),
)
@@ -574,9 +574,15 @@ describe("AzurePlugin resource name", () => {
name: "prefers the connection's resource over configuration, as requests do",
env: { AZURE_RESOURCE_NAME: "from-env" },
settings: { resourceName: "from-config" },
connection: true,
credential: keyCredential(),
expected: "test-resource",
},
{
name: "reads the resource of an API key imported from V1",
env: { AZURE_RESOURCE_NAME: "from-env" },
credential: Credential.Key.make({ type: "key", key: "secret", metadata: { resourceName: "imported-resource" } }),
expected: "imported-resource",
},
]
cases.forEach((item) =>
@@ -586,7 +592,7 @@ describe("AzurePlugin resource name", () => {
yield* seedProvider(item.settings)
const providers = yield* Provider.Service
yield* providers.transform((editor) => editor.update(Provider.ID.openai, () => {}))
if (item.connection) yield* connect(keyCredential())
if (item.credential) yield* connect(item.credential)
yield* addPlugin()
expect(required(yield* providers.get(Provider.ID.azure)).settings?.resourceName).toBe(item.expected)