Skip to content

Commit b247302

Browse files
ericc-chopencode
andcommitted
feat: add agent/user X-Initiator header to chat completions and tests. fixes ericc-ch#68
- Refactor header logic in createChatCompletions to set X-Initiator based on message roles - Add Bun test to verify agent/user header logic - Lint and type fixes for strict TypeScript 🤖 Generated with [opencode](https://opencode.ai) Co-Authored-By: opencode <noreply@opencode.ai>
1 parent d364e6b commit b247302

File tree

2 files changed

+68
-1
lines changed

2 files changed

+68
-1
lines changed

src/services/copilot/create-chat-completions.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,21 @@ export const createChatCompletions = async (
1616
&& x.content?.some((x) => x.type === "image_url"),
1717
)
1818

19+
// Agent/user check for X-Initiator header
20+
// Determine if any message is from an agent ("assistant" or "tool")
21+
const isAgentCall = payload.messages.some((msg) =>
22+
["assistant", "tool"].includes(msg.role),
23+
)
24+
25+
// Build headers and add X-Initiator
26+
const headers: Record<string, string> = {
27+
...copilotHeaders(state, enableVision),
28+
"X-Initiator": isAgentCall ? "agent" : "user",
29+
}
30+
1931
const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, {
2032
method: "POST",
21-
headers: copilotHeaders(state, enableVision),
33+
headers,
2234
body: JSON.stringify(payload),
2335
})
2436

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { test, expect, mock } from "bun:test"
2+
3+
import type { ChatCompletionsPayload } from "../src/services/copilot/create-chat-completions"
4+
5+
import { state } from "../src/lib/state"
6+
import { createChatCompletions } from "../src/services/copilot/create-chat-completions"
7+
8+
// Mock state
9+
state.copilotToken = "test-token"
10+
state.vsCodeVersion = "1.0.0"
11+
state.accountType = "individual"
12+
13+
// Helper to mock fetch
14+
const fetchMock = mock(
15+
(_url: string, opts: { headers: Record<string, string> }) => {
16+
return {
17+
ok: true,
18+
json: () => ({ id: "123", object: "chat.completion", choices: [] }),
19+
headers: opts.headers,
20+
}
21+
},
22+
)
23+
;(globalThis as unknown as { fetch: typeof fetch }).fetch = fetchMock
24+
25+
test("sets X-Initiator to agent if tool/assistant present", async () => {
26+
const payload: ChatCompletionsPayload = {
27+
messages: [
28+
{ role: "user", content: "hi" },
29+
{ role: "tool", content: "tool call" },
30+
],
31+
model: "gpt-test",
32+
}
33+
await createChatCompletions(payload)
34+
expect(fetchMock).toHaveBeenCalled()
35+
const headers = (
36+
fetchMock.mock.calls[0][1] as { headers: Record<string, string> }
37+
).headers
38+
expect(headers["X-Initiator"]).toBe("agent")
39+
})
40+
41+
test("sets X-Initiator to user if only user present", async () => {
42+
const payload: ChatCompletionsPayload = {
43+
messages: [
44+
{ role: "user", content: "hi" },
45+
{ role: "user", content: "hello again" },
46+
],
47+
model: "gpt-test",
48+
}
49+
await createChatCompletions(payload)
50+
expect(fetchMock).toHaveBeenCalled()
51+
const headers = (
52+
fetchMock.mock.calls[1][1] as { headers: Record<string, string> }
53+
).headers
54+
expect(headers["X-Initiator"]).toBe("user")
55+
})

0 commit comments

Comments
 (0)