mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-21 22:01:41 +00:00
refactor(ai): fix inconsistencies, trim ai code+replace tests, remove unnceccessary tool_result check
This commit is contained in:
parent
0a132a30a1
commit
2419412483
11 changed files with 214 additions and 523 deletions
|
|
@ -483,102 +483,6 @@ function isOAuthToken(apiKey: string): boolean {
|
|||
return apiKey.includes("sk-ant-oat");
|
||||
}
|
||||
|
||||
export interface BuildAnthropicClientOptionsParams {
|
||||
model: Model<"anthropic-messages">;
|
||||
apiKey: string;
|
||||
interleavedThinking: boolean;
|
||||
dynamicHeaders?: Record<string, string>;
|
||||
optionsHeaders?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface AnthropicClientConfig {
|
||||
apiKey: string | null;
|
||||
authToken?: string;
|
||||
baseURL: string;
|
||||
defaultHeaders: Record<string, string>;
|
||||
dangerouslyAllowBrowser: boolean;
|
||||
isOAuthToken: boolean;
|
||||
}
|
||||
|
||||
export function buildAnthropicClientOptions(params: BuildAnthropicClientOptionsParams): AnthropicClientConfig {
|
||||
const { model, apiKey, interleavedThinking, dynamicHeaders, optionsHeaders } = params;
|
||||
|
||||
// Copilot: Bearer auth, selective betas
|
||||
if (model.provider === "github-copilot") {
|
||||
const betaFeatures: string[] = [];
|
||||
if (interleavedThinking) {
|
||||
betaFeatures.push("interleaved-thinking-2025-05-14");
|
||||
}
|
||||
|
||||
const defaultHeaders = mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
dynamicHeaders,
|
||||
model.headers,
|
||||
optionsHeaders,
|
||||
);
|
||||
|
||||
return {
|
||||
apiKey: null,
|
||||
baseURL: model.baseUrl,
|
||||
defaultHeaders,
|
||||
dangerouslyAllowBrowser: true,
|
||||
isOAuthToken: false,
|
||||
};
|
||||
}
|
||||
|
||||
const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
|
||||
if (interleavedThinking) {
|
||||
betaFeatures.push("interleaved-thinking-2025-05-14");
|
||||
}
|
||||
|
||||
const oauthToken = isOAuthToken(apiKey);
|
||||
if (oauthToken) {
|
||||
const defaultHeaders = mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
"anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`,
|
||||
"user-agent": `claude-cli/${claudeCodeVersion} (external, cli)`,
|
||||
"x-app": "cli",
|
||||
},
|
||||
model.headers,
|
||||
optionsHeaders,
|
||||
);
|
||||
|
||||
return {
|
||||
apiKey: null,
|
||||
authToken: apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
defaultHeaders,
|
||||
dangerouslyAllowBrowser: true,
|
||||
isOAuthToken: true,
|
||||
};
|
||||
}
|
||||
|
||||
const defaultHeaders = mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
"anthropic-beta": betaFeatures.join(","),
|
||||
},
|
||||
model.headers,
|
||||
optionsHeaders,
|
||||
);
|
||||
|
||||
return {
|
||||
apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
defaultHeaders,
|
||||
dangerouslyAllowBrowser: true,
|
||||
isOAuthToken: false,
|
||||
};
|
||||
}
|
||||
|
||||
function createClient(
|
||||
model: Model<"anthropic-messages">,
|
||||
apiKey: string,
|
||||
|
|
@ -586,23 +490,78 @@ function createClient(
|
|||
optionsHeaders?: Record<string, string>,
|
||||
dynamicHeaders?: Record<string, string>,
|
||||
): { client: Anthropic; isOAuthToken: boolean } {
|
||||
const config = buildAnthropicClientOptions({
|
||||
model,
|
||||
apiKey,
|
||||
interleavedThinking,
|
||||
dynamicHeaders,
|
||||
optionsHeaders,
|
||||
});
|
||||
// Copilot: Bearer auth, selective betas (no fine-grained-tool-streaming)
|
||||
if (model.provider === "github-copilot") {
|
||||
const betaFeatures: string[] = [];
|
||||
if (interleavedThinking) {
|
||||
betaFeatures.push("interleaved-thinking-2025-05-14");
|
||||
}
|
||||
|
||||
const client = new Anthropic({
|
||||
apiKey: null,
|
||||
authToken: apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
...(betaFeatures.length > 0 ? { "anthropic-beta": betaFeatures.join(",") } : {}),
|
||||
},
|
||||
model.headers,
|
||||
dynamicHeaders,
|
||||
optionsHeaders,
|
||||
),
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: false };
|
||||
}
|
||||
|
||||
const betaFeatures = ["fine-grained-tool-streaming-2025-05-14"];
|
||||
if (interleavedThinking) {
|
||||
betaFeatures.push("interleaved-thinking-2025-05-14");
|
||||
}
|
||||
|
||||
// OAuth: Bearer auth, Claude Code identity headers
|
||||
if (isOAuthToken(apiKey)) {
|
||||
const client = new Anthropic({
|
||||
apiKey: null,
|
||||
authToken: apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
"anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}`,
|
||||
"user-agent": `claude-cli/${claudeCodeVersion} (external, cli)`,
|
||||
"x-app": "cli",
|
||||
},
|
||||
model.headers,
|
||||
optionsHeaders,
|
||||
),
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: true };
|
||||
}
|
||||
|
||||
// API key auth
|
||||
const client = new Anthropic({
|
||||
apiKey: config.apiKey,
|
||||
...(config.authToken ? { authToken: config.authToken } : {}),
|
||||
baseURL: config.baseURL,
|
||||
defaultHeaders: config.defaultHeaders,
|
||||
dangerouslyAllowBrowser: config.dangerouslyAllowBrowser,
|
||||
apiKey,
|
||||
baseURL: model.baseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: mergeHeaders(
|
||||
{
|
||||
accept: "application/json",
|
||||
"anthropic-dangerous-direct-browser-access": "true",
|
||||
"anthropic-beta": betaFeatures.join(","),
|
||||
},
|
||||
model.headers,
|
||||
optionsHeaders,
|
||||
),
|
||||
});
|
||||
|
||||
return { client, isOAuthToken: config.isOAuthToken };
|
||||
return { client, isOAuthToken: false };
|
||||
}
|
||||
|
||||
function buildParams(
|
||||
|
|
|
|||
|
|
@ -1,31 +1,13 @@
|
|||
import type { Message } from "../types.js";
|
||||
|
||||
/**
|
||||
* Infer whether the current request to Copilot is user-initiated or agent-initiated.
|
||||
* Accepts `unknown[]` because providers may pass pre-converted message shapes.
|
||||
*/
|
||||
export function inferCopilotInitiator(messages: unknown[]): "user" | "agent" {
|
||||
if (messages.length === 0) return "user";
|
||||
|
||||
const last = messages[messages.length - 1] as Record<string, unknown>;
|
||||
const role = last.role as string | undefined;
|
||||
if (!role) return "user";
|
||||
|
||||
if (role !== "user") return "agent";
|
||||
|
||||
// Check if last content block is a tool_result (Anthropic-converted shape)
|
||||
const content = last.content;
|
||||
if (Array.isArray(content) && content.length > 0) {
|
||||
const lastBlock = content[content.length - 1] as Record<string, unknown>;
|
||||
if (lastBlock.type === "tool_result") {
|
||||
return "agent";
|
||||
}
|
||||
}
|
||||
|
||||
return "user";
|
||||
// Copilot expects X-Initiator to indicate whether the request is user-initiated
|
||||
// or agent-initiated (e.g. follow-up after assistant/tool messages).
|
||||
export function inferCopilotInitiator(messages: Message[]): "user" | "agent" {
|
||||
const last = messages[messages.length - 1];
|
||||
return last && last.role !== "user" ? "agent" : "user";
|
||||
}
|
||||
|
||||
/** Check whether any message in the conversation contains image content. */
|
||||
// Copilot requires Copilot-Vision-Request header when sending images
|
||||
export function hasCopilotVisionInput(messages: Message[]): boolean {
|
||||
return messages.some((msg) => {
|
||||
if (msg.role === "user" && Array.isArray(msg.content)) {
|
||||
|
|
@ -38,12 +20,8 @@ export function hasCopilotVisionInput(messages: Message[]): boolean {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build dynamic Copilot headers that vary per-request.
|
||||
* Static headers (User-Agent, Editor-Version, etc.) come from model.headers.
|
||||
*/
|
||||
export function buildCopilotDynamicHeaders(params: {
|
||||
messages: unknown[];
|
||||
messages: Message[];
|
||||
hasImages: boolean;
|
||||
}): Record<string, string> {
|
||||
const headers: Record<string, string> = {
|
||||
|
|
|
|||
|
|
@ -508,10 +508,6 @@ export function convertMessages(
|
|||
}
|
||||
|
||||
if (model.provider === "openai") return id.length > 40 ? id.slice(0, 40) : id;
|
||||
// Copilot Claude models route to Claude backend which requires Anthropic ID format
|
||||
if (model.provider === "github-copilot" && model.id.toLowerCase().includes("claude")) {
|
||||
return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue