From 63fe12cfd4311c78ffefc4dc96c1f32f4616e786 Mon Sep 17 00:00:00 2001 From: Roomote Date: Wed, 20 May 2026 12:56:15 +0000 Subject: [PATCH 1/2] improve: update outbound request identity to Zoo Code --- .../src/__tests__/CloudShareService.test.ts | 10 +++++----- .../cloud/src/__tests__/WebAuthService.spec.ts | 2 +- packages/cloud/src/utils.ts | 2 +- src/api/providers/__tests__/bedrock.spec.ts | 8 ++++++++ src/api/providers/__tests__/constants.spec.ts | 8 ++++---- .../providers/__tests__/openai-native.spec.ts | 18 ++++++++++++++++++ src/api/providers/__tests__/openai.spec.ts | 6 +++--- src/api/providers/__tests__/openrouter.spec.ts | 6 +++--- src/api/providers/__tests__/requesty.spec.ts | 12 ++++++------ .../__tests__/vercel-ai-gateway.spec.ts | 6 +++--- src/api/providers/bedrock.ts | 2 +- src/api/providers/constants.ts | 6 +++--- src/api/providers/openai-native.ts | 12 ++++++------ src/api/providers/utils/image-generation.ts | 8 ++++---- src/integrations/openai-codex/oauth.ts | 2 +- .../embedders/__tests__/bedrock.spec.ts | 8 ++++++++ .../embedders/__tests__/openrouter.spec.ts | 4 ++-- src/services/code-index/embedders/bedrock.ts | 2 +- .../code-index/embedders/openrouter.ts | 4 ++-- 19 files changed, 80 insertions(+), 46 deletions(-) diff --git a/packages/cloud/src/__tests__/CloudShareService.test.ts b/packages/cloud/src/__tests__/CloudShareService.test.ts index d8a3820b92..3fe67d5ec0 100644 --- a/packages/cloud/src/__tests__/CloudShareService.test.ts +++ b/packages/cloud/src/__tests__/CloudShareService.test.ts @@ -34,12 +34,12 @@ vi.mock("vscode", () => ({ }, })) -vi.mock("../Config", () => ({ +vi.mock("../config", () => ({ getRooCodeApiUrl: () => "https://app.roocode.com", })) vi.mock("../utils", () => ({ - getUserAgent: () => "Roo-Code 1.0.0", + getUserAgent: () => "Zoo-Code 1.0.0", })) describe("CloudShareService", () => { @@ -93,7 +93,7 @@ describe("CloudShareService", () => { headers: { "Content-Type": "application/json", Authorization: "Bearer session-token", - "User-Agent": "Roo-Code 1.0.0", + "User-Agent": "Zoo-Code 1.0.0", }, body: JSON.stringify({ taskId: "task-123", @@ -127,7 +127,7 @@ describe("CloudShareService", () => { headers: { "Content-Type": "application/json", Authorization: "Bearer session-token", - "User-Agent": "Roo-Code 1.0.0", + "User-Agent": "Zoo-Code 1.0.0", }, body: JSON.stringify({ taskId: "task-123", visibility: "public" }), signal: expect.any(AbortSignal), @@ -154,7 +154,7 @@ describe("CloudShareService", () => { headers: { "Content-Type": "application/json", Authorization: "Bearer session-token", - "User-Agent": "Roo-Code 1.0.0", + "User-Agent": "Zoo-Code 1.0.0", }, body: JSON.stringify({ taskId: "task-123", diff --git a/packages/cloud/src/__tests__/WebAuthService.spec.ts b/packages/cloud/src/__tests__/WebAuthService.spec.ts index aa406e400d..4f8045e27c 100644 --- a/packages/cloud/src/__tests__/WebAuthService.spec.ts +++ b/packages/cloud/src/__tests__/WebAuthService.spec.ts @@ -104,7 +104,7 @@ describe("WebAuthService", () => { vi.mocked(getRooCodeApiUrl).mockReturnValue("https://api.test.com") // Setup utils mock - vi.mocked(getUserAgent).mockReturnValue("Roo-Code 1.0.0") + vi.mocked(getUserAgent).mockReturnValue("Zoo-Code 1.0.0") // Setup crypto mock vi.mocked(crypto.randomBytes).mockReturnValue(Buffer.from("test-random-bytes") as never) diff --git a/packages/cloud/src/utils.ts b/packages/cloud/src/utils.ts index bd53fe1ce3..e32fd299d1 100644 --- a/packages/cloud/src/utils.ts +++ b/packages/cloud/src/utils.ts @@ -1,5 +1,5 @@ import type { ExtensionContext } from "vscode" export function getUserAgent(context?: ExtensionContext): string { - return `Roo-Code ${context?.extension?.packageJSON?.version || "unknown"}` + return `Zoo-Code ${context?.extension?.packageJSON?.version || "unknown"}` } diff --git a/src/api/providers/__tests__/bedrock.spec.ts b/src/api/providers/__tests__/bedrock.spec.ts index 975e38af12..4ddf9f77af 100644 --- a/src/api/providers/__tests__/bedrock.spec.ts +++ b/src/api/providers/__tests__/bedrock.spec.ts @@ -73,6 +73,14 @@ describe("AwsBedrockHandler", () => { expect(modelInfo.info.contextWindow).toBeDefined() }) + it("should identify itself as Zoo Code in the AWS client app id", () => { + expect(mockBedrockRuntimeClient).toHaveBeenCalledWith( + expect.objectContaining({ + userAgentAppId: expect.stringMatching(/^ZooCode#/), + }), + ) + }) + it("should use custom ARN when provided", () => { // This test is incompatible with the refactored implementation // The implementation now extracts the model ID from the ARN instead of using the ARN directly diff --git a/src/api/providers/__tests__/constants.spec.ts b/src/api/providers/__tests__/constants.spec.ts index ba28b44e68..87917fcc1e 100644 --- a/src/api/providers/__tests__/constants.spec.ts +++ b/src/api/providers/__tests__/constants.spec.ts @@ -11,16 +11,16 @@ describe("DEFAULT_HEADERS", () => { }) it("should have correct HTTP-Referer value", () => { - expect(DEFAULT_HEADERS["HTTP-Referer"]).toBe("https://github.com/RooVetGit/Roo-Cline") + expect(DEFAULT_HEADERS["HTTP-Referer"]).toBe("https://github.com/Zoo-Code-Org/Zoo-Code") }) it("should have correct X-Title value", () => { - expect(DEFAULT_HEADERS["X-Title"]).toBe("Roo Code") + expect(DEFAULT_HEADERS["X-Title"]).toBe("Zoo Code") }) it("should have correct User-Agent format", () => { const userAgent = DEFAULT_HEADERS["User-Agent"] - expect(userAgent).toBe(`RooCode/${Package.version}`) + expect(userAgent).toBe(`ZooCode/${Package.version}`) // Verify it follows the tool_name/version pattern expect(userAgent).toMatch(/^[a-zA-Z-]+\/\d+\.\d+\.\d+$/) @@ -28,7 +28,7 @@ describe("DEFAULT_HEADERS", () => { it("should have User-Agent with correct tool name", () => { const userAgent = DEFAULT_HEADERS["User-Agent"] - expect(userAgent.startsWith("RooCode/")).toBe(true) + expect(userAgent.startsWith("ZooCode/")).toBe(true) }) it("should have User-Agent with semantic version format", () => { diff --git a/src/api/providers/__tests__/openai-native.spec.ts b/src/api/providers/__tests__/openai-native.spec.ts index 6887da4d20..4337df18cf 100644 --- a/src/api/providers/__tests__/openai-native.spec.ts +++ b/src/api/providers/__tests__/openai-native.spec.ts @@ -17,6 +17,7 @@ import { ApiProviderError } from "@roo-code/types" import { OpenAiNativeHandler } from "../openai-native" import { ApiHandlerOptions } from "../../../shared/api" +import { Package } from "../../../shared/package" // Mock OpenAI client - now everything uses Responses API const mockResponsesCreate = vitest.fn() @@ -99,6 +100,23 @@ describe("OpenAiNativeHandler", () => { expect.objectContaining({ baseURL: "https://custom-openai.example.com/v1" }), ) }) + + it("should identify itself as Zoo Code in request headers", () => { + ;(OpenAI as unknown as ReturnType).mockClear() + new OpenAiNativeHandler({ + apiModelId: "gpt-4.1", + openAiNativeApiKey: "test-key", + }) + + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + defaultHeaders: expect.objectContaining({ + originator: "zoo-code", + "User-Agent": expect.stringContaining(`zoo-code/${Package.version}`), + }), + }), + ) + }) }) describe("createMessage", () => { diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 73b542dbc7..fa2d18b4b2 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -111,9 +111,9 @@ describe("OpenAiHandler", () => { baseURL: expect.any(String), apiKey: expect.any(String), defaultHeaders: { - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - "User-Agent": `RooCode/${Package.version}`, + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", + "User-Agent": `ZooCode/${Package.version}`, }, timeout: expect.any(Number), }) diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts index e03abea635..b53e608510 100644 --- a/src/api/providers/__tests__/openrouter.spec.ts +++ b/src/api/providers/__tests__/openrouter.spec.ts @@ -100,9 +100,9 @@ describe("OpenRouterHandler", () => { baseURL: "https://openrouter.ai/api/v1", apiKey: mockOptions.openRouterApiKey, defaultHeaders: { - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - "User-Agent": `RooCode/${Package.version}`, + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", + "User-Agent": `ZooCode/${Package.version}`, }, }) }) diff --git a/src/api/providers/__tests__/requesty.spec.ts b/src/api/providers/__tests__/requesty.spec.ts index ea6a36b4b4..8b93fa6b1a 100644 --- a/src/api/providers/__tests__/requesty.spec.ts +++ b/src/api/providers/__tests__/requesty.spec.ts @@ -58,9 +58,9 @@ describe("RequestyHandler", () => { baseURL: "https://router.requesty.ai/v1", apiKey: mockOptions.requestyApiKey, defaultHeaders: { - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - "User-Agent": `RooCode/${Package.version}`, + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", + "User-Agent": `ZooCode/${Package.version}`, }, }) }) @@ -73,9 +73,9 @@ describe("RequestyHandler", () => { baseURL: "https://custom.requesty.ai/v1", apiKey: mockOptions.requestyApiKey, defaultHeaders: { - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - "User-Agent": `RooCode/${Package.version}`, + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", + "User-Agent": `ZooCode/${Package.version}`, }, }) }) diff --git a/src/api/providers/__tests__/vercel-ai-gateway.spec.ts b/src/api/providers/__tests__/vercel-ai-gateway.spec.ts index 9ff804e0c4..2fe4390fb5 100644 --- a/src/api/providers/__tests__/vercel-ai-gateway.spec.ts +++ b/src/api/providers/__tests__/vercel-ai-gateway.spec.ts @@ -96,9 +96,9 @@ describe("VercelAiGatewayHandler", () => { baseURL: "https://ai-gateway.vercel.sh/v1", apiKey: mockOptions.vercelAiGatewayApiKey, defaultHeaders: expect.objectContaining({ - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - "User-Agent": expect.stringContaining("RooCode/"), + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", + "User-Agent": expect.stringContaining("ZooCode/"), }), }) }) diff --git a/src/api/providers/bedrock.ts b/src/api/providers/bedrock.ts index 3ceb251003..bb7f0d89c5 100644 --- a/src/api/providers/bedrock.ts +++ b/src/api/providers/bedrock.ts @@ -252,7 +252,7 @@ export class AwsBedrockHandler extends BaseProvider implements SingleCompletionH this.costModelConfig = this.getModel() const clientConfig: BedrockRuntimeClientConfig = { - userAgentAppId: `RooCode#${Package.version}`, + userAgentAppId: `ZooCode#${Package.version}`, region: this.options.awsRegion, // Add the endpoint configuration when specified and enabled ...(this.options.awsBedrockEndpoint && diff --git a/src/api/providers/constants.ts b/src/api/providers/constants.ts index 144ff72349..2269ce9b6c 100644 --- a/src/api/providers/constants.ts +++ b/src/api/providers/constants.ts @@ -1,7 +1,7 @@ import { Package } from "../../shared/package" export const DEFAULT_HEADERS = { - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Cline", - "X-Title": "Roo Code", - "User-Agent": `RooCode/${Package.version}`, + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", + "User-Agent": `ZooCode/${Package.version}`, } diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index 6ce9382763..37545f9979 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -95,12 +95,12 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio } const apiKey = this.options.openAiNativeApiKey ?? "not-provided" // Include originator, session_id, and User-Agent headers for API tracking and debugging - const userAgent = `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}` + const userAgent = `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}` this.client = new OpenAI({ baseURL: this.options.openAiNativeBaseUrl || undefined, apiKey, defaultHeaders: { - originator: "roo-code", + originator: "zoo-code", session_id: this.sessionId, "User-Agent": userAgent, }, @@ -415,9 +415,9 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio // Build per-request headers using taskId when available, falling back to sessionId const taskId = metadata?.taskId - const userAgent = `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}` + const userAgent = `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}` const requestHeaders: Record = { - originator: "roo-code", + originator: "zoo-code", session_id: taskId || this.sessionId, "User-Agent": userAgent, } @@ -563,7 +563,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio // Build per-request headers using taskId when available, falling back to sessionId const taskId = metadata?.taskId - const userAgent = `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}` + const userAgent = `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}` try { const response = await fetch(url, { @@ -571,7 +571,7 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, - originator: "roo-code", + originator: "zoo-code", session_id: taskId || this.sessionId, "User-Agent": userAgent, }, diff --git a/src/api/providers/utils/image-generation.ts b/src/api/providers/utils/image-generation.ts index 16ddb9c815..a191b31f34 100644 --- a/src/api/providers/utils/image-generation.ts +++ b/src/api/providers/utils/image-generation.ts @@ -70,8 +70,8 @@ export async function generateImageWithProvider(options: ImageGenerationOptions) headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Code", - "X-Title": "Roo Code", + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", }, body: JSON.stringify({ model, @@ -216,8 +216,8 @@ export async function generateImageWithImagesApi(options: ImagesApiOptions): Pro headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json", - "HTTP-Referer": "https://github.com/RooVetGit/Roo-Code", - "X-Title": "Roo Code", + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", }, body: JSON.stringify(requestBody), } diff --git a/src/integrations/openai-codex/oauth.ts b/src/integrations/openai-codex/oauth.ts index 0cae6a4164..d29b50fae5 100644 --- a/src/integrations/openai-codex/oauth.ts +++ b/src/integrations/openai-codex/oauth.ts @@ -211,7 +211,7 @@ export function buildAuthorizationUrl(codeChallenge: string, state: string): str state, // Codex-specific parameters codex_cli_simplified_flow: "true", - originator: "roo-code", + originator: "zoo-code", }) return `${OPENAI_CODEX_OAUTH_CONFIG.authorizationEndpoint}?${params.toString()}` diff --git a/src/services/code-index/embedders/__tests__/bedrock.spec.ts b/src/services/code-index/embedders/__tests__/bedrock.spec.ts index de4b3b15bb..77f5d40966 100644 --- a/src/services/code-index/embedders/__tests__/bedrock.spec.ts +++ b/src/services/code-index/embedders/__tests__/bedrock.spec.ts @@ -98,6 +98,14 @@ describe("BedrockEmbedder", () => { const profileEmbedder = new BedrockEmbedder("us-west-2", "dev-profile") expect(profileEmbedder).toBeDefined() }) + + it("should identify itself as Zoo Code in the AWS client app id", () => { + expect(BedrockRuntimeClient).toHaveBeenCalledWith( + expect.objectContaining({ + userAgentAppId: expect.stringMatching(/^ZooCode#/), + }), + ) + }) }) describe("createEmbeddings", () => { diff --git a/src/services/code-index/embedders/__tests__/openrouter.spec.ts b/src/services/code-index/embedders/__tests__/openrouter.spec.ts index 250cc4bf01..2c24b57a9b 100644 --- a/src/services/code-index/embedders/__tests__/openrouter.spec.ts +++ b/src/services/code-index/embedders/__tests__/openrouter.spec.ts @@ -90,8 +90,8 @@ describe("OpenRouterEmbedder", () => { baseURL: "https://openrouter.ai/api/v1", apiKey: mockApiKey, defaultHeaders: { - "HTTP-Referer": "https://github.com/RooCodeInc/Roo-Code", - "X-Title": "Roo Code", + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", }, }) }) diff --git a/src/services/code-index/embedders/bedrock.ts b/src/services/code-index/embedders/bedrock.ts index 7652840c29..b03d062cb7 100644 --- a/src/services/code-index/embedders/bedrock.ts +++ b/src/services/code-index/embedders/bedrock.ts @@ -41,7 +41,7 @@ export class BedrockEmbedder implements IEmbedder { const credentials = this.profile ? fromIni({ profile: this.profile }) : fromNodeProviderChain() this.bedrockClient = new BedrockRuntimeClient({ - userAgentAppId: `RooCode#${Package.version}`, + userAgentAppId: `ZooCode#${Package.version}`, region: this.region, credentials, }) diff --git a/src/services/code-index/embedders/openrouter.ts b/src/services/code-index/embedders/openrouter.ts index 2ffdd7afb6..d98aaeeeb5 100644 --- a/src/services/code-index/embedders/openrouter.ts +++ b/src/services/code-index/embedders/openrouter.ts @@ -76,8 +76,8 @@ export class OpenRouterEmbedder implements IEmbedder { baseURL: this.baseUrl, apiKey: apiKey, defaultHeaders: { - "HTTP-Referer": "https://github.com/RooCodeInc/Roo-Code", - "X-Title": "Roo Code", + "HTTP-Referer": "https://github.com/Zoo-Code-Org/Zoo-Code", + "X-Title": "Zoo Code", }, }) } catch (error) { From 23fbb962fb89c4b6dd68737fdca66e3f01272182 Mon Sep 17 00:00:00 2001 From: Roomote Date: Wed, 20 May 2026 13:40:16 +0000 Subject: [PATCH 2/2] fix: address remaining outbound identity review feedback --- .../openai-codex-native-tool-calls.spec.ts | 122 ++++++++++++++++++ src/api/providers/__tests__/unbound.spec.ts | 93 +++++++++++++ src/api/providers/openai-codex.ts | 12 +- src/api/providers/unbound.ts | 4 +- 4 files changed, 223 insertions(+), 8 deletions(-) create mode 100644 src/api/providers/__tests__/unbound.spec.ts diff --git a/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts b/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts index 0ac1e9b884..80ab4e1887 100644 --- a/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts +++ b/src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts @@ -6,6 +6,7 @@ import { OpenAiCodexHandler } from "../openai-codex" import type { ApiHandlerOptions } from "../../../shared/api" import { NativeToolCallParser } from "../../../core/assistant-message/NativeToolCallParser" import { openAiCodexOAuthManager } from "../../../integrations/openai-codex/oauth" +import { Package } from "../../../shared/package" describe("OpenAiCodexHandler native tool calls", () => { let handler: OpenAiCodexHandler @@ -405,4 +406,125 @@ describe("OpenAiCodexHandler native tool calls", () => { const textChunks = chunks.filter((c) => c.type === "text") expect(textChunks.map((c) => c.text).join("")).toBe("hello world") }) + + it("identifies SDK requests as Zoo Code", async () => { + vi.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token") + vi.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test") + + const mockCreate = vi.fn().mockResolvedValue({ + async *[Symbol.asyncIterator]() { + yield { type: "response.output_text.delta", delta: "ok" } + yield { + type: "response.completed", + response: { + id: "resp_sdk_headers", + status: "completed", + output: [], + usage: { input_tokens: 1, output_tokens: 1 }, + }, + } + }, + }) + ;(handler as any).client = { responses: { create: mockCreate } } + + const stream = handler.createMessage("system", [{ role: "user", content: "headers" } as any], { + taskId: "task-123", + tools: [], + }) + for await (const _chunk of stream) { + // drain stream + } + + expect(mockCreate).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + headers: expect.objectContaining({ + originator: "zoo-code", + session_id: "task-123", + "ChatGPT-Account-Id": "acct_test", + "User-Agent": expect.stringContaining(`zoo-code/${Package.version}`), + }), + }), + ) + }) + + it("identifies fetch fallback requests as Zoo Code", async () => { + vi.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token") + vi.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test") + + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + body: new ReadableStream({ + start(controller) { + controller.enqueue( + new TextEncoder().encode('data: {"type":"response.output_text.delta","delta":"fallback"}\n\n'), + ) + controller.enqueue( + new TextEncoder().encode( + 'data: {"type":"response.completed","response":{"id":"resp_fetch_headers","status":"completed","output":[],"usage":{"input_tokens":1,"output_tokens":1}}}\n\n', + ), + ) + controller.close() + }, + }), + }) + global.fetch = mockFetch as any + ;(handler as any).client = { + responses: { + create: vi.fn().mockRejectedValue(new Error("SDK unavailable")), + }, + } + + const stream = handler.createMessage("system", [{ role: "user", content: "fallback" } as any], { + taskId: "task-456", + tools: [], + }) + for await (const _chunk of stream) { + // drain stream + } + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining("/responses"), + expect.objectContaining({ + headers: expect.objectContaining({ + originator: "zoo-code", + session_id: "task-456", + "ChatGPT-Account-Id": "acct_test", + "User-Agent": expect.stringContaining(`zoo-code/${Package.version}`), + }), + }), + ) + }) + + it("identifies completePrompt requests as Zoo Code", async () => { + vi.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token") + vi.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test") + + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: vi.fn().mockResolvedValue({ + output: [ + { + type: "message", + content: [{ type: "output_text", text: "done" }], + }, + ], + }), + }) + global.fetch = mockFetch as any + + await expect(handler.completePrompt("Test prompt")).resolves.toBe("done") + + expect(mockFetch).toHaveBeenCalledWith( + expect.stringContaining("/responses"), + expect.objectContaining({ + headers: expect.objectContaining({ + originator: "zoo-code", + "ChatGPT-Account-Id": "acct_test", + "User-Agent": expect.stringContaining(`zoo-code/${Package.version}`), + session_id: expect.any(String), + }), + }), + ) + }) }) diff --git a/src/api/providers/__tests__/unbound.spec.ts b/src/api/providers/__tests__/unbound.spec.ts new file mode 100644 index 0000000000..2619681909 --- /dev/null +++ b/src/api/providers/__tests__/unbound.spec.ts @@ -0,0 +1,93 @@ +import { Anthropic } from "@anthropic-ai/sdk" +import OpenAI from "openai" + +import { UnboundHandler } from "../unbound" + +vi.mock("openai", () => { + const createMock = vi.fn() + return { + default: vi.fn(() => ({ + chat: { + completions: { + create: createMock, + }, + }, + })), + } +}) + +vi.mock("../fetchers/modelCache", () => ({ + getModels: vi.fn().mockResolvedValue({ + "openai/gpt-4o": { + maxTokens: 4096, + contextWindow: 128000, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 2.5, + outputPrice: 10, + description: "GPT-4o", + }, + }), +})) + +describe("UnboundHandler", () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it("identifies itself as Zoo Code in the Unbound request headers", () => { + new UnboundHandler({ + unboundApiKey: "test-key", + unboundModelId: "openai/gpt-4o", + }) + + expect(OpenAI).toHaveBeenCalledWith( + expect.objectContaining({ + defaultHeaders: expect.objectContaining({ + "X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "zoo-code" }] }), + }), + }), + ) + }) + + it("identifies itself as Zoo Code in per-request Unbound metadata", async () => { + const mockCreate = (OpenAI as unknown as any)().chat.completions.create + mockCreate.mockResolvedValue({ + async *[Symbol.asyncIterator]() { + yield { + choices: [{ delta: { content: "ok" } }], + } + yield { + choices: [{ delta: {} }], + usage: { prompt_tokens: 1, completion_tokens: 1 }, + } + }, + }) + + const handler = new UnboundHandler({ + unboundApiKey: "test-key", + unboundModelId: "openai/gpt-4o", + }) + + const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "hello" }] + const stream = handler.createMessage("system", messages, { + taskId: "task-123", + mode: "architect", + tools: [], + }) + + for await (const _chunk of stream) { + // drain stream + } + + expect(mockCreate).toHaveBeenCalledWith( + expect.objectContaining({ + unbound_metadata: { + originApp: "zoo-code", + taskId: "task-123", + mode: "architect", + }, + }), + ) + }) +}) diff --git a/src/api/providers/openai-codex.ts b/src/api/providers/openai-codex.ts index 9dfb37bc72..b5891c0e47 100644 --- a/src/api/providers/openai-codex.ts +++ b/src/api/providers/openai-codex.ts @@ -358,9 +358,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion // Build Codex-specific headers. Authorization is provided by the SDK apiKey. const codexHeaders: Record = { - originator: "roo-code", + originator: "zoo-code", session_id: taskId || this.sessionId, - "User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`, + "User-Agent": `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`, ...(accountId ? { "ChatGPT-Account-Id": accountId } : {}), } @@ -503,9 +503,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion const headers: Record = { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, - originator: "roo-code", + originator: "zoo-code", session_id: taskId || this.sessionId, - "User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`, + "User-Agent": `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`, } // Add ChatGPT-Account-Id if available (required for organization subscriptions) @@ -1199,9 +1199,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion const headers: Record = { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, - originator: "roo-code", + originator: "zoo-code", session_id: this.sessionId, - "User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`, + "User-Agent": `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`, } // Add ChatGPT-Account-Id if available diff --git a/src/api/providers/unbound.ts b/src/api/providers/unbound.ts index d50bfcc85d..a1de7dfa14 100644 --- a/src/api/providers/unbound.ts +++ b/src/api/providers/unbound.ts @@ -60,7 +60,7 @@ export class UnboundHandler extends BaseProvider implements SingleCompletionHand apiKey: apiKey, defaultHeaders: { ...DEFAULT_HEADERS, - "X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "roo-code" }] }), + "X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "zoo-code" }] }), }, }) } @@ -142,7 +142,7 @@ export class UnboundHandler extends BaseProvider implements SingleCompletionHand ...(thinking && { thinking }), stream: true, stream_options: { include_usage: true }, - unbound_metadata: { originApp: "roo-code", taskId: metadata?.taskId, mode: metadata?.mode }, + unbound_metadata: { originApp: "zoo-code", taskId: metadata?.taskId, mode: metadata?.mode }, tools: this.convertToolsForOpenAI(metadata?.tools), tool_choice: metadata?.tool_choice, }