Promote AgentCompany-New to the repository root and retire the old app

The legacy application - its src, tests, landing site, promo assets, docs,
release tooling, CI workflows, and root configs - leaves the tree; it stays
reachable in history. Everything under AgentCompany-New moves up unchanged:
same package name, so the production userData/database path is untouched,
and e2e/build scripts already resolved paths relative to the project. The
developer cred.txt stays gitignored at its new location. Full verify:ci
passes from the new root.
This commit is contained in:
ZacharyZhang-NY committed 2026-07-07 00:04:13 -04:00
1 parent 2350f744e3
commit 07d0b4587d
1904 files changed
+1133 -282075

No files matched your search

@@ -0,0 +1,38 @@
/** Anthropic Messages API encoding/decoding, shared by anthropic + claude-pro. */
import { isRecord, type JsonRecord } from "../provider-http";
import { type AgentTurnMessage, type ToolCall, type ToolDefinition } from "../provider-tool-turn";
export function anthropicToolDefs(tools: ToolDefinition[]): unknown[] {
return tools.map((tool) => ({ name: tool.name, description: tool.description, input_schema: tool.parameters }));
}
export function toAnthropicMessages(messages: AgentTurnMessage[]): unknown[] {
const out: unknown[] = [];
for (const message of messages) {
if (message.role === "user") {
out.push({ role: "user", content: [{ type: "text", text: message.text }] });
} else if (message.role === "assistant") {
const content: unknown[] = [];
if (message.text) content.push({ type: "text", text: message.text });
for (const call of message.toolCalls) content.push({ type: "tool_use", id: call.id, name: call.name, input: call.arguments });
out.push({ role: "assistant", content });
} else {
out.push({ role: "user", content: message.results.map((result) => ({ type: "tool_result", tool_use_id: result.toolCallId, content: result.output })) });
}
}
return out;
}
export function parseAnthropicContent(data: JsonRecord): { text: string; toolCalls: ToolCall[] } {
const content = Array.isArray(data.content) ? data.content : [];
const textParts: string[] = [];
const toolCalls: ToolCall[] = [];
for (const block of content) {
if (!isRecord(block)) continue;
if (block.type === "text" && typeof block.text === "string") textParts.push(block.text);
else if (block.type === "tool_use" && typeof block.id === "string" && typeof block.name === "string") {
toolCalls.push({ id: block.id, name: block.name, arguments: isRecord(block.input) ? block.input : {} });
}
}
return { text: textParts.join("\n").trim(), toolCalls };
}
@@ -0,0 +1,88 @@
/**
* AWS Signature Version 4 signing for Bedrock Runtime requests. Implements the
* canonical-request / string-to-sign / signing-key chain with node:crypto so no
* AWS SDK is needed.
*/
import { createHash, createHmac } from "node:crypto";
export interface AwsSigner {
region: string;
service: string;
accessKeyId: string;
secretAccessKey: string;
sessionToken?: string | null;
}
function sha256Hex(data: string): string {
return createHash("sha256").update(data, "utf8").digest("hex");
}
function hmac(key: Buffer | string, data: string): Buffer {
return createHmac("sha256", typeof key === "string" ? Buffer.from(key, "utf8") : key).update(data, "utf8").digest();
}
/** RFC 3986 encoding used by SigV4; keeps "/" only when encodeSlash is false. */
function awsUriEncode(value: string, encodeSlash: boolean): string {
let out = "";
for (const ch of value) {
if (/[A-Za-z0-9\-._~]/.test(ch)) out += ch;
else if (ch === "/" && !encodeSlash) out += ch;
else out += [...Buffer.from(ch, "utf8")].map((byte) => `%${byte.toString(16).toUpperCase().padStart(2, "0")}`).join("");
}
return out;
}
function amzTimestamp(): { amzDate: string; dateStamp: string } {
const iso = new Date().toISOString().replace(/[:-]|\.\d{3}/g, "");
return { amzDate: iso, dateStamp: iso.slice(0, 8) };
}
/**
* Signs a request and returns the absolute (encoded) URL plus the headers to
* send. `rawPath` is the unencoded absolute path (e.g. /model/<modelId>/converse).
*/
export function signAwsRequest(input: {
method: string;
host: string;
rawPath: string;
body: string;
signer: AwsSigner;
}): { url: string; headers: Record<string, string> } {
const { host, rawPath, body, signer } = input;
const { region, service, accessKeyId, secretAccessKey, sessionToken } = signer;
const { amzDate, dateStamp } = amzTimestamp();
const canonicalUri = awsUriEncode(rawPath, false);
const payloadHash = sha256Hex(body);
const baseHeaders: Record<string, string> = {
host,
"x-amz-content-sha256": payloadHash,
"x-amz-date": amzDate,
};
if (sessionToken) baseHeaders["x-amz-security-token"] = sessionToken;
const sortedHeaderKeys = Object.keys(baseHeaders).sort();
const canonicalHeaders = sortedHeaderKeys.map((key) => `${key}:${baseHeaders[key].trim()}\n`).join("");
const signedHeaders = sortedHeaderKeys.join(";");
const canonicalRequest = [input.method, canonicalUri, "", canonicalHeaders, signedHeaders, payloadHash].join("\n");
const scope = `${dateStamp}/${region}/${service}/aws4_request`;
const stringToSign = ["AWS4-HMAC-SHA256", amzDate, scope, sha256Hex(canonicalRequest)].join("\n");
const kDate = hmac(`AWS4${secretAccessKey}`, dateStamp);
const kRegion = hmac(kDate, region);
const kService = hmac(kRegion, service);
const kSigning = hmac(kService, "aws4_request");
const signature = createHmac("sha256", kSigning).update(stringToSign, "utf8").digest("hex");
// `host` is signed but not returned: fetch sets the Host header from the URL.
return {
url: `https://${host}${canonicalUri}`,
headers: {
"x-amz-content-sha256": payloadHash,
"x-amz-date": amzDate,
...(sessionToken ? { "x-amz-security-token": sessionToken } : {}),
Authorization: `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,
},
};
}
@@ -0,0 +1,46 @@
/** AWS Bedrock Converse API encoding/decoding (ported from Pi's bedrock handler). */
import { isRecord, type JsonRecord } from "../provider-http";
import type { AgentTurnMessage, ToolCall, ToolDefinition } from "../provider-tool-turn";
export function bedrockToolConfig(tools: ToolDefinition[]): JsonRecord {
return {
tools: tools.map((tool) => ({ toolSpec: { name: tool.name, description: tool.description, inputSchema: { json: tool.parameters } } })),
toolChoice: { auto: {} },
};
}
export function toBedrockMessages(messages: AgentTurnMessage[]): JsonRecord[] {
const out: JsonRecord[] = [];
for (const message of messages) {
if (message.role === "user") {
out.push({ role: "user", content: [{ text: message.text }] });
} else if (message.role === "assistant") {
const content: unknown[] = [];
if (message.text) content.push({ text: message.text });
for (const call of message.toolCalls) content.push({ toolUse: { toolUseId: call.id, name: call.name, input: call.arguments } });
out.push({ role: "assistant", content });
} else {
out.push({ role: "user", content: message.results.map((result) => ({ toolResult: { toolUseId: result.toolCallId, content: [{ text: result.output }] } })) });
}
}
return out;
}
export function parseBedrockOutput(data: JsonRecord): { text: string; toolCalls: ToolCall[] } {
const output = isRecord(data.output) ? data.output : null;
const message = output && isRecord(output.message) ? output.message : null;
const content = message && Array.isArray(message.content) ? message.content : [];
const textParts: string[] = [];
const toolCalls: ToolCall[] = [];
for (const block of content) {
if (!isRecord(block)) continue;
if (typeof block.text === "string") textParts.push(block.text);
else if (isRecord(block.toolUse)) {
const toolUse = block.toolUse;
const id = typeof toolUse.toolUseId === "string" ? toolUse.toolUseId : "";
const name = typeof toolUse.name === "string" ? toolUse.name : "";
if (id && name) toolCalls.push({ id, name, arguments: isRecord(toolUse.input) ? toolUse.input : {} });
}
}
return { text: textParts.join("\n").trim(), toolCalls };
}
@@ -0,0 +1,46 @@
/** OpenAI Chat Completions encoding/decoding (xAI, Groq, Mistral, Cerebras, OpenRouter). */
import { isRecord, type JsonRecord } from "../provider-http";
import { parseArguments, type AgentTurnMessage, type ToolCall, type ToolDefinition } from "../provider-tool-turn";
export function chatToolDefs(tools: ToolDefinition[]): unknown[] {
return tools.map((tool) => ({ type: "function", function: { name: tool.name, description: tool.description, parameters: tool.parameters } }));
}
export function toChatMessages(instructions: string | null, messages: AgentTurnMessage[]): unknown[] {
const out: unknown[] = [];
if (instructions) out.push({ role: "system", content: instructions });
for (const message of messages) {
if (message.role === "user") {
out.push({ role: "user", content: message.text });
} else if (message.role === "assistant") {
const entry: JsonRecord = { role: "assistant", content: message.text || null };
if (message.toolCalls.length > 0) {
entry.tool_calls = message.toolCalls.map((call) => ({ id: call.id, type: "function", function: { name: call.name, arguments: JSON.stringify(call.arguments) } }));
}
out.push(entry);
} else {
for (const result of message.results) {
out.push({ role: "tool", tool_call_id: result.toolCallId, content: result.output });
}
}
}
return out;
}
export function parseChatChoice(data: JsonRecord): { text: string; toolCalls: ToolCall[] } {
const choices = Array.isArray(data.choices) ? data.choices : [];
const first = choices.length > 0 && isRecord(choices[0]) ? choices[0] : null;
const message = first && isRecord(first.message) ? first.message : null;
if (!message) return { text: "", toolCalls: [] };
const text = typeof message.content === "string" ? message.content : "";
const rawCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
const toolCalls: ToolCall[] = [];
for (const call of rawCalls) {
if (!isRecord(call)) continue;
const fn = isRecord(call.function) ? call.function : null;
const id = typeof call.id === "string" ? call.id : "";
const name = fn && typeof fn.name === "string" ? fn.name : "";
if (id && name) toolCalls.push({ id, name, arguments: parseArguments(fn?.arguments) });
}
return { text: text.trim(), toolCalls };
}
@@ -0,0 +1,131 @@
/**
* Resolves the per-request credential for a tool-use turn. API-key providers
* pass the key straight through; OAuth/subscription providers parse the stored
* credential JSON, refresh the access token when it has expired (persisting the
* rotated token), and hand the turn a usable access token plus the extras
* (accountId / projectId / enterpriseUrl) its backend needs.
*/
import { PROVIDER_AUTH_TYPE } from "../../../shared/domain";
import type { AuthBrokerService } from "../auth-broker-service";
import { loadCredential, type ProviderRow } from "../provider-internal";
import {
parseAnthropicCompatProfile,
parseApiKey,
parseAzureProfile,
parseBedrockProfile,
parseOpenAiCompatProfile,
parseVertexProfile,
requireAnthropicCompatProfile,
requireApiKeyProfile,
requireAzureProfile,
requireBedrockProfile,
requireOpenAiCompatProfile,
requireVertexProfile,
} from "../provider-auth-profiles";
import type { TurnCredential } from "../provider-tool-turn";
import type { OAuthCredentials } from "../oauth/oauth-types";
import { refreshCodex } from "../oauth/openai-codex-flow";
import { refreshClaudePro } from "../oauth/anthropic-claude-flow";
import { refreshAntigravity } from "../oauth/google-antigravity-flow";
import { refreshGitHubCopilotToken } from "../oauth/github-copilot-flow";
interface StoredOAuth {
access: string;
refresh: string;
expires: number;
accountId?: string;
projectId?: string;
enterpriseUrl?: string | null;
}
function parseStoredOAuth(secret: string): StoredOAuth {
let parsed: unknown;
try {
parsed = JSON.parse(secret);
} catch {
throw new Error("Stored OAuth credential is not valid JSON; reconnect the provider.");
}
const creds = parsed as StoredOAuth;
if (typeof creds.access !== "string" || typeof creds.refresh !== "string" || typeof creds.expires !== "number") {
throw new Error("Stored OAuth credential is incomplete; reconnect the provider.");
}
return creds;
}
function refreshOAuth(providerKey: string, creds: StoredOAuth): Promise<OAuthCredentials> {
switch (providerKey) {
case "gpt-account":
return refreshCodex(creds.refresh);
case "claude-pro":
return refreshClaudePro(creds.refresh);
case "google-antigravity":
if (!creds.projectId) throw new Error("Antigravity credential is missing its project id; reconnect the provider.");
return refreshAntigravity(creds.refresh, creds.projectId);
case "github-copilot":
return refreshGitHubCopilotToken(creds.refresh, creds.enterpriseUrl ?? undefined);
default:
throw new Error(`${providerKey} cannot refresh its credential.`);
}
}
function resolveCloudCredential(providerKey: string, secret: string): TurnCredential {
switch (providerKey) {
case "custom-openai": {
const profile = requireOpenAiCompatProfile(parseOpenAiCompatProfile(secret));
return { apiKey: profile.apiKey, baseUrl: profile.baseUrl };
}
case "custom-anthropic": {
const profile = requireAnthropicCompatProfile(parseAnthropicCompatProfile(secret));
return { apiKey: profile.apiKey, baseUrl: profile.baseUrl };
}
case "azure-openai": {
const profile = requireAzureProfile(parseAzureProfile(secret));
return { apiKey: profile.apiKey, baseUrl: profile.baseUrl, deployment: profile.deployment };
}
case "aws-bedrock": {
const profile = requireBedrockProfile(parseBedrockProfile(secret));
return { region: profile.region, accessKeyId: profile.accessKeyId, secretAccessKey: profile.secretAccessKey, sessionToken: profile.sessionToken };
}
case "vertex-ai": {
const profile = requireVertexProfile(parseVertexProfile(secret));
return { projectId: profile.projectId, region: profile.region, serviceAccountJson: profile.serviceAccountJson };
}
default:
throw new Error(`${providerKey} does not support agent execution.`);
}
}
function toTurnCredential(creds: StoredOAuth | OAuthCredentials): TurnCredential {
const enterpriseUrl = creds.enterpriseUrl;
return {
accessToken: creds.access,
accountId: typeof creds.accountId === "string" ? creds.accountId : undefined,
projectId: typeof creds.projectId === "string" ? creds.projectId : undefined,
enterpriseUrl: typeof enterpriseUrl === "string" ? enterpriseUrl : enterpriseUrl === null ? null : undefined,
};
}
export async function resolveTurnCredential(deps: {
authBroker: AuthBrokerService;
orgId: string;
provider: ProviderRow;
}): Promise<TurnCredential> {
const secret = loadCredential(deps.authBroker, deps.orgId, deps.provider).secret;
if (deps.provider.auth_type === PROVIDER_AUTH_TYPE.API_KEY) {
return { apiKey: requireApiKeyProfile(parseApiKey(secret), deps.provider.provider_key).apiKey };
}
if (deps.provider.auth_type === PROVIDER_AUTH_TYPE.CLOUD) {
return resolveCloudCredential(deps.provider.provider_key, secret);
}
const creds = parseStoredOAuth(secret);
if (Date.now() < creds.expires) return toTurnCredential(creds);
if (!deps.provider.credential_id) throw new Error("Provider credential id is missing; reconnect the provider.");
const refreshed = await refreshOAuth(deps.provider.provider_key, creds);
deps.authBroker.updateProviderSecret({
orgId: deps.orgId,
providerKey: deps.provider.provider_key,
credentialId: deps.provider.credential_id,
secret: JSON.stringify(refreshed),
});
return toTurnCredential(refreshed);
}
@@ -0,0 +1,58 @@
/**
* Mints a Google Cloud access token from a service-account JSON by signing a
* JWT (RS256) and exchanging it at the OAuth token endpoint. Tokens are cached
* per service account until shortly before expiry.
*/
import { createSign } from "node:crypto";
interface ServiceAccount {
client_email: string;
private_key: string;
}
const TOKEN_CACHE = new Map<string, { token: string; expiresAt: number }>();
function base64url(value: string | Buffer): string {
return Buffer.from(value).toString("base64url");
}
export async function getGcpAccessToken(serviceAccountJson: string): Promise<string> {
let account: ServiceAccount;
try {
account = JSON.parse(serviceAccountJson) as ServiceAccount;
} catch {
throw new Error("Vertex service-account credential is not valid JSON.");
}
if (typeof account.client_email !== "string" || typeof account.private_key !== "string") {
throw new Error("Vertex service-account JSON is missing client_email or private_key.");
}
const cached = TOKEN_CACHE.get(account.client_email);
if (cached && Date.now() < cached.expiresAt) return cached.token;
const now = Math.floor(Date.now() / 1000);
const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
const claims = base64url(JSON.stringify({
iss: account.client_email,
scope: "https://www.googleapis.com/auth/cloud-platform",
aud: "https://oauth2.googleapis.com/token",
iat: now,
exp: now + 3600,
}));
const signingInput = `${header}.${claims}`;
const signature = base64url(createSign("RSA-SHA256").update(signingInput).sign(account.private_key));
const assertion = `${signingInput}.${signature}`;
const response = await fetch("https://oauth2.googleapis.com/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", assertion }),
});
const text = await response.text();
if (!response.ok) throw new Error(`Vertex authentication failed: ${response.status} ${text}`);
const json = JSON.parse(text) as { access_token?: unknown; expires_in?: unknown };
if (typeof json.access_token !== "string" || typeof json.expires_in !== "number") {
throw new Error("Vertex authentication response was missing an access token.");
}
TOKEN_CACHE.set(account.client_email, { token: json.access_token, expiresAt: Date.now() + json.expires_in * 1000 - 60 * 1000 });
return json.access_token;
}
@@ -0,0 +1,160 @@
/**
* Google Cloud Code Assist (Gemini) encoding for the Antigravity backend.
* Ported from Pi's google-shared `convertMessages` / `convertTools` and the
* Antigravity request wrapper in google-gemini-cli.
*/
import { isRecord, type JsonRecord, type ProviderEventStreamMessage } from "../provider-http";
import { type AgentTurnMessage, type AgentTurnResult, type ToolCall, type ToolDefinition } from "../provider-tool-turn";
const ANTIGRAVITY_SYSTEM_INSTRUCTION =
"You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding." +
"You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question." +
"**Absolute paths only**" +
"**Proactiveness**";
function usesToolCallId(model: string): boolean {
return model.startsWith("claude-") || model.startsWith("gpt-oss-");
}
function geminiToolDefs(model: string, tools: ToolDefinition[]): unknown[] {
const useParameters = model.startsWith("claude-");
return [
{
functionDeclarations: tools.map((tool) => ({
name: tool.name,
description: tool.description,
...(useParameters ? { parameters: tool.parameters } : { parametersJsonSchema: tool.parameters }),
})),
},
];
}
function toGeminiContents(model: string, messages: AgentTurnMessage[]): JsonRecord[] {
const withId = usesToolCallId(model);
const contents: JsonRecord[] = [];
for (const message of messages) {
if (message.role === "user") {
contents.push({ role: "user", parts: [{ text: message.text }] });
} else if (message.role === "assistant") {
const parts: unknown[] = [];
if (message.text) parts.push({ text: message.text });
for (const call of message.toolCalls) {
parts.push({ functionCall: { name: call.name, args: call.arguments, ...(withId ? { id: call.id } : {}) } });
}
if (parts.length > 0) contents.push({ role: "model", parts });
} else {
const responses = message.results.map((result) => ({
functionResponse: { name: result.toolName, response: { output: result.output }, ...(withId ? { id: result.toolCallId } : {}) },
}));
const last = contents[contents.length - 1];
if (last && last.role === "user" && Array.isArray(last.parts) && last.parts.some((part) => isRecord(part) && "functionResponse" in part)) {
(last.parts as unknown[]).push(...responses);
} else {
contents.push({ role: "user", parts: responses });
}
}
}
return contents;
}
/** The full Antigravity request envelope ({project, model, request, ...}). */
export function antigravityRequestBody(input: {
model: string;
projectId: string;
instructions: string | null;
messages: AgentTurnMessage[];
tools: ToolDefinition[];
requestId: string;
}): JsonRecord {
const request: JsonRecord = {
contents: toGeminiContents(input.model, input.messages),
systemInstruction: {
role: "user",
parts: [
{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION },
{ text: `Please ignore following [ignore]${ANTIGRAVITY_SYSTEM_INSTRUCTION}[/ignore]` },
...(input.instructions ? [{ text: input.instructions }] : []),
],
},
};
if (input.tools.length > 0) {
request.tools = geminiToolDefs(input.model, input.tools);
request.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
}
return { project: input.projectId, model: input.model, request, requestType: "agent", userAgent: "antigravity", requestId: input.requestId };
}
/** The Vertex AI generateContent body (Gemini, no Antigravity wrapper). */
export function vertexRequestBody(input: {
model: string;
instructions: string | null;
messages: AgentTurnMessage[];
tools: ToolDefinition[];
}): JsonRecord {
const body: JsonRecord = { contents: toGeminiContents(input.model, input.messages) };
if (input.instructions) body.systemInstruction = { parts: [{ text: input.instructions }] };
if (input.tools.length > 0) {
body.tools = geminiToolDefs(input.model, input.tools);
body.toolConfig = { functionCallingConfig: { mode: "AUTO" } };
}
return body;
}
function partsToResult(parts: unknown[]): { text: string[]; toolCalls: ToolCall[] } {
const text: string[] = [];
const toolCalls: ToolCall[] = [];
let counter = 0;
for (const part of parts) {
if (!isRecord(part)) continue;
if (typeof part.text === "string" && part.thought !== true) text.push(part.text);
else if (isRecord(part.functionCall)) {
const fc = part.functionCall;
const name = typeof fc.name === "string" ? fc.name : "";
if (!name) continue;
const id = typeof fc.id === "string" && fc.id ? fc.id : `${name}_${(counter += 1)}`;
toolCalls.push({ id, name, arguments: isRecord(fc.args) ? fc.args : {} });
}
}
return { text, toolCalls };
}
/** Parse a non-streaming Gemini generateContent response (Vertex). */
export function parseGeminiResponse(data: JsonRecord): { text: string; toolCalls: ToolCall[] } {
const candidates = Array.isArray(data.candidates) ? data.candidates : [];
const content = candidates.length > 0 && isRecord(candidates[0]) && isRecord(candidates[0].content) ? candidates[0].content : null;
const parts = content && Array.isArray(content.parts) ? content.parts : [];
const result = partsToResult(parts);
return { text: result.text.join("").trim(), toolCalls: result.toolCalls };
}
export async function accumulateGeminiStream(messages: AsyncIterable<ProviderEventStreamMessage>): Promise<AgentTurnResult> {
const raw: string[] = [];
const textParts: string[] = [];
const toolCalls: ToolCall[] = [];
let counter = 0;
for await (const message of messages) {
raw.push(message.rawData);
if (isRecord(message.data.error)) {
const error = message.data.error;
throw new Error(typeof error.message === "string" ? error.message : "Antigravity stream failed.");
}
const response = isRecord(message.data.response) ? message.data.response : null;
if (!response) continue;
const candidates = Array.isArray(response.candidates) ? response.candidates : [];
const content = candidates.length > 0 && isRecord(candidates[0]) && isRecord(candidates[0].content) ? candidates[0].content : null;
const parts = content && Array.isArray(content.parts) ? content.parts : [];
for (const part of parts) {
if (!isRecord(part)) continue;
if (typeof part.text === "string" && part.thought !== true) {
textParts.push(part.text);
} else if (isRecord(part.functionCall)) {
const fc = part.functionCall;
const name = typeof fc.name === "string" ? fc.name : "";
if (!name) continue;
const id = typeof fc.id === "string" && fc.id ? fc.id : `${name}_${(counter += 1)}`;
toolCalls.push({ id, name, arguments: isRecord(fc.args) ? fc.args : {} });
}
}
}
return { text: textParts.join("").trim(), toolCalls, rawResponseJson: JSON.stringify(raw) };
}
@@ -0,0 +1,110 @@
/**
* OpenAI Responses API encoding/decoding, shared by the openai, gpt-account
* (Codex) and github-copilot turns. Matches Pi's `convertResponsesMessages` /
* `convertResponsesTools` and the Responses streaming event shapes.
*/
import { isRecord, type JsonRecord, type ProviderEventStreamMessage } from "../provider-http";
import { parseArguments, type AgentTurnMessage, type AgentTurnResult, type ToolCall, type ToolDefinition } from "../provider-tool-turn";
export function responsesToolDefs(tools: ToolDefinition[], strict: boolean | null = false): unknown[] {
// `strict` must be a boolean or omitted. Passing `strict: null` (the Codex
// path) is malformed and makes the endpoint drop the whole tool list, so the
// model has nothing to call. Omit the field instead of sending null.
return tools.map((tool) => ({
type: "function",
name: tool.name,
description: tool.description,
parameters: tool.parameters,
...(strict === null ? {} : { strict }),
}));
}
export function toResponsesInput(messages: AgentTurnMessage[]): unknown[] {
const items: unknown[] = [];
for (const message of messages) {
if (message.role === "user") {
items.push({ role: "user", content: [{ type: "input_text", text: message.text }] });
} else if (message.role === "assistant") {
if (message.text) {
items.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: message.text, annotations: [] }] });
}
for (const call of message.toolCalls) {
items.push({ type: "function_call", call_id: call.id, name: call.name, arguments: JSON.stringify(call.arguments) });
}
} else {
for (const result of message.results) {
items.push({ type: "function_call_output", call_id: result.toolCallId, output: result.output });
}
}
}
return items;
}
/** Parse a non-streaming Responses payload (`data.output[]`). */
export function parseResponsesOutput(data: JsonRecord): { text: string; toolCalls: ToolCall[] } {
const output = Array.isArray(data.output) ? data.output : [];
const textParts: string[] = [];
const toolCalls: ToolCall[] = [];
for (const item of output) {
if (!isRecord(item)) continue;
if (item.type === "function_call") {
const id = typeof item.call_id === "string" ? item.call_id : typeof item.id === "string" ? item.id : "";
const name = typeof item.name === "string" ? item.name : "";
if (id && name) toolCalls.push({ id, name, arguments: parseArguments(item.arguments) });
} else if (item.type === "message" && Array.isArray(item.content)) {
for (const block of item.content) {
if (isRecord(block) && block.type === "output_text" && typeof block.text === "string") textParts.push(block.text);
}
}
}
return { text: textParts.join("\n").trim(), toolCalls };
}
function eventType(message: ProviderEventStreamMessage): string | null {
const type = message.data.type;
return typeof type === "string" ? type : message.eventName;
}
function errorText(data: JsonRecord): string {
const error = data.error;
if (typeof error === "string") return error;
if (isRecord(error) && typeof error.message === "string") return error.message;
if (typeof data.message === "string") return data.message;
return "Provider stream failed.";
}
/**
* Accumulate a Responses SSE stream (Codex/Copilot) into the final result.
*
* The ChatGPT/Codex backend streams each output item in a
* `response.output_item.done` event and then sends a `response.completed`
* whose `response.output` is EMPTY. Reading only the completion event there
* loses every tool call (the model acted, but the turn parses as empty). So
* collect items as they finish and fall back to them when the completion
* carries no output of its own.
*/
export async function accumulateResponsesStream(messages: AsyncIterable<ProviderEventStreamMessage>): Promise<AgentTurnResult> {
const raw: string[] = [];
const streamed: unknown[] = [];
for await (const message of messages) {
raw.push(message.rawData);
const type = eventType(message);
if (!type) continue;
if (type === "error" || type === "response.failed") throw new Error(errorText(message.data));
if (type === "response.output_item.done" && isRecord(message.data.item)) {
streamed.push(message.data.item);
}
if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
const response = isRecord(message.data.response) ? message.data.response : null;
const completionOutput = response && Array.isArray(response.output) ? response.output : [];
const output = completionOutput.length > 0 ? completionOutput : streamed;
const parsed = parseResponsesOutput({ output });
return { text: parsed.text, toolCalls: parsed.toolCalls, rawResponseJson: JSON.stringify(raw) };
}
}
if (streamed.length > 0) {
const parsed = parseResponsesOutput({ output: streamed });
return { text: parsed.text, toolCalls: parsed.toolCalls, rawResponseJson: JSON.stringify(raw) };
}
throw new Error("Responses stream ended before a completion event.");
}
@@ -0,0 +1,45 @@
import type { ToolTurnFn } from "../provider-tool-turn";
import {
makeChatCompletionsTurn,
runAnthropicTurn,
runAntigravityTurn,
runAzureTurn,
runBedrockTurn,
runClaudeProTurn,
runCodexTurn,
runCopilotTurn,
runCustomAnthropicTurn,
runCustomOpenAiTurn,
runOpenAiResponsesTurn,
runVertexTurn,
} from "./turns";
/** Provider key -> the tool-use turn function that drives the agent loop. */
const TURNS: Record<string, ToolTurnFn> = {
openai: runOpenAiResponsesTurn,
anthropic: runAnthropicTurn,
xai: makeChatCompletionsTurn("https://api.x.ai/v1/chat/completions", "xAI"),
groq: makeChatCompletionsTurn("https://api.groq.com/openai/v1/chat/completions", "Groq"),
mistral: makeChatCompletionsTurn("https://api.mistral.ai/v1/chat/completions", "Mistral"),
cerebras: makeChatCompletionsTurn("https://api.cerebras.ai/v1/chat/completions", "Cerebras"),
openrouter: makeChatCompletionsTurn("https://openrouter.ai/api/v1/chat/completions", "OpenRouter"),
"gpt-account": runCodexTurn,
"github-copilot": runCopilotTurn,
"google-antigravity": runAntigravityTurn,
"claude-pro": runClaudeProTurn,
"custom-openai": runCustomOpenAiTurn,
"custom-anthropic": runCustomAnthropicTurn,
"azure-openai": runAzureTurn,
"aws-bedrock": runBedrockTurn,
"vertex-ai": runVertexTurn,
};
export function supportsToolTurn(providerKey: string): boolean {
return providerKey in TURNS;
}
export function selectToolTurn(providerKey: string): ToolTurnFn {
const turn = TURNS[providerKey];
if (!turn) throw new Error(`${providerKey} does not support agent execution.`);
return turn;
}
@@ -0,0 +1,267 @@
/**
* Per-backend tool-use turn functions. Each takes the normalized
* {@link ProviderToolTurnInput} (with a resolved credential) and returns an
* {@link AgentTurnResult}. Faithful ports of Pi's provider request handlers.
*/
import { release as osRelease, arch as osArch, platform as osPlatform } from "node:os";
import { postEventStream, postJson, type JsonRecord } from "../provider-http";
import type { AgentTurnMessage, AgentTurnResult, ProviderToolTurnInput, TurnCredential } from "../provider-tool-turn";
import { accumulateResponsesStream, parseResponsesOutput, responsesToolDefs, toResponsesInput } from "./responses-format";
import { anthropicToolDefs, parseAnthropicContent, toAnthropicMessages } from "./anthropic-format";
import { chatToolDefs, parseChatChoice, toChatMessages } from "./chat-format";
import { accumulateGeminiStream, antigravityRequestBody, parseGeminiResponse, vertexRequestBody } from "./gemini-format";
import { bedrockToolConfig, parseBedrockOutput, toBedrockMessages } from "./bedrock-format";
import { signAwsRequest } from "./aws-sigv4";
import { getGcpAccessToken } from "./gcp-auth";
import { COPILOT_HEADERS, copilotBaseUrl } from "../oauth/github-copilot-flow";
const ANTIGRAVITY_VERSION = "1.21.9";
const ANTIGRAVITY_ENDPOINT = "https://cloudcode-pa.googleapis.com";
const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
function requireApiKey(credential: TurnCredential, label: string): string {
if (!credential.apiKey) throw new Error(`${label} requires an API key.`);
return credential.apiKey;
}
function requireAccessToken(credential: TurnCredential, label: string): string {
if (!credential.accessToken) throw new Error(`${label} requires a signed-in access token.`);
return credential.accessToken;
}
function lastMessageIsUser(messages: AgentTurnMessage[]): boolean {
const last = messages[messages.length - 1];
return last?.role === "user";
}
// ---- OpenAI Responses (api key) ----
export async function runOpenAiResponsesTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const body: JsonRecord = {
model: input.model,
input: toResponsesInput(input.messages),
tools: responsesToolDefs(input.tools),
tool_choice: "auto",
store: false,
...(input.instructions ? { instructions: input.instructions } : {}),
};
const response = await postJson("https://api.openai.com/v1/responses", { authorization: `Bearer ${requireApiKey(input.credential, "OpenAI")}` }, body);
const parsed = parseResponsesOutput(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- OpenAI Chat Completions (xAI, Groq, Mistral, Cerebras, OpenRouter) ----
export function makeChatCompletionsTurn(endpoint: string, label: string): (input: ProviderToolTurnInput) => Promise<AgentTurnResult> {
return async (input) => {
const body: JsonRecord = {
model: input.model,
messages: toChatMessages(input.instructions, input.messages),
tools: chatToolDefs(input.tools),
tool_choice: "auto",
stream: false,
};
const response = await postJson(endpoint, { authorization: `Bearer ${requireApiKey(input.credential, label)}` }, body);
const parsed = parseChatChoice(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
};
}
// ---- Anthropic Messages (api key) ----
export async function runAnthropicTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const body: JsonRecord = {
model: input.model,
max_tokens: 4096,
tools: anthropicToolDefs(input.tools),
messages: toAnthropicMessages(input.messages),
...(input.instructions ? { system: input.instructions } : {}),
};
const response = await postJson("https://api.anthropic.com/v1/messages", { "x-api-key": requireApiKey(input.credential, "Anthropic"), "anthropic-version": "2023-06-01" }, body);
const parsed = parseAnthropicContent(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- Claude Pro/Max (OAuth subscription) ----
export async function runClaudeProTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const system: unknown[] = [
{ type: "text", text: "You are Claude Code, Anthropic's official CLI for Claude." },
...(input.instructions ? [{ type: "text", text: input.instructions }] : []),
];
const body: JsonRecord = {
model: input.model,
max_tokens: 4096,
tools: anthropicToolDefs(input.tools),
messages: toAnthropicMessages(input.messages),
system,
};
const response = await postJson("https://api.anthropic.com/v1/messages", {
authorization: `Bearer ${requireAccessToken(input.credential, "Claude Pro")}`,
"anthropic-version": "2023-06-01",
"anthropic-beta": "claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14",
"anthropic-dangerous-direct-browser-access": "true",
"user-agent": "claude-cli/2.1.75",
"x-app": "cli",
}, body);
const parsed = parseAnthropicContent(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- GPT account / OpenAI Codex (OAuth, SSE) ----
export async function runCodexTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const access = requireAccessToken(input.credential, "GPT account");
if (!input.credential.accountId) throw new Error("GPT account credential is missing its account id.");
const body: JsonRecord = {
model: input.model,
store: false,
stream: true,
...(input.instructions ? { instructions: input.instructions } : {}),
input: toResponsesInput(input.messages),
text: { verbosity: "medium" },
include: ["reasoning.encrypted_content"],
tool_choice: "auto",
parallel_tool_calls: true,
tools: responsesToolDefs(input.tools, null),
};
const response = await postEventStream(`${CODEX_BASE_URL}/codex/responses`, {
Authorization: `Bearer ${access}`,
"chatgpt-account-id": input.credential.accountId,
originator: "agentcompany",
"User-Agent": `agentcompany (${osPlatform()} ${osRelease()}; ${osArch()})`,
"OpenAI-Beta": "responses=experimental",
}, body);
return accumulateResponsesStream(response.messages);
}
// ---- GitHub Copilot (OAuth, openai-responses at the proxy) ----
export async function runCopilotTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const access = requireAccessToken(input.credential, "GitHub Copilot");
const baseUrl = copilotBaseUrl(access, input.credential.enterpriseUrl ?? undefined);
const body: JsonRecord = {
model: input.model,
input: toResponsesInput(input.messages),
tools: responsesToolDefs(input.tools),
tool_choice: "auto",
store: false,
};
const response = await postJson(`${baseUrl}/responses`, {
Authorization: `Bearer ${access}`,
...COPILOT_HEADERS,
"X-Initiator": lastMessageIsUser(input.messages) ? "user" : "agent",
"Openai-Intent": "conversation-edits",
}, body);
const parsed = parseResponsesOutput(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- Custom OpenAI-compatible endpoint (base URL + key) ----
export async function runCustomOpenAiTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
if (!input.credential.baseUrl) throw new Error("Custom OpenAI-compatible provider is missing its base URL.");
const body: JsonRecord = {
model: input.model,
messages: toChatMessages(input.instructions, input.messages),
tools: chatToolDefs(input.tools),
tool_choice: "auto",
stream: false,
};
const response = await postJson(`${input.credential.baseUrl}/chat/completions`, { authorization: `Bearer ${requireApiKey(input.credential, "Custom OpenAI-compatible provider")}` }, body);
const parsed = parseChatChoice(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- Custom Anthropic-compatible endpoint (base URL + key) ----
export async function runCustomAnthropicTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
if (!input.credential.baseUrl) throw new Error("Custom Anthropic-compatible provider is missing its base URL.");
const body: JsonRecord = {
model: input.model,
max_tokens: 4096,
tools: anthropicToolDefs(input.tools),
messages: toAnthropicMessages(input.messages),
...(input.instructions ? { system: input.instructions } : {}),
};
const response = await postJson(`${input.credential.baseUrl}/v1/messages`, { "x-api-key": requireApiKey(input.credential, "Custom Anthropic-compatible provider"), "anthropic-version": "2023-06-01" }, body);
const parsed = parseAnthropicContent(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- Google Antigravity (OAuth, Gemini Code Assist, SSE) ----
export async function runAntigravityTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const access = requireAccessToken(input.credential, "Google Antigravity");
if (!input.credential.projectId) throw new Error("Antigravity credential is missing its project id.");
const requestId = `agent-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
const body = antigravityRequestBody({
model: input.model,
projectId: input.credential.projectId,
instructions: input.instructions,
messages: input.messages,
tools: input.tools,
requestId,
});
const response = await postEventStream(`${ANTIGRAVITY_ENDPOINT}/v1internal:streamGenerateContent?alt=sse`, {
Authorization: `Bearer ${access}`,
"User-Agent": `antigravity/${ANTIGRAVITY_VERSION} darwin/arm64`,
}, body);
return accumulateGeminiStream(response.messages);
}
// ---- Azure OpenAI (Chat Completions at the resource deployment) ----
export async function runAzureTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const apiKey = requireApiKey(input.credential, "Azure OpenAI");
if (!input.credential.baseUrl) throw new Error("Azure OpenAI credential is missing its base URL.");
if (!input.credential.deployment) throw new Error("Azure OpenAI credential is missing its deployment name.");
const body: JsonRecord = {
model: input.credential.deployment,
messages: toChatMessages(input.instructions, input.messages),
tools: chatToolDefs(input.tools),
tool_choice: "auto",
stream: false,
};
const url = `${input.credential.baseUrl}/openai/deployments/${input.credential.deployment}/chat/completions?api-version=2024-10-21`;
const response = await postJson(url, { "api-key": apiKey }, body);
const parsed = parseChatChoice(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- AWS Bedrock (Converse API, SigV4) ----
export async function runBedrockTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const { region, accessKeyId, secretAccessKey, sessionToken } = input.credential;
if (!region || !accessKeyId || !secretAccessKey) throw new Error("AWS Bedrock credential is incomplete.");
const converse: JsonRecord = {
messages: toBedrockMessages(input.messages),
inferenceConfig: { maxTokens: 4096 },
toolConfig: bedrockToolConfig(input.tools),
...(input.instructions ? { system: [{ text: input.instructions }] } : {}),
};
const bodyString = JSON.stringify(converse);
const signed = signAwsRequest({
method: "POST",
host: `bedrock-runtime.${region}.amazonaws.com`,
rawPath: `/model/${input.model}/converse`,
body: bodyString,
signer: { region, service: "bedrock", accessKeyId, secretAccessKey, sessionToken },
});
const response = await postJson(signed.url, signed.headers, converse);
const parsed = parseBedrockOutput(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}
// ---- Google Vertex AI (Gemini generateContent, service-account auth) ----
export async function runVertexTurn(input: ProviderToolTurnInput): Promise<AgentTurnResult> {
const { projectId, region, serviceAccountJson } = input.credential;
if (!projectId || !region || !serviceAccountJson) throw new Error("Vertex AI credential is incomplete.");
const token = await getGcpAccessToken(serviceAccountJson);
const body = vertexRequestBody({ model: input.model, instructions: input.instructions, messages: input.messages, tools: input.tools });
const url = `https://${region}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${region}/publishers/google/models/${input.model}:generateContent`;
const response = await postJson(url, { authorization: `Bearer ${token}` }, body);
const parsed = parseGeminiResponse(response.data);
return { ...parsed, rawResponseJson: JSON.stringify(response.data) };
}