diff --git a/AgentCompany-New/src/main/database/migrations/036-additional-connectors.ts b/AgentCompany-New/src/main/database/migrations/036-additional-connectors.ts new file mode 100644 index 00000000..8d51a400 --- /dev/null +++ b/AgentCompany-New/src/main/database/migrations/036-additional-connectors.ts @@ -0,0 +1,20 @@ +export const additionalConnectorsSchema = ` + insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'claude-pro', 'Claude Pro/Max', 'oauth_pkce', 'claude-sonnet-4-5', null, 0, datetime('now') + from organizations; + + insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'mistral', 'Mistral', 'api_key', 'mistral-large-latest', null, 0, datetime('now') + from organizations; + + insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'cerebras', 'Cerebras', 'api_key', 'qwen-3-235b-a22b-instruct-2507', null, 0, datetime('now') + from organizations; + + insert or ignore into providers(id, org_id, provider_key, label, auth_type, model, credential_id, enabled, created_at) + select 'prv_' || lower(hex(randomblob(16))), organizations.id, 'openrouter', 'OpenRouter', 'api_key', 'anthropic/claude-sonnet-4.5', null, 0, datetime('now') + from organizations; + + insert into schema_migrations(version, name, applied_at) + values (36, 'additional_connectors', datetime('now')); +`; diff --git a/AgentCompany-New/src/main/database/schema.ts b/AgentCompany-New/src/main/database/schema.ts index 00cc74b7..6b594a33 100644 --- a/AgentCompany-New/src/main/database/schema.ts +++ b/AgentCompany-New/src/main/database/schema.ts @@ -34,6 +34,7 @@ import { browserActionMetadataSchema } from "./migrations/032-browser-action-met import { auditEventImmutabilitySchema } from "./migrations/033-audit-event-immutability"; import { providerAuthSessionsSchema } from "./migrations/034-provider-auth-sessions"; import { taskRunIsolationSchema } from "./migrations/035-task-run-isolation"; +import { additionalConnectorsSchema } from "./migrations/036-additional-connectors"; interface Migration { version: number; @@ -77,6 +78,7 @@ const MIGRATIONS: Migration[] = [ { version: 33, name: "audit_event_immutability", sql: auditEventImmutabilitySchema }, { version: 34, name: "provider_auth_sessions", sql: providerAuthSessionsSchema }, { version: 35, name: "task_run_isolation", sql: taskRunIsolationSchema }, + { version: 36, name: "additional_connectors", sql: additionalConnectorsSchema }, ]; const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version; diff --git a/AgentCompany-New/src/main/services/oauth/anthropic-claude-flow.ts b/AgentCompany-New/src/main/services/oauth/anthropic-claude-flow.ts new file mode 100644 index 00000000..442c45c0 --- /dev/null +++ b/AgentCompany-New/src/main/services/oauth/anthropic-claude-flow.ts @@ -0,0 +1,70 @@ +/** + * Anthropic Claude Pro/Max — authorization-code + PKCE flow. + * + * Ported from the reference implementation in @mariozechner/pi-ai. Uses a + * loopback callback server on the registered redirect URI and a JSON token + * exchange. + */ +import { startOAuthCallbackServer } from "./oauth-callback-server"; +import { generatePkce } from "./pkce"; +import type { OAuthCredentials, OAuthFlow, OAuthLoginCallbacks } from "./oauth-types"; + +const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; +const AUTHORIZE_URL = "https://claude.ai/oauth/authorize"; +const TOKEN_URL = "https://platform.claude.com/v1/oauth/token"; +const CALLBACK_PORT = 53692; +const CALLBACK_PATH = "/callback"; +const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`; +const SCOPES = "org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload"; + +function buildAuthorizeUrl(challenge: string, state: string): string { + const params = new URLSearchParams({ + code: "true", + client_id: CLIENT_ID, + response_type: "code", + redirect_uri: REDIRECT_URI, + scope: SCOPES, + code_challenge: challenge, + code_challenge_method: "S256", + state, + }); + return `${AUTHORIZE_URL}?${params.toString()}`; +} + +async function exchangeCode(code: string, state: string, verifier: string): Promise { + const response = await fetch(TOKEN_URL, { + method: "POST", + headers: { "Content-Type": "application/json", Accept: "application/json" }, + body: JSON.stringify({ + grant_type: "authorization_code", + client_id: CLIENT_ID, + code, + state, + redirect_uri: REDIRECT_URI, + code_verifier: verifier, + }), + }); + const text = await response.text(); + if (!response.ok) throw new Error(`Token exchange failed: ${response.status} ${text}`); + const json = JSON.parse(text) as { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown }; + if (typeof json.access_token !== "string" || typeof json.refresh_token !== "string" || typeof json.expires_in !== "number") { + throw new Error("Token response was missing required fields."); + } + return { access: json.access_token, refresh: json.refresh_token, expires: Date.now() + json.expires_in * 1000 - 5 * 60 * 1000 }; +} + +export const anthropicClaudeFlow: OAuthFlow = { + providerKey: "claude-pro", + async login(callbacks: OAuthLoginCallbacks): Promise { + const { verifier, challenge } = generatePkce(); + const server = await startOAuthCallbackServer({ port: CALLBACK_PORT, path: CALLBACK_PATH, signal: callbacks.signal }); + try { + callbacks.onAuth({ url: buildAuthorizeUrl(challenge, verifier), instructions: "Complete the Claude sign-in in your browser." }); + const result = await server.waitForCode(); + if (result.state !== verifier) throw new Error("OAuth state mismatch."); + return await exchangeCode(result.code, result.state, verifier); + } finally { + server.close(); + } + }, +}; diff --git a/AgentCompany-New/src/main/services/oauth/oauth-flows.ts b/AgentCompany-New/src/main/services/oauth/oauth-flows.ts index da4c7f5c..8c265621 100644 --- a/AgentCompany-New/src/main/services/oauth/oauth-flows.ts +++ b/AgentCompany-New/src/main/services/oauth/oauth-flows.ts @@ -1,6 +1,7 @@ import { githubCopilotFlow } from "./github-copilot-flow"; import { openaiCodexFlow } from "./openai-codex-flow"; import { antigravityFlow } from "./google-antigravity-flow"; +import { anthropicClaudeFlow } from "./anthropic-claude-flow"; import type { OAuthFlow } from "./oauth-types"; /** Provider keys (from the catalog) mapped to their native OAuth login flow. */ @@ -8,6 +9,7 @@ const OAUTH_FLOWS: Record = { "github-copilot": githubCopilotFlow, "gpt-account": openaiCodexFlow, "google-antigravity": antigravityFlow, + "claude-pro": anthropicClaudeFlow, }; export function hasOAuthFlow(providerKey: string): boolean { diff --git a/AgentCompany-New/src/main/services/provider-adapters.ts b/AgentCompany-New/src/main/services/provider-adapters.ts index f4ecd435..cf332843 100644 --- a/AgentCompany-New/src/main/services/provider-adapters.ts +++ b/AgentCompany-New/src/main/services/provider-adapters.ts @@ -253,9 +253,28 @@ export const providerAdapters: ProviderAdapterRegistry = { endpoint: "https://api.groq.com/openai/v1/chat/completions", healthUrl: "https://api.groq.com/openai/v1/models", }), + mistral: openAiCompatibleAdapter({ + providerKey: "mistral", + label: "Mistral", + endpoint: "https://api.mistral.ai/v1/chat/completions", + healthUrl: "https://api.mistral.ai/v1/models", + }), + cerebras: openAiCompatibleAdapter({ + providerKey: "cerebras", + label: "Cerebras", + endpoint: "https://api.cerebras.ai/v1/chat/completions", + healthUrl: "https://api.cerebras.ai/v1/models", + }), + openrouter: openAiCompatibleAdapter({ + providerKey: "openrouter", + label: "OpenRouter", + endpoint: "https://openrouter.ai/api/v1/chat/completions", + healthUrl: "https://openrouter.ai/api/v1/models", + }), "github-copilot": browserTokenAdapter("github-copilot", PROVIDER_AUTH_TYPE.OAUTH_PKCE), "google-antigravity": browserTokenAdapter("google-antigravity", PROVIDER_AUTH_TYPE.OAUTH_PKCE), "gpt-account": browserTokenAdapter("gpt-account", PROVIDER_AUTH_TYPE.ACCOUNT_SUBSCRIPTION), + "claude-pro": browserTokenAdapter("claude-pro", PROVIDER_AUTH_TYPE.OAUTH_PKCE), "azure-openai": { providerKey: "azure-openai", authType: PROVIDER_AUTH_TYPE.CLOUD, diff --git a/AgentCompany-New/src/shared/provider-catalog.ts b/AgentCompany-New/src/shared/provider-catalog.ts index e6c0dcf8..1671fa33 100644 --- a/AgentCompany-New/src/shared/provider-catalog.ts +++ b/AgentCompany-New/src/shared/provider-catalog.ts @@ -20,6 +20,15 @@ const RAW_CATALOG: Omit[] = [ signInUrl: null, credentialHelp: "Generate an API key at console.anthropic.com → API keys.", }, + { + key: "claude-pro", + label: "Claude Pro/Max", + authType: PROVIDER_AUTH_TYPE.OAUTH_PKCE, + defaultModel: "claude-sonnet-4-5", + capabilityTags: ["account", "subscription"], + signInUrl: "https://claude.ai/login", + credentialHelp: "Sign in with your Claude Pro or Max account in the browser; AgentCompany finishes the handshake.", + }, { key: "xai", label: "xAI Grok", @@ -38,6 +47,33 @@ const RAW_CATALOG: Omit[] = [ signInUrl: null, credentialHelp: "Generate an API key at console.groq.com → API keys.", }, + { + key: "mistral", + label: "Mistral", + authType: PROVIDER_AUTH_TYPE.API_KEY, + defaultModel: "mistral-large-latest", + capabilityTags: ["chat", "reasoning"], + signInUrl: null, + credentialHelp: "Generate an API key at console.mistral.ai → API keys.", + }, + { + key: "cerebras", + label: "Cerebras", + authType: PROVIDER_AUTH_TYPE.API_KEY, + defaultModel: "qwen-3-235b-a22b-instruct-2507", + capabilityTags: ["inference", "fast"], + signInUrl: null, + credentialHelp: "Generate an API key at cloud.cerebras.ai → API keys.", + }, + { + key: "openrouter", + label: "OpenRouter", + authType: PROVIDER_AUTH_TYPE.API_KEY, + defaultModel: "anthropic/claude-sonnet-4.5", + capabilityTags: ["router", "multi-model"], + signInUrl: null, + credentialHelp: "Generate an API key at openrouter.ai → Keys.", + }, { key: "github-copilot", label: "GitHub Copilot", diff --git a/AgentCompany-New/src/shared/provider-models.ts b/AgentCompany-New/src/shared/provider-models.ts index de320fb5..17800966 100644 --- a/AgentCompany-New/src/shared/provider-models.ts +++ b/AgentCompany-New/src/shared/provider-models.ts @@ -3,6 +3,26 @@ export interface ProviderModelEntry { name: string; } +const ANTHROPIC_MODELS: ProviderModelEntry[] = [ + { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, + { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, + { id: "claude-opus-4-5", name: "Claude Opus 4.5 (latest)" }, + { id: "claude-opus-4-1", name: "Claude Opus 4.1 (latest)" }, + { id: "claude-opus-4-0", name: "Claude Opus 4 (latest)" }, + { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, + { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5 (latest)" }, + { id: "claude-sonnet-4-0", name: "Claude Sonnet 4 (latest)" }, + { id: "claude-haiku-4-5", name: "Claude Haiku 4.5 (latest)" }, + { id: "claude-3-7-sonnet-20250219", name: "Claude Sonnet 3.7" }, + { id: "claude-3-5-sonnet-20241022", name: "Claude Sonnet 3.5 v2" }, + { id: "claude-3-5-sonnet-20240620", name: "Claude Sonnet 3.5" }, + { id: "claude-3-5-haiku-latest", name: "Claude Haiku 3.5 (latest)" }, + { id: "claude-3-5-haiku-20241022", name: "Claude Haiku 3.5" }, + { id: "claude-3-haiku-20240307", name: "Claude Haiku 3" }, + { id: "claude-3-sonnet-20240229", name: "Claude Sonnet 3" }, + { id: "claude-3-opus-20240229", name: "Claude Opus 3" }, +]; + export const PROVIDER_MODELS: Record = { openai: [ { id: "gpt-5.4-pro", name: "GPT-5.4 Pro" }, @@ -43,25 +63,8 @@ export const PROVIDER_MODELS: Record = { { id: "gpt-4-turbo", name: "GPT-4 Turbo" }, { id: "gpt-4", name: "GPT-4" }, ], - anthropic: [ - { id: "claude-opus-4-7", name: "Claude Opus 4.7" }, - { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, - { id: "claude-opus-4-5", name: "Claude Opus 4.5 (latest)" }, - { id: "claude-opus-4-1", name: "Claude Opus 4.1 (latest)" }, - { id: "claude-opus-4-0", name: "Claude Opus 4 (latest)" }, - { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, - { id: "claude-sonnet-4-5", name: "Claude Sonnet 4.5 (latest)" }, - { id: "claude-sonnet-4-0", name: "Claude Sonnet 4 (latest)" }, - { id: "claude-haiku-4-5", name: "Claude Haiku 4.5 (latest)" }, - { id: "claude-3-7-sonnet-20250219", name: "Claude Sonnet 3.7" }, - { id: "claude-3-5-sonnet-20241022", name: "Claude Sonnet 3.5 v2" }, - { id: "claude-3-5-sonnet-20240620", name: "Claude Sonnet 3.5" }, - { id: "claude-3-5-haiku-latest", name: "Claude Haiku 3.5 (latest)" }, - { id: "claude-3-5-haiku-20241022", name: "Claude Haiku 3.5" }, - { id: "claude-3-haiku-20240307", name: "Claude Haiku 3" }, - { id: "claude-3-sonnet-20240229", name: "Claude Sonnet 3" }, - { id: "claude-3-opus-20240229", name: "Claude Opus 3" }, - ], + anthropic: ANTHROPIC_MODELS, + "claude-pro": ANTHROPIC_MODELS, xai: [ { id: "grok-4", name: "Grok 4" }, { id: "grok-4-fast", name: "Grok 4 Fast" }, @@ -186,6 +189,43 @@ export const PROVIDER_MODELS: Record = { { id: "gemini-1.5-pro", name: "Gemini 1.5 Pro" }, { id: "gemini-1.5-flash", name: "Gemini 1.5 Flash" }, ], + mistral: [ + { id: "mistral-large-latest", name: "Mistral Large (latest)" }, + { id: "mistral-large-2512", name: "Mistral Large 3" }, + { id: "mistral-medium-latest", name: "Mistral Medium (latest)" }, + { id: "mistral-medium-2508", name: "Mistral Medium 3.1" }, + { id: "mistral-small-latest", name: "Mistral Small (latest)" }, + { id: "mistral-small-2603", name: "Mistral Small 4" }, + { id: "magistral-medium-latest", name: "Magistral Medium (latest)" }, + { id: "magistral-small", name: "Magistral Small" }, + { id: "codestral-latest", name: "Codestral (latest)" }, + { id: "devstral-medium-latest", name: "Devstral Medium (latest)" }, + { id: "devstral-small-2507", name: "Devstral Small" }, + { id: "ministral-8b-latest", name: "Ministral 8B (latest)" }, + { id: "ministral-3b-latest", name: "Ministral 3B (latest)" }, + { id: "pixtral-large-latest", name: "Pixtral Large (latest)" }, + { id: "open-mistral-nemo", name: "Mistral Nemo" }, + ], + cerebras: [ + { id: "qwen-3-235b-a22b-instruct-2507", name: "Qwen 3 235B Instruct" }, + { id: "gpt-oss-120b", name: "GPT OSS 120B" }, + { id: "zai-glm-4.7", name: "Z.AI GLM-4.7" }, + { id: "llama3.1-8b", name: "Llama 3.1 8B" }, + ], + openrouter: [ + { id: "anthropic/claude-sonnet-4.5", name: "Anthropic: Claude Sonnet 4.5" }, + { id: "anthropic/claude-opus-4.5", name: "Anthropic: Claude Opus 4.5" }, + { id: "anthropic/claude-haiku-4.5", name: "Anthropic: Claude Haiku 4.5" }, + { id: "openai/gpt-5.2", name: "OpenAI: GPT-5.2" }, + { id: "openai/gpt-5.1", name: "OpenAI: GPT-5.1" }, + { id: "google/gemini-3-pro-preview", name: "Google: Gemini 3 Pro Preview" }, + { id: "google/gemini-2.5-pro", name: "Google: Gemini 2.5 Pro" }, + { id: "google/gemini-2.5-flash", name: "Google: Gemini 2.5 Flash" }, + { id: "x-ai/grok-4", name: "xAI: Grok 4" }, + { id: "deepseek/deepseek-chat", name: "DeepSeek: DeepSeek Chat" }, + { id: "meta-llama/llama-4-maverick", name: "Meta: Llama 4 Maverick" }, + { id: "qwen/qwen3-235b-a22b", name: "Qwen: Qwen3 235B A22B" }, + ], }; export function listProviderModels(providerKey: string): ProviderModelEntry[] { diff --git a/AgentCompany-New/tests/contract/provider-contracts.test.ts b/AgentCompany-New/tests/contract/provider-contracts.test.ts index 3c37181b..77c065af 100644 --- a/AgentCompany-New/tests/contract/provider-contracts.test.ts +++ b/AgentCompany-New/tests/contract/provider-contracts.test.ts @@ -12,8 +12,12 @@ import { PROVIDER_CATALOG } from "../../src/shared/provider-catalog"; const PROVIDER_SECRET_SAMPLES: Record = { openai: "sk-live-provider", anthropic: "sk-live-provider", + "claude-pro": "claude-session-token", xai: "sk-live-provider", groq: "sk-live-provider", + mistral: "sk-live-provider", + cerebras: "csk-live-provider", + openrouter: "sk-or-live-provider", "github-copilot": "ghu_live_provider", "google-antigravity": "antigravity-session", "gpt-account": "chatgpt-session", diff --git a/AgentCompany-New/tests/unit/domain-services.test.ts b/AgentCompany-New/tests/unit/domain-services.test.ts index 8dc9c098..fbc897af 100644 --- a/AgentCompany-New/tests/unit/domain-services.test.ts +++ b/AgentCompany-New/tests/unit/domain-services.test.ts @@ -24,7 +24,7 @@ describe("domain services", () => { const connection = createConnection(); const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>; - expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]); + expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]); connection.close(); });