diff --git a/packages/core/src/model-resolver.ts b/packages/core/src/model-resolver.ts index 448b18d8b2aa..5ab794c7cc44 100644 --- a/packages/core/src/model-resolver.ts +++ b/packages/core/src/model-resolver.ts @@ -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( diff --git a/packages/core/src/plugin/provider/azure.ts b/packages/core/src/plugin/provider/azure.ts index 40cdeb015161..0045908c0c20 100644 --- a/packages/core/src/plugin/provider/azure.ts +++ b/packages/core/src/plugin/provider/azure.ts @@ -482,11 +482,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 diff --git a/packages/core/test/model-resolver.test.ts b/packages/core/test/model-resolver.test.ts index c8213d5adca4..aa0e4b5e6cde 100644 --- a/packages/core/test/model-resolver.test.ts +++ b/packages/core/test/model-resolver.test.ts @@ -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({}) }), ) diff --git a/packages/core/test/plugin/provider-azure.test.ts b/packages/core/test/plugin/provider-azure.test.ts index 77bc333350ff..82b80250e980 100644 --- a/packages/core/test/plugin/provider-azure.test.ts +++ b/packages/core/test/plugin/provider-azure.test.ts @@ -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)