Skip to content

Commit 23fbb96

Browse files
roomoteedelauna
authored andcommitted
fix: address remaining outbound identity review feedback
1 parent 63fe12c commit 23fbb96

4 files changed

Lines changed: 223 additions & 8 deletions

File tree

src/api/providers/__tests__/openai-codex-native-tool-calls.spec.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { OpenAiCodexHandler } from "../openai-codex"
66
import type { ApiHandlerOptions } from "../../../shared/api"
77
import { NativeToolCallParser } from "../../../core/assistant-message/NativeToolCallParser"
88
import { openAiCodexOAuthManager } from "../../../integrations/openai-codex/oauth"
9+
import { Package } from "../../../shared/package"
910

1011
describe("OpenAiCodexHandler native tool calls", () => {
1112
let handler: OpenAiCodexHandler
@@ -405,4 +406,125 @@ describe("OpenAiCodexHandler native tool calls", () => {
405406
const textChunks = chunks.filter((c) => c.type === "text")
406407
expect(textChunks.map((c) => c.text).join("")).toBe("hello world")
407408
})
409+
410+
it("identifies SDK requests as Zoo Code", async () => {
411+
vi.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
412+
vi.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
413+
414+
const mockCreate = vi.fn().mockResolvedValue({
415+
async *[Symbol.asyncIterator]() {
416+
yield { type: "response.output_text.delta", delta: "ok" }
417+
yield {
418+
type: "response.completed",
419+
response: {
420+
id: "resp_sdk_headers",
421+
status: "completed",
422+
output: [],
423+
usage: { input_tokens: 1, output_tokens: 1 },
424+
},
425+
}
426+
},
427+
})
428+
;(handler as any).client = { responses: { create: mockCreate } }
429+
430+
const stream = handler.createMessage("system", [{ role: "user", content: "headers" } as any], {
431+
taskId: "task-123",
432+
tools: [],
433+
})
434+
for await (const _chunk of stream) {
435+
// drain stream
436+
}
437+
438+
expect(mockCreate).toHaveBeenCalledWith(
439+
expect.anything(),
440+
expect.objectContaining({
441+
headers: expect.objectContaining({
442+
originator: "zoo-code",
443+
session_id: "task-123",
444+
"ChatGPT-Account-Id": "acct_test",
445+
"User-Agent": expect.stringContaining(`zoo-code/${Package.version}`),
446+
}),
447+
}),
448+
)
449+
})
450+
451+
it("identifies fetch fallback requests as Zoo Code", async () => {
452+
vi.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
453+
vi.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
454+
455+
const mockFetch = vi.fn().mockResolvedValue({
456+
ok: true,
457+
body: new ReadableStream({
458+
start(controller) {
459+
controller.enqueue(
460+
new TextEncoder().encode('data: {"type":"response.output_text.delta","delta":"fallback"}\n\n'),
461+
)
462+
controller.enqueue(
463+
new TextEncoder().encode(
464+
'data: {"type":"response.completed","response":{"id":"resp_fetch_headers","status":"completed","output":[],"usage":{"input_tokens":1,"output_tokens":1}}}\n\n',
465+
),
466+
)
467+
controller.close()
468+
},
469+
}),
470+
})
471+
global.fetch = mockFetch as any
472+
;(handler as any).client = {
473+
responses: {
474+
create: vi.fn().mockRejectedValue(new Error("SDK unavailable")),
475+
},
476+
}
477+
478+
const stream = handler.createMessage("system", [{ role: "user", content: "fallback" } as any], {
479+
taskId: "task-456",
480+
tools: [],
481+
})
482+
for await (const _chunk of stream) {
483+
// drain stream
484+
}
485+
486+
expect(mockFetch).toHaveBeenCalledWith(
487+
expect.stringContaining("/responses"),
488+
expect.objectContaining({
489+
headers: expect.objectContaining({
490+
originator: "zoo-code",
491+
session_id: "task-456",
492+
"ChatGPT-Account-Id": "acct_test",
493+
"User-Agent": expect.stringContaining(`zoo-code/${Package.version}`),
494+
}),
495+
}),
496+
)
497+
})
498+
499+
it("identifies completePrompt requests as Zoo Code", async () => {
500+
vi.spyOn(openAiCodexOAuthManager, "getAccessToken").mockResolvedValue("test-token")
501+
vi.spyOn(openAiCodexOAuthManager, "getAccountId").mockResolvedValue("acct_test")
502+
503+
const mockFetch = vi.fn().mockResolvedValue({
504+
ok: true,
505+
json: vi.fn().mockResolvedValue({
506+
output: [
507+
{
508+
type: "message",
509+
content: [{ type: "output_text", text: "done" }],
510+
},
511+
],
512+
}),
513+
})
514+
global.fetch = mockFetch as any
515+
516+
await expect(handler.completePrompt("Test prompt")).resolves.toBe("done")
517+
518+
expect(mockFetch).toHaveBeenCalledWith(
519+
expect.stringContaining("/responses"),
520+
expect.objectContaining({
521+
headers: expect.objectContaining({
522+
originator: "zoo-code",
523+
"ChatGPT-Account-Id": "acct_test",
524+
"User-Agent": expect.stringContaining(`zoo-code/${Package.version}`),
525+
session_id: expect.any(String),
526+
}),
527+
}),
528+
)
529+
})
408530
})
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { Anthropic } from "@anthropic-ai/sdk"
2+
import OpenAI from "openai"
3+
4+
import { UnboundHandler } from "../unbound"
5+
6+
vi.mock("openai", () => {
7+
const createMock = vi.fn()
8+
return {
9+
default: vi.fn(() => ({
10+
chat: {
11+
completions: {
12+
create: createMock,
13+
},
14+
},
15+
})),
16+
}
17+
})
18+
19+
vi.mock("../fetchers/modelCache", () => ({
20+
getModels: vi.fn().mockResolvedValue({
21+
"openai/gpt-4o": {
22+
maxTokens: 4096,
23+
contextWindow: 128000,
24+
supportsImages: true,
25+
supportsPromptCache: false,
26+
inputPrice: 2.5,
27+
outputPrice: 10,
28+
description: "GPT-4o",
29+
},
30+
}),
31+
}))
32+
33+
describe("UnboundHandler", () => {
34+
beforeEach(() => {
35+
vi.clearAllMocks()
36+
})
37+
38+
it("identifies itself as Zoo Code in the Unbound request headers", () => {
39+
new UnboundHandler({
40+
unboundApiKey: "test-key",
41+
unboundModelId: "openai/gpt-4o",
42+
})
43+
44+
expect(OpenAI).toHaveBeenCalledWith(
45+
expect.objectContaining({
46+
defaultHeaders: expect.objectContaining({
47+
"X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "zoo-code" }] }),
48+
}),
49+
}),
50+
)
51+
})
52+
53+
it("identifies itself as Zoo Code in per-request Unbound metadata", async () => {
54+
const mockCreate = (OpenAI as unknown as any)().chat.completions.create
55+
mockCreate.mockResolvedValue({
56+
async *[Symbol.asyncIterator]() {
57+
yield {
58+
choices: [{ delta: { content: "ok" } }],
59+
}
60+
yield {
61+
choices: [{ delta: {} }],
62+
usage: { prompt_tokens: 1, completion_tokens: 1 },
63+
}
64+
},
65+
})
66+
67+
const handler = new UnboundHandler({
68+
unboundApiKey: "test-key",
69+
unboundModelId: "openai/gpt-4o",
70+
})
71+
72+
const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "hello" }]
73+
const stream = handler.createMessage("system", messages, {
74+
taskId: "task-123",
75+
mode: "architect",
76+
tools: [],
77+
})
78+
79+
for await (const _chunk of stream) {
80+
// drain stream
81+
}
82+
83+
expect(mockCreate).toHaveBeenCalledWith(
84+
expect.objectContaining({
85+
unbound_metadata: {
86+
originApp: "zoo-code",
87+
taskId: "task-123",
88+
mode: "architect",
89+
},
90+
}),
91+
)
92+
})
93+
})

