fix(ai): fall back to ambient Cloudflare account id for key-only credentials (#6292)
Cloudflare Workers AI / AI Gateway resolved provider config from the
credential only, never consulting ambient env for a field the credential
omitted. The coding-agent /login flow stores just the API key, so
CLOUDFLARE_ACCOUNT_ID lives only in the environment; the key-only credential
short-circuited the env lookup, the account id stayed unresolved, and requests
hit the literal {CLOUDFLARE_ACCOUNT_ID} base URL -> 404.
resolveValue now merges per field: prefer the credential value, fall back to
ctx.env(name).
closes #6021
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
- Fixed Amazon Bedrock Claude 5 prompt-cache pricing metadata by removing stale fallback overrides.
|
- Fixed Amazon Bedrock Claude 5 prompt-cache pricing metadata by removing stale fallback overrides.
|
||||||
- Fixed DS4 server context overflow detection for `Prompt has ... tokens, but the configured context size is ... tokens` errors ([#6262](https://github.com/earendil-works/pi/issues/6262)).
|
- Fixed DS4 server context overflow detection for `Prompt has ... tokens, but the configured context size is ... tokens` errors ([#6262](https://github.com/earendil-works/pi/issues/6262)).
|
||||||
- Fixed OpenAI Codex WebSocket sessions to rotate cached connections before the backend's 60-minute limit, avoiding connection-limit failures on long sessions ([#6268](https://github.com/earendil-works/pi/issues/6268)).
|
- Fixed OpenAI Codex WebSocket sessions to rotate cached connections before the backend's 60-minute limit, avoiding connection-limit failures on long sessions ([#6268](https://github.com/earendil-works/pi/issues/6268)).
|
||||||
|
- Fixed Cloudflare Workers AI / AI Gateway auth to fall back to the ambient `CLOUDFLARE_ACCOUNT_ID` (and `CLOUDFLARE_GATEWAY_ID`) when the stored credential carries only the API key, so `/login`-style key-only credentials no longer leave the `{CLOUDFLARE_ACCOUNT_ID}` placeholder unresolved and return 404 ([#6021](https://github.com/earendil-works/pi/issues/6021)).
|
||||||
- Fixed OpenAI Completions and Responses providers to send `(no tool output)` instead of `(see attached image)` when a tool result has empty text and no image content, preventing the model from hallucinating image attachments.
|
- Fixed OpenAI Completions and Responses providers to send `(no tool output)` instead of `(see attached image)` when a tool result has empty text and no image content, preventing the model from hallucinating image attachments.
|
||||||
- Fixed OpenAI Responses and Azure OpenAI Responses requests to avoid sending `max_output_tokens` values below the provider minimum ([#6265](https://github.com/earendil-works/pi/issues/6265)).
|
- Fixed OpenAI Responses and Azure OpenAI Responses requests to avoid sending `max_output_tokens` values below the provider minimum ([#6265](https://github.com/earendil-works/pi/issues/6265)).
|
||||||
- Fixed retry classification for Cloudflare 524 timeout responses ([#6239](https://github.com/earendil-works/pi/issues/6239)).
|
- Fixed retry classification for Cloudflare 524 timeout responses ([#6239](https://github.com/earendil-works/pi/issues/6239)).
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ async function resolveValue(
|
|||||||
ctx: AuthContext,
|
ctx: AuthContext,
|
||||||
credential: ApiKeyCredential | undefined,
|
credential: ApiKeyCredential | undefined,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
if (credential) {
|
// Per-field merge: prefer the credential value, fall back to ambient env.
|
||||||
if (name === CLOUDFLARE_API_KEY) return credential.key;
|
// A credential carrying only the API key must still pick up the account /
|
||||||
return credential.env?.[name];
|
// gateway id from the environment.
|
||||||
}
|
const fromCredential = credential
|
||||||
return ctx.env(name);
|
? name === CLOUDFLARE_API_KEY
|
||||||
|
? credential.key
|
||||||
|
: credential.env?.[name]
|
||||||
|
: undefined;
|
||||||
|
return fromCredential ?? (await ctx.env(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveCloudflareBaseUrl(
|
function resolveCloudflareBaseUrl(
|
||||||
|
|||||||
@@ -99,6 +99,26 @@ describe("builtin providers", () => {
|
|||||||
expect(result?.env).toEqual({ CLOUDFLARE_ACCOUNT_ID: "account-id" });
|
expect(result?.env).toEqual({ CLOUDFLARE_ACCOUNT_ID: "account-id" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression for #6021: a credential carrying only the API key (as stored
|
||||||
|
// by `/login`) must still resolve CLOUDFLARE_ACCOUNT_ID from ambient env.
|
||||||
|
it("falls back to ambient CLOUDFLARE_ACCOUNT_ID when the credential carries only the API key", async () => {
|
||||||
|
const provider = cloudflareWorkersAIProvider();
|
||||||
|
const model = builtinModels().getModels("cloudflare-workers-ai")[0];
|
||||||
|
const auth = provider.auth.apiKey;
|
||||||
|
if (!auth) throw new Error("expected api-key auth");
|
||||||
|
|
||||||
|
const result = await auth.resolve({
|
||||||
|
model,
|
||||||
|
ctx: fakeAuthContext({ CLOUDFLARE_ACCOUNT_ID: "account-id" }),
|
||||||
|
credential: { type: "api_key", key: "cf-key" },
|
||||||
|
});
|
||||||
|
expect(result?.auth).toEqual({
|
||||||
|
apiKey: "cf-key",
|
||||||
|
baseUrl: "https://api.cloudflare.com/client/v4/accounts/account-id/ai/v1",
|
||||||
|
});
|
||||||
|
expect(result?.env).toEqual({ CLOUDFLARE_ACCOUNT_ID: "account-id" });
|
||||||
|
});
|
||||||
|
|
||||||
it("requires Cloudflare AI Gateway account and gateway config and returns scoped env headers", async () => {
|
it("requires Cloudflare AI Gateway account and gateway config and returns scoped env headers", async () => {
|
||||||
const missingGateway = createModels({
|
const missingGateway = createModels({
|
||||||
authContext: fakeAuthContext({ CLOUDFLARE_API_KEY: "cf-key", CLOUDFLARE_ACCOUNT_ID: "account-id" }),
|
authContext: fakeAuthContext({ CLOUDFLARE_API_KEY: "cf-key", CLOUDFLARE_ACCOUNT_ID: "account-id" }),
|
||||||
|
|||||||
Reference in New Issue
Block a user