Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions packages/core/src/model-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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(
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/plugin/provider/azure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 20 additions & 2 deletions packages/core/test/model-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand All @@ -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({})
}),
)

Expand Down
10 changes: 8 additions & 2 deletions packages/core/test/plugin/provider-azure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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)
Expand Down
Loading