src/api/providers/openai-codex.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -358,9 +358,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
358358

359359
// Build Codex-specific headers. Authorization is provided by the SDK apiKey.
360360
const codexHeaders: Record<string, string> = {
361-
originator: "roo-code",
361+
originator: "zoo-code",
362362
session_id: taskId || this.sessionId,
363-
"User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
363+
"User-Agent": `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
364364
...(accountId ? { "ChatGPT-Account-Id": accountId } : {}),
365365
}
366366

@@ -503,9 +503,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
503503
const headers: Record<string, string> = {
504504
"Content-Type": "application/json",
505505
Authorization: `Bearer ${accessToken}`,
506-
originator: "roo-code",
506+
originator: "zoo-code",
507507
session_id: taskId || this.sessionId,
508-
"User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
508+
"User-Agent": `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
509509
}
510510

511511
// Add ChatGPT-Account-Id if available (required for organization subscriptions)
@@ -1199,9 +1199,9 @@ export class OpenAiCodexHandler extends BaseProvider implements SingleCompletion
11991199
const headers: Record<string, string> = {
12001200
"Content-Type": "application/json",
12011201
Authorization: `Bearer ${accessToken}`,
1202-
originator: "roo-code",
1202+
originator: "zoo-code",
12031203
session_id: this.sessionId,
1204-
"User-Agent": `roo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
1204+
"User-Agent": `zoo-code/${Package.version} (${os.platform()} ${os.release()}; ${os.arch()}) node/${process.version.slice(1)}`,
12051205
}
12061206

12071207
// Add ChatGPT-Account-Id if available

src/api/providers/unbound.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class UnboundHandler extends BaseProvider implements SingleCompletionHand
6060
apiKey: apiKey,
6161
defaultHeaders: {
6262
...DEFAULT_HEADERS,
63-
"X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "roo-code" }] }),
63+
"X-Unbound-Metadata": JSON.stringify({ labels: [{ key: "app", value: "zoo-code" }] }),
6464
},
6565
})
6666
}
@@ -142,7 +142,7 @@ export class UnboundHandler extends BaseProvider implements SingleCompletionHand
142142
...(thinking && { thinking }),
143143
stream: true,
144144
stream_options: { include_usage: true },
145-
unbound_metadata: { originApp: "roo-code", taskId: metadata?.taskId, mode: metadata?.mode },
145+
unbound_metadata: { originApp: "zoo-code", taskId: metadata?.taskId, mode: metadata?.mode },
146146
tools: this.convertToolsForOpenAI(metadata?.tools),
147147
tool_choice: metadata?.tool_choice,
148148
}

0 commit comments

Comments
 (0)