@bastani/atomic 0.9.4-alpha.8 → 0.9.4-alpha.9
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 +16 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +7 -0
- package/dist/builtin/subagents/agents/code-simplifier.md +2 -2
- package/dist/builtin/subagents/agents/codebase-analyzer.md +2 -2
- package/dist/builtin/subagents/agents/codebase-locator.md +2 -2
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +2 -2
- package/dist/builtin/subagents/agents/codebase-pattern-finder.md +2 -2
- package/dist/builtin/subagents/agents/codebase-research-analyzer.md +2 -2
- package/dist/builtin/subagents/agents/codebase-research-locator.md +2 -2
- package/dist/builtin/subagents/agents/debugger.md +1 -1
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +8 -0
- package/dist/builtin/workflows/builtin/deep-research-codebase-utils.ts +24 -29
- package/dist/builtin/workflows/builtin/goal-runner.ts +13 -37
- package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +6 -9
- package/dist/builtin/workflows/builtin/ralph-models.ts +58 -115
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +3 -2
- package/dist/bun/cli.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +10 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +14 -8
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-export.d.ts.map +1 -1
- package/dist/core/agent-session-export.js +4 -2
- package/dist/core/agent-session-export.js.map +1 -1
- package/dist/core/agent-session-extension-bindings.d.ts +3 -1
- package/dist/core/agent-session-extension-bindings.d.ts.map +1 -1
- package/dist/core/agent-session-extension-bindings.js +7 -4
- package/dist/core/agent-session-extension-bindings.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +4 -1
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +2 -0
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +66 -7
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/copilot-model-static-fallbacks.d.ts +43 -0
- package/dist/core/copilot-model-static-fallbacks.d.ts.map +1 -0
- package/dist/core/copilot-model-static-fallbacks.js +50 -0
- package/dist/core/copilot-model-static-fallbacks.js.map +1 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +64 -11
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/model-registry-builtins.d.ts.map +1 -1
- package/dist/core/model-registry-builtins.js +9 -1
- package/dist/core/model-registry-builtins.js.map +1 -1
- package/dist/core/resource-loader-reload.d.ts.map +1 -1
- package/dist/core/resource-loader-reload.js +4 -2
- package/dist/core/resource-loader-reload.js.map +1 -1
- package/dist/core/resource-loader-types.d.ts +6 -0
- package/dist/core/resource-loader-types.d.ts.map +1 -1
- package/dist/core/resource-loader-types.js.map +1 -1
- package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
- package/dist/core/tools/bash-pty-native.js +2 -2
- package/dist/core/tools/bash-pty-native.js.map +1 -1
- package/dist/core/tools/search-native.d.ts.map +1 -1
- package/dist/core/tools/search-native.js +2 -2
- package/dist/core/tools/search-native.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +56 -32
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-deferred-startup.d.ts +2 -0
- package/dist/modes/interactive/interactive-deferred-startup.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-deferred-startup.js +59 -0
- package/dist/modes/interactive/interactive-deferred-startup.js.map +1 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +4 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-session-runtime.js +4 -2
- package/dist/modes/interactive/interactive-session-runtime.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +11 -1
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/dist/utils/module-require.d.ts +11 -0
- package/dist/utils/module-require.d.ts.map +1 -0
- package/dist/utils/module-require.js +16 -0
- package/dist/utils/module-require.js.map +1 -0
- package/npm-shrinkwrap.json +23 -23
- package/package.json +5 -3
|
@@ -10,13 +10,16 @@ export function _isRetryableError(message) {
|
|
|
10
10
|
if (isContextOverflow(message, contextWindow))
|
|
11
11
|
return false;
|
|
12
12
|
const err = message.errorMessage;
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
// Safety triggers surface through structured API signals that pi-ai maps to
|
|
14
|
+
// stopReason "error":
|
|
15
|
+
// - Anthropic `refusal` stops become pi-ai's canned "The model refused to
|
|
16
|
+
// complete the request" error message;
|
|
17
|
+
// - OpenAI-style APIs (and github-copilot CAPI, which also maps spurious
|
|
18
|
+
// Gemini RECITATION/safety blocks this way) surface
|
|
19
|
+
// `finish_reason: content_filter`.
|
|
20
|
+
// Spurious safety triggers are common in agentic settings, so these are
|
|
21
|
+
// re-requested like transient failures, bounded by maxRetries (issue #1608).
|
|
22
|
+
if (/refused to complete the request|finish.?reason:?\s*content.?filter/i.test(err)) {
|
|
20
23
|
return true;
|
|
21
24
|
}
|
|
22
25
|
// Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded, and a bare/transient provider finish_reason "error" (e.g. github-copilot Gemini's CAPI mapping of MALFORMED_FUNCTION_CALL/OTHER/UNEXPECTED_TOOL_CALL). These are provider-agnostic transient failures.
|
|
@@ -85,6 +88,61 @@ export function _isEmptyCompletion(message) {
|
|
|
85
88
|
// output) keeps that false-positive risk low in practice.
|
|
86
89
|
return (message.usage?.output ?? 0) === 0;
|
|
87
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Detect a canned provider-side safety refusal that arrives as a *successful*
|
|
93
|
+
* completion instead of an error. Seen with github-copilot GPT models under
|
|
94
|
+
* heavy contexts: the endpoint intercepts the request and returns exactly
|
|
95
|
+
* "I'm sorry, but I cannot assist with that request." with zero usage and a
|
|
96
|
+
* spurious stopReason of "length" (or "stop"), which the agent would otherwise
|
|
97
|
+
* accept as the final answer and dead-end the turn (issue #1608).
|
|
98
|
+
*
|
|
99
|
+
* A text heuristic is unavoidable here: the OpenAI Responses API does signal
|
|
100
|
+
* these interceptions structurally (`refusal` content parts plus
|
|
101
|
+
* `incomplete_details.reason: "content_filter"`), but pi-ai's normalization
|
|
102
|
+
* folds refusal parts into plain text blocks and collapses the `incomplete`
|
|
103
|
+
* status to stopReason "length", discarding the reason — so no structured
|
|
104
|
+
* refusal marker survives on the AssistantMessage. Providers whose safety
|
|
105
|
+
* signals DO survive as structured errors (Anthropic refusal stops, OpenAI
|
|
106
|
+
* `finish_reason: content_filter`) are matched in _isRetryableError instead.
|
|
107
|
+
*
|
|
108
|
+
* Guarded tightly so legitimate turns are never matched:
|
|
109
|
+
* - only non-error completion stops ("stop" | "length" | "toolUse");
|
|
110
|
+
* - content must be a single short canned refusal text — any tool call,
|
|
111
|
+
* thinking block, or additional prose disqualifies the message;
|
|
112
|
+
* - usage.output must be 0: a genuine model-authored refusal bills output
|
|
113
|
+
* tokens, while the intercepted canned refusal reports zero usage.
|
|
114
|
+
*
|
|
115
|
+
* Treated as retryable so the harness re-requests the model call rather than
|
|
116
|
+
* accepting the refusal; bounded by `maxRetries` like every other retry path.
|
|
117
|
+
*/
|
|
118
|
+
const CANNED_SAFETY_REFUSAL_PATTERN = /^(?:i['’]?m sorry[,.]?\s+(?:but\s+)?|sorry[,.]?\s+(?:but\s+)?)?i\s+(?:cannot|can['’]?t|can\s+not|am\s+unable\s+to|am\s+not\s+able\s+to)\s+(?:assist|help|comply|continue)(?:\s+with)?(?:\s+(?:that|this))?(?:\s+(?:request|task))?\.?$/i;
|
|
119
|
+
const CANNED_SAFETY_REFUSAL_MAX_LENGTH = 120;
|
|
120
|
+
export function _isSafetyRefusal(message) {
|
|
121
|
+
// Real errors are handled by _isRetryableError; aborts are user-initiated.
|
|
122
|
+
if (message.stopReason !== "stop" && message.stopReason !== "length" && message.stopReason !== "toolUse") {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
const content = message.content;
|
|
126
|
+
if (!Array.isArray(content) || content.length === 0)
|
|
127
|
+
return false;
|
|
128
|
+
let text = "";
|
|
129
|
+
for (const part of content) {
|
|
130
|
+
if (part.type === "toolCall")
|
|
131
|
+
return false;
|
|
132
|
+
if (part.type === "thinking" && (part.redacted === true || part.thinking.trim().length > 0))
|
|
133
|
+
return false;
|
|
134
|
+
if (part.type === "text")
|
|
135
|
+
text += part.text;
|
|
136
|
+
}
|
|
137
|
+
text = text.trim();
|
|
138
|
+
if (text.length === 0 || text.length > CANNED_SAFETY_REFUSAL_MAX_LENGTH)
|
|
139
|
+
return false;
|
|
140
|
+
if (!CANNED_SAFETY_REFUSAL_PATTERN.test(text))
|
|
141
|
+
return false;
|
|
142
|
+
// Zero billed output distinguishes a provider interception from legitimate
|
|
143
|
+
// model-authored refusal prose, which is never auto-retried.
|
|
144
|
+
return (message.usage?.output ?? 0) === 0;
|
|
145
|
+
}
|
|
88
146
|
/**
|
|
89
147
|
* Handle retryable errors with exponential backoff.
|
|
90
148
|
* @returns true if retry was initiated, false if max retries exceeded or disabled
|
|
@@ -194,6 +252,7 @@ export const agentSessionRetryMethods = {
|
|
|
194
252
|
_isRetryableError,
|
|
195
253
|
_normalizePersistedGeminiToolArgs,
|
|
196
254
|
_isEmptyCompletion,
|
|
255
|
+
_isSafetyRefusal,
|
|
197
256
|
_handleRetryableError,
|
|
198
257
|
abortRetry,
|
|
199
258
|
waitForRetry,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-retry.js","sourceRoot":"","sources":["../../src/core/agent-session-retry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai/compat\";\nimport { isContextOverflow } from \"@earendil-works/pi-ai/compat\";\nimport { sleep } from \"../utils/sleep.ts\";\nimport { isCopilotGeminiModel } from \"./copilot-gemini-payload-sanitizer.ts\";\nimport { normalizeToolArgumentsForModel } from \"./copilot-gemini-tool-arguments.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\n\nexport function _isRetryableError(this: AgentSession, message: AssistantMessage): boolean {\n\tif (message.stopReason !== \"error\" || !message.errorMessage) return false;\n\n\t// Context overflow is handled by compaction, not retry\n\tconst contextWindow = this.model?.contextWindow ?? 0;\n\tif (isContextOverflow(message, contextWindow)) return false;\n\n\tconst err = message.errorMessage;\n\n\t// A genuine `content_filter` stop is a deliberate safety block: retrying it\n\t// re-issues the same blocked request up to maxRetries times for no benefit.\n\t// GitHub Copilot Gemini is the exception — CAPI maps spurious Gemini blocks\n\t// (RECITATION/safety on MALFORMED_FUNCTION_CALL etc.) to `content_filter`, so\n\t// only treat `content_filter` as retryable for those models.\n\tif (\n\t\tisCopilotGeminiModel({ provider: message.provider, api: message.api, id: message.model }) &&\n\t\t/finish.?reason:?\\s*content.?filter/i.test(err)\n\t) {\n\t\treturn true;\n\t}\n\n\t// Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded, and a bare/transient provider finish_reason \"error\" (e.g. github-copilot Gemini's CAPI mapping of MALFORMED_FUNCTION_CALL/OTHER/UNEXPECTED_TOOL_CALL). These are provider-agnostic transient failures.\n\treturn /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay|finish.?reason:?\\s*error/i.test(\n\t\terr,\n\t);\n}\n\n/**\n * For GitHub Copilot Gemini, reconstruct flattened tool-call arguments\n * (for example `edits[0].newText`) into the nested arrays/objects Gemini\n * produced before the assistant message is persisted, so saved transcripts\n * never carry the flattened CAPI wire shape and replays loaded from disk match\n * the structure Gemini signed. In-place, gated to Copilot Gemini, and a no-op\n * for well-formed arguments or any other provider/model. The outbound replay\n * normalizer still heals already-persisted (legacy) sessions on the wire.\n */\n\nexport function _normalizePersistedGeminiToolArgs(this: AgentSession, message: AssistantMessage): void {\n\tconst model = this.model;\n\tif (!model || !isCopilotGeminiModel(model)) return;\n\tfor (const block of message.content) {\n\t\tif (block.type !== \"toolCall\") continue;\n\t\tconst tool = this._toolRegistry.get(block.name);\n\t\tconst normalized = normalizeToolArgumentsForModel(block.arguments, model, tool?.parameters);\n\t\tif (normalized !== block.arguments && normalized !== null && typeof normalized === \"object\") {\n\t\t\tblock.arguments = normalized as Record<string, unknown>;\n\t\t}\n\t}\n}\n\n/**\n * Detect a degenerate empty completion: the provider ended the stream with no\n * usable content and zero output tokens. Seen with github-copilot Gemini models\n * that emit finish_reason \"stop\" (or a tool-use stop) with an empty content array\n * and 0 output tokens, leaving the turn dead instead of producing the next step.\n *\n * These are treated as retryable so the harness re-issues the request rather than\n * silently stopping mid-task. Guarded tightly (no text, no tool call, no thinking,\n * and output === 0) so legitimate non-empty turns are never matched.\n *\n * Intentionally provider-agnostic (not gated to Copilot Gemini): a degenerate\n * empty turn is a transient failure for any provider. It is bounded by\n * `maxRetries` and falls through to normal handling on exhaustion.\n */\n\nexport function _isEmptyCompletion(this: AgentSession, message: AssistantMessage): boolean {\n\t// Only \"completed\" stop reasons can be deceptively empty. Real errors are handled\n\t// by _isRetryableError; aborted/length turns are intentional outcomes.\n\tif (message.stopReason !== \"stop\" && message.stopReason !== \"toolUse\") return false;\n\n\tconst content = message.content;\n\tif (Array.isArray(content)) {\n\t\tconst hasContent = content.some((part) => {\n\t\t\tif (part.type === \"text\") return part.text.trim().length > 0;\n\t\t\tif (part.type === \"toolCall\") return true;\n\t\t\tif (part.type === \"thinking\") return part.redacted === true || part.thinking.trim().length > 0;\n\t\t\treturn true; // unknown part types count as content\n\t\t});\n\t\tif (hasContent) return false;\n\t}\n\n\t// A turn that produced output tokens but no surfaced content is not \"empty\"\n\t// (e.g. reasoning-only responses); leave those alone. Note: a provider that\n\t// fails to report `usage` (output defaults to 0) would make every\n\t// content-less turn match here; the dual requirement (empty content AND zero\n\t// output) keeps that false-positive risk low in practice.\n\treturn (message.usage?.output ?? 0) === 0;\n}\n\n/**\n * Handle retryable errors with exponential backoff.\n * @returns true if retry was initiated, false if max retries exceeded or disabled\n */\n\nexport async function _handleRetryableError(this: AgentSession, message: AssistantMessage): Promise<boolean> {\n\tconst settings = this.settingsManager.getRetrySettings();\n\tif (!settings.enabled) {\n\t\tthis._resolveRetry();\n\t\treturn false;\n\t}\n\n\t// Retry promise is created synchronously in _handleAgentEvent for agent_end.\n\t// Keep a defensive fallback here in case a future refactor bypasses that path.\n\tif (!this._retryPromise) {\n\t\tthis._retryPromise = new Promise((resolve) => {\n\t\t\tthis._retryResolve = resolve;\n\t\t});\n\t}\n\n\tthis._retryAttempt++;\n\n\tif (this._retryAttempt > settings.maxRetries) {\n\t\t// Max retries exceeded, emit final failure and reset\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: false,\n\t\t\tattempt: this._retryAttempt - 1,\n\t\t\tfinalError: message.errorMessage,\n\t\t});\n\t\tthis._retryAttempt = 0;\n\t\tthis._resolveRetry(); // Resolve so waitForRetry() completes\n\t\treturn false;\n\t}\n\n\tconst delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);\n\n\tthis._emit({\n\t\ttype: \"auto_retry_start\",\n\t\tattempt: this._retryAttempt,\n\t\tmaxAttempts: settings.maxRetries,\n\t\tdelayMs,\n\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t});\n\n\t// Remove error message from agent state (keep in session for history)\n\tconst messages = this.agent.state.messages;\n\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t}\n\n\t// Wait with exponential backoff (abortable)\n\tthis._retryAbortController = new AbortController();\n\ttry {\n\t\tawait sleep(delayMs, this._retryAbortController.signal);\n\t} catch {\n\t\t// Aborted during sleep - emit end event so UI can clean up\n\t\tconst attempt = this._retryAttempt;\n\t\tthis._retryAttempt = 0;\n\t\tthis._retryAbortController = undefined;\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: false,\n\t\t\tattempt,\n\t\t\tfinalError: \"Retry cancelled\",\n\t\t});\n\t\tthis._resolveRetry();\n\t\treturn false;\n\t}\n\tthis._retryAbortController = undefined;\n\n\t// Retry via continue() - use setTimeout to break out of event handler chain\n\tsetTimeout(() => {\n\t\tthis.agent.continue().catch(() => {\n\t\t\t// Retry failed - will be caught by next agent_end\n\t\t});\n\t}, 0);\n\n\treturn true;\n}\n\n/**\n * Cancel in-progress retry.\n */\n\nexport function abortRetry(this: AgentSession): void {\n\tthis._retryAbortController?.abort();\n\t// Note: _retryAttempt is reset in the catch block of _autoRetry\n\tthis._resolveRetry();\n}\n\n/**\n * Wait for any in-progress retry to complete.\n * Returns immediately if no retry is in progress.\n */\n\nexport async function waitForRetry(this: AgentSession): Promise<void> {\n\tif (!this._retryPromise) {\n\t\treturn;\n\t}\n\n\tawait this._retryPromise;\n\tawait this.agent.waitForIdle();\n}\n\n/** Whether auto-retry is currently in progress */\n\nexport function setAutoRetryEnabled(this: AgentSession, enabled: boolean): void {\n\tthis.settingsManager.setRetryEnabled(enabled);\n}\n\n// =========================================================================\n// Bash Execution\n// =========================================================================\n\n/**\n * Execute a bash command.\n * Adds result to agent context and session.\n * @param command The bash command to execute\n * @param onChunk Optional streaming callback for output\n * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)\n * @param options.operations Custom BashOperations for remote execution\n */\n\nexport const agentSessionRetryMethods = {\n\t_isRetryableError,\n\t_normalizePersistedGeminiToolArgs,\n\t_isEmptyCompletion,\n\t_handleRetryableError,\n\tabortRetry,\n\twaitForRetry,\n\tsetAutoRetryEnabled,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"agent-session-retry.js","sourceRoot":"","sources":["../../src/core/agent-session-retry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAC7E,OAAO,EAAE,8BAA8B,EAAE,MAAM,oCAAoC,CAAC;AAGpF,MAAM,UAAU,iBAAiB,CAAqB,OAAyB;IAC9E,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAE1E,uDAAuD;IACvD,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,EAAE,aAAa,IAAI,CAAC,CAAC;IACrD,IAAI,iBAAiB,CAAC,OAAO,EAAE,aAAa,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5D,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC;IAEjC,4EAA4E;IAC5E,sBAAsB;IACtB,0EAA0E;IAC1E,yCAAyC;IACzC,yEAAyE;IACzE,sDAAsD;IACtD,qCAAqC;IACrC,wEAAwE;IACxE,6EAA6E;IAC7E,IAAI,qEAAqE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACrF,OAAO,IAAI,CAAC;IACb,CAAC;IAED,wfAAwf;IACxf,OAAO,ieAAie,CAAC,IAAI,CAC5e,GAAG,CACH,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AAEH,MAAM,UAAU,iCAAiC,CAAqB,OAAyB;IAC9F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,IAAI,CAAC,KAAK,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC;QAAE,OAAO;IACnD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,8BAA8B,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;QAC5F,IAAI,UAAU,KAAK,KAAK,CAAC,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC7F,KAAK,CAAC,SAAS,GAAG,UAAqC,CAAC;QACzD,CAAC;IACF,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;GAaG;AAEH,MAAM,UAAU,kBAAkB,CAAqB,OAAyB;IAC/E,kFAAkF;IAClF,uEAAuE;IACvE,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAEpF,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;YACxC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;YAC7D,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAC;YAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;gBAAE,OAAO,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;YAC/F,OAAO,IAAI,CAAC,CAAC,sCAAsC;QACpD,CAAC,CAAC,CAAC;QACH,IAAI,UAAU;YAAE,OAAO,KAAK,CAAC;IAC9B,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,kEAAkE;IAClE,6EAA6E;IAC7E,0DAA0D;IAC1D,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,6BAA6B,GAClC,yOAAyO,CAAC;AAE3O,MAAM,gCAAgC,GAAG,GAAG,CAAC;AAE7C,MAAM,UAAU,gBAAgB,CAAqB,OAAyB;IAC7E,2EAA2E;IAC3E,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,IAAI,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QAC1G,OAAO,KAAK,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAElE,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU;YAAE,OAAO,KAAK,CAAC;QAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1G,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;IAC7C,CAAC;IACD,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACnB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,gCAAgC;QAAE,OAAO,KAAK,CAAC;IACtF,IAAI,CAAC,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5D,2EAA2E;IAC3E,6DAA6D;IAC7D,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AAEH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAqB,OAAyB;IACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,gBAAgB,EAAE,CAAC;IACzD,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACd,CAAC;IAED,6EAA6E;IAC7E,+EAA+E;IAC/E,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC5C,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC;QAC9B,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,aAAa,EAAE,CAAC;IAErB,IAAI,IAAI,CAAC,aAAa,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC9C,qDAAqD;QACrD,IAAI,CAAC,KAAK,CAAC;YACV,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,IAAI,CAAC,aAAa,GAAG,CAAC;YAC/B,UAAU,EAAE,OAAO,CAAC,YAAY;SAChC,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,sCAAsC;QAC5D,OAAO,KAAK,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,WAAW,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC;IAErE,IAAI,CAAC,KAAK,CAAC;QACV,IAAI,EAAE,kBAAkB;QACxB,OAAO,EAAE,IAAI,CAAC,aAAa;QAC3B,WAAW,EAAE,QAAQ,CAAC,UAAU;QAChC,OAAO;QACP,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,eAAe;KACrD,CAAC,CAAC;IAEH,sEAAsE;IACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC;IAC3C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC/E,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,4CAA4C;IAC5C,IAAI,CAAC,qBAAqB,GAAG,IAAI,eAAe,EAAE,CAAC;IACnD,IAAI,CAAC;QACJ,MAAM,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACR,2DAA2D;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC;YACV,IAAI,EAAE,gBAAgB;YACtB,OAAO,EAAE,KAAK;YACd,OAAO;YACP,UAAU,EAAE,iBAAiB;SAC7B,CAAC,CAAC;QACH,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACd,CAAC;IACD,IAAI,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAEvC,4EAA4E;IAC5E,UAAU,CAAC,GAAG,EAAE;QACf,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;YAChC,kDAAkD;QACnD,CAAC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,CAAC,CAAC;IAEN,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;GAEG;AAEH,MAAM,UAAU,UAAU;IACzB,IAAI,CAAC,qBAAqB,EAAE,KAAK,EAAE,CAAC;IACpC,gEAAgE;IAChE,IAAI,CAAC,aAAa,EAAE,CAAC;AACtB,CAAC;AAED;;;GAGG;AAEH,MAAM,CAAC,KAAK,UAAU,YAAY;IACjC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;QACzB,OAAO;IACR,CAAC;IAED,MAAM,IAAI,CAAC,aAAa,CAAC;IACzB,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;AAChC,CAAC;AAED,kDAAkD;AAElD,MAAM,UAAU,mBAAmB,CAAqB,OAAgB;IACvE,IAAI,CAAC,eAAe,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,4EAA4E;AAC5E,iBAAiB;AACjB,4EAA4E;AAE5E;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACvC,iBAAiB;IACjB,iCAAiC;IACjC,kBAAkB;IAClB,gBAAgB;IAChB,qBAAqB;IACrB,UAAU;IACV,YAAY;IACZ,mBAAmB;CACnB,CAAC","sourcesContent":["import type { AssistantMessage } from \"@earendil-works/pi-ai/compat\";\nimport { isContextOverflow } from \"@earendil-works/pi-ai/compat\";\nimport { sleep } from \"../utils/sleep.ts\";\nimport { isCopilotGeminiModel } from \"./copilot-gemini-payload-sanitizer.ts\";\nimport { normalizeToolArgumentsForModel } from \"./copilot-gemini-tool-arguments.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\n\nexport function _isRetryableError(this: AgentSession, message: AssistantMessage): boolean {\n\tif (message.stopReason !== \"error\" || !message.errorMessage) return false;\n\n\t// Context overflow is handled by compaction, not retry\n\tconst contextWindow = this.model?.contextWindow ?? 0;\n\tif (isContextOverflow(message, contextWindow)) return false;\n\n\tconst err = message.errorMessage;\n\n\t// Safety triggers surface through structured API signals that pi-ai maps to\n\t// stopReason \"error\":\n\t// - Anthropic `refusal` stops become pi-ai's canned \"The model refused to\n\t// complete the request\" error message;\n\t// - OpenAI-style APIs (and github-copilot CAPI, which also maps spurious\n\t// Gemini RECITATION/safety blocks this way) surface\n\t// `finish_reason: content_filter`.\n\t// Spurious safety triggers are common in agentic settings, so these are\n\t// re-requested like transient failures, bounded by maxRetries (issue #1608).\n\tif (/refused to complete the request|finish.?reason:?\\s*content.?filter/i.test(err)) {\n\t\treturn true;\n\t}\n\n\t// Match: overloaded_error, provider returned error, rate limit, 429, 500, 502, 503, 504, service unavailable, network/connection errors (including connection lost), WebSocket transport closes/errors, fetch failed, premature stream endings, HTTP/2 closed before response, terminated, retry delay exceeded, and a bare/transient provider finish_reason \"error\" (e.g. github-copilot Gemini's CAPI mapping of MALFORMED_FUNCTION_CALL/OTHER/UNEXPECTED_TOOL_CALL). These are provider-agnostic transient failures.\n\treturn /overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|network.?error|connection.?error|connection.?refused|connection.?lost|websocket.?closed|websocket.?error|other side closed|fetch failed|upstream.?connect|reset before headers|socket hang up|ended without|stream ended before message_stop|http2 request did not get a response|timed? out|timeout|terminated|retry delay|finish.?reason:?\\s*error/i.test(\n\t\terr,\n\t);\n}\n\n/**\n * For GitHub Copilot Gemini, reconstruct flattened tool-call arguments\n * (for example `edits[0].newText`) into the nested arrays/objects Gemini\n * produced before the assistant message is persisted, so saved transcripts\n * never carry the flattened CAPI wire shape and replays loaded from disk match\n * the structure Gemini signed. In-place, gated to Copilot Gemini, and a no-op\n * for well-formed arguments or any other provider/model. The outbound replay\n * normalizer still heals already-persisted (legacy) sessions on the wire.\n */\n\nexport function _normalizePersistedGeminiToolArgs(this: AgentSession, message: AssistantMessage): void {\n\tconst model = this.model;\n\tif (!model || !isCopilotGeminiModel(model)) return;\n\tfor (const block of message.content) {\n\t\tif (block.type !== \"toolCall\") continue;\n\t\tconst tool = this._toolRegistry.get(block.name);\n\t\tconst normalized = normalizeToolArgumentsForModel(block.arguments, model, tool?.parameters);\n\t\tif (normalized !== block.arguments && normalized !== null && typeof normalized === \"object\") {\n\t\t\tblock.arguments = normalized as Record<string, unknown>;\n\t\t}\n\t}\n}\n\n/**\n * Detect a degenerate empty completion: the provider ended the stream with no\n * usable content and zero output tokens. Seen with github-copilot Gemini models\n * that emit finish_reason \"stop\" (or a tool-use stop) with an empty content array\n * and 0 output tokens, leaving the turn dead instead of producing the next step.\n *\n * These are treated as retryable so the harness re-issues the request rather than\n * silently stopping mid-task. Guarded tightly (no text, no tool call, no thinking,\n * and output === 0) so legitimate non-empty turns are never matched.\n *\n * Intentionally provider-agnostic (not gated to Copilot Gemini): a degenerate\n * empty turn is a transient failure for any provider. It is bounded by\n * `maxRetries` and falls through to normal handling on exhaustion.\n */\n\nexport function _isEmptyCompletion(this: AgentSession, message: AssistantMessage): boolean {\n\t// Only \"completed\" stop reasons can be deceptively empty. Real errors are handled\n\t// by _isRetryableError; aborted/length turns are intentional outcomes.\n\tif (message.stopReason !== \"stop\" && message.stopReason !== \"toolUse\") return false;\n\n\tconst content = message.content;\n\tif (Array.isArray(content)) {\n\t\tconst hasContent = content.some((part) => {\n\t\t\tif (part.type === \"text\") return part.text.trim().length > 0;\n\t\t\tif (part.type === \"toolCall\") return true;\n\t\t\tif (part.type === \"thinking\") return part.redacted === true || part.thinking.trim().length > 0;\n\t\t\treturn true; // unknown part types count as content\n\t\t});\n\t\tif (hasContent) return false;\n\t}\n\n\t// A turn that produced output tokens but no surfaced content is not \"empty\"\n\t// (e.g. reasoning-only responses); leave those alone. Note: a provider that\n\t// fails to report `usage` (output defaults to 0) would make every\n\t// content-less turn match here; the dual requirement (empty content AND zero\n\t// output) keeps that false-positive risk low in practice.\n\treturn (message.usage?.output ?? 0) === 0;\n}\n\n/**\n * Detect a canned provider-side safety refusal that arrives as a *successful*\n * completion instead of an error. Seen with github-copilot GPT models under\n * heavy contexts: the endpoint intercepts the request and returns exactly\n * \"I'm sorry, but I cannot assist with that request.\" with zero usage and a\n * spurious stopReason of \"length\" (or \"stop\"), which the agent would otherwise\n * accept as the final answer and dead-end the turn (issue #1608).\n *\n * A text heuristic is unavoidable here: the OpenAI Responses API does signal\n * these interceptions structurally (`refusal` content parts plus\n * `incomplete_details.reason: \"content_filter\"`), but pi-ai's normalization\n * folds refusal parts into plain text blocks and collapses the `incomplete`\n * status to stopReason \"length\", discarding the reason — so no structured\n * refusal marker survives on the AssistantMessage. Providers whose safety\n * signals DO survive as structured errors (Anthropic refusal stops, OpenAI\n * `finish_reason: content_filter`) are matched in _isRetryableError instead.\n *\n * Guarded tightly so legitimate turns are never matched:\n * - only non-error completion stops (\"stop\" | \"length\" | \"toolUse\");\n * - content must be a single short canned refusal text — any tool call,\n * thinking block, or additional prose disqualifies the message;\n * - usage.output must be 0: a genuine model-authored refusal bills output\n * tokens, while the intercepted canned refusal reports zero usage.\n *\n * Treated as retryable so the harness re-requests the model call rather than\n * accepting the refusal; bounded by `maxRetries` like every other retry path.\n */\n\nconst CANNED_SAFETY_REFUSAL_PATTERN =\n\t/^(?:i['’]?m sorry[,.]?\\s+(?:but\\s+)?|sorry[,.]?\\s+(?:but\\s+)?)?i\\s+(?:cannot|can['’]?t|can\\s+not|am\\s+unable\\s+to|am\\s+not\\s+able\\s+to)\\s+(?:assist|help|comply|continue)(?:\\s+with)?(?:\\s+(?:that|this))?(?:\\s+(?:request|task))?\\.?$/i;\n\nconst CANNED_SAFETY_REFUSAL_MAX_LENGTH = 120;\n\nexport function _isSafetyRefusal(this: AgentSession, message: AssistantMessage): boolean {\n\t// Real errors are handled by _isRetryableError; aborts are user-initiated.\n\tif (message.stopReason !== \"stop\" && message.stopReason !== \"length\" && message.stopReason !== \"toolUse\") {\n\t\treturn false;\n\t}\n\n\tconst content = message.content;\n\tif (!Array.isArray(content) || content.length === 0) return false;\n\n\tlet text = \"\";\n\tfor (const part of content) {\n\t\tif (part.type === \"toolCall\") return false;\n\t\tif (part.type === \"thinking\" && (part.redacted === true || part.thinking.trim().length > 0)) return false;\n\t\tif (part.type === \"text\") text += part.text;\n\t}\n\ttext = text.trim();\n\tif (text.length === 0 || text.length > CANNED_SAFETY_REFUSAL_MAX_LENGTH) return false;\n\tif (!CANNED_SAFETY_REFUSAL_PATTERN.test(text)) return false;\n\n\t// Zero billed output distinguishes a provider interception from legitimate\n\t// model-authored refusal prose, which is never auto-retried.\n\treturn (message.usage?.output ?? 0) === 0;\n}\n\n/**\n * Handle retryable errors with exponential backoff.\n * @returns true if retry was initiated, false if max retries exceeded or disabled\n */\n\nexport async function _handleRetryableError(this: AgentSession, message: AssistantMessage): Promise<boolean> {\n\tconst settings = this.settingsManager.getRetrySettings();\n\tif (!settings.enabled) {\n\t\tthis._resolveRetry();\n\t\treturn false;\n\t}\n\n\t// Retry promise is created synchronously in _handleAgentEvent for agent_end.\n\t// Keep a defensive fallback here in case a future refactor bypasses that path.\n\tif (!this._retryPromise) {\n\t\tthis._retryPromise = new Promise((resolve) => {\n\t\t\tthis._retryResolve = resolve;\n\t\t});\n\t}\n\n\tthis._retryAttempt++;\n\n\tif (this._retryAttempt > settings.maxRetries) {\n\t\t// Max retries exceeded, emit final failure and reset\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: false,\n\t\t\tattempt: this._retryAttempt - 1,\n\t\t\tfinalError: message.errorMessage,\n\t\t});\n\t\tthis._retryAttempt = 0;\n\t\tthis._resolveRetry(); // Resolve so waitForRetry() completes\n\t\treturn false;\n\t}\n\n\tconst delayMs = settings.baseDelayMs * 2 ** (this._retryAttempt - 1);\n\n\tthis._emit({\n\t\ttype: \"auto_retry_start\",\n\t\tattempt: this._retryAttempt,\n\t\tmaxAttempts: settings.maxRetries,\n\t\tdelayMs,\n\t\terrorMessage: message.errorMessage || \"Unknown error\",\n\t});\n\n\t// Remove error message from agent state (keep in session for history)\n\tconst messages = this.agent.state.messages;\n\tif (messages.length > 0 && messages[messages.length - 1].role === \"assistant\") {\n\t\tthis.agent.state.messages = messages.slice(0, -1);\n\t}\n\n\t// Wait with exponential backoff (abortable)\n\tthis._retryAbortController = new AbortController();\n\ttry {\n\t\tawait sleep(delayMs, this._retryAbortController.signal);\n\t} catch {\n\t\t// Aborted during sleep - emit end event so UI can clean up\n\t\tconst attempt = this._retryAttempt;\n\t\tthis._retryAttempt = 0;\n\t\tthis._retryAbortController = undefined;\n\t\tthis._emit({\n\t\t\ttype: \"auto_retry_end\",\n\t\t\tsuccess: false,\n\t\t\tattempt,\n\t\t\tfinalError: \"Retry cancelled\",\n\t\t});\n\t\tthis._resolveRetry();\n\t\treturn false;\n\t}\n\tthis._retryAbortController = undefined;\n\n\t// Retry via continue() - use setTimeout to break out of event handler chain\n\tsetTimeout(() => {\n\t\tthis.agent.continue().catch(() => {\n\t\t\t// Retry failed - will be caught by next agent_end\n\t\t});\n\t}, 0);\n\n\treturn true;\n}\n\n/**\n * Cancel in-progress retry.\n */\n\nexport function abortRetry(this: AgentSession): void {\n\tthis._retryAbortController?.abort();\n\t// Note: _retryAttempt is reset in the catch block of _autoRetry\n\tthis._resolveRetry();\n}\n\n/**\n * Wait for any in-progress retry to complete.\n * Returns immediately if no retry is in progress.\n */\n\nexport async function waitForRetry(this: AgentSession): Promise<void> {\n\tif (!this._retryPromise) {\n\t\treturn;\n\t}\n\n\tawait this._retryPromise;\n\tawait this.agent.waitForIdle();\n}\n\n/** Whether auto-retry is currently in progress */\n\nexport function setAutoRetryEnabled(this: AgentSession, enabled: boolean): void {\n\tthis.settingsManager.setRetryEnabled(enabled);\n}\n\n// =========================================================================\n// Bash Execution\n// =========================================================================\n\n/**\n * Execute a bash command.\n * Adds result to agent context and session.\n * @param command The bash command to execute\n * @param onChunk Optional streaming callback for output\n * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix)\n * @param options.operations Custom BashOperations for remote execution\n */\n\nexport const agentSessionRetryMethods = {\n\t_isRetryableError,\n\t_normalizePersistedGeminiToolArgs,\n\t_isEmptyCompletion,\n\t_isSafetyRefusal,\n\t_handleRetryableError,\n\tabortRetry,\n\twaitForRetry,\n\tsetAutoRetryEnabled,\n};\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { CopilotModelContext } from "./copilot-model-catalog.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Static CAPI-derived limit snapshot for bundled GitHub Copilot models.
|
|
4
|
+
*
|
|
5
|
+
* CAPI is the source of truth for what GitHub actually enforces, and the live
|
|
6
|
+
* `/models` catalog (or its disk cache) is always authoritative when
|
|
7
|
+
* available — this table is consulted **only when the active catalog has no
|
|
8
|
+
* entry for the model** (cold start without cache, catalog fetch failure,
|
|
9
|
+
* network-restricted environments such as eval containers or CI). Without it,
|
|
10
|
+
* the bundled pi-ai metadata wins, and several bundled entries disagree with
|
|
11
|
+
* CAPI (snapshotted 2026-07-02): the gpt-5.x family claims a 400k window vs
|
|
12
|
+
* CAPI's enforced 272k default tier, claude-opus-4.6/claude-sonnet-4.6 claim
|
|
13
|
+
* the branded 1M window as their base tier vs CAPI's 200k default, and
|
|
14
|
+
* claude-opus-4.6/4.7 ship a 32k output cap vs CAPI's real 64k.
|
|
15
|
+
*
|
|
16
|
+
* Why this matters (issue #1608): auto-compaction thresholds and overflow
|
|
17
|
+
* recovery are driven by `model.contextWindow`, and request output caps by
|
|
18
|
+
* `model.maxTokens`. With an overstated window a session never compacts,
|
|
19
|
+
* sails past the server-side cap, and CAPI starts intercepting requests with
|
|
20
|
+
* canned zero-usage refusals ("I'm sorry, but I cannot assist with that
|
|
21
|
+
* request.") reported as `stopReason: "length"`. Understated output caps
|
|
22
|
+
* instead truncate long responses below the real limit.
|
|
23
|
+
*
|
|
24
|
+
* Entries mirror the shape `resolveCopilotModelContext` derives from CAPI
|
|
25
|
+
* `capabilities.limits` / `billing.token_prices`, including the long-context
|
|
26
|
+
* tier where CAPI advertises one (`contextWindowOptions` with the branded
|
|
27
|
+
* window plus the hard `maxInputTokens` prompt cap, e.g. 922k input + 128k
|
|
28
|
+
* output for gpt-5.5's 1.05M tier). This keeps a returning user's persisted
|
|
29
|
+
* long-context selection valid on a cold start without a catalog; actual long
|
|
30
|
+
* tier use is still gated server-side by entitlement, with the existing
|
|
31
|
+
* friendly-error handling on rejection. The snapshot covers every bundled
|
|
32
|
+
* model present in the CAPI catalog — also the currently-agreeing ones — so
|
|
33
|
+
* upstream metadata drift cannot silently reintroduce the failure. Bundled
|
|
34
|
+
* models absent from the CAPI catalog (retired or not-yet-listed ids) are
|
|
35
|
+
* intentionally omitted — there is no CAPI ground truth to snapshot for them.
|
|
36
|
+
*/
|
|
37
|
+
export type StaticCopilotModelFallback = Pick<CopilotModelContext, "contextWindow" | "contextWindowOptions" | "maxInputTokens" | "maxTokens">;
|
|
38
|
+
/**
|
|
39
|
+
* Look up the static CAPI-derived limits for a bundled Copilot model. Returns
|
|
40
|
+
* undefined for models with no CAPI catalog presence to snapshot.
|
|
41
|
+
*/
|
|
42
|
+
export declare function getStaticCopilotModelFallback(modelId: string): StaticCopilotModelFallback | undefined;
|
|
43
|
+
//# sourceMappingURL=copilot-model-static-fallbacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-model-static-fallbacks.d.ts","sourceRoot":"","sources":["../../src/core/copilot-model-static-fallbacks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,mBAAmB,EACnB,eAAe,GAAG,sBAAsB,GAAG,gBAAgB,GAAG,WAAW,CACzE,CAAC;AA+CF;;;GAGG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS,CAErG","sourcesContent":["import type { CopilotModelContext } from \"./copilot-model-catalog.ts\";\n\n/**\n * Static CAPI-derived limit snapshot for bundled GitHub Copilot models.\n *\n * CAPI is the source of truth for what GitHub actually enforces, and the live\n * `/models` catalog (or its disk cache) is always authoritative when\n * available — this table is consulted **only when the active catalog has no\n * entry for the model** (cold start without cache, catalog fetch failure,\n * network-restricted environments such as eval containers or CI). Without it,\n * the bundled pi-ai metadata wins, and several bundled entries disagree with\n * CAPI (snapshotted 2026-07-02): the gpt-5.x family claims a 400k window vs\n * CAPI's enforced 272k default tier, claude-opus-4.6/claude-sonnet-4.6 claim\n * the branded 1M window as their base tier vs CAPI's 200k default, and\n * claude-opus-4.6/4.7 ship a 32k output cap vs CAPI's real 64k.\n *\n * Why this matters (issue #1608): auto-compaction thresholds and overflow\n * recovery are driven by `model.contextWindow`, and request output caps by\n * `model.maxTokens`. With an overstated window a session never compacts,\n * sails past the server-side cap, and CAPI starts intercepting requests with\n * canned zero-usage refusals (\"I'm sorry, but I cannot assist with that\n * request.\") reported as `stopReason: \"length\"`. Understated output caps\n * instead truncate long responses below the real limit.\n *\n * Entries mirror the shape `resolveCopilotModelContext` derives from CAPI\n * `capabilities.limits` / `billing.token_prices`, including the long-context\n * tier where CAPI advertises one (`contextWindowOptions` with the branded\n * window plus the hard `maxInputTokens` prompt cap, e.g. 922k input + 128k\n * output for gpt-5.5's 1.05M tier). This keeps a returning user's persisted\n * long-context selection valid on a cold start without a catalog; actual long\n * tier use is still gated server-side by entitlement, with the existing\n * friendly-error handling on rejection. The snapshot covers every bundled\n * model present in the CAPI catalog — also the currently-agreeing ones — so\n * upstream metadata drift cannot silently reintroduce the failure. Bundled\n * models absent from the CAPI catalog (retired or not-yet-listed ids) are\n * intentionally omitted — there is no CAPI ground truth to snapshot for them.\n */\n\nexport type StaticCopilotModelFallback = Pick<\n\tCopilotModelContext,\n\t\"contextWindow\" | \"contextWindowOptions\" | \"maxInputTokens\" | \"maxTokens\"\n>;\n\n/** CAPI 1.05M long-context tier shared by the tiered OpenAI gpt-5.x models. */\nconst OPENAI_LONG_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 272_000,\n\tcontextWindowOptions: [272_000, 1_050_000],\n\tmaxInputTokens: 922_000,\n\tmaxTokens: 128_000,\n};\n\n/** CAPI 1M long-context tier shared by tiered Anthropic/Google models. */\nconst BRANDED_1M_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 200_000,\n\tcontextWindowOptions: [200_000, 1_000_000],\n\tmaxInputTokens: 936_000,\n\tmaxTokens: 64_000,\n};\n\nconst STATIC_COPILOT_MODEL_FALLBACKS: ReadonlyMap<string, StaticCopilotModelFallback> = new Map<string, StaticCopilotModelFallback>([\n\t// OpenAI: bundled metadata claims a 400k base window; CAPI's default tier is\n\t// 272k, with a 1.05M long tier (922k input cap) on gpt-5.5/gpt-5.4.\n\t[\"gpt-5.5\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4-mini\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t[\"gpt-5.3-codex\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t// Bundled metadata claims 264k; CAPI enforces 128k.\n\t[\"gpt-5-mini\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gpt-4.1\", { contextWindow: 128_000, maxTokens: 16_384 }],\n\t// Anthropic: CAPI's default tier is 200k with a 1M long tier (936k input\n\t// cap). Bundled claude-opus-4.6/claude-sonnet-4.6 wrongly claim the branded\n\t// 1M window as the base tier, and bundled opus-4.6/4.7 output caps are half\n\t// of CAPI's real 64k.\n\t[\"claude-opus-4.6\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.7\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.8\", BRANDED_1M_TIER],\n\t[\"claude-sonnet-4.6\", BRANDED_1M_TIER],\n\t// Bundled metadata claims 200k; CAPI enforces smaller default windows, with\n\t// no long tier.\n\t[\"claude-haiku-4.5\", { contextWindow: 136_000, maxTokens: 64_000 }],\n\t[\"claude-sonnet-4.5\", { contextWindow: 168_000, maxTokens: 32_000 }],\n\t// Google: the pro/3.5 models share the branded 1M tier structure.\n\t[\"gemini-3.1-pro-preview\", BRANDED_1M_TIER],\n\t[\"gemini-3.5-flash\", BRANDED_1M_TIER],\n\t[\"gemini-2.5-pro\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gemini-3-flash-preview\", { contextWindow: 128_000, maxTokens: 64_000 }],\n]);\n\n/**\n * Look up the static CAPI-derived limits for a bundled Copilot model. Returns\n * undefined for models with no CAPI catalog presence to snapshot.\n */\nexport function getStaticCopilotModelFallback(modelId: string): StaticCopilotModelFallback | undefined {\n\treturn STATIC_COPILOT_MODEL_FALLBACKS.get(modelId);\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** CAPI 1.05M long-context tier shared by the tiered OpenAI gpt-5.x models. */
|
|
2
|
+
const OPENAI_LONG_TIER = {
|
|
3
|
+
contextWindow: 272_000,
|
|
4
|
+
contextWindowOptions: [272_000, 1_050_000],
|
|
5
|
+
maxInputTokens: 922_000,
|
|
6
|
+
maxTokens: 128_000,
|
|
7
|
+
};
|
|
8
|
+
/** CAPI 1M long-context tier shared by tiered Anthropic/Google models. */
|
|
9
|
+
const BRANDED_1M_TIER = {
|
|
10
|
+
contextWindow: 200_000,
|
|
11
|
+
contextWindowOptions: [200_000, 1_000_000],
|
|
12
|
+
maxInputTokens: 936_000,
|
|
13
|
+
maxTokens: 64_000,
|
|
14
|
+
};
|
|
15
|
+
const STATIC_COPILOT_MODEL_FALLBACKS = new Map([
|
|
16
|
+
// OpenAI: bundled metadata claims a 400k base window; CAPI's default tier is
|
|
17
|
+
// 272k, with a 1.05M long tier (922k input cap) on gpt-5.5/gpt-5.4.
|
|
18
|
+
["gpt-5.5", OPENAI_LONG_TIER],
|
|
19
|
+
["gpt-5.4", OPENAI_LONG_TIER],
|
|
20
|
+
["gpt-5.4-mini", { contextWindow: 272_000, maxTokens: 128_000 }],
|
|
21
|
+
["gpt-5.3-codex", { contextWindow: 272_000, maxTokens: 128_000 }],
|
|
22
|
+
// Bundled metadata claims 264k; CAPI enforces 128k.
|
|
23
|
+
["gpt-5-mini", { contextWindow: 128_000, maxTokens: 64_000 }],
|
|
24
|
+
["gpt-4.1", { contextWindow: 128_000, maxTokens: 16_384 }],
|
|
25
|
+
// Anthropic: CAPI's default tier is 200k with a 1M long tier (936k input
|
|
26
|
+
// cap). Bundled claude-opus-4.6/claude-sonnet-4.6 wrongly claim the branded
|
|
27
|
+
// 1M window as the base tier, and bundled opus-4.6/4.7 output caps are half
|
|
28
|
+
// of CAPI's real 64k.
|
|
29
|
+
["claude-opus-4.6", BRANDED_1M_TIER],
|
|
30
|
+
["claude-opus-4.7", BRANDED_1M_TIER],
|
|
31
|
+
["claude-opus-4.8", BRANDED_1M_TIER],
|
|
32
|
+
["claude-sonnet-4.6", BRANDED_1M_TIER],
|
|
33
|
+
// Bundled metadata claims 200k; CAPI enforces smaller default windows, with
|
|
34
|
+
// no long tier.
|
|
35
|
+
["claude-haiku-4.5", { contextWindow: 136_000, maxTokens: 64_000 }],
|
|
36
|
+
["claude-sonnet-4.5", { contextWindow: 168_000, maxTokens: 32_000 }],
|
|
37
|
+
// Google: the pro/3.5 models share the branded 1M tier structure.
|
|
38
|
+
["gemini-3.1-pro-preview", BRANDED_1M_TIER],
|
|
39
|
+
["gemini-3.5-flash", BRANDED_1M_TIER],
|
|
40
|
+
["gemini-2.5-pro", { contextWindow: 128_000, maxTokens: 64_000 }],
|
|
41
|
+
["gemini-3-flash-preview", { contextWindow: 128_000, maxTokens: 64_000 }],
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Look up the static CAPI-derived limits for a bundled Copilot model. Returns
|
|
45
|
+
* undefined for models with no CAPI catalog presence to snapshot.
|
|
46
|
+
*/
|
|
47
|
+
export function getStaticCopilotModelFallback(modelId) {
|
|
48
|
+
return STATIC_COPILOT_MODEL_FALLBACKS.get(modelId);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=copilot-model-static-fallbacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copilot-model-static-fallbacks.js","sourceRoot":"","sources":["../../src/core/copilot-model-static-fallbacks.ts"],"names":[],"mappings":"AA2CA,+EAA+E;AAC/E,MAAM,gBAAgB,GAA+B;IACpD,aAAa,EAAE,OAAO;IACtB,oBAAoB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAC1C,cAAc,EAAE,OAAO;IACvB,SAAS,EAAE,OAAO;CAClB,CAAC;AAEF,0EAA0E;AAC1E,MAAM,eAAe,GAA+B;IACnD,aAAa,EAAE,OAAO;IACtB,oBAAoB,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;IAC1C,cAAc,EAAE,OAAO;IACvB,SAAS,EAAE,MAAM;CACjB,CAAC;AAEF,MAAM,8BAA8B,GAAoD,IAAI,GAAG,CAAqC;IACnI,6EAA6E;IAC7E,oEAAoE;IACpE,CAAC,SAAS,EAAE,gBAAgB,CAAC;IAC7B,CAAC,SAAS,EAAE,gBAAgB,CAAC;IAC7B,CAAC,cAAc,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAChE,CAAC,eAAe,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IACjE,oDAAoD;IACpD,CAAC,YAAY,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7D,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAC1D,yEAAyE;IACzE,4EAA4E;IAC5E,4EAA4E;IAC5E,sBAAsB;IACtB,CAAC,iBAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,iBAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,iBAAiB,EAAE,eAAe,CAAC;IACpC,CAAC,mBAAmB,EAAE,eAAe,CAAC;IACtC,4EAA4E;IAC5E,gBAAgB;IAChB,CAAC,kBAAkB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACnE,CAAC,mBAAmB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpE,kEAAkE;IAClE,CAAC,wBAAwB,EAAE,eAAe,CAAC;IAC3C,CAAC,kBAAkB,EAAE,eAAe,CAAC;IACrC,CAAC,gBAAgB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACjE,CAAC,wBAAwB,EAAE,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;CACzE,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAAC,OAAe;IAC5D,OAAO,8BAA8B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACpD,CAAC","sourcesContent":["import type { CopilotModelContext } from \"./copilot-model-catalog.ts\";\n\n/**\n * Static CAPI-derived limit snapshot for bundled GitHub Copilot models.\n *\n * CAPI is the source of truth for what GitHub actually enforces, and the live\n * `/models` catalog (or its disk cache) is always authoritative when\n * available — this table is consulted **only when the active catalog has no\n * entry for the model** (cold start without cache, catalog fetch failure,\n * network-restricted environments such as eval containers or CI). Without it,\n * the bundled pi-ai metadata wins, and several bundled entries disagree with\n * CAPI (snapshotted 2026-07-02): the gpt-5.x family claims a 400k window vs\n * CAPI's enforced 272k default tier, claude-opus-4.6/claude-sonnet-4.6 claim\n * the branded 1M window as their base tier vs CAPI's 200k default, and\n * claude-opus-4.6/4.7 ship a 32k output cap vs CAPI's real 64k.\n *\n * Why this matters (issue #1608): auto-compaction thresholds and overflow\n * recovery are driven by `model.contextWindow`, and request output caps by\n * `model.maxTokens`. With an overstated window a session never compacts,\n * sails past the server-side cap, and CAPI starts intercepting requests with\n * canned zero-usage refusals (\"I'm sorry, but I cannot assist with that\n * request.\") reported as `stopReason: \"length\"`. Understated output caps\n * instead truncate long responses below the real limit.\n *\n * Entries mirror the shape `resolveCopilotModelContext` derives from CAPI\n * `capabilities.limits` / `billing.token_prices`, including the long-context\n * tier where CAPI advertises one (`contextWindowOptions` with the branded\n * window plus the hard `maxInputTokens` prompt cap, e.g. 922k input + 128k\n * output for gpt-5.5's 1.05M tier). This keeps a returning user's persisted\n * long-context selection valid on a cold start without a catalog; actual long\n * tier use is still gated server-side by entitlement, with the existing\n * friendly-error handling on rejection. The snapshot covers every bundled\n * model present in the CAPI catalog — also the currently-agreeing ones — so\n * upstream metadata drift cannot silently reintroduce the failure. Bundled\n * models absent from the CAPI catalog (retired or not-yet-listed ids) are\n * intentionally omitted — there is no CAPI ground truth to snapshot for them.\n */\n\nexport type StaticCopilotModelFallback = Pick<\n\tCopilotModelContext,\n\t\"contextWindow\" | \"contextWindowOptions\" | \"maxInputTokens\" | \"maxTokens\"\n>;\n\n/** CAPI 1.05M long-context tier shared by the tiered OpenAI gpt-5.x models. */\nconst OPENAI_LONG_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 272_000,\n\tcontextWindowOptions: [272_000, 1_050_000],\n\tmaxInputTokens: 922_000,\n\tmaxTokens: 128_000,\n};\n\n/** CAPI 1M long-context tier shared by tiered Anthropic/Google models. */\nconst BRANDED_1M_TIER: StaticCopilotModelFallback = {\n\tcontextWindow: 200_000,\n\tcontextWindowOptions: [200_000, 1_000_000],\n\tmaxInputTokens: 936_000,\n\tmaxTokens: 64_000,\n};\n\nconst STATIC_COPILOT_MODEL_FALLBACKS: ReadonlyMap<string, StaticCopilotModelFallback> = new Map<string, StaticCopilotModelFallback>([\n\t// OpenAI: bundled metadata claims a 400k base window; CAPI's default tier is\n\t// 272k, with a 1.05M long tier (922k input cap) on gpt-5.5/gpt-5.4.\n\t[\"gpt-5.5\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4\", OPENAI_LONG_TIER],\n\t[\"gpt-5.4-mini\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t[\"gpt-5.3-codex\", { contextWindow: 272_000, maxTokens: 128_000 }],\n\t// Bundled metadata claims 264k; CAPI enforces 128k.\n\t[\"gpt-5-mini\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gpt-4.1\", { contextWindow: 128_000, maxTokens: 16_384 }],\n\t// Anthropic: CAPI's default tier is 200k with a 1M long tier (936k input\n\t// cap). Bundled claude-opus-4.6/claude-sonnet-4.6 wrongly claim the branded\n\t// 1M window as the base tier, and bundled opus-4.6/4.7 output caps are half\n\t// of CAPI's real 64k.\n\t[\"claude-opus-4.6\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.7\", BRANDED_1M_TIER],\n\t[\"claude-opus-4.8\", BRANDED_1M_TIER],\n\t[\"claude-sonnet-4.6\", BRANDED_1M_TIER],\n\t// Bundled metadata claims 200k; CAPI enforces smaller default windows, with\n\t// no long tier.\n\t[\"claude-haiku-4.5\", { contextWindow: 136_000, maxTokens: 64_000 }],\n\t[\"claude-sonnet-4.5\", { contextWindow: 168_000, maxTokens: 32_000 }],\n\t// Google: the pro/3.5 models share the branded 1M tier structure.\n\t[\"gemini-3.1-pro-preview\", BRANDED_1M_TIER],\n\t[\"gemini-3.5-flash\", BRANDED_1M_TIER],\n\t[\"gemini-2.5-pro\", { contextWindow: 128_000, maxTokens: 64_000 }],\n\t[\"gemini-3-flash-preview\", { contextWindow: 128_000, maxTokens: 64_000 }],\n]);\n\n/**\n * Look up the static CAPI-derived limits for a bundled Copilot model. Returns\n * undefined for models with no CAPI catalog presence to snapshot.\n */\nexport function getStaticCopilotModelFallback(modelId: string): StaticCopilotModelFallback | undefined {\n\treturn STATIC_COPILOT_MODEL_FALLBACKS.get(modelId);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-virtual-modules.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader-virtual-modules.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnD,iBAAe,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAqCnE;AAuBD,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAOrE;AAiBD;;;GAGG;AACH,iBAAS,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkD5C;AAED,kEAAkE;AAClE,eAAO,MAAM,wBAAwB;;;CAGpC,CAAC;AAEF,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAmBvC","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti/static\";\nimport { isBunBinary } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport type { ExtensionFactory } from \"./types.ts\";\n\nconst require = createRequire(import.meta.url);\nlet _virtualModules: Record<string, object> | null = null;\nlet _virtualModulesPromise: Promise<Record<string, object>> | null = null;\n\nasync function loadVirtualModules(): Promise<Record<string, object>> {\n const [typebox, typeboxCompile, typeboxValue, piAgentCore, piTui, piAi, piAiOauth, piCodingAgent] = await Promise.all([\n import(\"typebox\"),\n import(\"typebox/compile\"),\n import(\"typebox/value\"),\n import(\"@earendil-works/pi-agent-core\"),\n import(\"@earendil-works/pi-tui\"),\n // pi 0.80.2: the old global pi-ai API moved off the root entrypoint onto\n // `/compat` (a strict superset). Extensions still `import ... from\n // \"@earendil-works/pi-ai\"`, so we load the compat module here and key it\n // under the root specifier below to keep every extension working unchanged.\n import(\"@earendil-works/pi-ai/compat\"),\n import(\"@earendil-works/pi-ai/oauth\"),\n // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n // avoiding a circular dependency while preserving the package-name extension import path.\n import(\"../../index.ts\"),\n ]);\n\n return {\n typebox,\n \"typebox/compile\": typeboxCompile,\n \"typebox/value\": typeboxValue,\n \"@sinclair/typebox\": typebox,\n \"@sinclair/typebox/compile\": typeboxCompile,\n \"@sinclair/typebox/value\": typeboxValue,\n \"@earendil-works/pi-agent-core\": piAgentCore,\n \"@earendil-works/pi-tui\": piTui,\n \"@earendil-works/pi-ai\": piAi,\n \"@earendil-works/pi-ai/compat\": piAi,\n \"@earendil-works/pi-ai/oauth\": piAiOauth,\n \"@bastani/atomic\": piCodingAgent,\n \"@mariozechner/pi-agent-core\": piAgentCore,\n \"@mariozechner/pi-tui\": piTui,\n \"@mariozechner/pi-ai\": piAi,\n \"@mariozechner/pi-ai/compat\": piAi,\n \"@mariozechner/pi-ai/oauth\": piAiOauth,\n };\n}\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary). */\nasync function getVirtualModules(): Promise<Record<string, object>> {\n if (_virtualModules) return _virtualModules;\n _virtualModulesPromise ??= loadVirtualModules().then(\n (virtualModules) => {\n _virtualModules = virtualModules;\n return virtualModules;\n },\n (error: Error) => {\n _virtualModulesPromise = null;\n throw error;\n },\n );\n return _virtualModulesPromise;\n}\nlet _aliases: Record<string, string> | null = null;\n\nlet extensionCacheCwd: string | undefined;\nlet extensionCacheGeneration = 0;\nconst extensionCache = new Map<string, ExtensionFactory>();\n\nexport interface ExtensionCacheToken {\n cwd: string;\n generation: number;\n}\n\nexport function clearExtensionCache(): void {\n extensionCache.clear();\n extensionCacheCwd = undefined;\n extensionCacheGeneration++;\n}\n\nexport function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {\n const resolvedCwd = resolvePath(cwd);\n if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {\n clearExtensionCache();\n }\n extensionCacheCwd = resolvedCwd;\n return { cwd: resolvedCwd, generation: extensionCacheGeneration };\n}\n\nfunction isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {\n return (\n cacheToken !== undefined &&\n extensionCacheCwd === cacheToken.cwd &&\n extensionCacheGeneration === cacheToken.generation\n );\n}\n\nfunction extensionImportSpecifier(extensionPath: string, cacheToken: ExtensionCacheToken | undefined): string {\n const url = pathToFileURL(extensionPath);\n const cacheKey = cacheToken ? `${cacheToken.generation}:${cacheToken.cwd}` : `${Date.now()}:${Math.random()}`;\n url.searchParams.set(\"atomicExtensionCache\", cacheKey);\n return url.href;\n}\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nfunction getAliases(): Record<string, string> {\n if (_aliases) return _aliases;\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n const typeboxEntry = require.resolve(\"typebox\");\n const typeboxCompileEntry = require.resolve(\"typebox/compile\");\n const typeboxValueEntry = require.resolve(\"typebox/value\");\n\n const packagesRoot = path.resolve(__dirname, \"../../../../\");\n const resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {\n const workspacePath = path.join(packagesRoot, workspaceRelativePath);\n if (fs.existsSync(workspacePath)) {\n return workspacePath;\n }\n return fileURLToPath(import.meta.resolve(specifier));\n };\n\n const piCodingAgentEntry = packageIndex;\n const piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@earendil-works/pi-agent-core\");\n const piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@earendil-works/pi-tui\");\n // The workspace path mirrors pi-ai 0.80.x's built compat export. If an\n // upstream layout change moves that file, fall back to package export\n // resolution so installed Atomic builds still load the canonical entrypoint.\n const piAiEntry = resolveWorkspaceOrImport(\"ai/dist/compat.js\", \"@earendil-works/pi-ai/compat\");\n const piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@earendil-works/pi-ai/oauth\");\n\n _aliases = {\n \"@bastani/atomic\": piCodingAgentEntry,\n \"@earendil-works/pi-coding-agent\": piCodingAgentEntry,\n \"@earendil-works/pi-agent-core\": piAgentCoreEntry,\n \"@earendil-works/pi-tui\": piTuiEntry,\n \"@earendil-works/pi-ai\": piAiEntry,\n \"@earendil-works/pi-ai/compat\": piAiEntry,\n \"@earendil-works/pi-ai/oauth\": piAiOauthEntry,\n \"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n \"@mariozechner/pi-tui\": piTuiEntry,\n \"@mariozechner/pi-ai\": piAiEntry,\n \"@mariozechner/pi-ai/compat\": piAiEntry,\n \"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n typebox: typeboxEntry,\n \"typebox/compile\": typeboxCompileEntry,\n \"typebox/value\": typeboxValueEntry,\n \"@sinclair/typebox\": typeboxEntry,\n \"@sinclair/typebox/compile\": typeboxCompileEntry,\n \"@sinclair/typebox/value\": typeboxValueEntry,\n };\n\n return _aliases;\n}\n\n/** Internal hooks for extension-loader alias regression tests. */\nexport const extensionLoaderTestHooks = {\n loadVirtualModules,\n getAliases,\n};\n\nexport async function loadExtensionModule(\n extensionPath: string,\n cacheToken?: ExtensionCacheToken,\n): Promise<ExtensionFactory | undefined> {\n if (isCurrentCacheToken(cacheToken)) {\n const cachedFactory = extensionCache.get(extensionPath);\n if (cachedFactory) return cachedFactory;\n }\n\n const forceTransformedImports = isBunBinary || process.platform === \"win32\";\n const jiti = createJiti(import.meta.url, {\n moduleCache: false,\n ...(forceTransformedImports ? { fsCache: false, tryNative: false } : {}),\n ...(isBunBinary ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),\n });\n const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });\n const factory = module as ExtensionFactory;\n if (typeof factory !== \"function\") return undefined;\n if (isCurrentCacheToken(cacheToken)) {\n extensionCache.set(extensionPath, factory);\n }\n return factory;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loader-virtual-modules.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader-virtual-modules.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAMnD,iBAAe,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAqCnE;AAoDD,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAOrE;AAiBD;;;GAGG;AACH,iBAAS,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwD5C;AAED,kEAAkE;AAClE,eAAO,MAAM,wBAAwB;;;CAGpC,CAAC;AAUF,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAmCvC","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti/static\";\nimport { getExtensionTranspileCacheDir, isBunBinary, isBundledBuild } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport type { ExtensionFactory } from \"./types.ts\";\n\nconst require = createRequire(import.meta.url);\nlet _virtualModules: Record<string, object> | null = null;\nlet _virtualModulesPromise: Promise<Record<string, object>> | null = null;\n\nasync function loadVirtualModules(): Promise<Record<string, object>> {\n const [typebox, typeboxCompile, typeboxValue, piAgentCore, piTui, piAi, piAiOauth, piCodingAgent] = await Promise.all([\n import(\"typebox\"),\n import(\"typebox/compile\"),\n import(\"typebox/value\"),\n import(\"@earendil-works/pi-agent-core\"),\n import(\"@earendil-works/pi-tui\"),\n // pi 0.80.2: the old global pi-ai API moved off the root entrypoint onto\n // `/compat` (a strict superset). Extensions still `import ... from\n // \"@earendil-works/pi-ai\"`, so we load the compat module here and key it\n // under the root specifier below to keep every extension working unchanged.\n import(\"@earendil-works/pi-ai/compat\"),\n import(\"@earendil-works/pi-ai/oauth\"),\n // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n // avoiding a circular dependency while preserving the package-name extension import path.\n import(\"../../index.ts\"),\n ]);\n\n return {\n typebox,\n \"typebox/compile\": typeboxCompile,\n \"typebox/value\": typeboxValue,\n \"@sinclair/typebox\": typebox,\n \"@sinclair/typebox/compile\": typeboxCompile,\n \"@sinclair/typebox/value\": typeboxValue,\n \"@earendil-works/pi-agent-core\": piAgentCore,\n \"@earendil-works/pi-tui\": piTui,\n \"@earendil-works/pi-ai\": piAi,\n \"@earendil-works/pi-ai/compat\": piAi,\n \"@earendil-works/pi-ai/oauth\": piAiOauth,\n \"@bastani/atomic\": piCodingAgent,\n \"@mariozechner/pi-agent-core\": piAgentCore,\n \"@mariozechner/pi-tui\": piTui,\n \"@mariozechner/pi-ai\": piAi,\n \"@mariozechner/pi-ai/compat\": piAi,\n \"@mariozechner/pi-ai/oauth\": piAiOauth,\n };\n}\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary). */\nasync function getVirtualModules(): Promise<Record<string, object>> {\n if (_virtualModules) return _virtualModules;\n _virtualModulesPromise ??= loadVirtualModules().then(\n (virtualModules) => {\n _virtualModules = virtualModules;\n return virtualModules;\n },\n (error: Error) => {\n _virtualModulesPromise = null;\n throw error;\n },\n );\n return _virtualModulesPromise;\n}\nlet _aliases: Record<string, string> | null = null;\nlet _transpileCacheDir: string | null = null;\n\n/**\n * Persistent on-disk cache for jiti-transpiled extension modules.\n * jiti keys cache entries by source-content hash, so entries self-invalidate\n * when extension sources change; stale sibling version dirs are pruned\n * in the background.\n */\nfunction getTranspileCacheDir(): string {\n if (_transpileCacheDir) return _transpileCacheDir;\n _transpileCacheDir = getExtensionTranspileCacheDir();\n pruneStaleTranspileCaches(_transpileCacheDir);\n return _transpileCacheDir;\n}\n\nfunction pruneStaleTranspileCaches(currentDir: string): void {\n const parent = path.dirname(currentDir);\n const keep = path.basename(currentDir);\n void fs.promises\n .readdir(parent)\n .then((entries) =>\n Promise.all(\n entries\n .filter((entry) => entry !== keep)\n .map((entry) => fs.promises.rm(path.join(parent, entry), { recursive: true, force: true })),\n ),\n )\n .catch(() => {});\n}\n\nlet extensionCacheCwd: string | undefined;\nlet extensionCacheGeneration = 0;\nconst extensionCache = new Map<string, ExtensionFactory>();\n\nexport interface ExtensionCacheToken {\n cwd: string;\n generation: number;\n}\n\nexport function clearExtensionCache(): void {\n extensionCache.clear();\n extensionCacheCwd = undefined;\n extensionCacheGeneration++;\n}\n\nexport function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {\n const resolvedCwd = resolvePath(cwd);\n if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {\n clearExtensionCache();\n }\n extensionCacheCwd = resolvedCwd;\n return { cwd: resolvedCwd, generation: extensionCacheGeneration };\n}\n\nfunction isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {\n return (\n cacheToken !== undefined &&\n extensionCacheCwd === cacheToken.cwd &&\n extensionCacheGeneration === cacheToken.generation\n );\n}\n\nfunction extensionImportSpecifier(extensionPath: string, cacheToken: ExtensionCacheToken | undefined): string {\n const url = pathToFileURL(extensionPath);\n const cacheKey = cacheToken ? `${cacheToken.generation}:${cacheToken.cwd}` : `${Date.now()}:${Math.random()}`;\n url.searchParams.set(\"atomicExtensionCache\", cacheKey);\n return url.href;\n}\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nfunction getAliases(): Record<string, string> {\n if (_aliases) return _aliases;\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n const typeboxEntry = require.resolve(\"typebox\");\n const typeboxCompileEntry = require.resolve(\"typebox/compile\");\n const typeboxValueEntry = require.resolve(\"typebox/value\");\n\n const packagesRoot = path.resolve(__dirname, \"../../../../\");\n const resolveWorkspaceOrImport = (workspaceRelativePath: string, packageName: string): string => {\n const workspacePath = path.join(packagesRoot, workspaceRelativePath);\n if (fs.existsSync(workspacePath)) {\n return workspacePath;\n }\n // Resolve via the package.json export and join the built-entry path, not\n // import.meta.resolve: its mere presence silently breaks bytecode\n // generation for the compiled binary (CJS bundle), and require.resolve\n // fails on these ESM-only packages.\n const packageRoot = path.dirname(require.resolve(`${packageName}/package.json`));\n const entryRelativePath = workspaceRelativePath.split(\"/\").slice(1).join(\"/\");\n return path.join(packageRoot, entryRelativePath);\n };\n\n const piCodingAgentEntry = packageIndex;\n const piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@earendil-works/pi-agent-core\");\n const piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@earendil-works/pi-tui\");\n // The workspace path mirrors pi-ai 0.80.x's built dist layout. If an\n // upstream layout change moves these files, this join needs updating to\n // match the package's real dist paths.\n const piAiEntry = resolveWorkspaceOrImport(\"ai/dist/compat.js\", \"@earendil-works/pi-ai\");\n const piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@earendil-works/pi-ai\");\n\n _aliases = {\n \"@bastani/atomic\": piCodingAgentEntry,\n \"@earendil-works/pi-coding-agent\": piCodingAgentEntry,\n \"@earendil-works/pi-agent-core\": piAgentCoreEntry,\n \"@earendil-works/pi-tui\": piTuiEntry,\n \"@earendil-works/pi-ai\": piAiEntry,\n \"@earendil-works/pi-ai/compat\": piAiEntry,\n \"@earendil-works/pi-ai/oauth\": piAiOauthEntry,\n \"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n \"@mariozechner/pi-tui\": piTuiEntry,\n \"@mariozechner/pi-ai\": piAiEntry,\n \"@mariozechner/pi-ai/compat\": piAiEntry,\n \"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n typebox: typeboxEntry,\n \"typebox/compile\": typeboxCompileEntry,\n \"typebox/value\": typeboxValueEntry,\n \"@sinclair/typebox\": typeboxEntry,\n \"@sinclair/typebox/compile\": typeboxCompileEntry,\n \"@sinclair/typebox/value\": typeboxValueEntry,\n };\n\n return _aliases;\n}\n\n/** Internal hooks for extension-loader alias regression tests. */\nexport const extensionLoaderTestHooks = {\n loadVirtualModules,\n getAliases,\n};\n\n/**\n * Extension paths already evaluated via native import() in this process. Bun on\n * Windows ignores the cache-busting query on file URLs, so re-loads of these\n * paths (e.g. /reload) must go through jiti's transformed-import path to get a\n * fresh module evaluation.\n */\nconst nativelyImportedPaths = new Set<string>();\n\nexport async function loadExtensionModule(\n extensionPath: string,\n cacheToken?: ExtensionCacheToken,\n): Promise<ExtensionFactory | undefined> {\n if (isCurrentCacheToken(cacheToken)) {\n const cachedFactory = extensionCache.get(extensionPath);\n if (cachedFactory) return cachedFactory;\n }\n\n const isWindows = process.platform === \"win32\";\n // Single-file builds (compiled binary or dev bundle) cannot alias host\n // package specifiers to files on disk: extensions must share the live\n // module instances baked into the build, so virtualModules is used instead\n // (which requires jiti's transformed-import path).\n const isSingleFileBuild = isBunBinary || isBundledBuild;\n // Windows first-load fast path: native import() (jiti's default tryNative)\n // skips per-launch transpilation of the extension module graph. Re-loads of\n // the same path fall back to transformed imports for fresh evaluation.\n const forceTransformedImports = isSingleFileBuild || (isWindows && nativelyImportedPaths.has(extensionPath));\n const jiti = createJiti(import.meta.url, {\n moduleCache: false,\n ...(forceTransformedImports\n ? { fsCache: getTranspileCacheDir(), tryNative: false }\n : isWindows\n ? { fsCache: getTranspileCacheDir() }\n : {}),\n ...(isSingleFileBuild ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),\n });\n const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });\n if (isWindows && !forceTransformedImports) {\n nativelyImportedPaths.add(extensionPath);\n }\n const factory = module as ExtensionFactory;\n if (typeof factory !== \"function\") return undefined;\n if (isCurrentCacheToken(cacheToken)) {\n extensionCache.set(extensionPath, factory);\n }\n return factory;\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { createJiti } from "jiti/static";
|
|
6
|
-
import { isBunBinary } from "../../config.js";
|
|
6
|
+
import { getExtensionTranspileCacheDir, isBunBinary, isBundledBuild } from "../../config.js";
|
|
7
7
|
import { resolvePath } from "../../utils/paths.js";
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
let _virtualModules = null;
|
|
@@ -59,6 +59,30 @@ async function getVirtualModules() {
|
|
|
59
59
|
return _virtualModulesPromise;
|
|
60
60
|
}
|
|
61
61
|
let _aliases = null;
|
|
62
|
+
let _transpileCacheDir = null;
|
|
63
|
+
/**
|
|
64
|
+
* Persistent on-disk cache for jiti-transpiled extension modules.
|
|
65
|
+
* jiti keys cache entries by source-content hash, so entries self-invalidate
|
|
66
|
+
* when extension sources change; stale sibling version dirs are pruned
|
|
67
|
+
* in the background.
|
|
68
|
+
*/
|
|
69
|
+
function getTranspileCacheDir() {
|
|
70
|
+
if (_transpileCacheDir)
|
|
71
|
+
return _transpileCacheDir;
|
|
72
|
+
_transpileCacheDir = getExtensionTranspileCacheDir();
|
|
73
|
+
pruneStaleTranspileCaches(_transpileCacheDir);
|
|
74
|
+
return _transpileCacheDir;
|
|
75
|
+
}
|
|
76
|
+
function pruneStaleTranspileCaches(currentDir) {
|
|
77
|
+
const parent = path.dirname(currentDir);
|
|
78
|
+
const keep = path.basename(currentDir);
|
|
79
|
+
void fs.promises
|
|
80
|
+
.readdir(parent)
|
|
81
|
+
.then((entries) => Promise.all(entries
|
|
82
|
+
.filter((entry) => entry !== keep)
|
|
83
|
+
.map((entry) => fs.promises.rm(path.join(parent, entry), { recursive: true, force: true }))))
|
|
84
|
+
.catch(() => { });
|
|
85
|
+
}
|
|
62
86
|
let extensionCacheCwd;
|
|
63
87
|
let extensionCacheGeneration = 0;
|
|
64
88
|
const extensionCache = new Map();
|
|
@@ -99,21 +123,27 @@ function getAliases() {
|
|
|
99
123
|
const typeboxCompileEntry = require.resolve("typebox/compile");
|
|
100
124
|
const typeboxValueEntry = require.resolve("typebox/value");
|
|
101
125
|
const packagesRoot = path.resolve(__dirname, "../../../../");
|
|
102
|
-
const resolveWorkspaceOrImport = (workspaceRelativePath,
|
|
126
|
+
const resolveWorkspaceOrImport = (workspaceRelativePath, packageName) => {
|
|
103
127
|
const workspacePath = path.join(packagesRoot, workspaceRelativePath);
|
|
104
128
|
if (fs.existsSync(workspacePath)) {
|
|
105
129
|
return workspacePath;
|
|
106
130
|
}
|
|
107
|
-
|
|
131
|
+
// Resolve via the package.json export and join the built-entry path, not
|
|
132
|
+
// import.meta.resolve: its mere presence silently breaks bytecode
|
|
133
|
+
// generation for the compiled binary (CJS bundle), and require.resolve
|
|
134
|
+
// fails on these ESM-only packages.
|
|
135
|
+
const packageRoot = path.dirname(require.resolve(`${packageName}/package.json`));
|
|
136
|
+
const entryRelativePath = workspaceRelativePath.split("/").slice(1).join("/");
|
|
137
|
+
return path.join(packageRoot, entryRelativePath);
|
|
108
138
|
};
|
|
109
139
|
const piCodingAgentEntry = packageIndex;
|
|
110
140
|
const piAgentCoreEntry = resolveWorkspaceOrImport("agent/dist/index.js", "@earendil-works/pi-agent-core");
|
|
111
141
|
const piTuiEntry = resolveWorkspaceOrImport("tui/dist/index.js", "@earendil-works/pi-tui");
|
|
112
|
-
// The workspace path mirrors pi-ai 0.80.x's built
|
|
113
|
-
// upstream layout change moves
|
|
114
|
-
//
|
|
115
|
-
const piAiEntry = resolveWorkspaceOrImport("ai/dist/compat.js", "@earendil-works/pi-ai
|
|
116
|
-
const piAiOauthEntry = resolveWorkspaceOrImport("ai/dist/oauth.js", "@earendil-works/pi-ai
|
|
142
|
+
// The workspace path mirrors pi-ai 0.80.x's built dist layout. If an
|
|
143
|
+
// upstream layout change moves these files, this join needs updating to
|
|
144
|
+
// match the package's real dist paths.
|
|
145
|
+
const piAiEntry = resolveWorkspaceOrImport("ai/dist/compat.js", "@earendil-works/pi-ai");
|
|
146
|
+
const piAiOauthEntry = resolveWorkspaceOrImport("ai/dist/oauth.js", "@earendil-works/pi-ai");
|
|
117
147
|
_aliases = {
|
|
118
148
|
"@bastani/atomic": piCodingAgentEntry,
|
|
119
149
|
"@earendil-works/pi-coding-agent": piCodingAgentEntry,
|
|
@@ -141,19 +171,42 @@ export const extensionLoaderTestHooks = {
|
|
|
141
171
|
loadVirtualModules,
|
|
142
172
|
getAliases,
|
|
143
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* Extension paths already evaluated via native import() in this process. Bun on
|
|
176
|
+
* Windows ignores the cache-busting query on file URLs, so re-loads of these
|
|
177
|
+
* paths (e.g. /reload) must go through jiti's transformed-import path to get a
|
|
178
|
+
* fresh module evaluation.
|
|
179
|
+
*/
|
|
180
|
+
const nativelyImportedPaths = new Set();
|
|
144
181
|
export async function loadExtensionModule(extensionPath, cacheToken) {
|
|
145
182
|
if (isCurrentCacheToken(cacheToken)) {
|
|
146
183
|
const cachedFactory = extensionCache.get(extensionPath);
|
|
147
184
|
if (cachedFactory)
|
|
148
185
|
return cachedFactory;
|
|
149
186
|
}
|
|
150
|
-
const
|
|
187
|
+
const isWindows = process.platform === "win32";
|
|
188
|
+
// Single-file builds (compiled binary or dev bundle) cannot alias host
|
|
189
|
+
// package specifiers to files on disk: extensions must share the live
|
|
190
|
+
// module instances baked into the build, so virtualModules is used instead
|
|
191
|
+
// (which requires jiti's transformed-import path).
|
|
192
|
+
const isSingleFileBuild = isBunBinary || isBundledBuild;
|
|
193
|
+
// Windows first-load fast path: native import() (jiti's default tryNative)
|
|
194
|
+
// skips per-launch transpilation of the extension module graph. Re-loads of
|
|
195
|
+
// the same path fall back to transformed imports for fresh evaluation.
|
|
196
|
+
const forceTransformedImports = isSingleFileBuild || (isWindows && nativelyImportedPaths.has(extensionPath));
|
|
151
197
|
const jiti = createJiti(import.meta.url, {
|
|
152
198
|
moduleCache: false,
|
|
153
|
-
...(forceTransformedImports
|
|
154
|
-
|
|
199
|
+
...(forceTransformedImports
|
|
200
|
+
? { fsCache: getTranspileCacheDir(), tryNative: false }
|
|
201
|
+
: isWindows
|
|
202
|
+
? { fsCache: getTranspileCacheDir() }
|
|
203
|
+
: {}),
|
|
204
|
+
...(isSingleFileBuild ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),
|
|
155
205
|
});
|
|
156
206
|
const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });
|
|
207
|
+
if (isWindows && !forceTransformedImports) {
|
|
208
|
+
nativelyImportedPaths.add(extensionPath);
|
|
209
|
+
}
|
|
157
210
|
const factory = module;
|
|
158
211
|
if (typeof factory !== "function")
|
|
159
212
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-virtual-modules.js","sourceRoot":"","sources":["../../../src/core/extensions/loader-virtual-modules.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti/static\";\nimport { isBunBinary } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport type { ExtensionFactory } from \"./types.ts\";\n\nconst require = createRequire(import.meta.url);\nlet _virtualModules: Record<string, object> | null = null;\nlet _virtualModulesPromise: Promise<Record<string, object>> | null = null;\n\nasync function loadVirtualModules(): Promise<Record<string, object>> {\n const [typebox, typeboxCompile, typeboxValue, piAgentCore, piTui, piAi, piAiOauth, piCodingAgent] = await Promise.all([\n import(\"typebox\"),\n import(\"typebox/compile\"),\n import(\"typebox/value\"),\n import(\"@earendil-works/pi-agent-core\"),\n import(\"@earendil-works/pi-tui\"),\n // pi 0.80.2: the old global pi-ai API moved off the root entrypoint onto\n // `/compat` (a strict superset). Extensions still `import ... from\n // \"@earendil-works/pi-ai\"`, so we load the compat module here and key it\n // under the root specifier below to keep every extension working unchanged.\n import(\"@earendil-works/pi-ai/compat\"),\n import(\"@earendil-works/pi-ai/oauth\"),\n // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n // avoiding a circular dependency while preserving the package-name extension import path.\n import(\"../../index.ts\"),\n ]);\n\n return {\n typebox,\n \"typebox/compile\": typeboxCompile,\n \"typebox/value\": typeboxValue,\n \"@sinclair/typebox\": typebox,\n \"@sinclair/typebox/compile\": typeboxCompile,\n \"@sinclair/typebox/value\": typeboxValue,\n \"@earendil-works/pi-agent-core\": piAgentCore,\n \"@earendil-works/pi-tui\": piTui,\n \"@earendil-works/pi-ai\": piAi,\n \"@earendil-works/pi-ai/compat\": piAi,\n \"@earendil-works/pi-ai/oauth\": piAiOauth,\n \"@bastani/atomic\": piCodingAgent,\n \"@mariozechner/pi-agent-core\": piAgentCore,\n \"@mariozechner/pi-tui\": piTui,\n \"@mariozechner/pi-ai\": piAi,\n \"@mariozechner/pi-ai/compat\": piAi,\n \"@mariozechner/pi-ai/oauth\": piAiOauth,\n };\n}\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary). */\nasync function getVirtualModules(): Promise<Record<string, object>> {\n if (_virtualModules) return _virtualModules;\n _virtualModulesPromise ??= loadVirtualModules().then(\n (virtualModules) => {\n _virtualModules = virtualModules;\n return virtualModules;\n },\n (error: Error) => {\n _virtualModulesPromise = null;\n throw error;\n },\n );\n return _virtualModulesPromise;\n}\nlet _aliases: Record<string, string> | null = null;\n\nlet extensionCacheCwd: string | undefined;\nlet extensionCacheGeneration = 0;\nconst extensionCache = new Map<string, ExtensionFactory>();\n\nexport interface ExtensionCacheToken {\n cwd: string;\n generation: number;\n}\n\nexport function clearExtensionCache(): void {\n extensionCache.clear();\n extensionCacheCwd = undefined;\n extensionCacheGeneration++;\n}\n\nexport function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {\n const resolvedCwd = resolvePath(cwd);\n if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {\n clearExtensionCache();\n }\n extensionCacheCwd = resolvedCwd;\n return { cwd: resolvedCwd, generation: extensionCacheGeneration };\n}\n\nfunction isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {\n return (\n cacheToken !== undefined &&\n extensionCacheCwd === cacheToken.cwd &&\n extensionCacheGeneration === cacheToken.generation\n );\n}\n\nfunction extensionImportSpecifier(extensionPath: string, cacheToken: ExtensionCacheToken | undefined): string {\n const url = pathToFileURL(extensionPath);\n const cacheKey = cacheToken ? `${cacheToken.generation}:${cacheToken.cwd}` : `${Date.now()}:${Math.random()}`;\n url.searchParams.set(\"atomicExtensionCache\", cacheKey);\n return url.href;\n}\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nfunction getAliases(): Record<string, string> {\n if (_aliases) return _aliases;\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n const typeboxEntry = require.resolve(\"typebox\");\n const typeboxCompileEntry = require.resolve(\"typebox/compile\");\n const typeboxValueEntry = require.resolve(\"typebox/value\");\n\n const packagesRoot = path.resolve(__dirname, \"../../../../\");\n const resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {\n const workspacePath = path.join(packagesRoot, workspaceRelativePath);\n if (fs.existsSync(workspacePath)) {\n return workspacePath;\n }\n return fileURLToPath(import.meta.resolve(specifier));\n };\n\n const piCodingAgentEntry = packageIndex;\n const piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@earendil-works/pi-agent-core\");\n const piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@earendil-works/pi-tui\");\n // The workspace path mirrors pi-ai 0.80.x's built compat export. If an\n // upstream layout change moves that file, fall back to package export\n // resolution so installed Atomic builds still load the canonical entrypoint.\n const piAiEntry = resolveWorkspaceOrImport(\"ai/dist/compat.js\", \"@earendil-works/pi-ai/compat\");\n const piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@earendil-works/pi-ai/oauth\");\n\n _aliases = {\n \"@bastani/atomic\": piCodingAgentEntry,\n \"@earendil-works/pi-coding-agent\": piCodingAgentEntry,\n \"@earendil-works/pi-agent-core\": piAgentCoreEntry,\n \"@earendil-works/pi-tui\": piTuiEntry,\n \"@earendil-works/pi-ai\": piAiEntry,\n \"@earendil-works/pi-ai/compat\": piAiEntry,\n \"@earendil-works/pi-ai/oauth\": piAiOauthEntry,\n \"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n \"@mariozechner/pi-tui\": piTuiEntry,\n \"@mariozechner/pi-ai\": piAiEntry,\n \"@mariozechner/pi-ai/compat\": piAiEntry,\n \"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n typebox: typeboxEntry,\n \"typebox/compile\": typeboxCompileEntry,\n \"typebox/value\": typeboxValueEntry,\n \"@sinclair/typebox\": typeboxEntry,\n \"@sinclair/typebox/compile\": typeboxCompileEntry,\n \"@sinclair/typebox/value\": typeboxValueEntry,\n };\n\n return _aliases;\n}\n\n/** Internal hooks for extension-loader alias regression tests. */\nexport const extensionLoaderTestHooks = {\n loadVirtualModules,\n getAliases,\n};\n\nexport async function loadExtensionModule(\n extensionPath: string,\n cacheToken?: ExtensionCacheToken,\n): Promise<ExtensionFactory | undefined> {\n if (isCurrentCacheToken(cacheToken)) {\n const cachedFactory = extensionCache.get(extensionPath);\n if (cachedFactory) return cachedFactory;\n }\n\n const forceTransformedImports = isBunBinary || process.platform === \"win32\";\n const jiti = createJiti(import.meta.url, {\n moduleCache: false,\n ...(forceTransformedImports ? { fsCache: false, tryNative: false } : {}),\n ...(isBunBinary ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),\n });\n const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });\n const factory = module as ExtensionFactory;\n if (typeof factory !== \"function\") return undefined;\n if (isCurrentCacheToken(cacheToken)) {\n extensionCache.set(extensionPath, factory);\n }\n return factory;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loader-virtual-modules.js","sourceRoot":"","sources":["../../../src/core/extensions/loader-virtual-modules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,6BAA6B,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAC/C,IAAI,eAAe,GAAkC,IAAI,CAAC;AAC1D,IAAI,sBAAsB,GAA2C,IAAI,CAAC;AAE1E,KAAK,UAAU,kBAAkB;IAC/B,MAAM,CAAC,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpH,MAAM,CAAC,SAAS,CAAC;QACjB,MAAM,CAAC,iBAAiB,CAAC;QACzB,MAAM,CAAC,eAAe,CAAC;QACvB,MAAM,CAAC,+BAA+B,CAAC;QACvC,MAAM,CAAC,wBAAwB,CAAC;QAChC,yEAAyE;QACzE,mEAAmE;QACnE,yEAAyE;QACzE,4EAA4E;QAC5E,MAAM,CAAC,8BAA8B,CAAC;QACtC,MAAM,CAAC,6BAA6B,CAAC;QACrC,uFAAuF;QACvF,0FAA0F;QAC1F,MAAM,CAAC,gBAAgB,CAAC;KACzB,CAAC,CAAC;IAEH,OAAO;QACL,OAAO;QACP,iBAAiB,EAAE,cAAc;QACjC,eAAe,EAAE,YAAY;QAC7B,mBAAmB,EAAE,OAAO;QAC5B,2BAA2B,EAAE,cAAc;QAC3C,yBAAyB,EAAE,YAAY;QACvC,+BAA+B,EAAE,WAAW;QAC5C,wBAAwB,EAAE,KAAK;QAC/B,uBAAuB,EAAE,IAAI;QAC7B,8BAA8B,EAAE,IAAI;QACpC,6BAA6B,EAAE,SAAS;QACxC,iBAAiB,EAAE,aAAa;QAChC,6BAA6B,EAAE,WAAW;QAC1C,sBAAsB,EAAE,KAAK;QAC7B,qBAAqB,EAAE,IAAI;QAC3B,4BAA4B,EAAE,IAAI;QAClC,2BAA2B,EAAE,SAAS;KACvC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,KAAK,UAAU,iBAAiB;IAC9B,IAAI,eAAe;QAAE,OAAO,eAAe,CAAC;IAC5C,sBAAsB,KAAK,kBAAkB,EAAE,CAAC,IAAI,CAClD,CAAC,cAAc,EAAE,EAAE;QACjB,eAAe,GAAG,cAAc,CAAC;QACjC,OAAO,cAAc,CAAC;IACxB,CAAC,EACD,CAAC,KAAY,EAAE,EAAE;QACf,sBAAsB,GAAG,IAAI,CAAC;QAC9B,MAAM,KAAK,CAAC;IACd,CAAC,CACF,CAAC;IACF,OAAO,sBAAsB,CAAC;AAChC,CAAC;AACD,IAAI,QAAQ,GAAkC,IAAI,CAAC;AACnD,IAAI,kBAAkB,GAAkB,IAAI,CAAC;AAE7C;;;;;GAKG;AACH,SAAS,oBAAoB;IAC3B,IAAI,kBAAkB;QAAE,OAAO,kBAAkB,CAAC;IAClD,kBAAkB,GAAG,6BAA6B,EAAE,CAAC;IACrD,yBAAyB,CAAC,kBAAkB,CAAC,CAAC;IAC9C,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED,SAAS,yBAAyB,CAAC,UAAkB;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACvC,KAAK,EAAE,CAAC,QAAQ;SACb,OAAO,CAAC,MAAM,CAAC;SACf,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAChB,OAAO,CAAC,GAAG,CACT,OAAO;SACJ,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC;SACjC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAC9F,CACF;SACA,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,IAAI,iBAAqC,CAAC;AAC1C,IAAI,wBAAwB,GAAG,CAAC,CAAC;AACjC,MAAM,cAAc,GAAG,IAAI,GAAG,EAA4B,CAAC;AAO3D,MAAM,UAAU,mBAAmB;IACjC,cAAc,CAAC,KAAK,EAAE,CAAC;IACvB,iBAAiB,GAAG,SAAS,CAAC;IAC9B,wBAAwB,EAAE,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,iBAAiB,KAAK,SAAS,IAAI,iBAAiB,KAAK,WAAW,EAAE,CAAC;QACzE,mBAAmB,EAAE,CAAC;IACxB,CAAC;IACD,iBAAiB,GAAG,WAAW,CAAC;IAChC,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,UAAU,EAAE,wBAAwB,EAAE,CAAC;AACpE,CAAC;AAED,SAAS,mBAAmB,CAAC,UAA2C;IACtE,OAAO,CACL,UAAU,KAAK,SAAS;QACxB,iBAAiB,KAAK,UAAU,CAAC,GAAG;QACpC,wBAAwB,KAAK,UAAU,CAAC,UAAU,CACnD,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,aAAqB,EAAE,UAA2C;IAClG,MAAM,GAAG,GAAG,aAAa,CAAC,aAAa,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,UAAU,IAAI,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;IAC9G,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,sBAAsB,EAAE,QAAQ,CAAC,CAAC;IACvD,OAAO,GAAG,CAAC,IAAI,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU;IACjB,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAE9B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;IAElE,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC/D,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE3D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;IAC7D,MAAM,wBAAwB,GAAG,CAAC,qBAA6B,EAAE,WAAmB,EAAU,EAAE;QAC9F,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;QACrE,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;YACjC,OAAO,aAAa,CAAC;QACvB,CAAC;QACD,yEAAyE;QACzE,kEAAkE;QAClE,uEAAuE;QACvE,oCAAoC;QACpC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,WAAW,eAAe,CAAC,CAAC,CAAC;QACjF,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9E,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,CAAC,CAAC;IACnD,CAAC,CAAC;IAEF,MAAM,kBAAkB,GAAG,YAAY,CAAC;IACxC,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,qBAAqB,EAAE,+BAA+B,CAAC,CAAC;IAC1G,MAAM,UAAU,GAAG,wBAAwB,CAAC,mBAAmB,EAAE,wBAAwB,CAAC,CAAC;IAC3F,qEAAqE;IACrE,wEAAwE;IACxE,uCAAuC;IACvC,MAAM,SAAS,GAAG,wBAAwB,CAAC,mBAAmB,EAAE,uBAAuB,CAAC,CAAC;IACzF,MAAM,cAAc,GAAG,wBAAwB,CAAC,kBAAkB,EAAE,uBAAuB,CAAC,CAAC;IAE7F,QAAQ,GAAG;QACT,iBAAiB,EAAE,kBAAkB;QACrC,iCAAiC,EAAE,kBAAkB;QACrD,+BAA+B,EAAE,gBAAgB;QACjD,wBAAwB,EAAE,UAAU;QACpC,uBAAuB,EAAE,SAAS;QAClC,8BAA8B,EAAE,SAAS;QACzC,6BAA6B,EAAE,cAAc;QAC7C,6BAA6B,EAAE,gBAAgB;QAC/C,sBAAsB,EAAE,UAAU;QAClC,qBAAqB,EAAE,SAAS;QAChC,4BAA4B,EAAE,SAAS;QACvC,2BAA2B,EAAE,cAAc;QAC3C,OAAO,EAAE,YAAY;QACrB,iBAAiB,EAAE,mBAAmB;QACtC,eAAe,EAAE,iBAAiB;QAClC,mBAAmB,EAAE,YAAY;QACjC,2BAA2B,EAAE,mBAAmB;QAChD,yBAAyB,EAAE,iBAAiB;KAC7C,CAAC;IAEF,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,kBAAkB;IAClB,UAAU;CACX,CAAC;AAEF;;;;;GAKG;AACH,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAAU,CAAC;AAEhD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,aAAqB,EACrB,UAAgC;IAEhC,IAAI,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,MAAM,aAAa,GAAG,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxD,IAAI,aAAa;YAAE,OAAO,aAAa,CAAC;IAC1C,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC;IAC/C,uEAAuE;IACvE,sEAAsE;IACtE,2EAA2E;IAC3E,mDAAmD;IACnD,MAAM,iBAAiB,GAAG,WAAW,IAAI,cAAc,CAAC;IACxD,2EAA2E;IAC3E,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM,uBAAuB,GAAG,iBAAiB,IAAI,CAAC,SAAS,IAAI,qBAAqB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;IAC7G,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE;QACvC,WAAW,EAAE,KAAK;QAClB,GAAG,CAAC,uBAAuB;YACzB,CAAC,CAAC,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;YACvD,CAAC,CAAC,SAAS;gBACT,CAAC,CAAC,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE;gBACrC,CAAC,CAAC,EAAE,CAAC;QACT,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,iBAAiB,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;KACjG,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,wBAAwB,CAAC,aAAa,EAAE,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IACzG,IAAI,SAAS,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC1C,qBAAqB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3C,CAAC;IACD,MAAM,OAAO,GAAG,MAA0B,CAAC;IAC3C,IAAI,OAAO,OAAO,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IACpD,IAAI,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,cAAc,CAAC,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti/static\";\nimport { getExtensionTranspileCacheDir, isBunBinary, isBundledBuild } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport type { ExtensionFactory } from \"./types.ts\";\n\nconst require = createRequire(import.meta.url);\nlet _virtualModules: Record<string, object> | null = null;\nlet _virtualModulesPromise: Promise<Record<string, object>> | null = null;\n\nasync function loadVirtualModules(): Promise<Record<string, object>> {\n const [typebox, typeboxCompile, typeboxValue, piAgentCore, piTui, piAi, piAiOauth, piCodingAgent] = await Promise.all([\n import(\"typebox\"),\n import(\"typebox/compile\"),\n import(\"typebox/value\"),\n import(\"@earendil-works/pi-agent-core\"),\n import(\"@earendil-works/pi-tui\"),\n // pi 0.80.2: the old global pi-ai API moved off the root entrypoint onto\n // `/compat` (a strict superset). Extensions still `import ... from\n // \"@earendil-works/pi-ai\"`, so we load the compat module here and key it\n // under the root specifier below to keep every extension working unchanged.\n import(\"@earendil-works/pi-ai/compat\"),\n import(\"@earendil-works/pi-ai/oauth\"),\n // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n // avoiding a circular dependency while preserving the package-name extension import path.\n import(\"../../index.ts\"),\n ]);\n\n return {\n typebox,\n \"typebox/compile\": typeboxCompile,\n \"typebox/value\": typeboxValue,\n \"@sinclair/typebox\": typebox,\n \"@sinclair/typebox/compile\": typeboxCompile,\n \"@sinclair/typebox/value\": typeboxValue,\n \"@earendil-works/pi-agent-core\": piAgentCore,\n \"@earendil-works/pi-tui\": piTui,\n \"@earendil-works/pi-ai\": piAi,\n \"@earendil-works/pi-ai/compat\": piAi,\n \"@earendil-works/pi-ai/oauth\": piAiOauth,\n \"@bastani/atomic\": piCodingAgent,\n \"@mariozechner/pi-agent-core\": piAgentCore,\n \"@mariozechner/pi-tui\": piTui,\n \"@mariozechner/pi-ai\": piAi,\n \"@mariozechner/pi-ai/compat\": piAi,\n \"@mariozechner/pi-ai/oauth\": piAiOauth,\n };\n}\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary). */\nasync function getVirtualModules(): Promise<Record<string, object>> {\n if (_virtualModules) return _virtualModules;\n _virtualModulesPromise ??= loadVirtualModules().then(\n (virtualModules) => {\n _virtualModules = virtualModules;\n return virtualModules;\n },\n (error: Error) => {\n _virtualModulesPromise = null;\n throw error;\n },\n );\n return _virtualModulesPromise;\n}\nlet _aliases: Record<string, string> | null = null;\nlet _transpileCacheDir: string | null = null;\n\n/**\n * Persistent on-disk cache for jiti-transpiled extension modules.\n * jiti keys cache entries by source-content hash, so entries self-invalidate\n * when extension sources change; stale sibling version dirs are pruned\n * in the background.\n */\nfunction getTranspileCacheDir(): string {\n if (_transpileCacheDir) return _transpileCacheDir;\n _transpileCacheDir = getExtensionTranspileCacheDir();\n pruneStaleTranspileCaches(_transpileCacheDir);\n return _transpileCacheDir;\n}\n\nfunction pruneStaleTranspileCaches(currentDir: string): void {\n const parent = path.dirname(currentDir);\n const keep = path.basename(currentDir);\n void fs.promises\n .readdir(parent)\n .then((entries) =>\n Promise.all(\n entries\n .filter((entry) => entry !== keep)\n .map((entry) => fs.promises.rm(path.join(parent, entry), { recursive: true, force: true })),\n ),\n )\n .catch(() => {});\n}\n\nlet extensionCacheCwd: string | undefined;\nlet extensionCacheGeneration = 0;\nconst extensionCache = new Map<string, ExtensionFactory>();\n\nexport interface ExtensionCacheToken {\n cwd: string;\n generation: number;\n}\n\nexport function clearExtensionCache(): void {\n extensionCache.clear();\n extensionCacheCwd = undefined;\n extensionCacheGeneration++;\n}\n\nexport function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {\n const resolvedCwd = resolvePath(cwd);\n if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {\n clearExtensionCache();\n }\n extensionCacheCwd = resolvedCwd;\n return { cwd: resolvedCwd, generation: extensionCacheGeneration };\n}\n\nfunction isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {\n return (\n cacheToken !== undefined &&\n extensionCacheCwd === cacheToken.cwd &&\n extensionCacheGeneration === cacheToken.generation\n );\n}\n\nfunction extensionImportSpecifier(extensionPath: string, cacheToken: ExtensionCacheToken | undefined): string {\n const url = pathToFileURL(extensionPath);\n const cacheKey = cacheToken ? `${cacheToken.generation}:${cacheToken.cwd}` : `${Date.now()}:${Math.random()}`;\n url.searchParams.set(\"atomicExtensionCache\", cacheKey);\n return url.href;\n}\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nfunction getAliases(): Record<string, string> {\n if (_aliases) return _aliases;\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n const typeboxEntry = require.resolve(\"typebox\");\n const typeboxCompileEntry = require.resolve(\"typebox/compile\");\n const typeboxValueEntry = require.resolve(\"typebox/value\");\n\n const packagesRoot = path.resolve(__dirname, \"../../../../\");\n const resolveWorkspaceOrImport = (workspaceRelativePath: string, packageName: string): string => {\n const workspacePath = path.join(packagesRoot, workspaceRelativePath);\n if (fs.existsSync(workspacePath)) {\n return workspacePath;\n }\n // Resolve via the package.json export and join the built-entry path, not\n // import.meta.resolve: its mere presence silently breaks bytecode\n // generation for the compiled binary (CJS bundle), and require.resolve\n // fails on these ESM-only packages.\n const packageRoot = path.dirname(require.resolve(`${packageName}/package.json`));\n const entryRelativePath = workspaceRelativePath.split(\"/\").slice(1).join(\"/\");\n return path.join(packageRoot, entryRelativePath);\n };\n\n const piCodingAgentEntry = packageIndex;\n const piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@earendil-works/pi-agent-core\");\n const piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@earendil-works/pi-tui\");\n // The workspace path mirrors pi-ai 0.80.x's built dist layout. If an\n // upstream layout change moves these files, this join needs updating to\n // match the package's real dist paths.\n const piAiEntry = resolveWorkspaceOrImport(\"ai/dist/compat.js\", \"@earendil-works/pi-ai\");\n const piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@earendil-works/pi-ai\");\n\n _aliases = {\n \"@bastani/atomic\": piCodingAgentEntry,\n \"@earendil-works/pi-coding-agent\": piCodingAgentEntry,\n \"@earendil-works/pi-agent-core\": piAgentCoreEntry,\n \"@earendil-works/pi-tui\": piTuiEntry,\n \"@earendil-works/pi-ai\": piAiEntry,\n \"@earendil-works/pi-ai/compat\": piAiEntry,\n \"@earendil-works/pi-ai/oauth\": piAiOauthEntry,\n \"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n \"@mariozechner/pi-tui\": piTuiEntry,\n \"@mariozechner/pi-ai\": piAiEntry,\n \"@mariozechner/pi-ai/compat\": piAiEntry,\n \"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n typebox: typeboxEntry,\n \"typebox/compile\": typeboxCompileEntry,\n \"typebox/value\": typeboxValueEntry,\n \"@sinclair/typebox\": typeboxEntry,\n \"@sinclair/typebox/compile\": typeboxCompileEntry,\n \"@sinclair/typebox/value\": typeboxValueEntry,\n };\n\n return _aliases;\n}\n\n/** Internal hooks for extension-loader alias regression tests. */\nexport const extensionLoaderTestHooks = {\n loadVirtualModules,\n getAliases,\n};\n\n/**\n * Extension paths already evaluated via native import() in this process. Bun on\n * Windows ignores the cache-busting query on file URLs, so re-loads of these\n * paths (e.g. /reload) must go through jiti's transformed-import path to get a\n * fresh module evaluation.\n */\nconst nativelyImportedPaths = new Set<string>();\n\nexport async function loadExtensionModule(\n extensionPath: string,\n cacheToken?: ExtensionCacheToken,\n): Promise<ExtensionFactory | undefined> {\n if (isCurrentCacheToken(cacheToken)) {\n const cachedFactory = extensionCache.get(extensionPath);\n if (cachedFactory) return cachedFactory;\n }\n\n const isWindows = process.platform === \"win32\";\n // Single-file builds (compiled binary or dev bundle) cannot alias host\n // package specifiers to files on disk: extensions must share the live\n // module instances baked into the build, so virtualModules is used instead\n // (which requires jiti's transformed-import path).\n const isSingleFileBuild = isBunBinary || isBundledBuild;\n // Windows first-load fast path: native import() (jiti's default tryNative)\n // skips per-launch transpilation of the extension module graph. Re-loads of\n // the same path fall back to transformed imports for fresh evaluation.\n const forceTransformedImports = isSingleFileBuild || (isWindows && nativelyImportedPaths.has(extensionPath));\n const jiti = createJiti(import.meta.url, {\n moduleCache: false,\n ...(forceTransformedImports\n ? { fsCache: getTranspileCacheDir(), tryNative: false }\n : isWindows\n ? { fsCache: getTranspileCacheDir() }\n : {}),\n ...(isSingleFileBuild ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),\n });\n const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });\n if (isWindows && !forceTransformedImports) {\n nativelyImportedPaths.add(extensionPath);\n }\n const factory = module as ExtensionFactory;\n if (typeof factory !== \"function\") return undefined;\n if (isCurrentCacheToken(cacheToken)) {\n extensionCache.set(extensionPath, factory);\n }\n return factory;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-registry-builtins.d.ts","sourceRoot":"","sources":["../../src/core/model-registry-builtins.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,GAAG,EAIR,KAAK,KAAK,EAEV,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"model-registry-builtins.d.ts","sourceRoot":"","sources":["../../src/core/model-registry-builtins.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,GAAG,EAIR,KAAK,KAAK,EAEV,MAAM,8BAA8B,CAAC;AAKtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAkB,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAmBlF,wBAAgB,iCAAiC,CAChD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GACzC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAKpC;AAmCD,wBAAgB,WAAW,CAC1B,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,EAChC,cAAc,EAAE,aAAa,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,GAC5D,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,SAAS,CAiClC;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,aAAa,GAAG,KAAK,CAAC,GAAG,CAAC,CAgCzF;AAED,wBAAgB,iBAAiB,CAChC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACxC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,GACrD,KAAK,CAAC,GAAG,CAAC,EAAE,CAsBd;AAED,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAWvG","sourcesContent":["import {\n\ttype Api,\n\tgetModels,\n\tgetProviders,\n\ttype KnownProvider,\n\ttype Model,\n\ttype OpenAICompletionsCompat,\n} from \"@earendil-works/pi-ai/compat\";\nimport { normalizeContextWindowOptions, withContextWindowOptions } from \"./context-window.ts\";\nimport { copilotApiBaseUrlFromToken, copilotTokenFromEnvironment, DEFAULT_COPILOT_API_BASE_URL, getActiveCopilotModelCatalog } from \"./copilot-model-catalog.ts\";\nimport { copilotTemplateFromModels, copilotThinkingLevelMapFor, synthesizeCopilotCatalogModels } from \"./copilot-model-synthesis.ts\";\nimport { getStaticCopilotModelFallback } from \"./copilot-model-static-fallbacks.ts\";\nimport type { ModelOverride } from \"./model-registry-schemas.ts\";\nimport type { ProviderCompat, ProviderOverride } from \"./model-registry-types.ts\";\n\nconst GITHUB_COPILOT_API_VERSION_HEADER = \"X-GitHub-Api-Version\";\nconst GITHUB_COPILOT_API_VERSION = \"2026-06-01\";\n\nfunction withDynamicGitHubCopilotModels(provider: string, models: Model<Api>[]): Model<Api>[] {\n\tif (provider !== \"github-copilot\") return models;\n\tconst existingIds = new Set(models.map((model) => model.id));\n\tconst template = copilotTemplateFromModels(models);\n\tconst dynamicModels = synthesizeCopilotCatalogModels(getActiveCopilotModelCatalog(), existingIds, template);\n\treturn dynamicModels.length === 0 ? models : [...models, ...dynamicModels];\n}\n\nfunction hasHeader(headers: Record<string, string> | undefined, headerName: string): boolean {\n\tif (!headers) return false;\n\tconst normalizedHeaderName = headerName.toLowerCase();\n\treturn Object.keys(headers).some((key) => key.toLowerCase() === normalizedHeaderName);\n}\n\nexport function withGitHubCopilotApiVersionHeader(\n\tmodel: Model<Api>,\n\theaders: Record<string, string> | undefined,\n): Record<string, string> | undefined {\n\tif (model.provider !== \"github-copilot\" || hasHeader(headers, GITHUB_COPILOT_API_VERSION_HEADER)) {\n\t\treturn headers;\n\t}\n\treturn { ...(headers ?? {}), [GITHUB_COPILOT_API_VERSION_HEADER]: GITHUB_COPILOT_API_VERSION };\n}\n\nfunction withCopilotEnvironmentBaseUrl(model: Model<Api>): Model<Api> {\n\tif (model.provider !== \"github-copilot\") return model;\n\tconst resolvedBaseUrl = copilotApiBaseUrlFromToken(copilotTokenFromEnvironment());\n\tif (resolvedBaseUrl === DEFAULT_COPILOT_API_BASE_URL || resolvedBaseUrl === model.baseUrl) return model;\n\treturn { ...model, baseUrl: resolvedBaseUrl };\n}\n\nfunction withCopilotThinkingLevelMap(model: Model<Api>): Model<Api> {\n\tif (model.provider !== \"github-copilot\") return model;\n\tconst context = getActiveCopilotModelCatalog().get(model.id);\n\tif (!context?.supports?.reasoningEffortLevels) return model;\n\tconst thinkingLevelMap = copilotThinkingLevelMapFor(context, model.api);\n\treturn thinkingLevelMap ? { ...model, thinkingLevelMap } : model;\n}\n\nfunction withCopilotContextWindowOptions(model: Model<Api>): Model<Api> {\n\tif (model.provider !== \"github-copilot\") return model;\n\t// The live CAPI catalog (or its disk cache) is authoritative. Without an\n\t// entry (cold start, fetch failure, offline), fall back to the bundled\n\t// static CAPI snapshot: several bundled pi-ai definitions overstate the\n\t// enforced default-tier window or understate output caps, which lets\n\t// sessions sail past the server-side limits before compaction fires\n\t// (issue #1608). Fallback entries carry the same tier structure as catalog\n\t// entries so long-context (1M-class) tiers stay selectable offline.\n\tconst context = getActiveCopilotModelCatalog().get(model.id) ?? getStaticCopilotModelFallback(model.id);\n\tif (!context) return model;\n\tconst base = { ...model, contextWindow: context.contextWindow, maxInputTokens: context.maxInputTokens, maxTokens: context.maxTokens ?? model.maxTokens };\n\tif (context.contextWindowOptions && context.contextWindowOptions.length > 1) {\n\t\treturn withContextWindowOptions(base, context.contextWindowOptions);\n\t}\n\treturn base;\n}\n\nexport function mergeCompat(\n\tbaseCompat: Model<Api>[\"compat\"],\n\toverrideCompat: ModelOverride[\"compat\"] | Model<Api>[\"compat\"],\n): Model<Api>[\"compat\"] | undefined {\n\tif (!overrideCompat) return baseCompat;\n\n\tconst base = baseCompat as ProviderCompat | undefined;\n\tconst override = overrideCompat as ProviderCompat;\n\tconst merged = { ...base, ...override } as ProviderCompat;\n\n\tconst baseCompletions = base as OpenAICompletionsCompat | undefined;\n\tconst overrideCompletions = override as OpenAICompletionsCompat;\n\tconst mergedCompletions = merged as OpenAICompletionsCompat;\n\n\tif (baseCompletions?.openRouterRouting || overrideCompletions.openRouterRouting) {\n\t\tmergedCompletions.openRouterRouting = {\n\t\t\t...baseCompletions?.openRouterRouting,\n\t\t\t...overrideCompletions.openRouterRouting,\n\t\t};\n\t}\n\n\tif (baseCompletions?.vercelGatewayRouting || overrideCompletions.vercelGatewayRouting) {\n\t\tmergedCompletions.vercelGatewayRouting = {\n\t\t\t...baseCompletions?.vercelGatewayRouting,\n\t\t\t...overrideCompletions.vercelGatewayRouting,\n\t\t};\n\t}\n\n\tif (baseCompletions?.chatTemplateKwargs || overrideCompletions.chatTemplateKwargs) {\n\t\tmergedCompletions.chatTemplateKwargs = {\n\t\t\t...baseCompletions?.chatTemplateKwargs,\n\t\t\t...overrideCompletions.chatTemplateKwargs,\n\t\t};\n\t}\n\n\treturn merged as Model<Api>[\"compat\"];\n}\n\nexport function applyModelOverride(model: Model<Api>, override: ModelOverride): Model<Api> {\n\tconst result = { ...model };\n\n\tif (override.name !== undefined) result.name = override.name;\n\tif (override.reasoning !== undefined) result.reasoning = override.reasoning;\n\tif (override.thinkingLevelMap !== undefined) {\n\t\tresult.thinkingLevelMap = { ...model.thinkingLevelMap, ...override.thinkingLevelMap };\n\t}\n\tif (override.input !== undefined) result.input = override.input as (\"text\" | \"image\")[];\n\tif (override.contextWindow !== undefined) {\n\t\tresult.contextWindow = override.contextWindow;\n\t\tresult.defaultContextWindow = override.contextWindow;\n\t\tif (override.contextWindowOptions === undefined) {\n\t\t\tresult.contextWindowOptions = undefined;\n\t\t}\n\t}\n\tif (override.contextWindowOptions !== undefined) {\n\t\tresult.contextWindowOptions = normalizeContextWindowOptions(override.contextWindowOptions);\n\t}\n\tif (override.maxTokens !== undefined) result.maxTokens = override.maxTokens;\n\n\tif (override.cost) {\n\t\tresult.cost = {\n\t\t\tinput: override.cost.input ?? model.cost.input,\n\t\t\toutput: override.cost.output ?? model.cost.output,\n\t\t\tcacheRead: override.cost.cacheRead ?? model.cost.cacheRead,\n\t\t\tcacheWrite: override.cost.cacheWrite ?? model.cost.cacheWrite,\n\t\t};\n\t}\n\n\tresult.compat = mergeCompat(model.compat, override.compat);\n\treturn result;\n}\n\nexport function loadBuiltInModels(\n\toverrides: Map<string, ProviderOverride>,\n\tmodelOverrides: Map<string, Map<string, ModelOverride>>,\n): Model<Api>[] {\n\treturn getProviders().flatMap((provider) => {\n\t\tconst models = withDynamicGitHubCopilotModels(provider, getModels(provider as KnownProvider) as Model<Api>[]);\n\t\tconst providerOverride = overrides.get(provider);\n\t\tconst perModelOverrides = modelOverrides.get(provider);\n\n\t\treturn models.map((m) => {\n\t\t\tlet model = withCopilotEnvironmentBaseUrl(m);\n\n\t\t\tif (providerOverride) {\n\t\t\t\tmodel = {\n\t\t\t\t\t...model,\n\t\t\t\t\tbaseUrl: providerOverride.baseUrl ?? model.baseUrl,\n\t\t\t\t\tcompat: mergeCompat(model.compat, providerOverride.compat),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tmodel = withCopilotThinkingLevelMap(withCopilotContextWindowOptions(model));\n\t\t\tconst modelOverride = perModelOverrides?.get(m.id);\n\t\t\treturn modelOverride ? applyModelOverride(model, modelOverride) : model;\n\t\t});\n\t});\n}\n\nexport function mergeCustomModels(builtInModels: Model<Api>[], customModels: Model<Api>[]): Model<Api>[] {\n\tconst merged = [...builtInModels];\n\tfor (const customModel of customModels) {\n\t\tconst existingIndex = merged.findIndex((m) => m.provider === customModel.provider && m.id === customModel.id);\n\t\tif (existingIndex >= 0) {\n\t\t\tmerged[existingIndex] = customModel;\n\t\t} else {\n\t\t\tmerged.push(customModel);\n\t\t}\n\t}\n\treturn merged;\n}\n"]}
|