clanker-agent/packages/ai/test/github-copilot-anthropic.test.ts
Harivansh Rathi 0250f72976 move pi-mono into companion-cloud as apps/companion-os
- Copy all pi-mono source into apps/companion-os/
- Update Dockerfile to COPY pre-built binary instead of downloading from GitHub Releases
- Update deploy-staging.yml to build pi from source (bun compile) before Docker build
- Add apps/companion-os/** to path triggers
- No more cross-repo dispatch needed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 09:22:50 -08:00

115 lines
3.6 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import { getModel } from "../src/models.js";
import type { Context } from "../src/types.js";
const mockState = vi.hoisted(() => ({
constructorOpts: undefined as Record<string, unknown> | undefined,
streamParams: undefined as Record<string, unknown> | undefined,
}));
vi.mock("@anthropic-ai/sdk", () => {
const fakeStream = {
async *[Symbol.asyncIterator]() {
yield {
type: "message_start",
message: {
usage: { input_tokens: 10, output_tokens: 0 },
},
};
yield {
type: "message_delta",
delta: { stop_reason: "end_turn" },
usage: { output_tokens: 5 },
};
},
finalMessage: async () => ({
usage: {
input_tokens: 10,
output_tokens: 5,
cache_creation_input_tokens: 0,
cache_read_input_tokens: 0,
},
}),
};
class FakeAnthropic {
constructor(opts: Record<string, unknown>) {
mockState.constructorOpts = opts;
}
messages = {
stream: (params: Record<string, unknown>) => {
mockState.streamParams = params;
return fakeStream;
},
};
}
return { default: FakeAnthropic };
});
describe("Copilot Claude via Anthropic Messages", () => {
const context: Context = {
systemPrompt: "You are a helpful assistant.",
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
};
it("uses Bearer auth, Copilot headers, and valid Anthropic Messages payload", async () => {
const model = getModel("github-copilot", "claude-sonnet-4");
expect(model.api).toBe("anthropic-messages");
const { streamAnthropic } = await import("../src/providers/anthropic.js");
const s = streamAnthropic(model, context, {
apiKey: "tid_copilot_session_test_token",
});
for await (const event of s) {
if (event.type === "error") break;
}
const opts = mockState.constructorOpts!;
expect(opts).toBeDefined();
// Auth: apiKey null, authToken for Bearer
expect(opts.apiKey).toBeNull();
expect(opts.authToken).toBe("tid_copilot_session_test_token");
const headers = opts.defaultHeaders as Record<string, string>;
// Copilot static headers from model.headers
expect(headers["User-Agent"]).toContain("GitHubCopilotChat");
expect(headers["Copilot-Integration-Id"]).toBe("vscode-chat");
// Dynamic headers
expect(headers["X-Initiator"]).toBe("user");
expect(headers["Openai-Intent"]).toBe("conversation-edits");
// No fine-grained-tool-streaming (Copilot doesn't support it)
const beta = headers["anthropic-beta"] ?? "";
expect(beta).not.toContain("fine-grained-tool-streaming");
// Payload is valid Anthropic Messages format
const params = mockState.streamParams!;
expect(params.model).toBe("claude-sonnet-4");
expect(params.stream).toBe(true);
expect(params.max_tokens).toBeGreaterThan(0);
expect(Array.isArray(params.messages)).toBe(true);
});
it("includes interleaved-thinking beta when reasoning is enabled", async () => {
const model = getModel("github-copilot", "claude-sonnet-4");
const { streamAnthropic } = await import("../src/providers/anthropic.js");
const s = streamAnthropic(model, context, {
apiKey: "tid_copilot_session_test_token",
interleavedThinking: true,
});
for await (const event of s) {
if (event.type === "error") break;
}
const headers = mockState.constructorOpts!.defaultHeaders as Record<
string,
string
>;
expect(headers["anthropic-beta"]).toContain(
"interleaved-thinking-2025-05-14",
);
});
});