@mastra/code-sdk 1.3.1-alpha.0 → 1.4.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +59 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/auth/provider-auth-error.d.ts +12 -0
- package/dist/auth/provider-auth-error.d.ts.map +1 -0
- package/dist/auth/provider-auth-error.js +16 -0
- package/dist/auth/provider-auth-error.js.map +1 -0
- package/dist/headless/cli.d.ts.map +1 -1
- package/dist/headless/cli.js +29 -18
- package/dist/headless/cli.js.map +1 -1
- package/dist/process-memory-diagnostics.d.ts +135 -0
- package/dist/process-memory-diagnostics.d.ts.map +1 -0
- package/dist/process-memory-diagnostics.js +500 -0
- package/dist/process-memory-diagnostics.js.map +1 -0
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/claude-max.js +2 -1
- package/dist/providers/claude-max.js.map +1 -1
- package/dist/providers/github-copilot.d.ts.map +1 -1
- package/dist/providers/github-copilot.js +3 -2
- package/dist/providers/github-copilot.js.map +1 -1
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/providers/openai-codex.js +3 -2
- package/dist/providers/openai-codex.js.map +1 -1
- package/dist/providers/xai.d.ts.map +1 -1
- package/dist/providers/xai.js +3 -2
- package/dist/providers/xai.js.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +26 -2
- package/dist/utils/errors.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude-max.js","names":[],"sources":["../../src/providers/claude-max.ts"],"sourcesContent":["/**\n * Claude Max OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with Claude Max plan.\n * The OAuth endpoint requires a specific system message to be present.\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\nimport type { ThinkingLevel } from './openai-codex.js';\n\n// Required for Claude Max plan OAuth - the endpoint checks for this system message\nconst claudeCodeIdentity = \"You are Claude Code, Anthropic's official CLI for Claude.\";\n\n// Betas required for Claude Max plan OAuth. Merged with (not replacing) any\n// betas the AI SDK already set on the request — e.g. the SDK adds\n// `server-side-fallback-2026-06-01` when `providerOptions.anthropic.fallbacks`\n// is configured; dropping it makes the API reject the `fallbacks` body field\n// with \"Extra inputs are not permitted\".\nconst OAUTH_REQUIRED_BETAS = [\n 'oauth-2025-04-20',\n 'claude-code-20250219',\n 'interleaved-thinking-2025-05-14',\n 'fine-grained-tool-streaming-2025-05-14',\n];\n\n// Singleton auth storage instance\nlet authStorageInstance: AuthStorage | null = null;\n\n/**\n * Get or create the shared AuthStorage instance\n */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/**\n * Set a custom AuthStorage instance (useful for TUI integration)\n */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Middleware that injects the Claude Code identity system message\n * Required for Claude Max OAuth authentication\n */\nexport const claudeCodeMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n // Prepend the Claude Code identity as the first system message\n const systemMessage = {\n role: 'system' as const,\n content: claudeCodeIdentity,\n };\n\n if (params.temperature) {\n delete params.topP;\n }\n\n return {\n ...params,\n prompt: [systemMessage, ...params.prompt],\n };\n },\n};\n\n/**\n * Prompt caching middleware for Anthropic\n *\n * Adds cache breakpoints at strategic locations:\n * 1. Last system message (end of static instructions + dynamic memory)\n * 2. Most recent user/assistant message (conversation context)\n *\n * This allows Anthropic to cache:\n * - System prompts and instructions (rarely change)\n * - Conversation history up to the last message\n */\nexport const promptCacheMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const prompt = [...params.prompt];\n\n const cacheControl = { type: 'ephemeral' as const, ttl: '5m' as const };\n\n // Helper to add cache control to a message's last content part\n const addCacheToMessage = (msg: any) => {\n // For system messages with string content\n if (typeof msg.content === 'string') {\n return {\n ...msg,\n providerOptions: {\n ...msg.providerOptions,\n anthropic: { ...msg.providerOptions?.anthropic, cacheControl },\n },\n };\n }\n\n // For messages with array content, add to last part\n if (Array.isArray(msg.content) && msg.content.length > 0) {\n const content = [...msg.content];\n const lastPart = content[content.length - 1];\n content[content.length - 1] = {\n ...lastPart,\n providerOptions: {\n ...lastPart.providerOptions,\n anthropic: { ...lastPart.providerOptions?.anthropic, cacheControl },\n },\n };\n return { ...msg, content };\n }\n\n return msg;\n };\n\n // Find the last system message index\n let lastSystemIdx = -1;\n for (let i = prompt.length - 1; i >= 0; i--) {\n if ((prompt[i] as any).role === 'system') {\n lastSystemIdx = i;\n break;\n }\n }\n\n // Add cache breakpoint to last system message\n if (lastSystemIdx >= 0) {\n prompt[lastSystemIdx] = addCacheToMessage(prompt[lastSystemIdx]);\n }\n\n // Add cache breakpoint to the most recent message (last in array)\n const lastIdx = prompt.length - 1;\n if (lastIdx >= 0 && lastIdx !== lastSystemIdx) {\n prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);\n }\n\n return { ...params, prompt };\n },\n};\n\ntype ActiveThinkingLevel = Exclude<ThinkingLevel, 'off'>;\n\n// Anthropic's effort scale matches mastracode thinking levels 1:1 (minus 'off'),\n// including 'max' — the level OpenAI's scale stops short of.\nconst ANTHROPIC_EFFORT: Record<ActiveThinkingLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'> = {\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n};\n\nconst ANTHROPIC_XHIGH_EFFORT_RE = /claude-(?:opus-4-[78]|opus-5|sonnet-5|fable-5)/;\n\nfunction getAnthropicEffort(modelId: string, level: ActiveThinkingLevel) {\n if (level === 'xhigh' && !ANTHROPIC_XHIGH_EFFORT_RE.test(modelId)) return 'high';\n return ANTHROPIC_EFFORT[level];\n}\n\n// Extended-thinking budgets for models that predate adaptive thinking/effort.\n// Budgets count toward max_tokens, so they stay well below the smallest\n// output ceiling of the budget-era models (32k on Opus 4.0/4.1).\nconst ANTHROPIC_THINKING_BUDGET_TOKENS: Record<ActiveThinkingLevel, number> = {\n low: 4096,\n medium: 8192,\n high: 16384,\n xhigh: 24576,\n max: 24576,\n};\n\n/** Claude generations that support adaptive thinking + `output_config.effort`. */\nconst ADAPTIVE_THINKING_RE = /claude-(?:sonnet-4-6|opus-4-[678]|opus-5|sonnet-5|fable-5)/;\n/** Older generations that support extended thinking via `budget_tokens`. */\nconst BUDGET_THINKING_RE = /claude-(?:3-7|sonnet-4|opus-4|haiku-4-5)/;\n/** Generations with no extended-thinking support at all. */\nconst NO_THINKING_RE = /claude-(?:instant|v?2(?:[-.:]|$)|3(?:[-.]|$)|3-5)/;\n\nfunction getAnthropicThinkingCapability(modelId: string): 'adaptive' | 'budget' | 'none' {\n if (ADAPTIVE_THINKING_RE.test(modelId)) return 'adaptive';\n if (BUDGET_THINKING_RE.test(modelId)) return 'budget';\n if (NO_THINKING_RE.test(modelId)) return 'none';\n // Unknown (i.e. newer) Claude models: assume the current API surface.\n return 'adaptive';\n}\n\n/**\n * Middleware that maps the session thinking level onto Anthropic extended\n * thinking. Returns `undefined` (no middleware) when the level is `off` or the\n * model doesn't support thinking, preserving the previous request shape.\n *\n * - Adaptive-era models (Sonnet 4.6+/Opus 4.6+): `thinking: adaptive` plus\n * `output_config.effort` mapped 1:1 from the level (including `max`).\n * - Budget-era models (Claude 3.7 – Opus 4.5): `thinking: enabled` with a\n * `budget_tokens` value derived from the level.\n */\nexport function createAnthropicThinkingMiddleware(\n modelId: string,\n thinkingLevel?: ThinkingLevel,\n): LanguageModelMiddleware | undefined {\n if (!thinkingLevel || thinkingLevel === 'off') return undefined;\n const capability = getAnthropicThinkingCapability(modelId);\n if (capability === 'none') return undefined;\n const level = thinkingLevel as ActiveThinkingLevel;\n\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const anthropicOptions = (params.providerOptions?.anthropic ?? {}) as Record<string, unknown>;\n // Don't override explicit per-request thinking configuration.\n if (anthropicOptions.thinking !== undefined || anthropicOptions.effort !== undefined) {\n return params;\n }\n\n // Anthropic rejects sampling parameters when thinking is enabled.\n delete params.temperature;\n delete params.topP;\n delete params.topK;\n\n params.providerOptions = {\n ...params.providerOptions,\n anthropic: {\n ...anthropicOptions,\n ...(capability === 'adaptive'\n ? { thinking: { type: 'adaptive', display: 'summarized' }, effort: getAnthropicEffort(modelId, level) }\n : { thinking: { type: 'enabled', budgetTokens: ANTHROPIC_THINKING_BUDGET_TOKENS[level] } }),\n },\n } as typeof params.providerOptions;\n\n return params;\n },\n };\n}\n\n/**\n * Build a fetch function that handles Anthropic OAuth.\n * Preserves non-auth headers from init (critical for gateway auth header to survive\n * when used with the gateway). Strips `authorization` and `x-api-key`.\n */\nexport function buildAnthropicOAuthFetch(opts: { authStorage?: CredentialStore } = {}): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const storedCred = storage.get('anthropic');\n if (storedCred?.type === 'api_key') {\n throw new Error('Anthropic API key credential is configured, but OAuth is required.');\n }\n\n const accessToken = await storage.getApiKey('anthropic');\n if (!accessToken) {\n throw new Error('Not logged in to Anthropic. Run /login first.');\n }\n\n // Preserve existing headers, strip auth-related ones\n const headers = new Headers();\n if (init?.headers) {\n const source =\n init.headers instanceof Headers\n ? init.headers\n : Array.isArray(init.headers)\n ? new Headers(init.headers as Array<[string, string]>)\n : new Headers(init.headers as Record<string, string>);\n source.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower !== 'authorization' && lower !== 'x-api-key') {\n headers.set(key, value);\n }\n });\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n const requestBetas = (headers.get('anthropic-beta') ?? '')\n .split(',')\n .map(beta => beta.trim())\n .filter(Boolean);\n headers.set('anthropic-beta', Array.from(new Set([...OAUTH_REQUIRED_BETAS, ...requestBetas])).join(','));\n headers.set('anthropic-version', '2023-06-01');\n\n try {\n return await fetch(url, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: url instanceof URL ? url.toString() : typeof url === 'string' ? url : url.url,\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Creates an Anthropic model using Claude Max OAuth authentication\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed)\n */\nexport function opencodeClaudeMaxProvider(\n modelId: string = 'claude-sonnet-4-20250514',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore; thinkingLevel?: ThinkingLevel },\n): MastraModelConfig {\n const headers = options?.headers;\n const thinkingMiddleware = createAnthropicThinkingMiddleware(modelId, options?.thinkingLevel);\n const middleware = [claudeCodeMiddleware, promptCacheMiddleware, ...(thinkingMiddleware ? [thinkingMiddleware] : [])];\n\n // Test environment: use API key\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n const anthropic = createAnthropic({\n apiKey: 'test-api-key',\n headers,\n });\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n }\n\n const anthropic = createAnthropic({\n apiKey: 'oauth-placeholder',\n headers,\n fetch: buildAnthropicOAuthFetch({ authStorage: options?.authStorage }) as any,\n });\n\n // Wrap with middleware to inject Claude Code identity and enable prompt caching\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"claude-max.js","names":[],"sources":["../../src/providers/claude-max.ts"],"sourcesContent":["/**\n * Claude Max OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with Claude Max plan.\n * The OAuth endpoint requires a specific system message to be present.\n */\n\nimport { createAnthropic } from '@ai-sdk/anthropic';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { ProviderAuthRequiredError } from '../auth/provider-auth-error.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\nimport type { ThinkingLevel } from './openai-codex.js';\n\n// Required for Claude Max plan OAuth - the endpoint checks for this system message\nconst claudeCodeIdentity = \"You are Claude Code, Anthropic's official CLI for Claude.\";\n\n// Betas required for Claude Max plan OAuth. Merged with (not replacing) any\n// betas the AI SDK already set on the request — e.g. the SDK adds\n// `server-side-fallback-2026-06-01` when `providerOptions.anthropic.fallbacks`\n// is configured; dropping it makes the API reject the `fallbacks` body field\n// with \"Extra inputs are not permitted\".\nconst OAUTH_REQUIRED_BETAS = [\n 'oauth-2025-04-20',\n 'claude-code-20250219',\n 'interleaved-thinking-2025-05-14',\n 'fine-grained-tool-streaming-2025-05-14',\n];\n\n// Singleton auth storage instance\nlet authStorageInstance: AuthStorage | null = null;\n\n/**\n * Get or create the shared AuthStorage instance\n */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/**\n * Set a custom AuthStorage instance (useful for TUI integration)\n */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Middleware that injects the Claude Code identity system message\n * Required for Claude Max OAuth authentication\n */\nexport const claudeCodeMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n // Prepend the Claude Code identity as the first system message\n const systemMessage = {\n role: 'system' as const,\n content: claudeCodeIdentity,\n };\n\n if (params.temperature) {\n delete params.topP;\n }\n\n return {\n ...params,\n prompt: [systemMessage, ...params.prompt],\n };\n },\n};\n\n/**\n * Prompt caching middleware for Anthropic\n *\n * Adds cache breakpoints at strategic locations:\n * 1. Last system message (end of static instructions + dynamic memory)\n * 2. Most recent user/assistant message (conversation context)\n *\n * This allows Anthropic to cache:\n * - System prompts and instructions (rarely change)\n * - Conversation history up to the last message\n */\nexport const promptCacheMiddleware: LanguageModelMiddleware = {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const prompt = [...params.prompt];\n\n const cacheControl = { type: 'ephemeral' as const, ttl: '5m' as const };\n\n // Helper to add cache control to a message's last content part\n const addCacheToMessage = (msg: any) => {\n // For system messages with string content\n if (typeof msg.content === 'string') {\n return {\n ...msg,\n providerOptions: {\n ...msg.providerOptions,\n anthropic: { ...msg.providerOptions?.anthropic, cacheControl },\n },\n };\n }\n\n // For messages with array content, add to last part\n if (Array.isArray(msg.content) && msg.content.length > 0) {\n const content = [...msg.content];\n const lastPart = content[content.length - 1];\n content[content.length - 1] = {\n ...lastPart,\n providerOptions: {\n ...lastPart.providerOptions,\n anthropic: { ...lastPart.providerOptions?.anthropic, cacheControl },\n },\n };\n return { ...msg, content };\n }\n\n return msg;\n };\n\n // Find the last system message index\n let lastSystemIdx = -1;\n for (let i = prompt.length - 1; i >= 0; i--) {\n if ((prompt[i] as any).role === 'system') {\n lastSystemIdx = i;\n break;\n }\n }\n\n // Add cache breakpoint to last system message\n if (lastSystemIdx >= 0) {\n prompt[lastSystemIdx] = addCacheToMessage(prompt[lastSystemIdx]);\n }\n\n // Add cache breakpoint to the most recent message (last in array)\n const lastIdx = prompt.length - 1;\n if (lastIdx >= 0 && lastIdx !== lastSystemIdx) {\n prompt[lastIdx] = addCacheToMessage(prompt[lastIdx]);\n }\n\n return { ...params, prompt };\n },\n};\n\ntype ActiveThinkingLevel = Exclude<ThinkingLevel, 'off'>;\n\n// Anthropic's effort scale matches mastracode thinking levels 1:1 (minus 'off'),\n// including 'max' — the level OpenAI's scale stops short of.\nconst ANTHROPIC_EFFORT: Record<ActiveThinkingLevel, 'low' | 'medium' | 'high' | 'xhigh' | 'max'> = {\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n};\n\nconst ANTHROPIC_XHIGH_EFFORT_RE = /claude-(?:opus-4-[78]|opus-5|sonnet-5|fable-5)/;\n\nfunction getAnthropicEffort(modelId: string, level: ActiveThinkingLevel) {\n if (level === 'xhigh' && !ANTHROPIC_XHIGH_EFFORT_RE.test(modelId)) return 'high';\n return ANTHROPIC_EFFORT[level];\n}\n\n// Extended-thinking budgets for models that predate adaptive thinking/effort.\n// Budgets count toward max_tokens, so they stay well below the smallest\n// output ceiling of the budget-era models (32k on Opus 4.0/4.1).\nconst ANTHROPIC_THINKING_BUDGET_TOKENS: Record<ActiveThinkingLevel, number> = {\n low: 4096,\n medium: 8192,\n high: 16384,\n xhigh: 24576,\n max: 24576,\n};\n\n/** Claude generations that support adaptive thinking + `output_config.effort`. */\nconst ADAPTIVE_THINKING_RE = /claude-(?:sonnet-4-6|opus-4-[678]|opus-5|sonnet-5|fable-5)/;\n/** Older generations that support extended thinking via `budget_tokens`. */\nconst BUDGET_THINKING_RE = /claude-(?:3-7|sonnet-4|opus-4|haiku-4-5)/;\n/** Generations with no extended-thinking support at all. */\nconst NO_THINKING_RE = /claude-(?:instant|v?2(?:[-.:]|$)|3(?:[-.]|$)|3-5)/;\n\nfunction getAnthropicThinkingCapability(modelId: string): 'adaptive' | 'budget' | 'none' {\n if (ADAPTIVE_THINKING_RE.test(modelId)) return 'adaptive';\n if (BUDGET_THINKING_RE.test(modelId)) return 'budget';\n if (NO_THINKING_RE.test(modelId)) return 'none';\n // Unknown (i.e. newer) Claude models: assume the current API surface.\n return 'adaptive';\n}\n\n/**\n * Middleware that maps the session thinking level onto Anthropic extended\n * thinking. Returns `undefined` (no middleware) when the level is `off` or the\n * model doesn't support thinking, preserving the previous request shape.\n *\n * - Adaptive-era models (Sonnet 4.6+/Opus 4.6+): `thinking: adaptive` plus\n * `output_config.effort` mapped 1:1 from the level (including `max`).\n * - Budget-era models (Claude 3.7 – Opus 4.5): `thinking: enabled` with a\n * `budget_tokens` value derived from the level.\n */\nexport function createAnthropicThinkingMiddleware(\n modelId: string,\n thinkingLevel?: ThinkingLevel,\n): LanguageModelMiddleware | undefined {\n if (!thinkingLevel || thinkingLevel === 'off') return undefined;\n const capability = getAnthropicThinkingCapability(modelId);\n if (capability === 'none') return undefined;\n const level = thinkingLevel as ActiveThinkingLevel;\n\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n const anthropicOptions = (params.providerOptions?.anthropic ?? {}) as Record<string, unknown>;\n // Don't override explicit per-request thinking configuration.\n if (anthropicOptions.thinking !== undefined || anthropicOptions.effort !== undefined) {\n return params;\n }\n\n // Anthropic rejects sampling parameters when thinking is enabled.\n delete params.temperature;\n delete params.topP;\n delete params.topK;\n\n params.providerOptions = {\n ...params.providerOptions,\n anthropic: {\n ...anthropicOptions,\n ...(capability === 'adaptive'\n ? { thinking: { type: 'adaptive', display: 'summarized' }, effort: getAnthropicEffort(modelId, level) }\n : { thinking: { type: 'enabled', budgetTokens: ANTHROPIC_THINKING_BUDGET_TOKENS[level] } }),\n },\n } as typeof params.providerOptions;\n\n return params;\n },\n };\n}\n\n/**\n * Build a fetch function that handles Anthropic OAuth.\n * Preserves non-auth headers from init (critical for gateway auth header to survive\n * when used with the gateway). Strips `authorization` and `x-api-key`.\n */\nexport function buildAnthropicOAuthFetch(opts: { authStorage?: CredentialStore } = {}): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const storedCred = storage.get('anthropic');\n if (storedCred?.type === 'api_key') {\n throw new Error('Anthropic API key credential is configured, but OAuth is required.');\n }\n\n const accessToken = await storage.getApiKey('anthropic');\n if (!accessToken) {\n throw new ProviderAuthRequiredError('Not logged in to Anthropic.');\n }\n\n // Preserve existing headers, strip auth-related ones\n const headers = new Headers();\n if (init?.headers) {\n const source =\n init.headers instanceof Headers\n ? init.headers\n : Array.isArray(init.headers)\n ? new Headers(init.headers as Array<[string, string]>)\n : new Headers(init.headers as Record<string, string>);\n source.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower !== 'authorization' && lower !== 'x-api-key') {\n headers.set(key, value);\n }\n });\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n const requestBetas = (headers.get('anthropic-beta') ?? '')\n .split(',')\n .map(beta => beta.trim())\n .filter(Boolean);\n headers.set('anthropic-beta', Array.from(new Set([...OAUTH_REQUIRED_BETAS, ...requestBetas])).join(','));\n headers.set('anthropic-version', '2023-06-01');\n\n try {\n return await fetch(url, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: url instanceof URL ? url.toString() : typeof url === 'string' ? url : url.url,\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Creates an Anthropic model using Claude Max OAuth authentication\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed)\n */\nexport function opencodeClaudeMaxProvider(\n modelId: string = 'claude-sonnet-4-20250514',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore; thinkingLevel?: ThinkingLevel },\n): MastraModelConfig {\n const headers = options?.headers;\n const thinkingMiddleware = createAnthropicThinkingMiddleware(modelId, options?.thinkingLevel);\n const middleware = [claudeCodeMiddleware, promptCacheMiddleware, ...(thinkingMiddleware ? [thinkingMiddleware] : [])];\n\n // Test environment: use API key\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n const anthropic = createAnthropic({\n apiKey: 'test-api-key',\n headers,\n });\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n }\n\n const anthropic = createAnthropic({\n apiKey: 'oauth-placeholder',\n headers,\n fetch: buildAnthropicOAuthFetch({ authStorage: options?.authStorage }) as any,\n });\n\n // Wrap with middleware to inject Claude Code identity and enable prompt caching\n return wrapLanguageModel({\n model: anthropic(modelId),\n middleware,\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../src/providers/github-copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"github-copilot.d.ts","sourceRoot":"","sources":["../../src/providers/github-copilot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAO1D,OAAO,KAAK,EAAE,iBAAiB,EAA4B,MAAM,qCAAqC,CAAC;AACvG,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAOxD,qDAAqD;AACrD,wBAAgB,cAAc,IAAI,WAAW,CAK5C;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAErE;AAqED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,eAAe,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACjE,OAAO,KAAK,CAgFd;AAwED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,MAAkB,EAC3B,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,eAAe,CAAA;CAAE,GAC5E,iBAAiB,CAiBnB;AAsCD,wFAAwF;AACxF,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,WAAW,CAAA;CAAO,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyDnH"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { COPILOT_HEADERS, fetchCopilotModels, getGitHubCopilotBaseUrl } from "../auth/providers/github-copilot.js";
|
|
2
2
|
import { AuthStorage } from "../auth/storage.js";
|
|
3
|
+
import { ProviderAuthRequiredError } from "../auth/provider-auth-error.js";
|
|
3
4
|
import { wrapLanguageModel } from "ai";
|
|
4
5
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
5
6
|
import { GoogleSchemaCompatLayer, applyCompatLayer } from "@mastra/schema-compat";
|
|
@@ -77,9 +78,9 @@ function buildGitHubCopilotOAuthFetch(opts = {}) {
|
|
|
77
78
|
const storage = opts.authStorage ?? getAuthStorage();
|
|
78
79
|
storage.reload();
|
|
79
80
|
const cred = storage.get(COPILOT_PROVIDER_ID);
|
|
80
|
-
if (!cred || cred.type !== "oauth") throw new
|
|
81
|
+
if (!cred || cred.type !== "oauth") throw new ProviderAuthRequiredError("Not logged in to GitHub Copilot.");
|
|
81
82
|
const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);
|
|
82
|
-
if (!accessToken) throw new
|
|
83
|
+
if (!accessToken) throw new ProviderAuthRequiredError("Failed to refresh the GitHub Copilot token.");
|
|
83
84
|
storage.reload();
|
|
84
85
|
const enterpriseUrl = cred.enterpriseUrl;
|
|
85
86
|
let parsedBody;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-copilot.js","names":[],"sources":["../../src/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with GitHub Copilot's chat API.\n * The Copilot API speaks an OpenAI-compatible chat format, so we plug\n * `@ai-sdk/openai-compatible` into Copilot's API URL and use a custom fetch to inject\n * the bearer token and Copilot-specific headers.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n */\n\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport type { JSONSchema7 } from '@mastra/schema-compat';\nimport { applyCompatLayer, GoogleSchemaCompatLayer } from '@mastra/schema-compat';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { COPILOT_HEADERS, fetchCopilotModels, getGitHubCopilotBaseUrl } from '../auth/providers/github-copilot.js';\nimport type { CopilotModelEntry, GitHubCopilotCredentials } from '../auth/providers/github-copilot.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\nconst COPILOT_PROVIDER_ID = 'github-copilot';\n\n// Singleton auth storage instance (shared with claude-max.ts / openai-codex.ts when not overridden).\nlet authStorageInstance: AuthStorage | null = null;\n\n/** Get or create the shared AuthStorage instance. */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/** Set a custom AuthStorage instance (useful for tests / TUI integration). */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Heuristic: did this request come from the agent (e.g. tool result follow-ups) rather\n * than a fresh user turn? Mirrors opencode's `isAgent` logic — Copilot bills these\n * differently via the `x-initiator` header.\n */\nfunction detectIsAgent(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const messages = obj.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n const last = messages[messages.length - 1] as { role?: string; content?: unknown };\n if (last?.role && last.role !== 'user') return true;\n if (Array.isArray(last?.content)) {\n // If the last user turn carries any tool_result parts, treat it as an agent turn.\n const hasToolResult = last.content.some(\n (part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'tool_result',\n );\n if (hasToolResult) return true;\n }\n }\n\n const input = obj.input;\n if (Array.isArray(input) && input.length > 0) {\n const last = input[input.length - 1] as { role?: string };\n if (last?.role && last.role !== 'user') return true;\n }\n\n return false;\n}\n\n/** Detect image/vision content in a request body. */\nfunction detectIsVision(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const matchPart = (part: unknown): boolean => {\n if (!part || typeof part !== 'object') return false;\n const t = (part as { type?: string }).type;\n return t === 'image' || t === 'image_url' || t === 'input_image';\n };\n\n const messages = obj.messages;\n if (Array.isArray(messages)) {\n return messages.some(\n (msg: unknown) =>\n msg &&\n typeof msg === 'object' &&\n Array.isArray((msg as { content?: unknown }).content) &&\n ((msg as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n const input = obj.input;\n if (Array.isArray(input)) {\n return input.some(\n (item: unknown) =>\n item &&\n typeof item === 'object' &&\n Array.isArray((item as { content?: unknown }).content) &&\n ((item as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n return false;\n}\n\n/**\n * Build a fetch wrapper that authenticates with GitHub Copilot OAuth.\n *\n * - Injects the short-lived Copilot bearer token (auto-refreshed by AuthStorage).\n * - Adds the VS Code-like Copilot headers required by the API.\n * - Rewrites the request URL onto the per-token API base when `rewriteUrl` is true.\n */\nexport function buildGitHubCopilotOAuthFetch(\n opts: { authStorage?: CredentialStore; rewriteUrl?: boolean } = {},\n): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n throw new Error('Not logged in to GitHub Copilot. Run /login first.');\n }\n\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) {\n throw new Error('Failed to refresh GitHub Copilot token. Please /login again.');\n }\n storage.reload();\n\n const enterpriseUrl = (cred as GitHubCopilotCredentials).enterpriseUrl;\n\n let parsedBody: unknown;\n if (typeof init?.body === 'string') {\n try {\n parsedBody = JSON.parse(init.body);\n } catch {\n parsedBody = undefined;\n }\n }\n const isAgent = detectIsAgent(parsedBody);\n const isVision = detectIsVision(parsedBody);\n\n // Preserve non-auth headers from caller.\n const headers = new Headers();\n if (init?.headers) {\n const source =\n init.headers instanceof Headers\n ? init.headers\n : Array.isArray(init.headers)\n ? new Headers(init.headers as Array<[string, string]>)\n : new Headers(init.headers as Record<string, string>);\n source.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower !== 'authorization' && lower !== 'x-api-key') {\n headers.set(key, value);\n }\n });\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n headers.set('x-initiator', isAgent ? 'agent' : 'user');\n headers.set('Openai-Intent', 'conversation-edits');\n if (isVision) {\n headers.set('Copilot-Vision-Request', 'true');\n }\n for (const [key, value] of Object.entries(COPILOT_HEADERS)) {\n // Only set if caller didn't already provide it (allow overrides for tests).\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n }\n\n const finalUrl =\n opts.rewriteUrl !== false\n ? rewriteToCopilotBase(url, accessToken, enterpriseUrl)\n : url instanceof URL\n ? url\n : typeof url === 'string'\n ? new URL(url)\n : new URL((url as Request).url);\n\n try {\n return await fetch(finalUrl, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: finalUrl.toString(),\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\nfunction rewriteToCopilotBase(url: string | URL | Request, token: string, enterpriseDomain?: string): URL {\n const original = url instanceof URL ? url : new URL(typeof url === 'string' ? url : (url as Request).url);\n const base = new URL(getGitHubCopilotBaseUrl(token, enterpriseDomain));\n // Copilot's OpenAI-compatible API serves endpoints at the root of the base host\n // (`/chat/completions`, `/responses`, `/models`, ...) — not under a `/v1/` prefix\n // like api.openai.com does. The @ai-sdk/openai default baseURL is\n // `https://api.openai.com/v1`, so the SDK builds requests like\n // `https://api.openai.com/v1/chat/completions`. Strip the leading `/v1` segment\n // when rewriting onto the Copilot base or Copilot will return 404 Not Found.\n const pathname = original.pathname.replace(/^\\/v1(\\/|$)/, '/');\n return new URL(`${pathname}${original.search}`, base);\n}\n\nfunction isGeminiModel(modelId: string): boolean {\n return modelId.startsWith('gemini-');\n}\n\nfunction applyGeminiSchemaCompatToTools(modelId: string, tools: unknown): unknown {\n if (!Array.isArray(tools)) {\n return tools;\n }\n\n const compatLayer = new GoogleSchemaCompatLayer({\n provider: COPILOT_PROVIDER_ID,\n modelId,\n supportsStructuredOutputs: false,\n });\n\n return tools.map(tool => {\n if (!tool || typeof tool !== 'object' || (tool as { type?: unknown }).type !== 'function') {\n return tool;\n }\n\n const functionTool = tool as { inputSchema?: JSONSchema7 };\n if (!functionTool.inputSchema) {\n return tool;\n }\n\n return {\n ...functionTool,\n inputSchema: applyCompatLayer({\n schema: functionTool.inputSchema,\n compatLayers: [compatLayer],\n mode: 'aiSdkSchema',\n }).jsonSchema as JSONSchema7,\n };\n });\n}\n\n/** Middleware that prevents sending parameters Copilot's endpoint rejects. */\nfunction createCopilotMiddleware(modelId: string): LanguageModelMiddleware {\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n if (params.temperature !== undefined && params.temperature !== null) {\n delete params.topP;\n }\n\n if (isGeminiModel(modelId)) {\n (params as { tools?: unknown }).tools = applyGeminiSchemaCompatToTools(\n modelId,\n (params as { tools?: unknown }).tools,\n );\n }\n\n return params;\n },\n };\n}\n\n/**\n * Creates a model that talks to GitHub Copilot using OAuth credentials.\n *\n * Copilot's `/chat/completions` endpoint is OpenAI-compatible, but GitHub Copilot\n * is not OpenAI. Use the generic OpenAI-compatible adapter with Copilot's base URL\n * instead of the OpenAI provider plus URL rewriting.\n */\nexport function githubCopilotProvider(\n modelId: string = 'gpt-4.1',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const headers = options?.headers;\n const copilot = createOpenAICompatible({\n name: COPILOT_PROVIDER_ID,\n baseURL: 'https://api.githubcopilot.com',\n apiKey: process.env.NODE_ENV === 'test' || process.env.VITEST ? 'test-api-key' : 'oauth-placeholder',\n headers,\n fetch:\n process.env.NODE_ENV === 'test' || process.env.VITEST\n ? undefined\n : (buildGitHubCopilotOAuthFetch({ rewriteUrl: false, authStorage: options?.authStorage }) as any),\n });\n\n return wrapLanguageModel({\n model: copilot.chatModel(modelId),\n middleware: [createCopilotMiddleware(modelId)],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Live model catalog\n// ---------------------------------------------------------------------------\n\n/**\n * Hard-coded fallback advertised when the live `/models` request fails (network\n * down, expired token, etc.). Keep this conservative because the live catalog is\n * the source of truth for the user's currently-enabled Copilot models.\n *\n * Available across all paid Copilot tiers and free of premium-request charges.\n */\nconst COPILOT_FALLBACK_MODELS: CopilotModelEntry[] = [\n {\n id: 'gpt-4.1',\n name: 'GPT-4.1',\n vendor: 'OpenAI',\n supportedEndpoints: ['/chat/completions'],\n isAnthropicShaped: false,\n supportsVision: true,\n supportsToolCalls: true,\n },\n];\n\nconst CATALOG_TTL_MS = 10 * 60 * 1000;\nconst CATALOG_FAILURE_TTL_MS = 60 * 1000;\nconst CATALOG_FETCH_TIMEOUT_MS = 5_000;\n\ninterface CatalogCacheEntry {\n fetchedAt: number;\n ttl: number;\n models: CopilotModelEntry[];\n}\n\nlet catalogCache: CatalogCacheEntry | null = null;\nlet inflightFetch: Promise<CopilotModelEntry[]> | null = null;\n\n/** Reset the in-process Copilot catalog cache (test seam, also useful after logout). */\nexport function clearCopilotCatalogCache(): void {\n catalogCache = null;\n inflightFetch = null;\n}\n\n/**\n * Return the user's currently-available Copilot models.\n *\n * - Returns `[]` when the user is not logged in to GitHub Copilot.\n * - Returns the cached list when a recent fetch succeeded.\n * - On the cache-miss / expired path, fetches `/models` with a 5s timeout, filters\n * to picker-enabled and non-policy-disabled models, then caches for 10 minutes.\n * - On fetch failure, returns a small hard-coded fallback (so packs still work\n * offline) and caches that for 1 minute to avoid hammering the API.\n *\n * Concurrent calls during a fetch share the inflight promise.\n */\nexport async function getCopilotModelCatalog(opts: { authStorage?: AuthStorage } = {}): Promise<CopilotModelEntry[]> {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n return [];\n }\n\n const now = Date.now();\n if (catalogCache && now - catalogCache.fetchedAt < catalogCache.ttl) {\n return catalogCache.models;\n }\n\n if (inflightFetch) return inflightFetch;\n\n inflightFetch = (async (): Promise<CopilotModelEntry[]> => {\n try {\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) throw new Error('No Copilot bearer token');\n storage.reload();\n\n const refreshed = storage.get(COPILOT_PROVIDER_ID);\n const enterpriseUrl = (refreshed as GitHubCopilotCredentials | undefined)?.enterpriseUrl;\n const baseUrl = getGitHubCopilotBaseUrl(accessToken, enterpriseUrl);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);\n try {\n const models = await fetchCopilotModels({\n baseUrl,\n bearerToken: accessToken,\n signal: controller.signal,\n });\n catalogCache = { fetchedAt: Date.now(), ttl: CATALOG_TTL_MS, models };\n return models;\n } finally {\n clearTimeout(timer);\n }\n } catch (error) {\n catalogCache = {\n fetchedAt: Date.now(),\n ttl: CATALOG_FAILURE_TTL_MS,\n models: COPILOT_FALLBACK_MODELS,\n };\n console.warn(\n 'Failed to fetch live GitHub Copilot models, using fallback list:',\n error instanceof Error ? error.message : error,\n );\n return COPILOT_FALLBACK_MODELS;\n } finally {\n inflightFetch = null;\n }\n })();\n\n return inflightFetch;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,sBAAsB;AAG5B,IAAI,sBAA0C;;AAG9C,SAAgB,iBAA8B;CAC5C,IAAI,CAAC,qBACH,sBAAsB,IAAI,YAAY;CAExC,OAAO;AACT;;AAGA,SAAgB,eAAe,SAAwC;CACrE,sBAAsB,WAAW;AACnC;;;;;;AAOA,SAAS,cAAc,MAAwB;CAC7C,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,MAAM;CAEZ,MAAM,WAAW,IAAI;CACrB,IAAI,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS,GAAG;EAClD,MAAM,OAAO,SAAS,SAAS,SAAS;EACxC,IAAI,MAAM,QAAQ,KAAK,SAAS,QAAQ,OAAO;EAC/C,IAAI,MAAM,QAAQ,MAAM,OAAO,GAEP;OAAA,KAAK,QAAQ,MAChC,SAAkB,QAAQ,OAAO,SAAS,YAAa,KAA2B,SAAS,aAE9E,GAAG,OAAO;EAAA;CAE9B;CAEA,MAAM,QAAQ,IAAI;CAClB,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,GAAG;EAC5C,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,IAAI,MAAM,QAAQ,KAAK,SAAS,QAAQ,OAAO;CACjD;CAEA,OAAO;AACT;;AAGA,SAAS,eAAe,MAAwB;CAC9C,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;CAC9C,MAAM,MAAM;CAEZ,MAAM,aAAa,SAA2B;EAC5C,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO;EAC9C,MAAM,IAAK,KAA2B;EACtC,OAAO,MAAM,WAAW,MAAM,eAAe,MAAM;CACrD;CAEA,MAAM,WAAW,IAAI;CACrB,IAAI,MAAM,QAAQ,QAAQ,GACxB,OAAO,SAAS,MACb,QACC,OACA,OAAO,QAAQ,YACf,MAAM,QAAS,IAA8B,OAAO,KAClD,IAA+B,QAAsB,KAAK,SAAS,CACzE;CAGF,MAAM,QAAQ,IAAI;CAClB,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,MACV,SACC,QACA,OAAO,SAAS,YAChB,MAAM,QAAS,KAA+B,OAAO,KACnD,KAAgC,QAAsB,KAAK,SAAS,CAC1E;CAGF,OAAO;AACT;;;;;;;;AASA,SAAgB,6BACd,OAAgE,CAAC,GACnD;CACd,QAAQ,OAAO,KAA6B,SAAuC;EACjF,MAAM,UAAU,KAAK,eAAe,eAAe;EACnD,QAAQ,OAAO;EAEf,MAAM,OAAO,QAAQ,IAAI,mBAAmB;EAC5C,IAAI,CAAC,QAAQ,KAAK,SAAS,SACzB,MAAM,IAAI,MAAM,oDAAoD;EAItE,MAAM,cAAc,MAAM,QAAQ,UAAU,mBAAmB;EAC/D,IAAI,CAAC,aACH,MAAM,IAAI,MAAM,8DAA8D;EAEhF,QAAQ,OAAO;EAEf,MAAM,gBAAiB,KAAkC;EAEzD,IAAI;EACJ,IAAI,OAAO,MAAM,SAAS,UACxB,IAAI;GACF,aAAa,KAAK,MAAM,KAAK,IAAI;EACnC,QAAQ;GACN,aAAa,KAAA;EACf;EAEF,MAAM,UAAU,cAAc,UAAU;EACxC,MAAM,WAAW,eAAe,UAAU;EAG1C,MAAM,UAAU,IAAI,QAAQ;EAC5B,IAAI,MAAM,SAOR,CALE,KAAK,mBAAmB,UACpB,KAAK,UACL,MAAM,QAAQ,KAAK,OAAO,IACxB,IAAI,QAAQ,KAAK,OAAkC,IACnD,IAAI,QAAQ,KAAK,OAAiC,EAAA,CACnD,SAAS,OAAO,QAAQ;GAC7B,MAAM,QAAQ,IAAI,YAAY;GAC9B,IAAI,UAAU,mBAAmB,UAAU,aACzC,QAAQ,IAAI,KAAK,KAAK;EAE1B,CAAC;EAGH,QAAQ,IAAI,iBAAiB,UAAU,aAAa;EACpD,QAAQ,IAAI,eAAe,UAAU,UAAU,MAAM;EACrD,QAAQ,IAAI,iBAAiB,oBAAoB;EACjD,IAAI,UACF,QAAQ,IAAI,0BAA0B,MAAM;EAE9C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,eAAe,GAEvD,IAAI,CAAC,QAAQ,IAAI,GAAG,GAClB,QAAQ,IAAI,KAAK,KAAK;EAI1B,MAAM,WACJ,KAAK,eAAe,QAChB,qBAAqB,KAAK,aAAa,aAAa,IACpD,eAAe,MACb,MACA,OAAO,QAAQ,WACb,IAAI,IAAI,GAAG,IACX,IAAI,IAAK,IAAgB,GAAG;EAEtC,IAAI;GACF,OAAO,MAAM,MAAM,UAAU;IAAE,GAAG;IAAM;GAAQ,CAAC;EACnD,SAAS,OAAO;GACd,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,OAAO,OAAkC,EAC9C,YAAY,SAAS,SAAS,EAChC,CAAC;GAEH,MAAM;EACR;CACF;AACF;AAEA,SAAS,qBAAqB,KAA6B,OAAe,kBAAgC;CACxG,MAAM,WAAW,eAAe,MAAM,MAAM,IAAI,IAAI,OAAO,QAAQ,WAAW,MAAO,IAAgB,GAAG;CACxG,MAAM,OAAO,IAAI,IAAI,wBAAwB,OAAO,gBAAgB,CAAC;CAOrE,MAAM,WAAW,SAAS,SAAS,QAAQ,eAAe,GAAG;CAC7D,OAAO,IAAI,IAAI,GAAG,WAAW,SAAS,UAAU,IAAI;AACtD;AAEA,SAAS,cAAc,SAA0B;CAC/C,OAAO,QAAQ,WAAW,SAAS;AACrC;AAEA,SAAS,+BAA+B,SAAiB,OAAyB;CAChF,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,OAAO;CAGT,MAAM,cAAc,IAAI,wBAAwB;EAC9C,UAAU;EACV;EACA,2BAA2B;CAC7B,CAAC;CAED,OAAO,MAAM,KAAI,SAAQ;EACvB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAa,KAA4B,SAAS,YAC7E,OAAO;EAGT,MAAM,eAAe;EACrB,IAAI,CAAC,aAAa,aAChB,OAAO;EAGT,OAAO;GACL,GAAG;GACH,aAAa,iBAAiB;IAC5B,QAAQ,aAAa;IACrB,cAAc,CAAC,WAAW;IAC1B,MAAM;GACR,CAAC,CAAC,CAAC;EACL;CACF,CAAC;AACH;;AAGA,SAAS,wBAAwB,SAA0C;CACzE,OAAO;EACL,sBAAsB;EACtB,iBAAiB,OAAO,EAAE,aAAa;GACrC,IAAI,OAAO,gBAAgB,KAAA,KAAa,OAAO,gBAAgB,MAC7D,OAAO,OAAO;GAGhB,IAAI,cAAc,OAAO,GACvB,OAAgC,QAAQ,+BACtC,SACC,OAA+B,KAClC;GAGF,OAAO;EACT;CACF;AACF;;;;;;;;AASA,SAAgB,sBACd,UAAkB,WAClB,SACmB;CACnB,MAAM,UAAU,SAAS;CAYzB,OAAO,kBAAkB;EACvB,OAZc,uBAAuB;GACrC,MAAM;GACN,SAAS;GACT,QAAQ,QAAQ,IAAI,aAAa,UAAU,QAAQ,IAAI,SAAS,iBAAiB;GACjF;GACA,OACE,QAAQ,IAAI,aAAa,UAAU,QAAQ,IAAI,SAC3C,KAAA,IACC,6BAA6B;IAAE,YAAY;IAAO,aAAa,SAAS;GAAY,CAAC;EAC9F,CAGe,CAAC,CAAC,UAAU,OAAO;EAChC,YAAY,CAAC,wBAAwB,OAAO,CAAC;CAC/C,CAAC;AACH;;;;;;;;AAaA,MAAM,0BAA+C,CACnD;CACE,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,oBAAoB,CAAC,mBAAmB;CACxC,mBAAmB;CACnB,gBAAgB;CAChB,mBAAmB;AACrB,CACF;AAEA,MAAM,iBAAiB,MAAU;AACjC,MAAM,yBAAyB,KAAK;AACpC,MAAM,2BAA2B;AAQjC,IAAI,eAAyC;AAC7C,IAAI,gBAAqD;;AAGzD,SAAgB,2BAAiC;CAC/C,eAAe;CACf,gBAAgB;AAClB;;;;;;;;;;;;;AAcA,eAAsB,uBAAuB,OAAsC,CAAC,GAAiC;CACnH,MAAM,UAAU,KAAK,eAAe,eAAe;CACnD,QAAQ,OAAO;CAEf,MAAM,OAAO,QAAQ,IAAI,mBAAmB;CAC5C,IAAI,CAAC,QAAQ,KAAK,SAAS,SACzB,OAAO,CAAC;CAIV,IAAI,gBADQ,KAAK,IACK,IAAI,aAAa,YAAY,aAAa,KAC9D,OAAO,aAAa;CAGtB,IAAI,eAAe,OAAO;CAE1B,iBAAiB,YAA0C;EACzD,IAAI;GAEF,MAAM,cAAc,MAAM,QAAQ,UAAU,mBAAmB;GAC/D,IAAI,CAAC,aAAa,MAAM,IAAI,MAAM,yBAAyB;GAC3D,QAAQ,OAAO;GAGf,MAAM,gBADY,QAAQ,IAAI,mBACC,CAAC,EAA2C;GAC3E,MAAM,UAAU,wBAAwB,aAAa,aAAa;GAElE,MAAM,aAAa,IAAI,gBAAgB;GACvC,MAAM,QAAQ,iBAAiB,WAAW,MAAM,GAAG,wBAAwB;GAC3E,IAAI;IACF,MAAM,SAAS,MAAM,mBAAmB;KACtC;KACA,aAAa;KACb,QAAQ,WAAW;IACrB,CAAC;IACD,eAAe;KAAE,WAAW,KAAK,IAAI;KAAG,KAAK;KAAgB;IAAO;IACpE,OAAO;GACT,UAAU;IACR,aAAa,KAAK;GACpB;EACF,SAAS,OAAO;GACd,eAAe;IACb,WAAW,KAAK,IAAI;IACpB,KAAK;IACL,QAAQ;GACV;GACA,QAAQ,KACN,oEACA,iBAAiB,QAAQ,MAAM,UAAU,KAC3C;GACA,OAAO;EACT,UAAU;GACR,gBAAgB;EAClB;CACF,EAAA,CAAG;CAEH,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"github-copilot.js","names":[],"sources":["../../src/providers/github-copilot.ts"],"sourcesContent":["/**\n * GitHub Copilot OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with GitHub Copilot's chat API.\n * The Copilot API speaks an OpenAI-compatible chat format, so we plug\n * `@ai-sdk/openai-compatible` into Copilot's API URL and use a custom fetch to inject\n * the bearer token and Copilot-specific headers.\n *\n * Inspired by:\n * - opencode: https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/plugin/github-copilot/copilot.ts\n * - pi-mono: https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/utils/oauth/github-copilot.ts\n */\n\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport type { JSONSchema7 } from '@mastra/schema-compat';\nimport { applyCompatLayer, GoogleSchemaCompatLayer } from '@mastra/schema-compat';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { ProviderAuthRequiredError } from '../auth/provider-auth-error.js';\nimport { COPILOT_HEADERS, fetchCopilotModels, getGitHubCopilotBaseUrl } from '../auth/providers/github-copilot.js';\nimport type { CopilotModelEntry, GitHubCopilotCredentials } from '../auth/providers/github-copilot.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\nconst COPILOT_PROVIDER_ID = 'github-copilot';\n\n// Singleton auth storage instance (shared with claude-max.ts / openai-codex.ts when not overridden).\nlet authStorageInstance: AuthStorage | null = null;\n\n/** Get or create the shared AuthStorage instance. */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/** Set a custom AuthStorage instance (useful for tests / TUI integration). */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Heuristic: did this request come from the agent (e.g. tool result follow-ups) rather\n * than a fresh user turn? Mirrors opencode's `isAgent` logic — Copilot bills these\n * differently via the `x-initiator` header.\n */\nfunction detectIsAgent(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const messages = obj.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n const last = messages[messages.length - 1] as { role?: string; content?: unknown };\n if (last?.role && last.role !== 'user') return true;\n if (Array.isArray(last?.content)) {\n // If the last user turn carries any tool_result parts, treat it as an agent turn.\n const hasToolResult = last.content.some(\n (part: unknown) => part && typeof part === 'object' && (part as { type?: string }).type === 'tool_result',\n );\n if (hasToolResult) return true;\n }\n }\n\n const input = obj.input;\n if (Array.isArray(input) && input.length > 0) {\n const last = input[input.length - 1] as { role?: string };\n if (last?.role && last.role !== 'user') return true;\n }\n\n return false;\n}\n\n/** Detect image/vision content in a request body. */\nfunction detectIsVision(body: unknown): boolean {\n if (!body || typeof body !== 'object') return false;\n const obj = body as Record<string, unknown>;\n\n const matchPart = (part: unknown): boolean => {\n if (!part || typeof part !== 'object') return false;\n const t = (part as { type?: string }).type;\n return t === 'image' || t === 'image_url' || t === 'input_image';\n };\n\n const messages = obj.messages;\n if (Array.isArray(messages)) {\n return messages.some(\n (msg: unknown) =>\n msg &&\n typeof msg === 'object' &&\n Array.isArray((msg as { content?: unknown }).content) &&\n ((msg as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n const input = obj.input;\n if (Array.isArray(input)) {\n return input.some(\n (item: unknown) =>\n item &&\n typeof item === 'object' &&\n Array.isArray((item as { content?: unknown }).content) &&\n ((item as { content: unknown[] }).content as unknown[]).some(matchPart),\n );\n }\n\n return false;\n}\n\n/**\n * Build a fetch wrapper that authenticates with GitHub Copilot OAuth.\n *\n * - Injects the short-lived Copilot bearer token (auto-refreshed by AuthStorage).\n * - Adds the VS Code-like Copilot headers required by the API.\n * - Rewrites the request URL onto the per-token API base when `rewriteUrl` is true.\n */\nexport function buildGitHubCopilotOAuthFetch(\n opts: { authStorage?: CredentialStore; rewriteUrl?: boolean } = {},\n): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n throw new ProviderAuthRequiredError('Not logged in to GitHub Copilot.');\n }\n\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) {\n throw new ProviderAuthRequiredError('Failed to refresh the GitHub Copilot token.');\n }\n storage.reload();\n\n const enterpriseUrl = (cred as GitHubCopilotCredentials).enterpriseUrl;\n\n let parsedBody: unknown;\n if (typeof init?.body === 'string') {\n try {\n parsedBody = JSON.parse(init.body);\n } catch {\n parsedBody = undefined;\n }\n }\n const isAgent = detectIsAgent(parsedBody);\n const isVision = detectIsVision(parsedBody);\n\n // Preserve non-auth headers from caller.\n const headers = new Headers();\n if (init?.headers) {\n const source =\n init.headers instanceof Headers\n ? init.headers\n : Array.isArray(init.headers)\n ? new Headers(init.headers as Array<[string, string]>)\n : new Headers(init.headers as Record<string, string>);\n source.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (lower !== 'authorization' && lower !== 'x-api-key') {\n headers.set(key, value);\n }\n });\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n headers.set('x-initiator', isAgent ? 'agent' : 'user');\n headers.set('Openai-Intent', 'conversation-edits');\n if (isVision) {\n headers.set('Copilot-Vision-Request', 'true');\n }\n for (const [key, value] of Object.entries(COPILOT_HEADERS)) {\n // Only set if caller didn't already provide it (allow overrides for tests).\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n }\n\n const finalUrl =\n opts.rewriteUrl !== false\n ? rewriteToCopilotBase(url, accessToken, enterpriseUrl)\n : url instanceof URL\n ? url\n : typeof url === 'string'\n ? new URL(url)\n : new URL((url as Request).url);\n\n try {\n return await fetch(finalUrl, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: finalUrl.toString(),\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\nfunction rewriteToCopilotBase(url: string | URL | Request, token: string, enterpriseDomain?: string): URL {\n const original = url instanceof URL ? url : new URL(typeof url === 'string' ? url : (url as Request).url);\n const base = new URL(getGitHubCopilotBaseUrl(token, enterpriseDomain));\n // Copilot's OpenAI-compatible API serves endpoints at the root of the base host\n // (`/chat/completions`, `/responses`, `/models`, ...) — not under a `/v1/` prefix\n // like api.openai.com does. The @ai-sdk/openai default baseURL is\n // `https://api.openai.com/v1`, so the SDK builds requests like\n // `https://api.openai.com/v1/chat/completions`. Strip the leading `/v1` segment\n // when rewriting onto the Copilot base or Copilot will return 404 Not Found.\n const pathname = original.pathname.replace(/^\\/v1(\\/|$)/, '/');\n return new URL(`${pathname}${original.search}`, base);\n}\n\nfunction isGeminiModel(modelId: string): boolean {\n return modelId.startsWith('gemini-');\n}\n\nfunction applyGeminiSchemaCompatToTools(modelId: string, tools: unknown): unknown {\n if (!Array.isArray(tools)) {\n return tools;\n }\n\n const compatLayer = new GoogleSchemaCompatLayer({\n provider: COPILOT_PROVIDER_ID,\n modelId,\n supportsStructuredOutputs: false,\n });\n\n return tools.map(tool => {\n if (!tool || typeof tool !== 'object' || (tool as { type?: unknown }).type !== 'function') {\n return tool;\n }\n\n const functionTool = tool as { inputSchema?: JSONSchema7 };\n if (!functionTool.inputSchema) {\n return tool;\n }\n\n return {\n ...functionTool,\n inputSchema: applyCompatLayer({\n schema: functionTool.inputSchema,\n compatLayers: [compatLayer],\n mode: 'aiSdkSchema',\n }).jsonSchema as JSONSchema7,\n };\n });\n}\n\n/** Middleware that prevents sending parameters Copilot's endpoint rejects. */\nfunction createCopilotMiddleware(modelId: string): LanguageModelMiddleware {\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n if (params.temperature !== undefined && params.temperature !== null) {\n delete params.topP;\n }\n\n if (isGeminiModel(modelId)) {\n (params as { tools?: unknown }).tools = applyGeminiSchemaCompatToTools(\n modelId,\n (params as { tools?: unknown }).tools,\n );\n }\n\n return params;\n },\n };\n}\n\n/**\n * Creates a model that talks to GitHub Copilot using OAuth credentials.\n *\n * Copilot's `/chat/completions` endpoint is OpenAI-compatible, but GitHub Copilot\n * is not OpenAI. Use the generic OpenAI-compatible adapter with Copilot's base URL\n * instead of the OpenAI provider plus URL rewriting.\n */\nexport function githubCopilotProvider(\n modelId: string = 'gpt-4.1',\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const headers = options?.headers;\n const copilot = createOpenAICompatible({\n name: COPILOT_PROVIDER_ID,\n baseURL: 'https://api.githubcopilot.com',\n apiKey: process.env.NODE_ENV === 'test' || process.env.VITEST ? 'test-api-key' : 'oauth-placeholder',\n headers,\n fetch:\n process.env.NODE_ENV === 'test' || process.env.VITEST\n ? undefined\n : (buildGitHubCopilotOAuthFetch({ rewriteUrl: false, authStorage: options?.authStorage }) as any),\n });\n\n return wrapLanguageModel({\n model: copilot.chatModel(modelId),\n middleware: [createCopilotMiddleware(modelId)],\n });\n}\n\n// ---------------------------------------------------------------------------\n// Live model catalog\n// ---------------------------------------------------------------------------\n\n/**\n * Hard-coded fallback advertised when the live `/models` request fails (network\n * down, expired token, etc.). Keep this conservative because the live catalog is\n * the source of truth for the user's currently-enabled Copilot models.\n *\n * Available across all paid Copilot tiers and free of premium-request charges.\n */\nconst COPILOT_FALLBACK_MODELS: CopilotModelEntry[] = [\n {\n id: 'gpt-4.1',\n name: 'GPT-4.1',\n vendor: 'OpenAI',\n supportedEndpoints: ['/chat/completions'],\n isAnthropicShaped: false,\n supportsVision: true,\n supportsToolCalls: true,\n },\n];\n\nconst CATALOG_TTL_MS = 10 * 60 * 1000;\nconst CATALOG_FAILURE_TTL_MS = 60 * 1000;\nconst CATALOG_FETCH_TIMEOUT_MS = 5_000;\n\ninterface CatalogCacheEntry {\n fetchedAt: number;\n ttl: number;\n models: CopilotModelEntry[];\n}\n\nlet catalogCache: CatalogCacheEntry | null = null;\nlet inflightFetch: Promise<CopilotModelEntry[]> | null = null;\n\n/** Reset the in-process Copilot catalog cache (test seam, also useful after logout). */\nexport function clearCopilotCatalogCache(): void {\n catalogCache = null;\n inflightFetch = null;\n}\n\n/**\n * Return the user's currently-available Copilot models.\n *\n * - Returns `[]` when the user is not logged in to GitHub Copilot.\n * - Returns the cached list when a recent fetch succeeded.\n * - On the cache-miss / expired path, fetches `/models` with a 5s timeout, filters\n * to picker-enabled and non-policy-disabled models, then caches for 10 minutes.\n * - On fetch failure, returns a small hard-coded fallback (so packs still work\n * offline) and caches that for 1 minute to avoid hammering the API.\n *\n * Concurrent calls during a fetch share the inflight promise.\n */\nexport async function getCopilotModelCatalog(opts: { authStorage?: AuthStorage } = {}): Promise<CopilotModelEntry[]> {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(COPILOT_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n return [];\n }\n\n const now = Date.now();\n if (catalogCache && now - catalogCache.fetchedAt < catalogCache.ttl) {\n return catalogCache.models;\n }\n\n if (inflightFetch) return inflightFetch;\n\n inflightFetch = (async (): Promise<CopilotModelEntry[]> => {\n try {\n // getApiKey() refreshes the Copilot bearer if it has expired.\n const accessToken = await storage.getApiKey(COPILOT_PROVIDER_ID);\n if (!accessToken) throw new Error('No Copilot bearer token');\n storage.reload();\n\n const refreshed = storage.get(COPILOT_PROVIDER_ID);\n const enterpriseUrl = (refreshed as GitHubCopilotCredentials | undefined)?.enterpriseUrl;\n const baseUrl = getGitHubCopilotBaseUrl(accessToken, enterpriseUrl);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), CATALOG_FETCH_TIMEOUT_MS);\n try {\n const models = await fetchCopilotModels({\n baseUrl,\n bearerToken: accessToken,\n signal: controller.signal,\n });\n catalogCache = { fetchedAt: Date.now(), ttl: CATALOG_TTL_MS, models };\n return models;\n } finally {\n clearTimeout(timer);\n }\n } catch (error) {\n catalogCache = {\n fetchedAt: Date.now(),\n ttl: CATALOG_FAILURE_TTL_MS,\n models: COPILOT_FALLBACK_MODELS,\n };\n console.warn(\n 'Failed to fetch live GitHub Copilot models, using fallback list:',\n error instanceof Error ? error.message : error,\n );\n return COPILOT_FALLBACK_MODELS;\n } finally {\n inflightFetch = null;\n }\n })();\n\n return inflightFetch;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../src/providers/openai-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"openai-codex.d.ts","sourceRoot":"","sources":["../../src/providers/openai-codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,IAAI,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAUxD;;GAEG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAK5C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAErE;AAOD,mCAAmC;AACnC,MAAM,MAAM,aAAa,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAKhF,mFAAmF;AACnF,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMnE;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,aAAa,CAY9F;AAMD,eAAO,MAAM,kCAAkC,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAOxF,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG,uBAAuB,CA4BvF;AAqCD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,eAAe,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACjE,OAAO,KAAK,CAyDd;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,KAAK,CAgC/E;AAwID;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAA4B,EACrC,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,aAAa,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,eAAe,CAAA;CAAE,GAC3G,iBAAiB,CAmCnB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthStorage } from "../auth/storage.js";
|
|
2
|
+
import { ProviderAuthRequiredError } from "../auth/provider-auth-error.js";
|
|
2
3
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
3
4
|
import { wrapLanguageModel } from "ai";
|
|
4
5
|
//#region src/providers/openai-codex.ts
|
|
@@ -90,11 +91,11 @@ async function getCodexBearer(authStorage) {
|
|
|
90
91
|
const storage = authStorage ?? getAuthStorage();
|
|
91
92
|
storage.reload();
|
|
92
93
|
const cred = storage.get("openai-codex");
|
|
93
|
-
if (!cred || cred.type !== "oauth") throw new
|
|
94
|
+
if (!cred || cred.type !== "oauth") throw new ProviderAuthRequiredError("Not logged in to OpenAI Codex.");
|
|
94
95
|
let accessToken = cred.access;
|
|
95
96
|
if (Date.now() >= cred.expires) {
|
|
96
97
|
const refreshedToken = await storage.getApiKey("openai-codex");
|
|
97
|
-
if (!refreshedToken) throw new
|
|
98
|
+
if (!refreshedToken) throw new ProviderAuthRequiredError("Failed to refresh the OpenAI Codex token.");
|
|
98
99
|
accessToken = refreshedToken;
|
|
99
100
|
storage.reload();
|
|
100
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-codex.js","names":[],"sources":["../../src/providers/openai-codex.ts"],"sourcesContent":["/**\n * OpenAI Codex OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with ChatGPT Plus/Pro subscription.\n * This allows access to OpenAI models through the ChatGPT OAuth flow.\n *\n * Inspired by opencode's Codex plugin implementation:\n * https://github.com/sst/opencode/blob/main/packages/opencode/src/plugin/codex.ts\n */\n\nimport { createOpenAI } from '@ai-sdk/openai';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\n// Codex API endpoint (not standard OpenAI API)\nconst CODEX_API_ENDPOINT = 'https://chatgpt.com/backend-api/codex/responses';\nconst CODEX_ORIGINATOR = 'mastracode';\nconst CODEX_USER_AGENT = 'mastracode';\n\n// Singleton auth storage instance (shared with claude-max.ts)\nlet authStorageInstance: AuthStorage | null = null;\n\n/**\n * Get or create the shared AuthStorage instance\n */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/**\n * Set a custom AuthStorage instance (useful for TUI integration)\n */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n// Default instructions for Codex API (required)\nconst CODEX_INSTRUCTIONS = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: You should be concise, direct, and helpful. Focus on solving the user's problem efficiently.`;\n\n/** Valid thinking level values. */\nexport type ThinkingLevel = 'off' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';\n\nconst GPT5_MODEL_RE = /^gpt-5(?:\\.|-|$)/;\nconst GPT_VERSION_RE = /^gpt-(\\d+)(?:\\.(\\d+))?/;\n\n/** GPT-5.6+ accepts `reasoning effort: max`; older GPT models top out at xhigh. */\nexport function supportsMaxReasoningEffort(modelId: string): boolean {\n const match = GPT_VERSION_RE.exec(modelId);\n if (!match) return false;\n const major = Number(match[1]);\n const minor = Number(match[2] ?? 0);\n return major > 5 || (major === 5 && minor >= 6);\n}\n\nexport function getEffectiveThinkingLevel(modelId: string, level: ThinkingLevel): ThinkingLevel {\n // GPT-5.* models on Codex require at least low reasoning.\n if (GPT5_MODEL_RE.test(modelId) && level === 'off') {\n return 'low';\n }\n\n // Clamp `max` to `xhigh` only for models whose effort scale tops out there.\n if (level === 'max' && !supportsMaxReasoningEffort(modelId)) {\n return 'xhigh';\n }\n\n return level;\n}\n\n// Map thinkingLevel state values to OpenAI reasoningEffort values.\n// undefined means omit the parameter (no reasoning). Model-dependent clamping\n// (e.g. `max` → `xhigh` for pre-GPT-5.6 models) happens in\n// getEffectiveThinkingLevel before this lookup.\nexport const THINKING_LEVEL_TO_REASONING_EFFORT: Record<ThinkingLevel, string | undefined> = {\n off: undefined,\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n};\n\n/**\n * Create Codex middleware with the given reasoning effort level.\n */\nexport function createCodexMiddleware(reasoningEffort?: string): LanguageModelMiddleware {\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n // Remove topP if temperature is set (OpenAI doesn't like both)\n if (params.temperature !== undefined && params.temperature !== null) {\n delete params.topP;\n }\n\n // Codex API requires specific settings via providerOptions\n // Use type assertion to satisfy JSONValue constraints\n params.providerOptions = {\n ...params.providerOptions,\n openai: {\n ...(params.providerOptions?.openai ?? {}),\n instructions: CODEX_INSTRUCTIONS,\n // Codex API requires store to be false\n store: false,\n // Enable reasoning for Codex models — without this, the model\n // skips the reasoning/action phase and goes straight to final_answer,\n // resulting in narration instead of tool calls.\n ...(reasoningEffort ? { reasoningEffort } : {}),\n },\n } as typeof params.providerOptions;\n\n return params;\n },\n };\n}\n\n/**\n * Get a live OAuth bearer token for the Codex OAuth credential.\n *\n * Refreshes the token if it's expired, and returns the credential's\n * accountId alongside the access token. Throws if the user isn't logged in\n * or if the refresh fails.\n *\n * This is the only piece of Codex auth that is genuinely shared between\n * the main agent's fetch (`buildOpenAICodexOAuthFetch`) and the Stagehand\n * fetch (`buildCodexStagehandFetch`).\n */\nasync function getCodexBearer(\n authStorage?: CredentialStore,\n): Promise<{ accessToken: string; accountId: string | undefined }> {\n const storage = authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get('openai-codex');\n if (!cred || cred.type !== 'oauth') {\n throw new Error('Not logged in to OpenAI Codex. Run /login first.');\n }\n\n let accessToken = cred.access;\n if (Date.now() >= cred.expires) {\n const refreshedToken = await storage.getApiKey('openai-codex');\n if (!refreshedToken) {\n throw new Error('Failed to refresh OpenAI Codex token. Please /login again.');\n }\n accessToken = refreshedToken;\n storage.reload();\n }\n\n return { accessToken, accountId: (cred as any).accountId as string | undefined };\n}\n\n/**\n * Build a fetch function that handles OpenAI Codex OAuth.\n * Preserves non-authorization headers from init.\n * When rewriteUrl is true (default), rewrites /v1/responses and /chat/completions\n * to the Codex API endpoint. Set rewriteUrl: false for gateway usage where the\n * SDK already targets the correct URL.\n */\nexport function buildOpenAICodexOAuthFetch(\n opts: { authStorage?: CredentialStore; rewriteUrl?: boolean } = {},\n): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const { accessToken, accountId } = await getCodexBearer(opts.authStorage);\n\n // Preserve non-authorization headers\n const headers = new Headers();\n if (init?.headers) {\n if (init.headers instanceof Headers) {\n init.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'authorization') {\n headers.set(key, value);\n }\n });\n } else if (Array.isArray(init.headers)) {\n for (const [key, value] of init.headers) {\n if (key!.toLowerCase() !== 'authorization' && value !== undefined) {\n headers.set(key!, String(value));\n }\n }\n } else {\n for (const [key, value] of Object.entries(init.headers)) {\n if (key.toLowerCase() !== 'authorization' && value !== undefined) {\n headers.set(key, String(value));\n }\n }\n }\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (!headers.has('originator')) {\n headers.set('originator', CODEX_ORIGINATOR);\n }\n if (!headers.has('User-Agent')) {\n headers.set('User-Agent', CODEX_USER_AGENT);\n }\n if (accountId) {\n headers.set('ChatGPT-Account-ID', accountId);\n }\n\n // URL rewriting — only when rewriteUrl !== false\n const parsed = url instanceof URL ? url : new URL(typeof url === 'string' ? url : (url as Request).url);\n const shouldRewrite =\n opts.rewriteUrl !== false &&\n (parsed.pathname.includes('/v1/responses') || parsed.pathname.includes('/chat/completions'));\n const finalUrl = shouldRewrite ? new URL(CODEX_API_ENDPOINT) : parsed;\n\n try {\n return await fetch(finalUrl, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: finalUrl.toString(),\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Build a fetch function for Stagehand-on-Codex.\n *\n * The Codex backend has two requirements that AI SDK's non-streaming\n * `generateText` path doesn't naturally satisfy:\n *\n * 1. `stream: true` must be set on every request body.\n * 2. The response is delivered as Server-Sent Events; AI SDK's\n * non-streaming code path expects a single JSON body.\n *\n * This fetch forces streaming on the outgoing request, collects the SSE\n * events, and synthesizes the non-streaming JSON shape that\n * `@ai-sdk/openai`'s Responses API parser expects.\n *\n * Headers, OAuth refresh, and URL targeting are handled by the caller via\n * `baseURL` / `headers` on the AI SDK provider; this fetch only injects the\n * live OAuth bearer per call.\n */\nexport function buildCodexStagehandFetch(authStorage: AuthStorage): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n // Refresh + inject the OAuth bearer per call\n const { accessToken } = await getCodexBearer(authStorage);\n const headers = new Headers(init?.headers);\n headers.set('Authorization', `Bearer ${accessToken}`);\n headers.set('Accept', 'text/event-stream');\n\n // Force stream: true on the request body\n type FetchBody = NonNullable<Parameters<typeof fetch>[1]>['body'];\n let body: FetchBody | undefined = init?.body;\n if (typeof init?.body === 'string') {\n try {\n const parsed = JSON.parse(init.body) as Record<string, unknown>;\n parsed.stream = true;\n body = JSON.stringify(parsed);\n if (!headers.has('content-type')) headers.set('content-type', 'application/json');\n } catch {\n // Not JSON; leave as-is\n }\n }\n\n const upstream = await fetch(url, { ...init, headers, body });\n if (!upstream.ok) return upstream;\n\n // Aggregate SSE -> synthesized non-streaming Response\n const aggregated = await aggregateCodexStream(upstream);\n return new Response(aggregated, {\n status: 200,\n headers: { 'content-type': 'application/json' },\n });\n }) as typeof fetch;\n}\n\n/**\n * Read an SSE Response and reduce it to a single JSON string matching the\n * non-streaming OpenAI Responses-API shape.\n *\n * Event vocabulary we care about (per OpenAI Responses API streaming):\n * - response.created → carries `response` object (id, model, usage stub)\n * - response.output_item.added/done → output items (message, reasoning, etc.)\n * - response.output_text.delta → text chunks\n * - response.completed → final `response` snapshot incl. usage\n * - response.error / error → bubble up as a thrown body\n *\n * Reasoning events (`response.reasoning_summary.*`) are intentionally ignored\n * for the non-streaming text response.\n */\nasync function aggregateCodexStream(response: Response): Promise<string> {\n if (!response.body) {\n throw new Error('Codex streaming response had no body');\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n\n let finalResponse: any = null;\n let createdResponse: any = null;\n // Track output_items by index so we can rebuild the final array\n const items = new Map<number, any>();\n // Accumulate output_text deltas keyed by item_index + content_index\n const textBuffers = new Map<string, string>();\n\n const handleEvent = (event: { event?: string; data?: string }) => {\n if (!event.data || event.data === '[DONE]') return;\n let payload: any;\n try {\n payload = JSON.parse(event.data);\n } catch {\n return;\n }\n const type: string = payload.type ?? event.event ?? '';\n\n switch (type) {\n case 'response.created': {\n createdResponse = payload.response ?? createdResponse;\n break;\n }\n case 'response.output_item.added': {\n if (typeof payload.output_index === 'number' && payload.item) {\n items.set(payload.output_index, payload.item);\n }\n break;\n }\n case 'response.output_item.done': {\n if (typeof payload.output_index === 'number' && payload.item) {\n items.set(payload.output_index, payload.item);\n }\n break;\n }\n case 'response.output_text.delta': {\n const key = `${payload.output_index}:${payload.content_index ?? 0}`;\n textBuffers.set(key, (textBuffers.get(key) ?? '') + (payload.delta ?? ''));\n break;\n }\n case 'response.completed': {\n finalResponse = payload.response ?? finalResponse;\n break;\n }\n case 'response.error':\n case 'error': {\n throw new Error(`Codex stream error: ${JSON.stringify(payload.error ?? payload)}`);\n }\n default:\n // Ignore reasoning / unknown events\n break;\n }\n };\n\n // SSE parser: events separated by blank line; lines like \"event: x\" / \"data: y\"\n // Normalize CRLF→LF so \\r\\n\\r\\n event boundaries parse correctly (SSE spec allows CRLF).\n const processChunk = (chunk: string) => {\n buffer += chunk.replace(/\\r\\n/g, '\\n');\n let sepIdx: number;\n while ((sepIdx = buffer.indexOf('\\n\\n')) !== -1) {\n const raw = buffer.slice(0, sepIdx);\n buffer = buffer.slice(sepIdx + 2);\n const event: { event?: string; data?: string } = {};\n const dataLines: string[] = [];\n for (const line of raw.split('\\n')) {\n if (line.startsWith('event:')) {\n event.event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trimStart());\n }\n }\n if (dataLines.length > 0) {\n event.data = dataLines.join('\\n');\n }\n handleEvent(event);\n }\n };\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n processChunk(decoder.decode(value, { stream: true }));\n }\n processChunk(decoder.decode());\n } finally {\n reader.releaseLock();\n }\n\n // Stitch accumulated text deltas back into their items\n const base = finalResponse ?? createdResponse ?? { output: [] };\n const finalItems = Array.from(items.entries())\n .sort(([a], [b]) => a - b)\n .map(([index, item]) => {\n // Patch message-type items' content text using buffered deltas\n if (item?.type === 'message' && Array.isArray(item.content)) {\n item.content = item.content.map((c: any, ci: number) => {\n const key = `${index}:${ci}`;\n if (textBuffers.has(key)) {\n return { ...c, text: textBuffers.get(key) };\n }\n return c;\n });\n }\n return item;\n });\n\n base.output = finalItems.length > 0 ? finalItems : (base.output ?? []);\n\n return JSON.stringify(base);\n}\n\n/**\n * Creates an OpenAI model using ChatGPT OAuth authentication\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed)\n *\n * IMPORTANT: This uses the Codex API endpoint, not the standard OpenAI API.\n * URLs are rewritten from /v1/responses or /chat/completions to the Codex endpoint.\n */\nexport function openaiCodexProvider(\n modelId: string = 'codex-mini-latest',\n options?: { thinkingLevel?: ThinkingLevel; headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const requestedLevel: ThinkingLevel = options?.thinkingLevel ?? 'medium';\n const effectiveLevel = getEffectiveThinkingLevel(modelId, requestedLevel);\n const reasoningEffort = THINKING_LEVEL_TO_REASONING_EFFORT[effectiveLevel];\n const middleware = createCodexMiddleware(reasoningEffort);\n const headers = options?.headers;\n\n const baseURL = process.env.OPENAI_BASE_URL;\n\n // Test environment: use API key\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n const openai = createOpenAI({\n apiKey: 'test-api-key',\n baseURL,\n headers,\n });\n return wrapLanguageModel({\n model: openai.responses(modelId),\n middleware: [middleware],\n });\n }\n\n const openai = createOpenAI({\n apiKey: 'oauth-dummy-key',\n baseURL,\n headers,\n fetch: buildOpenAICodexOAuthFetch({ authStorage: options?.authStorage }) as any,\n });\n\n // Use the responses API for Codex models\n // Wrap with middleware\n return wrapLanguageModel({\n model: openai.responses(modelId),\n middleware: [middleware],\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"openai-codex.js","names":[],"sources":["../../src/providers/openai-codex.ts"],"sourcesContent":["/**\n * OpenAI Codex OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with ChatGPT Plus/Pro subscription.\n * This allows access to OpenAI models through the ChatGPT OAuth flow.\n *\n * Inspired by opencode's Codex plugin implementation:\n * https://github.com/sst/opencode/blob/main/packages/opencode/src/plugin/codex.ts\n */\n\nimport { createOpenAI } from '@ai-sdk/openai';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { wrapLanguageModel } from 'ai';\nimport type { LanguageModelMiddleware } from 'ai';\nimport { ProviderAuthRequiredError } from '../auth/provider-auth-error.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\n// Codex API endpoint (not standard OpenAI API)\nconst CODEX_API_ENDPOINT = 'https://chatgpt.com/backend-api/codex/responses';\nconst CODEX_ORIGINATOR = 'mastracode';\nconst CODEX_USER_AGENT = 'mastracode';\n\n// Singleton auth storage instance (shared with claude-max.ts)\nlet authStorageInstance: AuthStorage | null = null;\n\n/**\n * Get or create the shared AuthStorage instance\n */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/**\n * Set a custom AuthStorage instance (useful for TUI integration)\n */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n// Default instructions for Codex API (required)\nconst CODEX_INSTRUCTIONS = `You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.\n\nIMPORTANT: You should be concise, direct, and helpful. Focus on solving the user's problem efficiently.`;\n\n/** Valid thinking level values. */\nexport type ThinkingLevel = 'off' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';\n\nconst GPT5_MODEL_RE = /^gpt-5(?:\\.|-|$)/;\nconst GPT_VERSION_RE = /^gpt-(\\d+)(?:\\.(\\d+))?/;\n\n/** GPT-5.6+ accepts `reasoning effort: max`; older GPT models top out at xhigh. */\nexport function supportsMaxReasoningEffort(modelId: string): boolean {\n const match = GPT_VERSION_RE.exec(modelId);\n if (!match) return false;\n const major = Number(match[1]);\n const minor = Number(match[2] ?? 0);\n return major > 5 || (major === 5 && minor >= 6);\n}\n\nexport function getEffectiveThinkingLevel(modelId: string, level: ThinkingLevel): ThinkingLevel {\n // GPT-5.* models on Codex require at least low reasoning.\n if (GPT5_MODEL_RE.test(modelId) && level === 'off') {\n return 'low';\n }\n\n // Clamp `max` to `xhigh` only for models whose effort scale tops out there.\n if (level === 'max' && !supportsMaxReasoningEffort(modelId)) {\n return 'xhigh';\n }\n\n return level;\n}\n\n// Map thinkingLevel state values to OpenAI reasoningEffort values.\n// undefined means omit the parameter (no reasoning). Model-dependent clamping\n// (e.g. `max` → `xhigh` for pre-GPT-5.6 models) happens in\n// getEffectiveThinkingLevel before this lookup.\nexport const THINKING_LEVEL_TO_REASONING_EFFORT: Record<ThinkingLevel, string | undefined> = {\n off: undefined,\n low: 'low',\n medium: 'medium',\n high: 'high',\n xhigh: 'xhigh',\n max: 'max',\n};\n\n/**\n * Create Codex middleware with the given reasoning effort level.\n */\nexport function createCodexMiddleware(reasoningEffort?: string): LanguageModelMiddleware {\n return {\n specificationVersion: 'v3',\n transformParams: async ({ params }) => {\n // Remove topP if temperature is set (OpenAI doesn't like both)\n if (params.temperature !== undefined && params.temperature !== null) {\n delete params.topP;\n }\n\n // Codex API requires specific settings via providerOptions\n // Use type assertion to satisfy JSONValue constraints\n params.providerOptions = {\n ...params.providerOptions,\n openai: {\n ...(params.providerOptions?.openai ?? {}),\n instructions: CODEX_INSTRUCTIONS,\n // Codex API requires store to be false\n store: false,\n // Enable reasoning for Codex models — without this, the model\n // skips the reasoning/action phase and goes straight to final_answer,\n // resulting in narration instead of tool calls.\n ...(reasoningEffort ? { reasoningEffort } : {}),\n },\n } as typeof params.providerOptions;\n\n return params;\n },\n };\n}\n\n/**\n * Get a live OAuth bearer token for the Codex OAuth credential.\n *\n * Refreshes the token if it's expired, and returns the credential's\n * accountId alongside the access token. Throws if the user isn't logged in\n * or if the refresh fails.\n *\n * This is the only piece of Codex auth that is genuinely shared between\n * the main agent's fetch (`buildOpenAICodexOAuthFetch`) and the Stagehand\n * fetch (`buildCodexStagehandFetch`).\n */\nasync function getCodexBearer(\n authStorage?: CredentialStore,\n): Promise<{ accessToken: string; accountId: string | undefined }> {\n const storage = authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get('openai-codex');\n if (!cred || cred.type !== 'oauth') {\n throw new ProviderAuthRequiredError('Not logged in to OpenAI Codex.');\n }\n\n let accessToken = cred.access;\n if (Date.now() >= cred.expires) {\n const refreshedToken = await storage.getApiKey('openai-codex');\n if (!refreshedToken) {\n throw new ProviderAuthRequiredError('Failed to refresh the OpenAI Codex token.');\n }\n accessToken = refreshedToken;\n storage.reload();\n }\n\n return { accessToken, accountId: (cred as any).accountId as string | undefined };\n}\n\n/**\n * Build a fetch function that handles OpenAI Codex OAuth.\n * Preserves non-authorization headers from init.\n * When rewriteUrl is true (default), rewrites /v1/responses and /chat/completions\n * to the Codex API endpoint. Set rewriteUrl: false for gateway usage where the\n * SDK already targets the correct URL.\n */\nexport function buildOpenAICodexOAuthFetch(\n opts: { authStorage?: CredentialStore; rewriteUrl?: boolean } = {},\n): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const { accessToken, accountId } = await getCodexBearer(opts.authStorage);\n\n // Preserve non-authorization headers\n const headers = new Headers();\n if (init?.headers) {\n if (init.headers instanceof Headers) {\n init.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'authorization') {\n headers.set(key, value);\n }\n });\n } else if (Array.isArray(init.headers)) {\n for (const [key, value] of init.headers) {\n if (key!.toLowerCase() !== 'authorization' && value !== undefined) {\n headers.set(key!, String(value));\n }\n }\n } else {\n for (const [key, value] of Object.entries(init.headers)) {\n if (key.toLowerCase() !== 'authorization' && value !== undefined) {\n headers.set(key, String(value));\n }\n }\n }\n }\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n if (!headers.has('originator')) {\n headers.set('originator', CODEX_ORIGINATOR);\n }\n if (!headers.has('User-Agent')) {\n headers.set('User-Agent', CODEX_USER_AGENT);\n }\n if (accountId) {\n headers.set('ChatGPT-Account-ID', accountId);\n }\n\n // URL rewriting — only when rewriteUrl !== false\n const parsed = url instanceof URL ? url : new URL(typeof url === 'string' ? url : (url as Request).url);\n const shouldRewrite =\n opts.rewriteUrl !== false &&\n (parsed.pathname.includes('/v1/responses') || parsed.pathname.includes('/chat/completions'));\n const finalUrl = shouldRewrite ? new URL(CODEX_API_ENDPOINT) : parsed;\n\n try {\n return await fetch(finalUrl, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: finalUrl.toString(),\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Build a fetch function for Stagehand-on-Codex.\n *\n * The Codex backend has two requirements that AI SDK's non-streaming\n * `generateText` path doesn't naturally satisfy:\n *\n * 1. `stream: true` must be set on every request body.\n * 2. The response is delivered as Server-Sent Events; AI SDK's\n * non-streaming code path expects a single JSON body.\n *\n * This fetch forces streaming on the outgoing request, collects the SSE\n * events, and synthesizes the non-streaming JSON shape that\n * `@ai-sdk/openai`'s Responses API parser expects.\n *\n * Headers, OAuth refresh, and URL targeting are handled by the caller via\n * `baseURL` / `headers` on the AI SDK provider; this fetch only injects the\n * live OAuth bearer per call.\n */\nexport function buildCodexStagehandFetch(authStorage: AuthStorage): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n // Refresh + inject the OAuth bearer per call\n const { accessToken } = await getCodexBearer(authStorage);\n const headers = new Headers(init?.headers);\n headers.set('Authorization', `Bearer ${accessToken}`);\n headers.set('Accept', 'text/event-stream');\n\n // Force stream: true on the request body\n type FetchBody = NonNullable<Parameters<typeof fetch>[1]>['body'];\n let body: FetchBody | undefined = init?.body;\n if (typeof init?.body === 'string') {\n try {\n const parsed = JSON.parse(init.body) as Record<string, unknown>;\n parsed.stream = true;\n body = JSON.stringify(parsed);\n if (!headers.has('content-type')) headers.set('content-type', 'application/json');\n } catch {\n // Not JSON; leave as-is\n }\n }\n\n const upstream = await fetch(url, { ...init, headers, body });\n if (!upstream.ok) return upstream;\n\n // Aggregate SSE -> synthesized non-streaming Response\n const aggregated = await aggregateCodexStream(upstream);\n return new Response(aggregated, {\n status: 200,\n headers: { 'content-type': 'application/json' },\n });\n }) as typeof fetch;\n}\n\n/**\n * Read an SSE Response and reduce it to a single JSON string matching the\n * non-streaming OpenAI Responses-API shape.\n *\n * Event vocabulary we care about (per OpenAI Responses API streaming):\n * - response.created → carries `response` object (id, model, usage stub)\n * - response.output_item.added/done → output items (message, reasoning, etc.)\n * - response.output_text.delta → text chunks\n * - response.completed → final `response` snapshot incl. usage\n * - response.error / error → bubble up as a thrown body\n *\n * Reasoning events (`response.reasoning_summary.*`) are intentionally ignored\n * for the non-streaming text response.\n */\nasync function aggregateCodexStream(response: Response): Promise<string> {\n if (!response.body) {\n throw new Error('Codex streaming response had no body');\n }\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buffer = '';\n\n let finalResponse: any = null;\n let createdResponse: any = null;\n // Track output_items by index so we can rebuild the final array\n const items = new Map<number, any>();\n // Accumulate output_text deltas keyed by item_index + content_index\n const textBuffers = new Map<string, string>();\n\n const handleEvent = (event: { event?: string; data?: string }) => {\n if (!event.data || event.data === '[DONE]') return;\n let payload: any;\n try {\n payload = JSON.parse(event.data);\n } catch {\n return;\n }\n const type: string = payload.type ?? event.event ?? '';\n\n switch (type) {\n case 'response.created': {\n createdResponse = payload.response ?? createdResponse;\n break;\n }\n case 'response.output_item.added': {\n if (typeof payload.output_index === 'number' && payload.item) {\n items.set(payload.output_index, payload.item);\n }\n break;\n }\n case 'response.output_item.done': {\n if (typeof payload.output_index === 'number' && payload.item) {\n items.set(payload.output_index, payload.item);\n }\n break;\n }\n case 'response.output_text.delta': {\n const key = `${payload.output_index}:${payload.content_index ?? 0}`;\n textBuffers.set(key, (textBuffers.get(key) ?? '') + (payload.delta ?? ''));\n break;\n }\n case 'response.completed': {\n finalResponse = payload.response ?? finalResponse;\n break;\n }\n case 'response.error':\n case 'error': {\n throw new Error(`Codex stream error: ${JSON.stringify(payload.error ?? payload)}`);\n }\n default:\n // Ignore reasoning / unknown events\n break;\n }\n };\n\n // SSE parser: events separated by blank line; lines like \"event: x\" / \"data: y\"\n // Normalize CRLF→LF so \\r\\n\\r\\n event boundaries parse correctly (SSE spec allows CRLF).\n const processChunk = (chunk: string) => {\n buffer += chunk.replace(/\\r\\n/g, '\\n');\n let sepIdx: number;\n while ((sepIdx = buffer.indexOf('\\n\\n')) !== -1) {\n const raw = buffer.slice(0, sepIdx);\n buffer = buffer.slice(sepIdx + 2);\n const event: { event?: string; data?: string } = {};\n const dataLines: string[] = [];\n for (const line of raw.split('\\n')) {\n if (line.startsWith('event:')) {\n event.event = line.slice(6).trim();\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5).trimStart());\n }\n }\n if (dataLines.length > 0) {\n event.data = dataLines.join('\\n');\n }\n handleEvent(event);\n }\n };\n\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n processChunk(decoder.decode(value, { stream: true }));\n }\n processChunk(decoder.decode());\n } finally {\n reader.releaseLock();\n }\n\n // Stitch accumulated text deltas back into their items\n const base = finalResponse ?? createdResponse ?? { output: [] };\n const finalItems = Array.from(items.entries())\n .sort(([a], [b]) => a - b)\n .map(([index, item]) => {\n // Patch message-type items' content text using buffered deltas\n if (item?.type === 'message' && Array.isArray(item.content)) {\n item.content = item.content.map((c: any, ci: number) => {\n const key = `${index}:${ci}`;\n if (textBuffers.has(key)) {\n return { ...c, text: textBuffers.get(key) };\n }\n return c;\n });\n }\n return item;\n });\n\n base.output = finalItems.length > 0 ? finalItems : (base.output ?? []);\n\n return JSON.stringify(base);\n}\n\n/**\n * Creates an OpenAI model using ChatGPT OAuth authentication\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed)\n *\n * IMPORTANT: This uses the Codex API endpoint, not the standard OpenAI API.\n * URLs are rewritten from /v1/responses or /chat/completions to the Codex endpoint.\n */\nexport function openaiCodexProvider(\n modelId: string = 'codex-mini-latest',\n options?: { thinkingLevel?: ThinkingLevel; headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const requestedLevel: ThinkingLevel = options?.thinkingLevel ?? 'medium';\n const effectiveLevel = getEffectiveThinkingLevel(modelId, requestedLevel);\n const reasoningEffort = THINKING_LEVEL_TO_REASONING_EFFORT[effectiveLevel];\n const middleware = createCodexMiddleware(reasoningEffort);\n const headers = options?.headers;\n\n const baseURL = process.env.OPENAI_BASE_URL;\n\n // Test environment: use API key\n if (process.env.NODE_ENV === 'test' || process.env.VITEST) {\n const openai = createOpenAI({\n apiKey: 'test-api-key',\n baseURL,\n headers,\n });\n return wrapLanguageModel({\n model: openai.responses(modelId),\n middleware: [middleware],\n });\n }\n\n const openai = createOpenAI({\n apiKey: 'oauth-dummy-key',\n baseURL,\n headers,\n fetch: buildOpenAICodexOAuthFetch({ authStorage: options?.authStorage }) as any,\n });\n\n // Use the responses API for Codex models\n // Wrap with middleware\n return wrapLanguageModel({\n model: openai.responses(modelId),\n middleware: [middleware],\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"xai.d.ts","sourceRoot":"","sources":["../../src/providers/xai.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAQxD,qDAAqD;AACrD,wBAAgB,cAAc,IAAI,WAAW,CAK5C;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAErE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,eAAe,CAAA;CAAO,GAAG,OAAO,KAAK,CAsC7F;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,WAAW,CAAC,EAAE,eAAe,CAAA;CAAE,GAC5E,iBAAiB,CASnB"}
|
package/dist/providers/xai.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AuthStorage } from "../auth/storage.js";
|
|
2
|
+
import { ProviderAuthRequiredError } from "../auth/provider-auth-error.js";
|
|
2
3
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
3
4
|
//#region src/providers/xai.ts
|
|
4
5
|
/**
|
|
@@ -32,9 +33,9 @@ function buildXAIOAuthFetch(opts = {}) {
|
|
|
32
33
|
const storage = opts.authStorage ?? getAuthStorage();
|
|
33
34
|
storage.reload();
|
|
34
35
|
const cred = storage.get(XAI_PROVIDER_ID);
|
|
35
|
-
if (!cred || cred.type !== "oauth") throw new
|
|
36
|
+
if (!cred || cred.type !== "oauth") throw new ProviderAuthRequiredError("Not logged in to xAI.");
|
|
36
37
|
const accessToken = await storage.getApiKey(XAI_PROVIDER_ID);
|
|
37
|
-
if (!accessToken) throw new
|
|
38
|
+
if (!accessToken) throw new ProviderAuthRequiredError("Failed to refresh the xAI token.");
|
|
38
39
|
const headers = new Headers(url instanceof Request ? url.headers : void 0);
|
|
39
40
|
if (init?.headers) new Headers(init.headers).forEach((value, key) => headers.set(key, value));
|
|
40
41
|
headers.delete("authorization");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xai.js","names":[],"sources":["../../src/providers/xai.ts"],"sourcesContent":["/**\n * xAI (Grok) OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with the xAI API.\n * The xAI API speaks an OpenAI-compatible chat format, and the OAuth access\n * token is accepted as a bearer API key, so we plug `@ai-sdk/openai-compatible`\n * into `https://api.x.ai/v1` with a custom fetch that injects the (auto-refreshed)\n * access token.\n */\n\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\nconst XAI_PROVIDER_ID = 'xai';\nconst XAI_BASE_URL = 'https://api.x.ai/v1';\n\n// Singleton auth storage instance (shared with claude-max.ts / github-copilot.ts when not overridden).\nlet authStorageInstance: AuthStorage | null = null;\n\n/** Get or create the shared AuthStorage instance. */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/** Set a custom AuthStorage instance (useful for tests / TUI integration). */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Build a fetch wrapper that authenticates with xAI OAuth.\n * Injects the access token (auto-refreshed by AuthStorage) as a bearer token,\n * preserving non-auth headers from the caller.\n */\nexport function buildXAIOAuthFetch(opts: { authStorage?: CredentialStore } = {}): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(XAI_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n throw new
|
|
1
|
+
{"version":3,"file":"xai.js","names":[],"sources":["../../src/providers/xai.ts"],"sourcesContent":["/**\n * xAI (Grok) OAuth Provider\n *\n * Uses OAuth tokens from AuthStorage to authenticate with the xAI API.\n * The xAI API speaks an OpenAI-compatible chat format, and the OAuth access\n * token is accepted as a bearer API key, so we plug `@ai-sdk/openai-compatible`\n * into `https://api.x.ai/v1` with a custom fetch that injects the (auto-refreshed)\n * access token.\n */\n\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport type { MastraModelConfig } from '@mastra/core/llm';\nimport { ProviderAuthRequiredError } from '../auth/provider-auth-error.js';\nimport { AuthStorage } from '../auth/storage.js';\nimport type { CredentialStore } from '../auth/types.js';\n\nconst XAI_PROVIDER_ID = 'xai';\nconst XAI_BASE_URL = 'https://api.x.ai/v1';\n\n// Singleton auth storage instance (shared with claude-max.ts / github-copilot.ts when not overridden).\nlet authStorageInstance: AuthStorage | null = null;\n\n/** Get or create the shared AuthStorage instance. */\nexport function getAuthStorage(): AuthStorage {\n if (!authStorageInstance) {\n authStorageInstance = new AuthStorage();\n }\n return authStorageInstance;\n}\n\n/** Set a custom AuthStorage instance (useful for tests / TUI integration). */\nexport function setAuthStorage(storage: AuthStorage | undefined): void {\n authStorageInstance = storage ?? null;\n}\n\n/**\n * Build a fetch wrapper that authenticates with xAI OAuth.\n * Injects the access token (auto-refreshed by AuthStorage) as a bearer token,\n * preserving non-auth headers from the caller.\n */\nexport function buildXAIOAuthFetch(opts: { authStorage?: CredentialStore } = {}): typeof fetch {\n return (async (url: string | URL | Request, init?: Parameters<typeof fetch>[1]) => {\n const storage = opts.authStorage ?? getAuthStorage();\n storage.reload();\n\n const cred = storage.get(XAI_PROVIDER_ID);\n if (!cred || cred.type !== 'oauth') {\n throw new ProviderAuthRequiredError('Not logged in to xAI.');\n }\n\n // getApiKey() refreshes the access token if it has expired.\n const accessToken = await storage.getApiKey(XAI_PROVIDER_ID);\n if (!accessToken) {\n throw new ProviderAuthRequiredError('Failed to refresh the xAI token.');\n }\n\n // Preserve existing headers, strip auth-related ones. Explicit init\n // headers override headers carried by an incoming Request.\n const headers = new Headers(url instanceof Request ? url.headers : undefined);\n if (init?.headers) {\n new Headers(init.headers).forEach((value, key) => headers.set(key, value));\n }\n headers.delete('authorization');\n headers.delete('x-api-key');\n\n headers.set('Authorization', `Bearer ${accessToken}`);\n\n try {\n return await fetch(url, { ...init, headers });\n } catch (error) {\n if (error && typeof error === 'object') {\n Object.assign(error as Record<string, unknown>, {\n requestUrl: url instanceof URL ? url.toString() : typeof url === 'string' ? url : (url as Request).url,\n });\n }\n throw error;\n }\n }) as typeof fetch;\n}\n\n/**\n * Creates an xAI model using OAuth authentication.\n * Uses OAuth tokens from AuthStorage (auto-refreshes when needed).\n */\nexport function xaiProvider(\n modelId: string,\n options?: { headers?: Record<string, string>; authStorage?: CredentialStore },\n): MastraModelConfig {\n const provider = createOpenAICompatible({\n name: XAI_PROVIDER_ID,\n baseURL: XAI_BASE_URL,\n apiKey: 'oauth-placeholder', // real auth injected by the custom fetch\n headers: options?.headers,\n fetch: buildXAIOAuthFetch({ authStorage: options?.authStorage }),\n });\n return provider.chatModel(modelId);\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,MAAM,kBAAkB;AACxB,MAAM,eAAe;AAGrB,IAAI,sBAA0C;;AAG9C,SAAgB,iBAA8B;CAC5C,IAAI,CAAC,qBACH,sBAAsB,IAAI,YAAY;CAExC,OAAO;AACT;;AAGA,SAAgB,eAAe,SAAwC;CACrE,sBAAsB,WAAW;AACnC;;;;;;AAOA,SAAgB,mBAAmB,OAA0C,CAAC,GAAiB;CAC7F,QAAQ,OAAO,KAA6B,SAAuC;EACjF,MAAM,UAAU,KAAK,eAAe,eAAe;EACnD,QAAQ,OAAO;EAEf,MAAM,OAAO,QAAQ,IAAI,eAAe;EACxC,IAAI,CAAC,QAAQ,KAAK,SAAS,SACzB,MAAM,IAAI,0BAA0B,uBAAuB;EAI7D,MAAM,cAAc,MAAM,QAAQ,UAAU,eAAe;EAC3D,IAAI,CAAC,aACH,MAAM,IAAI,0BAA0B,kCAAkC;EAKxE,MAAM,UAAU,IAAI,QAAQ,eAAe,UAAU,IAAI,UAAU,KAAA,CAAS;EAC5E,IAAI,MAAM,SACR,IAAI,QAAQ,KAAK,OAAO,CAAC,CAAC,SAAS,OAAO,QAAQ,QAAQ,IAAI,KAAK,KAAK,CAAC;EAE3E,QAAQ,OAAO,eAAe;EAC9B,QAAQ,OAAO,WAAW;EAE1B,QAAQ,IAAI,iBAAiB,UAAU,aAAa;EAEpD,IAAI;GACF,OAAO,MAAM,MAAM,KAAK;IAAE,GAAG;IAAM;GAAQ,CAAC;EAC9C,SAAS,OAAO;GACd,IAAI,SAAS,OAAO,UAAU,UAC5B,OAAO,OAAO,OAAkC,EAC9C,YAAY,eAAe,MAAM,IAAI,SAAS,IAAI,OAAO,QAAQ,WAAW,MAAO,IAAgB,IACrG,CAAC;GAEH,MAAM;EACR;CACF;AACF;;;;;AAMA,SAAgB,YACd,SACA,SACmB;CAQnB,OAPiB,uBAAuB;EACtC,MAAM;EACN,SAAS;EACT,QAAQ;EACR,SAAS,SAAS;EAClB,OAAO,mBAAmB,EAAE,aAAa,SAAS,YAAY,CAAC;CACjE,CACc,CAAC,CAAC,UAAU,OAAO;AACnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,WAAW;IAC1B,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,aAAa,EAAE,KAAK,CAAC;CACtB;AAED,MAAM,MAAM,SAAS,GACjB,YAAY,GACZ,MAAM,GACN,SAAS,GACT,SAAS,GACT,iBAAiB,GACjB,cAAc,GACd,iBAAiB,GACjB,gBAAgB,GAChB,gBAAgB,GAChB,SAAS,CAAC;AAiFd,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAqLtD;AAkDD;;GAEG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE;IACP,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD,GACL,OAAO,CAAC,CAAC,CAAC,CA6BZ"}
|
package/dist/utils/errors.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import "../auth/provider-auth-error.js";
|
|
1
2
|
//#region src/utils/errors.ts
|
|
2
3
|
/**
|
|
4
|
+
* Error handling utilities for the Mastra Code TUI.
|
|
5
|
+
* Parses API errors and provides user-friendly messages.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
3
8
|
* Parse an error and return a user-friendly representation.
|
|
4
9
|
*/
|
|
5
10
|
function summarizeErrorDetail(error) {
|
|
@@ -31,12 +36,31 @@ function extractRequestUrl(error) {
|
|
|
31
36
|
}
|
|
32
37
|
for (const candidate of candidates) if (typeof candidate === "string" && candidate.trim().length > 0) return candidate;
|
|
33
38
|
}
|
|
39
|
+
/** A flattened error crossing the wire: the server sends `{ name, message }`, not an `Error`. */
|
|
40
|
+
function isSerializedError(value) {
|
|
41
|
+
return typeof value === "object" && value !== null && "name" in value && typeof value.name === "string" && "message" in value && typeof value.message === "string";
|
|
42
|
+
}
|
|
43
|
+
function toError(error) {
|
|
44
|
+
if (error instanceof Error) return error;
|
|
45
|
+
if (!isSerializedError(error)) return new Error(String(error));
|
|
46
|
+
const rebuilt = new Error(error.message);
|
|
47
|
+
rebuilt.name = error.name;
|
|
48
|
+
return rebuilt;
|
|
49
|
+
}
|
|
34
50
|
function parseError(error) {
|
|
35
|
-
const err =
|
|
51
|
+
const err = toError(error);
|
|
36
52
|
const message = err.message.toLowerCase();
|
|
37
53
|
const errorObj = error;
|
|
38
54
|
const detail = summarizeErrorDetail(error);
|
|
39
55
|
const requestUrl = extractRequestUrl(error);
|
|
56
|
+
if (err.name === "ProviderAuthRequiredError") return {
|
|
57
|
+
message: err.message,
|
|
58
|
+
detail,
|
|
59
|
+
requestUrl,
|
|
60
|
+
type: "auth",
|
|
61
|
+
retryable: false,
|
|
62
|
+
originalError: err
|
|
63
|
+
};
|
|
40
64
|
if (message.includes("rate limit") || message.includes("rate_limit") || message.includes("429") || errorObj.statusCode === 429 || errorObj.status === 429) return {
|
|
41
65
|
message: "Rate limited. Please wait a moment before trying again.",
|
|
42
66
|
type: "rate_limit",
|
|
@@ -45,7 +69,7 @@ function parseError(error) {
|
|
|
45
69
|
originalError: err
|
|
46
70
|
};
|
|
47
71
|
if (message.includes("unauthorized") || message.includes("authentication") || message.includes("invalid api key") || message.includes("invalid_api_key") || message.includes("api key") || errorObj.statusCode === 401 || errorObj.status === 401) return {
|
|
48
|
-
message: "Authentication failed.
|
|
72
|
+
message: "Authentication failed. Check the credential configured for this provider.",
|
|
49
73
|
detail,
|
|
50
74
|
requestUrl,
|
|
51
75
|
type: "auth",
|
package/dist/utils/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../../src/utils/errors.ts"],"sourcesContent":["/**\n * Error handling utilities for the Mastra Code TUI.\n * Parses API errors and provides user-friendly messages.\n */\n\nexport interface ParsedError {\n /** User-friendly error message */\n message: string;\n /** Extra diagnostic detail to surface to the user */\n detail?: string;\n /** Request URL involved in the failure, when available */\n requestUrl?: string;\n /** Error type for categorization */\n type: ErrorType;\n /** Whether this error is retryable */\n retryable: boolean;\n /** Suggested retry delay in ms (if retryable) */\n retryDelay?: number;\n /** Original error for debugging */\n originalError: Error;\n}\n\nexport type ErrorType =\n | 'rate_limit'\n | 'auth'\n | 'network'\n | 'timeout'\n | 'invalid_request'\n | 'server_error'\n | 'model_not_found'\n | 'context_length'\n | 'content_filter'\n | 'unknown';\n\n/**\n * Parse an error and return a user-friendly representation.\n */\nfunction summarizeErrorDetail(error: unknown): string | undefined {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message) {\n return error.cause.message;\n }\n\n if (typeof error.cause === 'string' && error.cause.trim().length > 0) {\n return error.cause;\n }\n\n if (error.message.trim().length > 0) {\n return error.message;\n }\n }\n\n if (typeof error === 'string' && error.trim().length > 0) {\n return error;\n }\n\n if (error && typeof error === 'object') {\n const errorObj = error as Record<string, unknown>;\n const candidates = [errorObj['message'], errorObj['cause'], errorObj['code'], errorObj['statusText']];\n\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\nfunction extractRequestUrl(error: unknown): string | undefined {\n if (!error || typeof error !== 'object') {\n return undefined;\n }\n\n const candidates: unknown[] = [];\n const errorObj = error as Record<string, unknown>;\n candidates.push(errorObj['requestUrl'], errorObj['url']);\n\n if (errorObj['cause'] && typeof errorObj['cause'] === 'object') {\n const causeObj = errorObj['cause'] as Record<string, unknown>;\n candidates.push(causeObj['requestUrl'], causeObj['url']);\n }\n\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate;\n }\n }\n\n return undefined;\n}\n\nexport function parseError(error: unknown): ParsedError {\n const err = error instanceof Error ? error : new Error(String(error));\n const message = err.message.toLowerCase();\n const errorObj = error as Record<string, unknown>;\n const detail = summarizeErrorDetail(error);\n const requestUrl = extractRequestUrl(error);\n\n // Check for rate limiting\n if (\n message.includes('rate limit') ||\n message.includes('rate_limit') ||\n message.includes('429') ||\n errorObj.statusCode === 429 ||\n errorObj.status === 429\n ) {\n const retryAfter = extractRetryAfter(errorObj);\n return {\n message: 'Rate limited. Please wait a moment before trying again.',\n type: 'rate_limit',\n retryable: true,\n retryDelay: retryAfter || 5000,\n originalError: err,\n };\n }\n\n // Check for authentication errors\n if (\n message.includes('unauthorized') ||\n message.includes('authentication') ||\n message.includes('invalid api key') ||\n message.includes('invalid_api_key') ||\n message.includes('api key') ||\n errorObj.statusCode === 401 ||\n errorObj.status === 401\n ) {\n return {\n message: 'Authentication failed. Please check your API key or login with /login.',\n detail,\n requestUrl,\n type: 'auth',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for forbidden/permission errors\n if (errorObj.statusCode === 403 || errorObj.status === 403) {\n return {\n message: 'Access denied. You may not have permission to use this model.',\n detail,\n requestUrl,\n type: 'auth',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for network errors\n if (\n message.includes('network') ||\n message.includes('econnrefused') ||\n message.includes('enotfound') ||\n message.includes('fetch failed') ||\n message.includes('connection')\n ) {\n return {\n message: 'Network error while contacting the provider or gateway.',\n detail,\n requestUrl,\n type: 'network',\n retryable: true,\n retryDelay: 2000,\n originalError: err,\n };\n }\n\n // Check for timeout errors\n if (message.includes('timeout') || message.includes('timed out') || message.includes('etimedout')) {\n return {\n message: 'Request timed out. The server may be overloaded.',\n type: 'timeout',\n retryable: true,\n retryDelay: 3000,\n originalError: err,\n };\n }\n\n // Check for model not found\n if (\n message.includes('model not found') ||\n message.includes('model_not_found') ||\n message.includes('does not exist') ||\n message.includes('invalid model')\n ) {\n return {\n message: 'Model not found. Please select a different model with /models.',\n type: 'model_not_found',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for context length errors\n if (\n message.includes('context length') ||\n message.includes('context_length') ||\n message.includes('too many tokens') ||\n message.includes('maximum context') ||\n message.includes('token limit')\n ) {\n return {\n message: 'Message too long. Try starting a new thread with /new.',\n type: 'context_length',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for content filter errors\n if (\n message.includes('content filter') ||\n message.includes('content_filter') ||\n message.includes('content policy') ||\n message.includes('safety') ||\n message.includes('prohibited')\n ) {\n return {\n message: \"Content was filtered by the model's safety system.\",\n type: 'content_filter',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for server errors\n if (\n message.includes('internal server') ||\n message.includes('server error') ||\n errorObj.statusCode === 500 ||\n errorObj.status === 500 ||\n errorObj.statusCode === 502 ||\n errorObj.status === 502 ||\n errorObj.statusCode === 503 ||\n errorObj.status === 503\n ) {\n return {\n message: 'Server error. The API may be experiencing issues.',\n type: 'server_error',\n retryable: true,\n retryDelay: 5000,\n originalError: err,\n };\n }\n\n // Check for invalid request errors\n if (\n message.includes('invalid request') ||\n message.includes('bad request') ||\n errorObj.statusCode === 400 ||\n errorObj.status === 400\n ) {\n return {\n message: `Invalid request: ${extractErrorDetail(err)}`,\n type: 'invalid_request',\n retryable: false,\n originalError: err,\n };\n }\n\n // Unknown error - try to extract useful info\n return {\n message: extractErrorDetail(err),\n type: 'unknown',\n retryable: false,\n originalError: err,\n };\n}\n\n/**\n * Extract retry-after header value from error.\n */\nfunction extractRetryAfter(error: Record<string, unknown>): number | undefined {\n const headers = error.headers as Record<string, unknown> | undefined;\n const retryAfter = error.retryAfter || headers?.['retry-after'];\n if (typeof retryAfter === 'number') {\n return retryAfter * 1000; // Convert seconds to ms\n }\n if (typeof retryAfter === 'string') {\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds)) {\n return seconds * 1000;\n }\n }\n return undefined;\n}\n\n/**\n * Extract a useful error detail from an error object.\n */\nfunction extractErrorDetail(error: Error): string {\n const errorObj = error as unknown as Record<string, unknown>;\n\n // Try to get a specific error message from common API error formats\n if (errorObj.error && typeof errorObj.error === 'object') {\n const apiError = errorObj.error as Record<string, unknown>;\n if (apiError.message) return String(apiError.message);\n }\n\n if (errorObj.message) return String(errorObj.message);\n if (errorObj.detail) return String(errorObj.detail);\n if (errorObj.reason) return String(errorObj.reason);\n\n // Clean up the error message\n let message = error.message;\n\n // Remove common prefixes\n message = message.replace(/^(error|exception|failed):\\s*/i, '');\n\n // Truncate very long messages\n if (message.length > 200) {\n message = message.substring(0, 200) + '...';\n }\n\n return message || 'An unknown error occurred';\n}\n\n/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Retry a function with exponential backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: {\n maxRetries?: number;\n initialDelay?: number;\n maxDelay?: number;\n onRetry?: (error: ParsedError, attempt: number) => void;\n } = {},\n): Promise<T> {\n const { maxRetries = 3, initialDelay = 1000, maxDelay = 30000, onRetry } = options;\n\n let lastError: ParsedError | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = parseError(error);\n\n // Don't retry non-retryable errors\n if (!lastError.retryable || attempt === maxRetries) {\n throw lastError.originalError;\n }\n\n // Calculate delay with exponential backoff\n const delay = Math.min(lastError.retryDelay || initialDelay * Math.pow(2, attempt), maxDelay);\n\n if (onRetry) {\n onRetry(lastError, attempt + 1);\n }\n\n await sleep(delay);\n }\n }\n\n // Should never reach here, but TypeScript needs this\n throw lastError?.originalError || new Error('Retry failed');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../../src/utils/errors.ts"],"sourcesContent":["/**\n * Error handling utilities for the Mastra Code TUI.\n * Parses API errors and provides user-friendly messages.\n */\n\nimport { PROVIDER_AUTH_REQUIRED_ERROR } from '../auth/provider-auth-error.js';\n\nexport interface ParsedError {\n /** User-friendly error message */\n message: string;\n /** Extra diagnostic detail to surface to the user */\n detail?: string;\n /** Request URL involved in the failure, when available */\n requestUrl?: string;\n /** Error type for categorization */\n type: ErrorType;\n /** Whether this error is retryable */\n retryable: boolean;\n /** Suggested retry delay in ms (if retryable) */\n retryDelay?: number;\n /** Original error for debugging */\n originalError: Error;\n}\n\nexport type ErrorType =\n | 'rate_limit'\n | 'auth'\n | 'network'\n | 'timeout'\n | 'invalid_request'\n | 'server_error'\n | 'model_not_found'\n | 'context_length'\n | 'content_filter'\n | 'unknown';\n\n/**\n * Parse an error and return a user-friendly representation.\n */\nfunction summarizeErrorDetail(error: unknown): string | undefined {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message) {\n return error.cause.message;\n }\n\n if (typeof error.cause === 'string' && error.cause.trim().length > 0) {\n return error.cause;\n }\n\n if (error.message.trim().length > 0) {\n return error.message;\n }\n }\n\n if (typeof error === 'string' && error.trim().length > 0) {\n return error;\n }\n\n if (error && typeof error === 'object') {\n const errorObj = error as Record<string, unknown>;\n const candidates = [errorObj['message'], errorObj['cause'], errorObj['code'], errorObj['statusText']];\n\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate;\n }\n }\n }\n\n return undefined;\n}\n\nfunction extractRequestUrl(error: unknown): string | undefined {\n if (!error || typeof error !== 'object') {\n return undefined;\n }\n\n const candidates: unknown[] = [];\n const errorObj = error as Record<string, unknown>;\n candidates.push(errorObj['requestUrl'], errorObj['url']);\n\n if (errorObj['cause'] && typeof errorObj['cause'] === 'object') {\n const causeObj = errorObj['cause'] as Record<string, unknown>;\n candidates.push(causeObj['requestUrl'], causeObj['url']);\n }\n\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim().length > 0) {\n return candidate;\n }\n }\n\n return undefined;\n}\n\n/** A flattened error crossing the wire: the server sends `{ name, message }`, not an `Error`. */\nfunction isSerializedError(value: unknown): value is { name: string; message: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'name' in value &&\n typeof value.name === 'string' &&\n 'message' in value &&\n typeof value.message === 'string'\n );\n}\n\nfunction toError(error: unknown): Error {\n if (error instanceof Error) return error;\n if (!isSerializedError(error)) return new Error(String(error));\n const rebuilt = new Error(error.message);\n rebuilt.name = error.name;\n return rebuilt;\n}\n\nexport function parseError(error: unknown): ParsedError {\n const err = toError(error);\n const message = err.message.toLowerCase();\n const errorObj = error as Record<string, unknown>;\n const detail = summarizeErrorDetail(error);\n const requestUrl = extractRequestUrl(error);\n\n // Matched by name, not instance: the error may have crossed a serialization boundary.\n if (err.name === PROVIDER_AUTH_REQUIRED_ERROR) {\n return { message: err.message, detail, requestUrl, type: 'auth', retryable: false, originalError: err };\n }\n\n // Check for rate limiting\n if (\n message.includes('rate limit') ||\n message.includes('rate_limit') ||\n message.includes('429') ||\n errorObj.statusCode === 429 ||\n errorObj.status === 429\n ) {\n const retryAfter = extractRetryAfter(errorObj);\n return {\n message: 'Rate limited. Please wait a moment before trying again.',\n type: 'rate_limit',\n retryable: true,\n retryDelay: retryAfter || 5000,\n originalError: err,\n };\n }\n\n // Check for authentication errors\n if (\n message.includes('unauthorized') ||\n message.includes('authentication') ||\n message.includes('invalid api key') ||\n message.includes('invalid_api_key') ||\n message.includes('api key') ||\n errorObj.statusCode === 401 ||\n errorObj.status === 401\n ) {\n return {\n message: 'Authentication failed. Check the credential configured for this provider.',\n detail,\n requestUrl,\n type: 'auth',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for forbidden/permission errors\n if (errorObj.statusCode === 403 || errorObj.status === 403) {\n return {\n message: 'Access denied. You may not have permission to use this model.',\n detail,\n requestUrl,\n type: 'auth',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for network errors\n if (\n message.includes('network') ||\n message.includes('econnrefused') ||\n message.includes('enotfound') ||\n message.includes('fetch failed') ||\n message.includes('connection')\n ) {\n return {\n message: 'Network error while contacting the provider or gateway.',\n detail,\n requestUrl,\n type: 'network',\n retryable: true,\n retryDelay: 2000,\n originalError: err,\n };\n }\n\n // Check for timeout errors\n if (message.includes('timeout') || message.includes('timed out') || message.includes('etimedout')) {\n return {\n message: 'Request timed out. The server may be overloaded.',\n type: 'timeout',\n retryable: true,\n retryDelay: 3000,\n originalError: err,\n };\n }\n\n // Check for model not found\n if (\n message.includes('model not found') ||\n message.includes('model_not_found') ||\n message.includes('does not exist') ||\n message.includes('invalid model')\n ) {\n return {\n message: 'Model not found. Please select a different model with /models.',\n type: 'model_not_found',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for context length errors\n if (\n message.includes('context length') ||\n message.includes('context_length') ||\n message.includes('too many tokens') ||\n message.includes('maximum context') ||\n message.includes('token limit')\n ) {\n return {\n message: 'Message too long. Try starting a new thread with /new.',\n type: 'context_length',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for content filter errors\n if (\n message.includes('content filter') ||\n message.includes('content_filter') ||\n message.includes('content policy') ||\n message.includes('safety') ||\n message.includes('prohibited')\n ) {\n return {\n message: \"Content was filtered by the model's safety system.\",\n type: 'content_filter',\n retryable: false,\n originalError: err,\n };\n }\n\n // Check for server errors\n if (\n message.includes('internal server') ||\n message.includes('server error') ||\n errorObj.statusCode === 500 ||\n errorObj.status === 500 ||\n errorObj.statusCode === 502 ||\n errorObj.status === 502 ||\n errorObj.statusCode === 503 ||\n errorObj.status === 503\n ) {\n return {\n message: 'Server error. The API may be experiencing issues.',\n type: 'server_error',\n retryable: true,\n retryDelay: 5000,\n originalError: err,\n };\n }\n\n // Check for invalid request errors\n if (\n message.includes('invalid request') ||\n message.includes('bad request') ||\n errorObj.statusCode === 400 ||\n errorObj.status === 400\n ) {\n return {\n message: `Invalid request: ${extractErrorDetail(err)}`,\n type: 'invalid_request',\n retryable: false,\n originalError: err,\n };\n }\n\n // Unknown error - try to extract useful info\n return {\n message: extractErrorDetail(err),\n type: 'unknown',\n retryable: false,\n originalError: err,\n };\n}\n\n/**\n * Extract retry-after header value from error.\n */\nfunction extractRetryAfter(error: Record<string, unknown>): number | undefined {\n const headers = error.headers as Record<string, unknown> | undefined;\n const retryAfter = error.retryAfter || headers?.['retry-after'];\n if (typeof retryAfter === 'number') {\n return retryAfter * 1000; // Convert seconds to ms\n }\n if (typeof retryAfter === 'string') {\n const seconds = parseInt(retryAfter, 10);\n if (!isNaN(seconds)) {\n return seconds * 1000;\n }\n }\n return undefined;\n}\n\n/**\n * Extract a useful error detail from an error object.\n */\nfunction extractErrorDetail(error: Error): string {\n const errorObj = error as unknown as Record<string, unknown>;\n\n // Try to get a specific error message from common API error formats\n if (errorObj.error && typeof errorObj.error === 'object') {\n const apiError = errorObj.error as Record<string, unknown>;\n if (apiError.message) return String(apiError.message);\n }\n\n if (errorObj.message) return String(errorObj.message);\n if (errorObj.detail) return String(errorObj.detail);\n if (errorObj.reason) return String(errorObj.reason);\n\n // Clean up the error message\n let message = error.message;\n\n // Remove common prefixes\n message = message.replace(/^(error|exception|failed):\\s*/i, '');\n\n // Truncate very long messages\n if (message.length > 200) {\n message = message.substring(0, 200) + '...';\n }\n\n return message || 'An unknown error occurred';\n}\n\n/**\n * Sleep for a given number of milliseconds.\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Retry a function with exponential backoff.\n */\nexport async function withRetry<T>(\n fn: () => Promise<T>,\n options: {\n maxRetries?: number;\n initialDelay?: number;\n maxDelay?: number;\n onRetry?: (error: ParsedError, attempt: number) => void;\n } = {},\n): Promise<T> {\n const { maxRetries = 3, initialDelay = 1000, maxDelay = 30000, onRetry } = options;\n\n let lastError: ParsedError | undefined;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = parseError(error);\n\n // Don't retry non-retryable errors\n if (!lastError.retryable || attempt === maxRetries) {\n throw lastError.originalError;\n }\n\n // Calculate delay with exponential backoff\n const delay = Math.min(lastError.retryDelay || initialDelay * Math.pow(2, attempt), maxDelay);\n\n if (onRetry) {\n onRetry(lastError, attempt + 1);\n }\n\n await sleep(delay);\n }\n }\n\n // Should never reach here, but TypeScript needs this\n throw lastError?.originalError || new Error('Retry failed');\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/code-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-alpha.1",
|
|
4
4
|
"description": "Mastra Code SDK: the agent core behind Mastra Code (everything except the TUI) — build your own UIs and surfaces on top of it",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -58,18 +58,18 @@
|
|
|
58
58
|
"yaml": "^2.7.1",
|
|
59
59
|
"zod": "^4.3.6",
|
|
60
60
|
"@mastra/agent-browser": "0.5.1",
|
|
61
|
-
"@mastra/core": "1.
|
|
62
|
-
"@mastra/
|
|
61
|
+
"@mastra/core": "1.61.0-alpha.1",
|
|
62
|
+
"@mastra/fastembed": "1.2.0",
|
|
63
63
|
"@mastra/duckdb": "1.6.2",
|
|
64
|
+
"@mastra/github-signals": "0.2.5",
|
|
64
65
|
"@mastra/libsql": "1.21.1-alpha.0",
|
|
65
|
-
"@mastra/fastembed": "1.2.0",
|
|
66
|
-
"@mastra/mcp": "1.17.1-alpha.0",
|
|
67
66
|
"@mastra/memory": "1.27.0",
|
|
68
67
|
"@mastra/pg": "1.21.1-alpha.0",
|
|
69
68
|
"@mastra/schema-compat": "1.3.7",
|
|
70
|
-
"@mastra/
|
|
69
|
+
"@mastra/mcp": "1.17.1-alpha.0",
|
|
71
70
|
"@mastra/observability": "1.17.1",
|
|
72
|
-
"@mastra/stagehand": "0.3.3"
|
|
71
|
+
"@mastra/stagehand": "0.3.3",
|
|
72
|
+
"@mastra/tavily": "1.1.1"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@libsql/client": "^0.17.4",
|