diff --git a/public/admin/styles.css b/public/admin/styles.css
index 0a259a1..9790c7c 100644
--- a/public/admin/styles.css
+++ b/public/admin/styles.css
@@ -17,8 +17,6 @@
--green-dim: rgba(74, 222, 128, 0.15);
--red: #f87171;
--red-dim: rgba(248, 113, 113, 0.15);
- --copilot: #79c0ff;
- --copilot-dim: rgba(121, 192, 255, 0.12);
--codex: #a78bfa;
--codex-dim: rgba(167, 139, 250, 0.12);
--claude: #fb923c;
@@ -573,10 +571,6 @@ body {
border-radius: var(--radius-sm);
line-height: 1;
}
-.badge-copilot {
- color: var(--copilot);
- background: var(--copilot-dim);
-}
.badge-codex {
color: var(--codex);
background: var(--codex-dim);
diff --git a/src/db/schema.ts b/src/db/schema.ts
index cd21de0..615124d 100644
--- a/src/db/schema.ts
+++ b/src/db/schema.ts
@@ -8,7 +8,7 @@ import {
uniqueIndex,
} from "drizzle-orm/sqlite-core";
-export const providers = ["copilot", "codex", "claude"] as const;
+export const providers = ["codex", "claude"] as const;
export type Provider = (typeof providers)[number];
export const providerAccounts = sqliteTable(
diff --git a/src/http/routes/proxy.ts b/src/http/routes/proxy.ts
index f2549e0..652f9ca 100644
--- a/src/http/routes/proxy.ts
+++ b/src/http/routes/proxy.ts
@@ -14,7 +14,6 @@ import {
readCodexSessionId,
} from "../../providers/proxies/codex-proxy";
import { tryProxyCodexWebSocket } from "../../providers/proxies/codex-websocket";
-import { prepareCopilotProxyRequest } from "../../providers/proxies/copilot-proxy";
import type { UsageRequestSource } from "../../usage/request-outcome";
import {
isTokenUsagePopulated,
@@ -296,24 +295,6 @@ const proxyRequest = async (
break;
}
- case "copilot": {
- const copilotProxy = prepareCopilotProxyRequest({
- endpoint: route.endpoint,
- requestUrl,
- headers,
- bodyText: requestBody,
- bodyJson: requestBodyJson,
- githubAccessToken: account.refreshToken,
- metadata:
- account.metadata?.provider === "copilot" ? account.metadata : null,
- onTokenUsage: usageRecorder.onTokenUsage,
- });
- upstreamUrl = copilotProxy.upstreamUrl;
- requestBody = copilotProxy.bodyText;
- responseTransformer = copilotProxy.transformResponse;
- break;
- }
-
case "claude": {
const claudeProxy = prepareClaudeProxyRequest({
requestUrl,
diff --git a/src/http/utils/request-timeout.ts b/src/http/utils/request-timeout.ts
index e582386..143fc2f 100644
--- a/src/http/utils/request-timeout.ts
+++ b/src/http/utils/request-timeout.ts
@@ -1,8 +1,4 @@
-const streamingProxyPathPrefixes = [
- "/openai/v1/",
- "/anthropic/v1/",
- "/copilot/v1/",
-] as const;
+const streamingProxyPathPrefixes = ["/openai/v1/", "/anthropic/v1/"] as const;
export const resolveRequestIdleTimeout = (pathname: string): number | null => {
for (const prefix of streamingProxyPathPrefixes) {
diff --git a/src/index.ts b/src/index.ts
index 022950e..e662ba7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -51,7 +51,6 @@ app.route("/admin", adminApi);
app.use("/openai/v1/*", requireProxyApiKey);
app.use("/anthropic/v1/*", requireProxyApiKey);
-app.use("/copilot/v1/*", requireProxyApiKey);
app.route("/", proxyRoutes);
export default {
diff --git a/src/providers/constants.ts b/src/providers/constants.ts
index 0e58641..5f2f289 100644
--- a/src/providers/constants.ts
+++ b/src/providers/constants.ts
@@ -8,13 +8,6 @@ export const CODEX_WEBSOCKET_BETA_HEADER = "responses_websockets=2026-02-06";
export const CODEX_ORIGINATOR = "opencode";
export const CODEX_USER_AGENT = "opencode";
-// https://github.com/anomalyco/opencode/blob/d848c9b6a32f408e8b9bf6448b83af05629454d0/packages/opencode/src/plugin/copilot.ts#L121-L131
-// https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/github-copilot-headers.ts#L27-L34
-export const COPILOT_DEFAULT_API_BASE_URL = "https://api.githubcopilot.com";
-export const COPILOT_OPENAI_INTENT = "conversation-edits";
-export const COPILOT_INITIATOR_HEADER = "x-initiator";
-export const COPILOT_VISION_HEADER = "Copilot-Vision-Request";
-
export const ANTHROPIC_API_BASE_URL = "https://api.anthropic.com";
// https://github.com/anomalyco/opencode/blob/d848c9b6a32f408e8b9bf6448b83af05629454d0/packages/opencode/src/provider/provider.ts#L124-L127
// https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/anthropic.ts#L536
diff --git a/src/providers/copilot.ts b/src/providers/copilot.ts
deleted file mode 100644
index 5b74f55..0000000
--- a/src/providers/copilot.ts
+++ /dev/null
@@ -1,386 +0,0 @@
-import { z } from "zod";
-
-import {
- consumeOAuthState,
- createOAuthState,
-} from "../db/repositories/oauth-states";
-import type { ProviderAccountRecord } from "../db/repositories/provider-accounts";
-import { sleep } from "../utils/sleep";
-import { requireOkResponse } from "./http";
-import type { CopilotAccountMetadata } from "./metadata";
-import { generateState } from "./oauth-utils";
-import { parseOAuthStateMetadata } from "./oauth-state";
-import type {
- ProviderAdapter,
- ProviderOAuthCompleteInput,
- ProviderOAuthStartInput,
- ProviderOAuthStartResult,
- ProviderTokenResult,
-} from "./types";
-
-const COPILOT_CLIENT_ID = "Ov23li8tweQw6odWQebz";
-const POLLING_SAFETY_MARGIN_MS = 3000;
-const COPILOT_TOKEN_TTL_MS = 30 * 24 * 60 * 60 * 1000;
-
-const copilotStateMetadataSchema = z.strictObject({
- domain: z.string().min(1),
- enterpriseDomain: z.string().nullable(),
- deviceCode: z.string().min(1),
- interval: z.int().positive(),
- expiresIn: z.int().positive(),
-});
-
-type DeviceCodeResponse = {
- device_code?: string;
- user_code?: string;
- verification_uri?: string;
- interval?: number;
- expires_in?: number;
-};
-
-type DeviceTokenResponse = {
- access_token?: string;
- error?: string;
- interval?: number;
-};
-
-type GithubUserResponse = {
- id?: number;
- login?: string;
- email?: string;
-};
-
-const normalizeDomain = (input: string): string | null => {
- const value = input.trim();
- if (!value) {
- return null;
- }
-
- try {
- const url = value.includes("://")
- ? new URL(value)
- : new URL(`https://${value}`);
- return url.hostname;
- } catch {
- return null;
- }
-};
-
-const resolveCopilotUrls = (domain: string) => ({
- deviceCodeUrl: `https://${domain}/login/device/code`,
- accessTokenUrl: `https://${domain}/login/oauth/access_token`,
- userUrl: `https://api.${domain}/user`,
-});
-
-const parseCopilotEnterpriseApiBaseUrl = (
- enterpriseDomain: string | null
-): string | null => {
- if (!enterpriseDomain) {
- return null;
- }
-
- return `https://copilot-api.${enterpriseDomain}`;
-};
-
-const parseGithubUserId = (value: string | null | undefined): number | null => {
- if (!value) {
- return null;
- }
-
- const parsed = Number(value);
- if (!Number.isFinite(parsed)) {
- return null;
- }
-
- return parsed;
-};
-
-const requestDeviceCode = async (
- domain: string
-): Promise<{
- deviceCode: string;
- userCode: string;
- verificationUri: string;
- interval: number;
- expiresIn: number;
-}> => {
- const urls = resolveCopilotUrls(domain);
- const response = await fetch(urls.deviceCodeUrl, {
- method: "POST",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- "User-Agent": "GitHubCopilotChat/0.35.0",
- },
- body: JSON.stringify({
- client_id: COPILOT_CLIENT_ID,
- scope: "read:user",
- }),
- });
-
- await requireOkResponse(response, "Copilot device flow start failed");
-
- const body = (await response.json()) as DeviceCodeResponse;
- if (
- !body.device_code ||
- !body.user_code ||
- !body.verification_uri ||
- typeof body.interval !== "number" ||
- typeof body.expires_in !== "number"
- ) {
- throw new Error("Copilot device flow response is malformed");
- }
-
- return {
- deviceCode: body.device_code,
- userCode: body.user_code,
- verificationUri: body.verification_uri,
- interval: body.interval,
- expiresIn: body.expires_in,
- };
-};
-
-const pollGithubAccessToken = async (input: {
- domain: string;
- deviceCode: string;
- interval: number;
- expiresAt: number;
-}): Promise
=> {
- const urls = resolveCopilotUrls(input.domain);
- let intervalMilliseconds = input.interval * 1000;
-
- while (Date.now() < input.expiresAt) {
- const response = await fetch(urls.accessTokenUrl, {
- method: "POST",
- headers: {
- Accept: "application/json",
- "Content-Type": "application/json",
- "User-Agent": "GitHubCopilotChat/0.35.0",
- },
- body: JSON.stringify({
- client_id: COPILOT_CLIENT_ID,
- device_code: input.deviceCode,
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
- }),
- });
-
- await requireOkResponse(response, "Copilot device token poll failed");
-
- const body = (await response.json()) as DeviceTokenResponse;
- if (body.access_token) {
- return body.access_token;
- }
-
- if (body.error === "authorization_pending") {
- await sleep(intervalMilliseconds + POLLING_SAFETY_MARGIN_MS);
- continue;
- }
-
- if (body.error === "slow_down") {
- intervalMilliseconds = (body.interval ?? input.interval + 5) * 1000;
- await sleep(intervalMilliseconds + POLLING_SAFETY_MARGIN_MS);
- continue;
- }
-
- if (body.error) {
- throw new Error(`Copilot device flow failed: ${body.error}`);
- }
-
- await sleep(intervalMilliseconds + POLLING_SAFETY_MARGIN_MS);
- }
-
- throw new Error("Copilot device flow timed out");
-};
-
-const requestGithubUser = async (
- domain: string,
- githubAccessToken: string
-): Promise => {
- const urls = resolveCopilotUrls(domain);
- const response = await fetch(urls.userUrl, {
- headers: {
- Accept: "application/json",
- Authorization: `Bearer ${githubAccessToken}`,
- "User-Agent": "GitHubCopilotChat/0.35.0",
- },
- });
-
- if (!response.ok) {
- return null;
- }
-
- return (await response.json()) as GithubUserResponse;
-};
-
-const buildCopilotMetadata = (input: {
- tokenType: string | null;
- scope: string | null;
- enterpriseDomain: string | null;
- accessToken: string;
- user: GithubUserResponse | null;
- existing: CopilotAccountMetadata | null;
-}): CopilotAccountMetadata => ({
- provider: "copilot",
- tokenType: input.tokenType ?? input.existing?.tokenType ?? null,
- scope: input.scope ?? input.existing?.scope ?? null,
- enterpriseDomain: input.enterpriseDomain,
- copilotApiBaseUrl:
- parseCopilotEnterpriseApiBaseUrl(input.enterpriseDomain) ??
- input.existing?.copilotApiBaseUrl ??
- null,
- githubUserId:
- typeof input.user?.id === "number" && Number.isFinite(input.user.id)
- ? String(input.user.id)
- : (input.existing?.githubUserId ?? null),
- githubLogin: input.user?.login ?? input.existing?.githubLogin ?? null,
- githubEmail: input.user?.email ?? input.existing?.githubEmail ?? null,
-});
-
-const buildTokenResult = (input: {
- accessToken: string;
- refreshToken: string;
- expiresAt: number;
- tokenType: string | null;
- scope: string | null;
- enterpriseDomain: string | null;
- user: GithubUserResponse | null;
- existing: CopilotAccountMetadata | null;
- fallbackAccountId: string | null;
- fallbackLabel: string | null;
-}): ProviderTokenResult => {
- const metadata = buildCopilotMetadata({
- tokenType: input.tokenType,
- scope: input.scope,
- enterpriseDomain: input.enterpriseDomain,
- accessToken: input.accessToken,
- user: input.user,
- existing: input.existing,
- });
- const accountId = metadata.githubUserId ?? input.fallbackAccountId;
- const label =
- metadata.githubEmail ??
- metadata.githubLogin ??
- input.fallbackLabel ??
- (accountId ? `github:${accountId}` : "copilot-account");
-
- return {
- accessToken: input.accessToken,
- refreshToken: input.refreshToken,
- expiresAt: input.expiresAt,
- accountId,
- metadata,
- label,
- };
-};
-
-export const copilotAdapter: ProviderAdapter = {
- provider: "copilot",
- async startOAuth(
- input: ProviderOAuthStartInput
- ): Promise {
- const enterpriseDomainInput =
- typeof input.options?.enterpriseDomain === "string"
- ? input.options.enterpriseDomain
- : "";
- const enterpriseDomain = normalizeDomain(enterpriseDomainInput);
- const domain = enterpriseDomain ?? "github.com";
-
- const deviceFlow = await requestDeviceCode(domain);
- const state = generateState();
- await createOAuthState(input.database, {
- state,
- provider: "copilot",
- pkceVerifier: null,
- metadataJson: JSON.stringify({
- domain,
- enterpriseDomain,
- deviceCode: deviceFlow.deviceCode,
- interval: deviceFlow.interval,
- expiresIn: deviceFlow.expiresIn,
- }),
- expiresAt: input.now + deviceFlow.expiresIn * 1000,
- });
-
- return {
- authorizationUrl: deviceFlow.verificationUri,
- state,
- method: "auto",
- instructions: `Enter code: ${deviceFlow.userCode}`,
- };
- },
- async completeOAuth(
- input: ProviderOAuthCompleteInput
- ): Promise {
- const stateRecord = await consumeOAuthState(
- input.database,
- input.state,
- "copilot",
- input.now
- );
- if (!stateRecord) {
- throw new Error("Copilot OAuth state is missing or expired");
- }
-
- const metadata = parseOAuthStateMetadata(
- "Copilot",
- stateRecord.metadataJson,
- copilotStateMetadataSchema
- );
- const githubAccessToken = await pollGithubAccessToken({
- domain: metadata.domain,
- deviceCode: metadata.deviceCode,
- interval: metadata.interval,
- expiresAt: stateRecord.expiresAt,
- });
-
- const user = await requestGithubUser(metadata.domain, githubAccessToken);
-
- return buildTokenResult({
- accessToken: githubAccessToken,
- refreshToken: githubAccessToken,
- expiresAt: input.now + COPILOT_TOKEN_TTL_MS,
- tokenType: null,
- scope: null,
- enterpriseDomain: metadata.enterpriseDomain,
- user,
- existing: null,
- fallbackAccountId: null,
- fallbackLabel: null,
- });
- },
- refreshAccount(
- account: ProviderAccountRecord,
- now: number
- ): Promise {
- const existing =
- account.metadata?.provider === "copilot" ? account.metadata : null;
- const existingGithubUserId = parseGithubUserId(existing?.githubUserId);
- const existingUser: GithubUserResponse | null =
- existingGithubUserId !== null ||
- existing?.githubLogin ||
- existing?.githubEmail
- ? {
- ...(existingGithubUserId !== null
- ? { id: existingGithubUserId }
- : {}),
- ...(existing?.githubLogin ? { login: existing.githubLogin } : {}),
- ...(existing?.githubEmail ? { email: existing.githubEmail } : {}),
- }
- : null;
-
- return Promise.resolve(
- buildTokenResult({
- accessToken: account.refreshToken,
- refreshToken: account.refreshToken,
- expiresAt: now + COPILOT_TOKEN_TTL_MS,
- tokenType: existing?.tokenType ?? null,
- scope: existing?.scope ?? null,
- enterpriseDomain: existing?.enterpriseDomain ?? null,
- user: existingUser,
- existing,
- fallbackAccountId: account.accountId,
- fallbackLabel: account.label,
- })
- );
- },
-};
diff --git a/src/providers/metadata.ts b/src/providers/metadata.ts
index 035305c..5d3c5b1 100644
--- a/src/providers/metadata.ts
+++ b/src/providers/metadata.ts
@@ -26,24 +26,6 @@ const codexMetadataSchema = z.strictObject({
.optional(),
});
-const copilotMetadataSchema = z.strictObject({
- provider: z.literal("copilot"),
- tokenType: z.string().nullable(),
- scope: z.string().nullable(),
- enterpriseDomain: z.string().nullable(),
- copilotApiBaseUrl: z.string().nullable(),
- githubUserId: z.string().nullable(),
- githubLogin: z.string().nullable(),
- githubEmail: z.string().nullable(),
- requestProfile: z
- .strictObject({
- openaiIntent: z.string().optional(),
- initiatorHeader: z.string().optional(),
- visionHeader: z.string().optional(),
- })
- .optional(),
-});
-
const claudeMetadataSchema = z.strictObject({
provider: z.literal("claude"),
tokenType: z.string().nullable(),
@@ -58,12 +40,10 @@ const claudeMetadataSchema = z.strictObject({
export const providerAccountMetadataSchema = z.discriminatedUnion("provider", [
codexMetadataSchema,
- copilotMetadataSchema,
claudeMetadataSchema,
]);
export type CodexAccountMetadata = z.infer;
-export type CopilotAccountMetadata = z.infer;
export type ClaudeAccountMetadata = z.infer;
export type ProviderAccountMetadata = z.infer<
@@ -86,19 +66,6 @@ const buildDefaultProviderAccountMetadata = (
};
}
- if (provider === "copilot") {
- return {
- provider,
- tokenType: null,
- scope: null,
- enterpriseDomain: null,
- copilotApiBaseUrl: null,
- githubUserId: accountId,
- githubLogin: null,
- githubEmail: null,
- };
- }
-
return {
provider,
tokenType: null,
@@ -151,10 +118,6 @@ export const resolveImportedProviderAccountId = (
return metadata.chatgptAccountId;
}
- if (metadata.provider === "copilot") {
- return metadata.githubUserId;
- }
-
return null;
};
diff --git a/src/providers/proxies/copilot-proxy.ts b/src/providers/proxies/copilot-proxy.ts
deleted file mode 100644
index 9942559..0000000
--- a/src/providers/proxies/copilot-proxy.ts
+++ /dev/null
@@ -1,307 +0,0 @@
-import type { CopilotAccountMetadata } from "../metadata";
-
-import {
- COPILOT_DEFAULT_API_BASE_URL,
- COPILOT_INITIATOR_HEADER,
- COPILOT_OPENAI_INTENT,
- COPILOT_VISION_HEADER,
-} from "../constants";
-import {
- requireProxyEndpointRoute,
- type ProxyEndpoint,
-} from "../proxy-endpoints";
-import {
- readOpenAiChatUsageFromResponse,
- readOpenAiChatUsageFromSseEvent,
- readOpenAiResponsesUsageFromResponse,
- readOpenAiResponsesUsageFromSseEvent,
- type TokenUsage,
-} from "../../usage/token-usage";
-import { isObjectRecord, readBooleanField } from "../../utils/object";
-import { transformOpenAiUsageResponse } from "./openai-usage-response";
-
-type CopilotMessageProfile = {
- isVision: boolean;
- isAgent: boolean;
-};
-
-const getArrayField = (value: unknown, key: string): unknown[] | null => {
- if (!isObjectRecord(value)) {
- return null;
- }
-
- const field = value[key];
- return Array.isArray(field) ? field : null;
-};
-
-const arrayHasPartType = (value: unknown, type: string): boolean => {
- if (!Array.isArray(value)) {
- return false;
- }
-
- for (const part of value) {
- if (isObjectRecord(part) && part.type === type) {
- return true;
- }
- }
-
- return false;
-};
-
-const hasNestedImageInToolResult = (value: unknown): boolean => {
- if (!Array.isArray(value)) {
- return false;
- }
-
- for (const part of value) {
- if (
- !isObjectRecord(part) ||
- part.type !== "tool_result" ||
- !Array.isArray(part.content)
- ) {
- continue;
- }
-
- if (arrayHasPartType(part.content, "image")) {
- return true;
- }
- }
-
- return false;
-};
-
-const deriveCompletionsProfile = (jsonBody: unknown): CopilotMessageProfile => {
- const messages = getArrayField(jsonBody, "messages");
- if (!messages || messages.length === 0) {
- return { isVision: false, isAgent: false };
- }
-
- const last = messages.at(-1);
- const lastRole = isObjectRecord(last) ? last.role : null;
- let isVision = false;
- for (const message of messages) {
- if (!isObjectRecord(message)) {
- continue;
- }
-
- if (arrayHasPartType(message.content, "image_url")) {
- isVision = true;
- break;
- }
- }
-
- return {
- isVision,
- isAgent: lastRole !== "user",
- };
-};
-
-const deriveResponsesProfile = (jsonBody: unknown): CopilotMessageProfile => {
- const input = getArrayField(jsonBody, "input");
- if (!input || input.length === 0) {
- return { isVision: false, isAgent: false };
- }
-
- const last = input.at(-1);
- const lastRole = isObjectRecord(last) ? last.role : null;
- let isVision = false;
- for (const item of input) {
- if (!isObjectRecord(item)) {
- continue;
- }
-
- if (arrayHasPartType(item.content, "input_image")) {
- isVision = true;
- break;
- }
- }
-
- return {
- isVision,
- isAgent: lastRole !== "user",
- };
-};
-
-const deriveMessagesProfile = (jsonBody: unknown): CopilotMessageProfile => {
- const messages = getArrayField(jsonBody, "messages");
- if (!messages || messages.length === 0) {
- return { isVision: false, isAgent: false };
- }
-
- const last = messages.at(-1);
- const lastRole = isObjectRecord(last) ? last.role : null;
- const lastContent = isObjectRecord(last) ? last.content : null;
- const hasNonToolCalls =
- Array.isArray(lastContent) &&
- lastContent.some(
- (part) => !isObjectRecord(part) || part.type !== "tool_result"
- );
-
- let isVision = false;
- for (const message of messages) {
- if (!isObjectRecord(message)) {
- continue;
- }
-
- if (
- arrayHasPartType(message.content, "image") ||
- hasNestedImageInToolResult(message.content)
- ) {
- isVision = true;
- break;
- }
- }
-
- return {
- isVision,
- isAgent: !(lastRole === "user" && hasNonToolCalls),
- };
-};
-
-// Copilot requires vision/initiator headers derived from message content.
-// https://github.com/anomalyco/opencode/blob/d848c9b6a32f408e8b9bf6448b83af05629454d0/packages/opencode/src/plugin/copilot.ts#L121-L131
-// https://github.com/badlogic/pi-mono/blob/5c0ec26c28c918c5301f218e8c13fcc540d8e3a4/packages/ai/src/providers/github-copilot-headers.ts#L5-L34
-const deriveCopilotRequestProfile = (
- endpoint: ProxyEndpoint,
- jsonBody: unknown
-): CopilotMessageProfile => {
- if (endpoint === "chat_completions") {
- return deriveCompletionsProfile(jsonBody);
- }
-
- if (endpoint === "responses") {
- return deriveResponsesProfile(jsonBody);
- }
-
- return deriveMessagesProfile(jsonBody);
-};
-
-const buildUpstreamUrl = (
- baseUrl: string,
- endpoint: ProxyEndpoint,
- search: string
-): string => {
- const upstreamSuffix = requireProxyEndpointRoute({
- publicProvider: "github-copilot",
- endpoint,
- }).upstreamSuffix;
- const upstream = new URL(`${upstreamSuffix}${search}`, baseUrl);
- return upstream.toString();
-};
-
-const withChatCompletionsStreamUsage = (
- endpoint: ProxyEndpoint,
- bodyJson: unknown,
- bodyText: string
-): string => {
- if (endpoint !== "chat_completions") {
- return bodyText;
- }
-
- if (!isObjectRecord(bodyJson) || bodyJson.stream !== true) {
- return bodyText;
- }
-
- const streamOptions = isObjectRecord(bodyJson.stream_options)
- ? bodyJson.stream_options
- : null;
- if (streamOptions?.include_usage === true) {
- return bodyText;
- }
-
- return JSON.stringify({
- ...bodyJson,
- stream_options: {
- ...(streamOptions ?? {}),
- include_usage: true,
- },
- });
-};
-
-const transformCopilotResponse = (
- endpoint: ProxyEndpoint,
- response: Response,
- onTokenUsage: ((usage: TokenUsage) => void) | null | undefined,
- isStreamingRequest: boolean
-): Promise => {
- const extractors =
- endpoint === "responses"
- ? {
- extractSseUsage: readOpenAiResponsesUsageFromSseEvent,
- extractJsonUsage: readOpenAiResponsesUsageFromResponse,
- }
- : {
- extractSseUsage: readOpenAiChatUsageFromSseEvent,
- extractJsonUsage: readOpenAiChatUsageFromResponse,
- };
-
- return transformOpenAiUsageResponse({
- response,
- extractSseUsage: extractors.extractSseUsage,
- extractJsonUsage: extractors.extractJsonUsage,
- onTokenUsage,
- isStreamingRequest,
- });
-};
-
-type CopilotProxyPreparationInput = {
- endpoint: ProxyEndpoint;
- requestUrl: URL;
- headers: Headers;
- bodyText: string;
- bodyJson: unknown;
- githubAccessToken: string;
- metadata: CopilotAccountMetadata | null;
- onTokenUsage?: ((usage: TokenUsage) => void) | null;
-};
-
-type CopilotProxyPreparationResult = {
- upstreamUrl: string;
- bodyText: string;
- transformResponse(response: Response): Promise;
-};
-
-export const prepareCopilotProxyRequest = (
- input: CopilotProxyPreparationInput
-): CopilotProxyPreparationResult => {
- const isStreamingRequest =
- readBooleanField(input.bodyJson, "stream") === true;
-
- const profile = deriveCopilotRequestProfile(input.endpoint, input.bodyJson);
- const baseUrl =
- input.metadata?.copilotApiBaseUrl ?? COPILOT_DEFAULT_API_BASE_URL;
-
- input.headers.set("authorization", `Bearer ${input.githubAccessToken}`);
- input.headers.set("Openai-Intent", COPILOT_OPENAI_INTENT);
- input.headers.set(
- COPILOT_INITIATOR_HEADER,
- profile.isAgent ? "agent" : "user"
- );
- if (profile.isVision) {
- input.headers.set(COPILOT_VISION_HEADER, "true");
- } else {
- input.headers.delete(COPILOT_VISION_HEADER);
- }
-
- const bodyText = withChatCompletionsStreamUsage(
- input.endpoint,
- input.bodyJson,
- input.bodyText
- );
-
- return {
- upstreamUrl: buildUpstreamUrl(
- baseUrl,
- input.endpoint,
- input.requestUrl.search
- ),
- bodyText,
- transformResponse: (response: Response): Promise =>
- transformCopilotResponse(
- input.endpoint,
- response,
- input.onTokenUsage,
- isStreamingRequest
- ),
- };
-};
diff --git a/src/providers/proxy-endpoints.ts b/src/providers/proxy-endpoints.ts
index 89ed229..0096a64 100644
--- a/src/providers/proxy-endpoints.ts
+++ b/src/providers/proxy-endpoints.ts
@@ -1,4 +1,4 @@
-export type CanonicalProvider = "openai" | "anthropic" | "github-copilot";
+export type CanonicalProvider = "openai" | "anthropic";
export type ProxyEndpoint = "chat_completions" | "responses" | "messages";
@@ -24,18 +24,6 @@ export const proxyEndpointRoutes: readonly ProxyEndpointRoute[] = [
publicSuffix: "/messages",
upstreamSuffix: "/v1/messages",
},
- {
- publicProvider: "github-copilot",
- endpoint: "chat_completions",
- publicSuffix: "/chat/completions",
- upstreamSuffix: "/chat/completions",
- },
- {
- publicProvider: "github-copilot",
- endpoint: "responses",
- publicSuffix: "/responses",
- upstreamSuffix: "/responses",
- },
] as const;
export const requireProxyEndpointRoute = (input: {
diff --git a/src/providers/proxy-provider.ts b/src/providers/proxy-provider.ts
index 7397028..bbea880 100644
--- a/src/providers/proxy-provider.ts
+++ b/src/providers/proxy-provider.ts
@@ -4,7 +4,7 @@ import type { CanonicalProvider, ProxyRouteSuffix } from "./proxy-endpoints";
type ProxyProviderMapping = {
internalProvider: Provider;
canonicalProvider: CanonicalProvider;
- routeBasePath: "/openai/v1" | "/anthropic/v1" | "/copilot/v1";
+ routeBasePath: "/openai/v1" | "/anthropic/v1";
npm: string;
defaultName: string;
};
@@ -24,13 +24,6 @@ export const proxyProviderMappings: readonly ProxyProviderMapping[] = [
npm: "@ai-sdk/anthropic",
defaultName: "Anthropic",
},
- {
- internalProvider: "copilot",
- canonicalProvider: "github-copilot",
- routeBasePath: "/copilot/v1",
- npm: "@ai-sdk/github-copilot",
- defaultName: "GitHub Copilot",
- },
] as const;
export const requireProxyProviderByCanonical = (
diff --git a/src/providers/registry.ts b/src/providers/registry.ts
index 9761450..3417c9b 100644
--- a/src/providers/registry.ts
+++ b/src/providers/registry.ts
@@ -1,11 +1,9 @@
import type { Provider } from "../db/schema";
import { claudeAdapter } from "./claude";
-import { copilotAdapter } from "./copilot";
import { codexAdapter } from "./codex";
import type { ProviderAdapter } from "./types";
const providerAdapters: Record = {
- copilot: copilotAdapter,
codex: codexAdapter,
claude: claudeAdapter,
};
diff --git a/tests/db/provider-accounts.test.ts b/tests/db/provider-accounts.test.ts
index 944465e..083300f 100644
--- a/tests/db/provider-accounts.test.ts
+++ b/tests/db/provider-accounts.test.ts
@@ -36,8 +36,8 @@ describe("provider account enablement", () => {
const now = Date.now();
await database.insert(providerAccounts).values([
{
- id: "copilot-primary",
- provider: "copilot",
+ id: "claude-primary",
+ provider: "claude",
isPrimary: true,
accessToken: "access-primary",
refreshToken: "refresh-primary",
@@ -46,8 +46,8 @@ describe("provider account enablement", () => {
updatedAt: now,
},
{
- id: "copilot-secondary",
- provider: "copilot",
+ id: "claude-secondary",
+ provider: "claude",
isPrimary: false,
accessToken: "access-secondary",
refreshToken: "refresh-secondary",
@@ -79,40 +79,40 @@ describe("provider account enablement", () => {
const now = Date.now();
const status = await setProviderAccountsEnabled(
database,
- "copilot",
+ "claude",
false,
now
);
expect(status).toEqual({
- provider: "copilot",
+ provider: "claude",
enabled: false,
accountCount: 2,
enabledAccountCount: 0,
});
expect(
(await listProviderAccounts(database))
- .filter((account) => account.provider === "copilot")
+ .filter((account) => account.provider === "claude")
.every((account) => !account.enabled)
).toBe(true);
expect(await listConfiguredProviders(database)).toEqual(["codex"]);
- expect(await findPrimaryProviderAccount(database, "copilot")).toBeNull();
+ expect(await findPrimaryProviderAccount(database, "claude")).toBeNull();
expect(
- await getRoutableProviderAccount(database, "copilot", now)
+ await getRoutableProviderAccount(database, "claude", now)
).toBeNull();
expect(
- await getRoutableProviderAccount(database, "copilot", now, {
- allowedAccountIds: ["copilot-secondary"],
+ await getRoutableProviderAccount(database, "claude", now, {
+ allowedAccountIds: ["claude-secondary"],
})
).toBeNull();
});
test("new accounts inherit disabled state and re-enabling restores routing", async () => {
const now = Date.now();
- await setProviderAccountsEnabled(database, "copilot", false, now);
+ await setProviderAccountsEnabled(database, "claude", false, now);
const created = await upsertProviderAccount(database, {
- provider: "copilot",
+ provider: "claude",
accountId: "new-account",
accessToken: "access-new",
refreshToken: "refresh-new",
@@ -124,15 +124,15 @@ describe("provider account enablement", () => {
const status = await setProviderAccountsEnabled(
database,
- "copilot",
+ "claude",
true,
now + 1
);
expect(status.enabledAccountCount).toBe(3);
- expect(await listConfiguredProviders(database)).toContain("copilot");
+ expect(await listConfiguredProviders(database)).toContain("claude");
expect(
- (await getRoutableProviderAccount(database, "copilot", now + 1))?.id
- ).toBe("copilot-primary");
+ (await getRoutableProviderAccount(database, "claude", now + 1))?.id
+ ).toBe("claude-primary");
expect(await listProviderStatuses(database)).toContainEqual(status);
});
});
diff --git a/tests/domain/models-dev-contract.test.ts b/tests/domain/models-dev-contract.test.ts
index 91bcd81..bf790ca 100644
--- a/tests/domain/models-dev-contract.test.ts
+++ b/tests/domain/models-dev-contract.test.ts
@@ -94,29 +94,6 @@ const upstreamRegistry = {
},
},
},
- "github-copilot": {
- id: "github-copilot",
- name: "GitHub Copilot",
- env: ["GITHUB_TOKEN"],
- models: {
- "gpt-5": {
- id: "gpt-5",
- name: "GPT-5",
- provider: {
- api: "https://api.githubcopilot.com",
- npm: "@ai-sdk/github-copilot",
- },
- },
- "gpt-5-mini": {
- id: "gpt-5-mini",
- name: "GPT-5 Mini",
- provider: {
- api: "https://api.githubcopilot.com",
- npm: "@ai-sdk/github-copilot",
- },
- },
- },
- },
} as const;
describe("models registry contract", () => {
@@ -124,7 +101,7 @@ describe("models registry contract", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record,
baseOrigin: "https://kleis.example/",
- configuredProviders: ["codex", "claude", "copilot"],
+ configuredProviders: ["codex", "claude"],
});
const anthropic = registry.anthropic as {
@@ -136,16 +113,6 @@ describe("models registry contract", () => {
"https://api.anthropic.com/v1"
);
- const copilot = registry["github-copilot"] as {
- env?: string[];
- models?: Record;
- };
- expect(copilot.env).toEqual(["GITHUB_TOKEN"]);
- expect(copilot.models?.["gpt-5"]?.id).toBe("gpt-5");
- expect(copilot.models?.["gpt-5"]?.provider?.api).toBe(
- "https://api.githubcopilot.com"
- );
-
const openai = registry.openai as {
env?: string[];
models?: Record;
@@ -166,7 +133,7 @@ describe("models registry contract", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record,
baseOrigin: "https://kleis.example/",
- configuredProviders: ["codex", "claude", "copilot"],
+ configuredProviders: ["codex", "claude"],
});
const kleis = registry.kleis as {
@@ -182,12 +149,6 @@ describe("models registry contract", () => {
expect(kleis.models?.["gpt-5.6"]).toBeUndefined();
expect(kleis.models?.["gpt-5.6-luna"]?.id).toBe("gpt-5.6-luna");
expect(kleis.models?.["openai/gpt-5.3-codex"]).toBeUndefined();
- expect(kleis.models?.["github-copilot/gpt-5"]?.id).toBe(
- "github-copilot/gpt-5"
- );
- expect(kleis.models?.["github-copilot/gpt-5"]?.provider?.api).toBe(
- "https://kleis.example/copilot/v1"
- );
expect(kleis.models?.["openai/text-embedding-3-large"]).toBeUndefined();
});
@@ -247,7 +208,7 @@ describe("models registry contract", () => {
},
} as unknown as Record,
baseOrigin: "https://kleis.example/",
- configuredProviders: ["codex", "claude", "copilot"],
+ configuredProviders: ["codex", "claude"],
});
const kleis = registry.kleis as {
@@ -283,15 +244,6 @@ describe("models registry contract", () => {
"https://api.anthropic.com/v1"
);
- const copilot = registry["github-copilot"] as {
- env?: string[];
- models?: Record;
- };
- expect(copilot.env).toEqual(["GITHUB_TOKEN"]);
- expect(copilot.models?.["gpt-5"]?.provider?.api).toBe(
- "https://api.githubcopilot.com"
- );
-
const openai = registry.openai as {
env?: string[];
models?: Record;
@@ -307,7 +259,6 @@ describe("models registry contract", () => {
"gpt-5.3-codex-spark"
);
expect(kleis.models?.["anthropic/claude-sonnet-4"]).toBeUndefined();
- expect(kleis.models?.["github-copilot/gpt-5"]).toBeUndefined();
});
test("preserves all upstream providers when none are configured", () => {
@@ -325,8 +276,6 @@ describe("models registry contract", () => {
expect(Object.keys(openai.models ?? {})).toHaveLength(8);
expect(registry.anthropic).toBeDefined();
- expect(registry["github-copilot"]).toBeDefined();
-
const kleis = registry.kleis as {
env?: string[];
models?: Record;
@@ -339,17 +288,16 @@ describe("models registry contract", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record,
baseOrigin: "https://kleis.example/api/kmd_abc123",
- configuredProviders: ["codex", "claude", "copilot"],
+ configuredProviders: ["codex", "claude"],
apiKeyScopes: {
- providerScopes: ["codex", "copilot"],
- modelScopes: ["openai/gpt-5.6-luna", "gpt-5-mini"],
+ providerScopes: ["codex"],
+ modelScopes: ["openai/gpt-5.6-luna"],
accountProviderScopes: null,
},
});
expect(Object.keys(registry).sort()).toEqual([
"anthropic",
- "github-copilot",
"kleis",
"openai",
]);
@@ -373,18 +321,10 @@ describe("models registry contract", () => {
"https://api.openai.com/v1"
);
- const copilot = registry["github-copilot"] as {
- models?: Record;
- };
- expect(Object.keys(copilot.models ?? {})).toEqual(["gpt-5", "gpt-5-mini"]);
-
const kleis = registry.kleis as {
models?: Record;
};
- expect(Object.keys(kleis.models ?? {}).sort()).toEqual([
- "github-copilot/gpt-5-mini",
- "gpt-5.6-luna",
- ]);
+ expect(Object.keys(kleis.models ?? {}).sort()).toEqual(["gpt-5.6-luna"]);
});
test("scoped mode preserves upstream providers unchanged", () => {
@@ -408,13 +348,6 @@ describe("models registry contract", () => {
"https://api.anthropic.com/v1"
);
- const copilot = registry["github-copilot"] as {
- env?: string[];
- models?: Record;
- };
- expect(copilot.env).toEqual(["GITHUB_TOKEN"]);
- expect(Object.keys(copilot.models ?? {})).toEqual(["gpt-5", "gpt-5-mini"]);
-
const kleis = registry.kleis as {
models?: Record;
};
@@ -429,7 +362,7 @@ describe("models registry contract", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record,
baseOrigin: "https://kleis.example/api/kmd_acc123",
- configuredProviders: ["codex", "claude", "copilot"],
+ configuredProviders: ["codex", "claude"],
apiKeyScopes: {
providerScopes: null,
modelScopes: null,
@@ -439,7 +372,6 @@ describe("models registry contract", () => {
expect(Object.keys(registry).sort()).toEqual([
"anthropic",
- "github-copilot",
"kleis",
"openai",
]);
@@ -466,7 +398,7 @@ describe("models registry contract", () => {
const registry = buildProxyModelsRegistry({
upstreamRegistry: upstreamRegistry as unknown as Record,
baseOrigin: "https://kleis.example/api/kmd_acc456",
- configuredProviders: ["codex", "claude", "copilot"],
+ configuredProviders: ["codex", "claude"],
apiKeyScopes: {
providerScopes: ["codex", "claude"],
modelScopes: null,
diff --git a/tests/http/routing-auth-contract.test.ts b/tests/http/routing-auth-contract.test.ts
index 1213c23..9e3a748 100644
--- a/tests/http/routing-auth-contract.test.ts
+++ b/tests/http/routing-auth-contract.test.ts
@@ -61,7 +61,7 @@ describe("proxy route mapping", () => {
});
test("rejects foreign prefixed model candidates", () => {
- const route = resolveProxyRoute("/copilot/v1/responses");
+ const route = resolveProxyRoute("/anthropic/v1/messages");
expect(route).not.toBeNull();
if (!route) {
throw new Error("route missing");
@@ -76,7 +76,6 @@ describe("request idle timeouts", () => {
test("disables Bun idle timeouts for streaming proxy routes", () => {
expect(resolveRequestIdleTimeout("/openai/v1/responses")).toBe(0);
expect(resolveRequestIdleTimeout("/anthropic/v1/messages")).toBe(0);
- expect(resolveRequestIdleTimeout("/copilot/v1/chat/completions")).toBe(0);
});
test("leaves normal app routes on the server default idle timeout", () => {
diff --git a/tests/providers/proxy-contract.test.ts b/tests/providers/proxy-contract.test.ts
index 5377140..c4882e1 100644
--- a/tests/providers/proxy-contract.test.ts
+++ b/tests/providers/proxy-contract.test.ts
@@ -8,13 +8,8 @@ import {
CODEX_RESPONSE_ENDPOINT,
CODEX_USER_AGENT,
CODEX_WEBSOCKET_BETA_HEADER,
- COPILOT_INITIATOR_HEADER,
- COPILOT_VISION_HEADER,
} from "../../src/providers/constants";
-import type {
- CodexAccountMetadata,
- CopilotAccountMetadata,
-} from "../../src/providers/metadata";
+import type { CodexAccountMetadata } from "../../src/providers/metadata";
import { prepareClaudeProxyRequest } from "../../src/providers/proxies/claude-proxy";
import { prepareCodexProxyRequest } from "../../src/providers/proxies/codex-proxy";
import {
@@ -22,7 +17,6 @@ import {
setCodexWebSocketConstructorForTests,
tryProxyCodexWebSocket,
} from "../../src/providers/proxies/codex-websocket";
-import { prepareCopilotProxyRequest } from "../../src/providers/proxies/copilot-proxy";
import { createOpenAiSseUsagePassthrough } from "../../src/providers/proxies/openai-sse-passthrough";
import type { TokenUsage } from "../../src/usage/token-usage";
@@ -2112,281 +2106,6 @@ describe("proxy contract: codex", () => {
});
});
-describe("proxy contract: copilot", () => {
- test("derives user + vision headers for chat completions", () => {
- const headers = new Headers();
- const bodyJson = {
- messages: [
- {
- role: "user",
- content: [
- { type: "text", text: "hello" },
- {
- type: "image_url",
- image_url: { url: "https://example.com/a.png" },
- },
- ],
- },
- ],
- stream: true,
- };
-
- const result = prepareCopilotProxyRequest({
- endpoint: "chat_completions",
- requestUrl: new URL("https://kleis.local/chat/completions?stream=true"),
- headers,
- bodyText: JSON.stringify(bodyJson),
- bodyJson,
- githubAccessToken: "gh-token",
- metadata: null,
- });
-
- expect(headers.get("authorization")).toBe("Bearer gh-token");
- expect(headers.get(COPILOT_INITIATOR_HEADER)).toBe("user");
- expect(headers.get(COPILOT_VISION_HEADER)).toBe("true");
- expect(result.upstreamUrl).toBe(
- "https://api.githubcopilot.com/chat/completions?stream=true"
- );
-
- const transformed = JSON.parse(result.bodyText) as {
- stream_options?: { include_usage?: boolean };
- };
- expect(transformed.stream_options?.include_usage).toBe(true);
- });
-
- test("derives agent and clears vision header for responses", () => {
- const headers = new Headers({
- [COPILOT_VISION_HEADER]: "true",
- });
- const metadata: CopilotAccountMetadata = {
- provider: "copilot",
- tokenType: null,
- scope: null,
- enterpriseDomain: null,
- copilotApiBaseUrl: "https://copilot.internal",
- githubUserId: null,
- githubLogin: null,
- githubEmail: null,
- };
-
- const bodyJson = {
- input: [
- {
- role: "user",
- content: [{ type: "input_text", text: "question" }],
- },
- {
- role: "assistant",
- content: [{ type: "output_text", text: "answer" }],
- },
- ],
- };
-
- const result = prepareCopilotProxyRequest({
- endpoint: "responses",
- requestUrl: new URL("https://kleis.local/responses"),
- headers,
- bodyText: JSON.stringify(bodyJson),
- bodyJson,
- githubAccessToken: "gh-token",
- metadata,
- });
-
- expect(headers.get(COPILOT_INITIATOR_HEADER)).toBe("agent");
- expect(headers.get(COPILOT_VISION_HEADER)).toBeNull();
- expect(result.upstreamUrl).toBe("https://copilot.internal/responses");
- });
-
- const copilotStreamUsageCases = [
- {
- name: "chat-completions stream chunks",
- endpoint: "chat_completions",
- requestUrl: "https://kleis.local/chat/completions?stream=true",
- bodyJson: {
- stream: true,
- messages: [
- {
- role: "user",
- content: [{ type: "text", text: "hello" }],
- },
- ],
- },
- event: {
- id: "cmpl_1",
- object: "chat.completion.chunk",
- choices: [],
- usage: {
- prompt_tokens: 50,
- completion_tokens: 12,
- prompt_tokens_details: {
- cached_tokens: 8,
- },
- },
- },
- expected: {
- inputTokens: 42,
- outputTokens: 12,
- cacheReadTokens: 8,
- cacheWriteTokens: 0,
- },
- },
- {
- name: "responses stream response.done events",
- endpoint: "responses",
- requestUrl: "https://kleis.local/responses?stream=true",
- bodyJson: {
- stream: true,
- input: [
- {
- role: "user",
- content: [{ type: "input_text", text: "hello" }],
- },
- ],
- },
- event: {
- type: "response.done",
- response: {
- usage: {
- input_tokens: 140,
- output_tokens: 50,
- input_tokens_details: {
- cached_tokens: 20,
- },
- },
- },
- },
- expected: {
- inputTokens: 120,
- outputTokens: 50,
- cacheReadTokens: 20,
- cacheWriteTokens: 0,
- },
- },
- ] as const;
-
- for (const testCase of copilotStreamUsageCases) {
- test(`extracts usage from ${testCase.name}`, async () => {
- const capture = createUsageCapture();
- const result = prepareCopilotProxyRequest({
- endpoint: testCase.endpoint,
- requestUrl: new URL(testCase.requestUrl),
- headers: new Headers(),
- bodyText: JSON.stringify(testCase.bodyJson),
- bodyJson: testCase.bodyJson,
- githubAccessToken: "gh-token",
- metadata: null,
- onTokenUsage: capture.onTokenUsage,
- });
-
- const transformed = await result.transformResponse(
- createSseResponse([testCase.event])
- );
- await transformed.text();
-
- expect(capture.read()).toEqual(testCase.expected);
- });
- }
-
- test("extracts usage from responses stream without content-type", async () => {
- const capture = createUsageCapture();
- const bodyJson = {
- stream: true,
- input: [
- {
- role: "user",
- content: [{ type: "input_text", text: "hello" }],
- },
- ],
- };
-
- const result = prepareCopilotProxyRequest({
- endpoint: "responses",
- requestUrl: new URL("https://kleis.local/responses?stream=true"),
- headers: new Headers(),
- bodyText: JSON.stringify(bodyJson),
- bodyJson,
- githubAccessToken: "gh-token",
- metadata: null,
- onTokenUsage: capture.onTokenUsage,
- });
-
- const transformed = await result.transformResponse(
- createSseResponse(
- [
- {
- type: "response.done",
- response: {
- usage: {
- input_tokens: 88,
- output_tokens: 21,
- input_tokens_details: {
- cached_tokens: 8,
- },
- },
- },
- },
- ],
- ""
- )
- );
- await transformed.text();
-
- expect(capture.read()).toEqual({
- inputTokens: 80,
- outputTokens: 21,
- cacheReadTokens: 8,
- cacheWriteTokens: 0,
- });
- });
-
- test("extracts usage from non-streaming responses without content-type", async () => {
- const capture = createUsageCapture();
- const bodyJson = {
- input: [
- {
- role: "user",
- content: [{ type: "input_text", text: "hello" }],
- },
- ],
- };
-
- const result = prepareCopilotProxyRequest({
- endpoint: "responses",
- requestUrl: new URL("https://kleis.local/responses"),
- headers: new Headers(),
- bodyText: JSON.stringify(bodyJson),
- bodyJson,
- githubAccessToken: "gh-token",
- metadata: null,
- onTokenUsage: capture.onTokenUsage,
- });
-
- const transformed = await result.transformResponse(
- new Response(
- new TextEncoder().encode(
- JSON.stringify({
- usage: {
- input_tokens: 54,
- output_tokens: 9,
- input_tokens_details: {
- cached_tokens: 4,
- },
- },
- })
- )
- )
- );
- await transformed.text();
-
- expect(capture.read()).toEqual({
- inputTokens: 50,
- outputTokens: 9,
- cacheReadTokens: 4,
- cacheWriteTokens: 0,
- });
- });
-});
-
describe("proxy contract: claude", () => {
const prepareClaudeUsageRequest = (
onTokenUsage?: ((usage: TokenUsage) => void) | null