From a17066bceca9c6686cfcebf898f19f2b70149a40 Mon Sep 17 00:00:00 2001 From: Lance Tuller Date: Wed, 1 Jul 2026 10:49:01 -0400 Subject: [PATCH 1/3] fix(coreweave): harden project header setup --- packages/ai/CHANGELOG.md | 4 ++ packages/ai/src/registry/coreweave.ts | 4 +- packages/ai/test/coreweave-login.test.ts | 16 +++--- .../ai/test/coreweave-project-header.test.ts | 49 ++++++++++++++++++- packages/catalog/CHANGELOG.md | 4 ++ packages/catalog/src/wire/coreweave.ts | 2 +- 6 files changed, 69 insertions(+), 10 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index ae883f27d..79596b449 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Changed + +- Clarified CoreWeave Serverless Inference login instructions to persist `COREWEAVE_PROJECT` in the user's shell startup file. + ## [16.2.12] - 2026-07-01 ### Changed diff --git a/packages/ai/src/registry/coreweave.ts b/packages/ai/src/registry/coreweave.ts index d07c2ff4c..27e1d2589 100644 --- a/packages/ai/src/registry/coreweave.ts +++ b/packages/ai/src/registry/coreweave.ts @@ -6,13 +6,13 @@ import type { OAuthLoginCallbacks } from "./oauth/types"; import type { ProviderDefinition } from "./types"; const PROJECT_SETUP_INSTRUCTIONS = - "Create or select a CoreWeave Serverless Inference project, set COREWEAVE_PROJECT=/ for the OpenAI-Project header, then copy your API key from account settings"; + "Create or select a CoreWeave Serverless Inference project, add export COREWEAVE_PROJECT=/ to your shell startup file (for example ~/.zshrc, ~/.bashrc, or your shell's profile/rc file) for the OpenAI-Project header, then copy your API key from account settings"; function requireCoreWeaveProjectHeaders(): Record { const headers = coreWeaveProjectHeaders($env); if (!headers) { throw new AIError.ConfigurationError( - "CoreWeave Serverless Inference requires OpenAI-Project. Set COREWEAVE_PROJECT=/ before running /login coreweave.", + "CoreWeave Serverless Inference requires OpenAI-Project. Set COREWEAVE_PROJECT=/ before running /login coreweave. To persist it, add export COREWEAVE_PROJECT=/ to your shell startup file, such as ~/.zshrc, ~/.bashrc, or your shell's profile/rc file.", ); } return headers; diff --git a/packages/ai/test/coreweave-login.test.ts b/packages/ai/test/coreweave-login.test.ts index 2516fc4e2..320c92a28 100644 --- a/packages/ai/test/coreweave-login.test.ts +++ b/packages/ai/test/coreweave-login.test.ts @@ -55,6 +55,9 @@ describe("CoreWeave Serverless Inference login", () => { expect(apiKey).toBe("coreweave-test-key"); expect(authMessages[0]).toContain("COREWEAVE_PROJECT=/"); + expect(authMessages[0]).toContain("~/.zshrc"); + expect(authMessages[0]).toContain("~/.bashrc"); + expect(authMessages[0]).toContain("your shell's profile/rc file"); expect(authMessages[0]).toContain("OpenAI-Project"); expect(fetchMock).toHaveBeenCalledTimes(1); }); @@ -71,12 +74,13 @@ describe("CoreWeave Serverless Inference login", () => { }); }); - await expect( - loginCoreWeave({ - onPrompt: async () => "coreweave-test-key", - fetch: fetchMock, - }), - ).rejects.toThrow("Set COREWEAVE_PROJECT=/"); + const login = loginCoreWeave({ + onPrompt: async () => "coreweave-test-key", + fetch: fetchMock, + }); + + await expect(login).rejects.toThrow("Set COREWEAVE_PROJECT=/"); + await expect(login).rejects.toThrow("your shell's profile/rc file"); expect(fetchMock).toHaveBeenCalledTimes(0); }); diff --git a/packages/ai/test/coreweave-project-header.test.ts b/packages/ai/test/coreweave-project-header.test.ts index a01c63ee1..2c136aff8 100644 --- a/packages/ai/test/coreweave-project-header.test.ts +++ b/packages/ai/test/coreweave-project-header.test.ts @@ -1,5 +1,8 @@ -import { afterEach, describe, expect, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "bun:test"; +import { completeSimple } from "@oh-my-pi/pi-ai"; import { resolveOpenAIRequestSetup } from "@oh-my-pi/pi-ai/providers/openai-shared"; +import type { Context, FetchImpl } from "@oh-my-pi/pi-ai/types"; +import { getBundledModel } from "@oh-my-pi/pi-catalog/models"; const COREWEAVE_ENV_KEYS = ["COREWEAVE_PROJECT", "WANDB_INFERENCE_PROJECT", "WANDB_ENTITY", "WANDB_PROJECT"] as const; const ORIGINAL_ENV = new Map(COREWEAVE_ENV_KEYS.map(key => [key, Bun.env[key]])); @@ -19,6 +22,22 @@ afterEach(() => { restoreCoreWeaveEnv(); }); +const context: Context = { messages: [{ role: "user", content: "hi", timestamp: 0 }] }; + +function chatSse(): Response { + const chunk = (delta: unknown, finish: string | null) => + JSON.stringify({ + id: "x", + object: "chat.completion.chunk", + created: 0, + choices: [{ index: 0, delta, finish_reason: finish }], + }); + return new Response(`data: ${chunk({ content: "ok" }, null)}\n\ndata: ${chunk({}, "stop")}\n\ndata: [DONE]\n\n`, { + status: 200, + headers: { "content-type": "text/event-stream" }, + }); +} + describe("CoreWeave Serverless Inference project header", () => { const coreWeaveModel = { provider: "coreweave", @@ -66,4 +85,32 @@ describe("CoreWeave Serverless Inference project header", () => { expect(setup.headers["openai-project"]).toBe("explicit/team"); expect(setup.headers["OpenAI-Project"]).toBeUndefined(); }); + + test("uses COREWEAVE_PROJECT when an explicit blank project header is present", () => { + Bun.env.COREWEAVE_PROJECT = "team/project"; + + const setup = resolveOpenAIRequestSetup(coreWeaveModel, { + apiKey: "coreweave-key", + extraHeaders: { "openai-project": " " }, + messages: [], + }); + + expect(setup.headers["OpenAI-Project"]).toBe("team/project"); + }); + + test("sends OpenAI-Project on chat-completions requests", async () => { + Bun.env.COREWEAVE_PROJECT = "team/project"; + const requestHeaders: Headers[] = []; + const fetchMock: FetchImpl = vi.fn(async (_input: string | URL | Request, init?: RequestInit) => { + requestHeaders.push(new Headers(init?.headers)); + return chatSse(); + }); + + await completeSimple(getBundledModel("coreweave", "zai-org/GLM-5.2"), context, { + apiKey: "coreweave-key", + fetch: fetchMock, + }); + + expect(requestHeaders[0]?.get("OpenAI-Project")).toBe("team/project"); + }); }); diff --git a/packages/catalog/CHANGELOG.md b/packages/catalog/CHANGELOG.md index 912cca759..aa9a0b41f 100644 --- a/packages/catalog/CHANGELOG.md +++ b/packages/catalog/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed CoreWeave Serverless Inference project-header detection so blank `OpenAI-Project` overrides do not block `COREWEAVE_PROJECT` fallback. + ## [16.2.12] - 2026-07-01 ### Breaking Changes diff --git a/packages/catalog/src/wire/coreweave.ts b/packages/catalog/src/wire/coreweave.ts index 8dadadb05..3f642c2d5 100644 --- a/packages/catalog/src/wire/coreweave.ts +++ b/packages/catalog/src/wire/coreweave.ts @@ -38,5 +38,5 @@ export function coreWeaveProjectHeaders(env: CoreWeaveProjectEnv): Record): boolean { const normalized = COREWEAVE_PROJECT_HEADER.toLowerCase(); - return Object.keys(headers).some(header => header.toLowerCase() === normalized); + return Object.entries(headers).some(([header, value]) => header.toLowerCase() === normalized && value.trim() !== ""); } From 526fd875a0c285d8e8cec2d1d729a19858b266a5 Mon Sep 17 00:00:00 2001 From: Lance Tuller Date: Wed, 1 Jul 2026 11:02:39 -0400 Subject: [PATCH 2/3] fix(coreweave): drop blank project header overrides --- packages/ai/src/providers/openai-shared.ts | 2 ++ packages/ai/test/coreweave-project-header.test.ts | 4 +++- packages/catalog/src/wire/coreweave.ts | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/ai/src/providers/openai-shared.ts b/packages/ai/src/providers/openai-shared.ts index b6cd43daa..6877682b2 100644 --- a/packages/ai/src/providers/openai-shared.ts +++ b/packages/ai/src/providers/openai-shared.ts @@ -17,6 +17,7 @@ import { COREWEAVE_PROJECT_HEADER, coreWeaveProjectHeaders, hasCoreWeaveProjectHeader, + removeBlankCoreWeaveProjectHeaders, } from "@oh-my-pi/pi-catalog/wire/coreweave"; import { parseGitHubCopilotApiKey } from "@oh-my-pi/pi-catalog/wire/github-copilot"; import { @@ -160,6 +161,7 @@ function resolveSakanaRequestBaseUrl(): string | undefined { } function applyCoreWeaveProjectHeader(headers: Record): void { + removeBlankCoreWeaveProjectHeaders(headers); if (hasCoreWeaveProjectHeader(headers)) { return; } diff --git a/packages/ai/test/coreweave-project-header.test.ts b/packages/ai/test/coreweave-project-header.test.ts index 2c136aff8..881ff5fe3 100644 --- a/packages/ai/test/coreweave-project-header.test.ts +++ b/packages/ai/test/coreweave-project-header.test.ts @@ -96,9 +96,10 @@ describe("CoreWeave Serverless Inference project header", () => { }); expect(setup.headers["OpenAI-Project"]).toBe("team/project"); + expect(setup.headers["openai-project"]).toBeUndefined(); }); - test("sends OpenAI-Project on chat-completions requests", async () => { + test("sends one OpenAI-Project header on chat-completions requests when a blank override is present", async () => { Bun.env.COREWEAVE_PROJECT = "team/project"; const requestHeaders: Headers[] = []; const fetchMock: FetchImpl = vi.fn(async (_input: string | URL | Request, init?: RequestInit) => { @@ -109,6 +110,7 @@ describe("CoreWeave Serverless Inference project header", () => { await completeSimple(getBundledModel("coreweave", "zai-org/GLM-5.2"), context, { apiKey: "coreweave-key", fetch: fetchMock, + headers: { "openai-project": " " }, }); expect(requestHeaders[0]?.get("OpenAI-Project")).toBe("team/project"); diff --git a/packages/catalog/src/wire/coreweave.ts b/packages/catalog/src/wire/coreweave.ts index 3f642c2d5..c12ad4f10 100644 --- a/packages/catalog/src/wire/coreweave.ts +++ b/packages/catalog/src/wire/coreweave.ts @@ -40,3 +40,12 @@ export function hasCoreWeaveProjectHeader(headers: Record): bool const normalized = COREWEAVE_PROJECT_HEADER.toLowerCase(); return Object.entries(headers).some(([header, value]) => header.toLowerCase() === normalized && value.trim() !== ""); } + +export function removeBlankCoreWeaveProjectHeaders(headers: Record): void { + const normalized = COREWEAVE_PROJECT_HEADER.toLowerCase(); + for (const [header, value] of Object.entries(headers)) { + if (header.toLowerCase() === normalized && value.trim() === "") { + delete headers[header]; + } + } +} From 1f89800b52eb6763eb008a1a4c4e71abbf33f2ea Mon Sep 17 00:00:00 2001 From: Lance Tuller Date: Wed, 1 Jul 2026 11:03:26 -0400 Subject: [PATCH 3/3] chore(coreweave): share project setup guidance --- packages/ai/src/registry/coreweave.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ai/src/registry/coreweave.ts b/packages/ai/src/registry/coreweave.ts index 27e1d2589..774d556fa 100644 --- a/packages/ai/src/registry/coreweave.ts +++ b/packages/ai/src/registry/coreweave.ts @@ -5,14 +5,15 @@ import { createApiKeyLogin } from "./api-key-login"; import type { OAuthLoginCallbacks } from "./oauth/types"; import type { ProviderDefinition } from "./types"; -const PROJECT_SETUP_INSTRUCTIONS = - "Create or select a CoreWeave Serverless Inference project, add export COREWEAVE_PROJECT=/ to your shell startup file (for example ~/.zshrc, ~/.bashrc, or your shell's profile/rc file) for the OpenAI-Project header, then copy your API key from account settings"; +const PROJECT_PERSIST_INSTRUCTIONS = + "add export COREWEAVE_PROJECT=/ to your shell startup file (for example ~/.zshrc, ~/.bashrc, or your shell's profile/rc file)"; +const PROJECT_SETUP_INSTRUCTIONS = `Create or select a CoreWeave Serverless Inference project, ${PROJECT_PERSIST_INSTRUCTIONS} for the OpenAI-Project header, then copy your API key from account settings`; function requireCoreWeaveProjectHeaders(): Record { const headers = coreWeaveProjectHeaders($env); if (!headers) { throw new AIError.ConfigurationError( - "CoreWeave Serverless Inference requires OpenAI-Project. Set COREWEAVE_PROJECT=/ before running /login coreweave. To persist it, add export COREWEAVE_PROJECT=/ to your shell startup file, such as ~/.zshrc, ~/.bashrc, or your shell's profile/rc file.", + `CoreWeave Serverless Inference requires OpenAI-Project. Set COREWEAVE_PROJECT=/ before running /login coreweave. To persist it, ${PROJECT_PERSIST_INSTRUCTIONS}.`, ); } return headers;