@bastani/atomic 0.9.4-alpha.4 → 0.9.4-alpha.5
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 +7 -0
- package/dist/builtin/cursor/CHANGELOG.md +6 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +6 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +8 -1
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +1 -0
- 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-prompt.d.ts +2 -0
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +6 -0
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +4 -0
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/copilot-model-catalog.d.ts +18 -5
- package/dist/core/copilot-model-catalog.d.ts.map +1 -1
- package/dist/core/copilot-model-catalog.js +64 -5
- package/dist/core/copilot-model-catalog.js.map +1 -1
- package/dist/core/model-registry-builtins.d.ts.map +1 -1
- package/dist/core/model-registry-builtins.js +10 -2
- package/dist/core/model-registry-builtins.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -3
- package/dist/core/model-registry.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +8 -0
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +5 -0
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-model-routing.js +1 -4
- package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
- package/docs/compaction.md +2 -0
- package/docs/providers.md +1 -0
- package/npm-shrinkwrap.json +23 -23
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-prompt.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-prompt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAK9E,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,wBAAsB,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAwKrG;AAGD,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhH;AAGD,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED;;GAEG;AAEH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgCvG;AAED;;GAEG;AAEH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBpG;AAED;;;;GAIG;AAEH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwB5E;AAED;;;;;;;GAOG;AAEH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpG;AAED;;;;;;GAMG;AAEH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvG;AAED;;GAEG;AAEH,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,EACvD,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,EAChD,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;CAUrC,CAAC","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport { ATOMIC_GUIDE_COMMAND_NAME, ATOMIC_GUIDE_HELP_CHOICES, atomicGuideModeForChoice, getAtomicGuideMessage, isAtomicGuideHelpChoice, normalizeAtomicGuideMode } from \"./atomic-guide-command.ts\";\nimport { formatAuthStorageLoadFailedMessage, formatNoApiKeyFoundMessage, formatNoModelSelectedMessage, formatUnresolvedModelMessage } from \"./auth-guidance.ts\";\nimport { expandPromptTemplate } from \"./prompt-templates.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { PromptOptions } from \"./agent-session-types.ts\";\n\nexport async function prompt(this: AgentSession, text: string, options?: PromptOptions): Promise<void> {\n\tconst expandPromptTemplates = options?.expandPromptTemplates ?? true;\n\tconst preflightResult = options?.preflightResult;\n\tlet messages: AgentMessage[] | undefined;\n\n\ttry {\n\t\t// Handle slash commands first (execute immediately, even during streaming).\n\t\t// Builtin and extension commands manage their own LLM interaction via custom messages.\n\t\tif (expandPromptTemplates && text.startsWith(\"/\")) {\n\t\t\tconst handledBuiltin = await this._tryExecuteBuiltinSlashCommand(text);\n\t\t\tif (handledBuiltin) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handledExtension = await this._tryExecuteExtensionCommand(text);\n\t\t\tif (handledExtension) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Emit input event for extension interception (before skill/template expansion)\n\t\tlet currentText = text;\n\t\tlet currentImages = options?.images;\n\t\tif (this._extensionRunner.hasHandlers(\"input\")) {\n\t\t\tconst inputResult = await this._extensionRunner.emitInput(\n\t\t\t\tcurrentText,\n\t\t\t\tcurrentImages,\n\t\t\t\toptions?.source ?? \"interactive\",\n\t\t\t\tthis.isStreaming ? options?.streamingBehavior : undefined,\n\t\t\t);\n\t\t\tif (inputResult.action === \"handled\") {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (inputResult.action === \"transform\") {\n\t\t\t\tcurrentText = inputResult.text;\n\t\t\t\tcurrentImages = inputResult.images ?? currentImages;\n\t\t\t}\n\t\t}\n\n\t\t// Expand skill commands (/skill:name args) and prompt templates (/template args)\n\t\tlet expandedText = currentText;\n\t\tif (expandPromptTemplates) {\n\t\t\texpandedText = this._expandSkillCommand(expandedText);\n\t\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\t\t}\n\n\t\t// If streaming, queue via steer() or followUp() based on option\n\t\tif (this.isStreaming) {\n\t\t\tif (!options?.streamingBehavior) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (options.streamingBehavior === \"followUp\") {\n\t\t\t\tawait this._queueFollowUp(expandedText, currentImages);\n\t\t\t} else {\n\t\t\t\tawait this._queueSteer(expandedText, currentImages);\n\t\t\t}\n\t\t\tpreflightResult?.(true);\n\t\t\treturn;\n\t\t}\n\n\t\t// Flush any pending bash messages before the new prompt\n\t\tthis._flushPendingBashMessages();\n\n\t\t// Validate model\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Defensive guard: a model that never resolved to a real provider\n\t\t// (for example an unknown/unresolved model id that reached this path\n\t\t// as a bare string) has no `provider`, which would otherwise fail deep\n\t\t// in auth resolution as the confusing \"No API key found for undefined\".\n\t\t// Surface a clear, accurate \"unknown model\" error instead.\n\t\tconst resolvedProvider = (this.model as { provider?: unknown }).provider;\n\t\tif (typeof resolvedProvider !== \"string\" || resolvedProvider.length === 0) {\n\t\t\tthrow new Error(formatUnresolvedModelMessage(this.model));\n\t\t}\n\n\t\tif (!this._modelRegistry.hasConfiguredAuth(this.model)) {\n\t\t\t// A failed credential-store load (for example auth.json briefly locked\n\t\t\t// by a concurrent process, or invalid JSON) leaves an empty in-memory\n\t\t\t// credential set. That would otherwise be misreported here as\n\t\t\t// \"No API key found\" even though the credentials exist on disk. Surface\n\t\t\t// the real load failure instead so configured providers are not falsely\n\t\t\t// reported as unauthenticated (issue #1431).\n\t\t\tconst authLoadError = this._modelRegistry.authStorage.getLoadError();\n\t\t\tif (authLoadError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tformatAuthStorageLoadFailedMessage(this.model.provider, authLoadError),\n\t\t\t\t\t{ cause: authLoadError },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst isOAuth = this._modelRegistry.isUsingOAuth(this.model);\n\t\t\tif (isOAuth) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Authentication failed for \"${this.model.provider}\". ` +\n\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t`Run '/login ${this.model.provider}' to re-authenticate.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(formatNoApiKeyFoundMessage(this.model.provider));\n\t\t}\n\n\t\t// Check if we need to compact before sending (catches aborted responses)\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (lastAssistant) {\n\t\t\tawait this._checkCompaction(lastAssistant, false);\n\t\t}\n\n\t\t// Build messages array (custom message if any, then user message)\n\t\tmessages = [];\n\n\t\t// Add user message\n\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: expandedText }];\n\t\tif (currentImages) {\n\t\t\tuserContent.push(...currentImages);\n\t\t}\n\t\tmessages.push({\n\t\t\trole: \"user\",\n\t\t\tcontent: userContent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\t// Inject any pending \"nextTurn\" messages as context alongside the user message\n\t\tfor (const msg of this._pendingNextTurnMessages) {\n\t\t\tmessages.push(msg);\n\t\t}\n\t\tthis._pendingNextTurnMessages = [];\n\n\t\t// Emit before_agent_start extension event\n\t\tconst result = await this._extensionRunner.emitBeforeAgentStart(\n\t\t\texpandedText,\n\t\t\tcurrentImages,\n\t\t\tthis._baseSystemPrompt,\n\t\t\tthis._baseSystemPromptOptions,\n\t\t);\n\t\t// Add all custom messages from extensions\n\t\tif (result?.messages) {\n\t\t\tfor (const msg of result.messages) {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t// Apply extension-modified system prompt, or reset to base\n\t\tif (result?.systemPrompt) {\n\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t} else {\n\t\t\t// Ensure we're using the base prompt (in case previous turn had modifications)\n\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t}\n\t} catch (error) {\n\t\tpreflightResult?.(false);\n\t\tthrow error;\n\t}\n\n\tpreflightResult?.(true);\n\tawait this._runAgentPrompt(messages);\n}\n\n\nexport async function _runAgentPrompt(this: AgentSession, messages: AgentMessage | AgentMessage[]): Promise<void> {\n\tawait this.agent.prompt(messages);\n\tawait this.waitForRetry();\n\tawait this._continueQueuedAgentMessages();\n}\n\n\nexport async function _continueQueuedAgentMessages(this: AgentSession): Promise<void> {\n\tawait this._agentEventQueue;\n\n\twhile (this.agent.hasQueuedMessages()) {\n\t\tawait this.agent.continue();\n\t\tawait this.waitForRetry();\n\t\tawait this._agentEventQueue;\n\t}\n}\n\n/**\n * Try to execute a built-in slash command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteBuiltinSlashCommand(this: AgentSession, text: string): Promise<boolean> {\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tif (commandName !== ATOMIC_GUIDE_COMMAND_NAME) return false;\n\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\tconst mode = normalizeAtomicGuideMode(args);\n\tif (mode === \"help\" && this._extensionUIContext) {\n\t\tconst choice = await this._extensionUIContext.select(\"Atomic. Select where to start:\", [\n\t\t\t...ATOMIC_GUIDE_HELP_CHOICES,\n\t\t]);\n\t\tif (!choice || !isAtomicGuideHelpChoice(choice)) return true;\n\t\tawait this.sendCustomMessage(\n\t\t\t{\n\t\t\t\tcustomType: \"atomic\",\n\t\t\t\tcontent: getAtomicGuideMessage(atomicGuideModeForChoice(choice), this._cwd),\n\t\t\t\tdisplay: true,\n\t\t\t},\n\t\t\t{ triggerTurn: false },\n\t\t);\n\t\treturn true;\n\t}\n\n\tawait this.sendCustomMessage(\n\t\t{\n\t\t\tcustomType: \"atomic\",\n\t\t\tcontent: getAtomicGuideMessage(mode, this._cwd),\n\t\t\tdisplay: true,\n\t\t},\n\t\t{ triggerTurn: false },\n\t);\n\treturn true;\n}\n\n/**\n * Try to execute an extension command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteExtensionCommand(this: AgentSession, text: string): Promise<boolean> {\n\t// Parse command name and args\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst command = this._extensionRunner.getCommand(commandName);\n\tif (!command) return false;\n\n\t// Get command context from extension runner (includes session control methods)\n\tconst ctx = this._extensionRunner.createCommandContext();\n\n\ttry {\n\t\tawait command.handler(args, ctx);\n\t\treturn true;\n\t} catch (err) {\n\t\t// Emit error via extension runner\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: `command:${commandName}`,\n\t\t\tevent: \"command\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn true;\n\t}\n}\n\n/**\n * Expand skill commands (/skill:name args) to their full content.\n * Returns the expanded text, or the original text if not a skill command or skill not found.\n * Emits errors via extension runner if file read fails.\n */\n\nexport function _expandSkillCommand(this: AgentSession, text: string): string {\n\tif (!text.startsWith(\"/skill:\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\tconst skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);\n\tif (!skill) return text; // Unknown skill, pass through\n\n\ttry {\n\t\tconst content = readFileSync(skill.filePath, \"utf-8\");\n\t\tconst body = stripFrontmatter(content).trim();\n\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t} catch (err) {\n\t\t// Emit error like extension commands do\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: skill.filePath,\n\t\t\tevent: \"skill_expansion\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn text; // Return original on error\n\t}\n}\n\n/**\n * Queue a steering message while the agent is running.\n * Delivered after the current assistant turn finishes executing its tool calls,\n * before the next LLM call.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function steer(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueSteer(expandedText, images);\n}\n\n/**\n * Queue a follow-up message to be processed after the agent finishes.\n * Delivered only when agent has no more tool calls or steering messages.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function followUp(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueFollowUp(expandedText, images);\n}\n\n/**\n * Internal: Queue a steering message (already expanded, no extension command check).\n */\n\nexport async function sendUserMessage(this: AgentSession, \n\tcontent: string | (TextContent | ImageContent)[],\n\toptions?: { deliverAs?: \"steer\" | \"followUp\" },\n): Promise<void> {\n\t// Normalize content to text string + optional images\n\tlet text: string;\n\tlet images: ImageContent[] | undefined;\n\n\tif (typeof content === \"string\") {\n\t\ttext = content;\n\t} else {\n\t\tconst textParts: string[] = [];\n\t\timages = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\") {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t} else {\n\t\t\t\timages.push(part);\n\t\t\t}\n\t\t}\n\t\ttext = textParts.join(\"\\n\");\n\t\tif (images.length === 0) images = undefined;\n\t}\n\n\t// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion\n\tawait this.prompt(text, {\n\t\texpandPromptTemplates: false,\n\t\tstreamingBehavior: options?.deliverAs,\n\t\timages,\n\t\tsource: \"extension\",\n\t});\n}\n\n/**\n * Clear all queued messages and return them.\n * Useful for restoring to editor when user aborts.\n * @returns Object with steering and followUp arrays\n */\n\nexport const agentSessionPromptMethods = {\n\tprompt,\n\t_runAgentPrompt,\n\t_continueQueuedAgentMessages,\n\t_tryExecuteBuiltinSlashCommand,\n\t_tryExecuteExtensionCommand,\n\t_expandSkillCommand,\n\tsteer,\n\tfollowUp,\n\tsendUserMessage,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"agent-session-prompt.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-prompt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAK9E,OAAO,KAAK,EAAE,2BAA2B,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC9F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D,wBAAsB,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAwKrG;AAGD,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,YAAY,GAAG,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIhH;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAIzE;AAGD,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpF;AAED;;GAEG;AAEH,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAgCvG;AAED;;GAEG;AAEH,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBpG;AAED;;;;GAIG;AAEH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAwB5E;AAED;;;;;;;GAOG;AAEH,wBAAsB,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAWpG;AAED;;;;;;GAMG;AAEH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvG;AAED;;GAEG;AAEH,wBAAsB,eAAe,CAAC,IAAI,EAAE,YAAY,EACvD,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,EAChD,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CA4Bf;AAED;;;;GAIG;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;;CAWrC,CAAC","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport { ATOMIC_GUIDE_COMMAND_NAME, ATOMIC_GUIDE_HELP_CHOICES, atomicGuideModeForChoice, getAtomicGuideMessage, isAtomicGuideHelpChoice, normalizeAtomicGuideMode } from \"./atomic-guide-command.ts\";\nimport { formatAuthStorageLoadFailedMessage, formatNoApiKeyFoundMessage, formatNoModelSelectedMessage, formatUnresolvedModelMessage } from \"./auth-guidance.ts\";\nimport { expandPromptTemplate } from \"./prompt-templates.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { PromptOptions } from \"./agent-session-types.ts\";\n\nexport async function prompt(this: AgentSession, text: string, options?: PromptOptions): Promise<void> {\n\tconst expandPromptTemplates = options?.expandPromptTemplates ?? true;\n\tconst preflightResult = options?.preflightResult;\n\tlet messages: AgentMessage[] | undefined;\n\n\ttry {\n\t\t// Handle slash commands first (execute immediately, even during streaming).\n\t\t// Builtin and extension commands manage their own LLM interaction via custom messages.\n\t\tif (expandPromptTemplates && text.startsWith(\"/\")) {\n\t\t\tconst handledBuiltin = await this._tryExecuteBuiltinSlashCommand(text);\n\t\t\tif (handledBuiltin) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handledExtension = await this._tryExecuteExtensionCommand(text);\n\t\t\tif (handledExtension) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Emit input event for extension interception (before skill/template expansion)\n\t\tlet currentText = text;\n\t\tlet currentImages = options?.images;\n\t\tif (this._extensionRunner.hasHandlers(\"input\")) {\n\t\t\tconst inputResult = await this._extensionRunner.emitInput(\n\t\t\t\tcurrentText,\n\t\t\t\tcurrentImages,\n\t\t\t\toptions?.source ?? \"interactive\",\n\t\t\t\tthis.isStreaming ? options?.streamingBehavior : undefined,\n\t\t\t);\n\t\t\tif (inputResult.action === \"handled\") {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (inputResult.action === \"transform\") {\n\t\t\t\tcurrentText = inputResult.text;\n\t\t\t\tcurrentImages = inputResult.images ?? currentImages;\n\t\t\t}\n\t\t}\n\n\t\t// Expand skill commands (/skill:name args) and prompt templates (/template args)\n\t\tlet expandedText = currentText;\n\t\tif (expandPromptTemplates) {\n\t\t\texpandedText = this._expandSkillCommand(expandedText);\n\t\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\t\t}\n\n\t\t// If streaming, queue via steer() or followUp() based on option\n\t\tif (this.isStreaming) {\n\t\t\tif (!options?.streamingBehavior) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (options.streamingBehavior === \"followUp\") {\n\t\t\t\tawait this._queueFollowUp(expandedText, currentImages);\n\t\t\t} else {\n\t\t\t\tawait this._queueSteer(expandedText, currentImages);\n\t\t\t}\n\t\t\tpreflightResult?.(true);\n\t\t\treturn;\n\t\t}\n\n\t\t// Flush any pending bash messages before the new prompt\n\t\tthis._flushPendingBashMessages();\n\n\t\t// Validate model\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Defensive guard: a model that never resolved to a real provider\n\t\t// (for example an unknown/unresolved model id that reached this path\n\t\t// as a bare string) has no `provider`, which would otherwise fail deep\n\t\t// in auth resolution as the confusing \"No API key found for undefined\".\n\t\t// Surface a clear, accurate \"unknown model\" error instead.\n\t\tconst resolvedProvider = (this.model as { provider?: unknown }).provider;\n\t\tif (typeof resolvedProvider !== \"string\" || resolvedProvider.length === 0) {\n\t\t\tthrow new Error(formatUnresolvedModelMessage(this.model));\n\t\t}\n\n\t\tif (!this._modelRegistry.hasConfiguredAuth(this.model)) {\n\t\t\t// A failed credential-store load (for example auth.json briefly locked\n\t\t\t// by a concurrent process, or invalid JSON) leaves an empty in-memory\n\t\t\t// credential set. That would otherwise be misreported here as\n\t\t\t// \"No API key found\" even though the credentials exist on disk. Surface\n\t\t\t// the real load failure instead so configured providers are not falsely\n\t\t\t// reported as unauthenticated (issue #1431).\n\t\t\tconst authLoadError = this._modelRegistry.authStorage.getLoadError();\n\t\t\tif (authLoadError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tformatAuthStorageLoadFailedMessage(this.model.provider, authLoadError),\n\t\t\t\t\t{ cause: authLoadError },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst isOAuth = this._modelRegistry.isUsingOAuth(this.model);\n\t\t\tif (isOAuth) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Authentication failed for \"${this.model.provider}\". ` +\n\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t`Run '/login ${this.model.provider}' to re-authenticate.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(formatNoApiKeyFoundMessage(this.model.provider));\n\t\t}\n\n\t\t// Check if we need to compact before sending (catches aborted responses)\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (lastAssistant) {\n\t\t\tawait this._checkCompaction(lastAssistant, false);\n\t\t}\n\n\t\t// Build messages array (custom message if any, then user message)\n\t\tmessages = [];\n\n\t\t// Add user message\n\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: expandedText }];\n\t\tif (currentImages) {\n\t\t\tuserContent.push(...currentImages);\n\t\t}\n\t\tmessages.push({\n\t\t\trole: \"user\",\n\t\t\tcontent: userContent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\t// Inject any pending \"nextTurn\" messages as context alongside the user message\n\t\tfor (const msg of this._pendingNextTurnMessages) {\n\t\t\tmessages.push(msg);\n\t\t}\n\t\tthis._pendingNextTurnMessages = [];\n\n\t\t// Emit before_agent_start extension event\n\t\tconst result = await this._extensionRunner.emitBeforeAgentStart(\n\t\t\texpandedText,\n\t\t\tcurrentImages,\n\t\t\tthis._baseSystemPrompt,\n\t\t\tthis._baseSystemPromptOptions,\n\t\t);\n\t\t// Add all custom messages from extensions\n\t\tif (result?.messages) {\n\t\t\tfor (const msg of result.messages) {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t// Apply extension-modified system prompt, or reset to base\n\t\tif (result?.systemPrompt) {\n\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t} else {\n\t\t\t// Ensure we're using the base prompt (in case previous turn had modifications)\n\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t}\n\t} catch (error) {\n\t\tpreflightResult?.(false);\n\t\tthrow error;\n\t}\n\n\tpreflightResult?.(true);\n\tawait this._runAgentPrompt(messages);\n}\n\n\nexport async function _runAgentPrompt(this: AgentSession, messages: AgentMessage | AgentMessage[]): Promise<void> {\n\tawait this.agent.prompt(messages);\n\tawait this.waitForRetry();\n\tawait this._continueQueuedAgentMessages();\n}\n\nexport async function _runAgentContinue(this: AgentSession): Promise<void> {\n\tawait this.agent.continue();\n\tawait this.waitForRetry();\n\tawait this._continueQueuedAgentMessages();\n}\n\n\nexport async function _continueQueuedAgentMessages(this: AgentSession): Promise<void> {\n\tawait this._agentEventQueue;\n\n\twhile (this.agent.hasQueuedMessages()) {\n\t\tawait this.agent.continue();\n\t\tawait this.waitForRetry();\n\t\tawait this._agentEventQueue;\n\t}\n}\n\n/**\n * Try to execute a built-in slash command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteBuiltinSlashCommand(this: AgentSession, text: string): Promise<boolean> {\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tif (commandName !== ATOMIC_GUIDE_COMMAND_NAME) return false;\n\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\tconst mode = normalizeAtomicGuideMode(args);\n\tif (mode === \"help\" && this._extensionUIContext) {\n\t\tconst choice = await this._extensionUIContext.select(\"Atomic. Select where to start:\", [\n\t\t\t...ATOMIC_GUIDE_HELP_CHOICES,\n\t\t]);\n\t\tif (!choice || !isAtomicGuideHelpChoice(choice)) return true;\n\t\tawait this.sendCustomMessage(\n\t\t\t{\n\t\t\t\tcustomType: \"atomic\",\n\t\t\t\tcontent: getAtomicGuideMessage(atomicGuideModeForChoice(choice), this._cwd),\n\t\t\t\tdisplay: true,\n\t\t\t},\n\t\t\t{ triggerTurn: false },\n\t\t);\n\t\treturn true;\n\t}\n\n\tawait this.sendCustomMessage(\n\t\t{\n\t\t\tcustomType: \"atomic\",\n\t\t\tcontent: getAtomicGuideMessage(mode, this._cwd),\n\t\t\tdisplay: true,\n\t\t},\n\t\t{ triggerTurn: false },\n\t);\n\treturn true;\n}\n\n/**\n * Try to execute an extension command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteExtensionCommand(this: AgentSession, text: string): Promise<boolean> {\n\t// Parse command name and args\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst command = this._extensionRunner.getCommand(commandName);\n\tif (!command) return false;\n\n\t// Get command context from extension runner (includes session control methods)\n\tconst ctx = this._extensionRunner.createCommandContext();\n\n\ttry {\n\t\tawait command.handler(args, ctx);\n\t\treturn true;\n\t} catch (err) {\n\t\t// Emit error via extension runner\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: `command:${commandName}`,\n\t\t\tevent: \"command\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn true;\n\t}\n}\n\n/**\n * Expand skill commands (/skill:name args) to their full content.\n * Returns the expanded text, or the original text if not a skill command or skill not found.\n * Emits errors via extension runner if file read fails.\n */\n\nexport function _expandSkillCommand(this: AgentSession, text: string): string {\n\tif (!text.startsWith(\"/skill:\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\tconst skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);\n\tif (!skill) return text; // Unknown skill, pass through\n\n\ttry {\n\t\tconst content = readFileSync(skill.filePath, \"utf-8\");\n\t\tconst body = stripFrontmatter(content).trim();\n\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t} catch (err) {\n\t\t// Emit error like extension commands do\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: skill.filePath,\n\t\t\tevent: \"skill_expansion\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn text; // Return original on error\n\t}\n}\n\n/**\n * Queue a steering message while the agent is running.\n * Delivered after the current assistant turn finishes executing its tool calls,\n * before the next LLM call.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function steer(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueSteer(expandedText, images);\n}\n\n/**\n * Queue a follow-up message to be processed after the agent finishes.\n * Delivered only when agent has no more tool calls or steering messages.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function followUp(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueFollowUp(expandedText, images);\n}\n\n/**\n * Internal: Queue a steering message (already expanded, no extension command check).\n */\n\nexport async function sendUserMessage(this: AgentSession, \n\tcontent: string | (TextContent | ImageContent)[],\n\toptions?: { deliverAs?: \"steer\" | \"followUp\" },\n): Promise<void> {\n\t// Normalize content to text string + optional images\n\tlet text: string;\n\tlet images: ImageContent[] | undefined;\n\n\tif (typeof content === \"string\") {\n\t\ttext = content;\n\t} else {\n\t\tconst textParts: string[] = [];\n\t\timages = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\") {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t} else {\n\t\t\t\timages.push(part);\n\t\t\t}\n\t\t}\n\t\ttext = textParts.join(\"\\n\");\n\t\tif (images.length === 0) images = undefined;\n\t}\n\n\t// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion\n\tawait this.prompt(text, {\n\t\texpandPromptTemplates: false,\n\t\tstreamingBehavior: options?.deliverAs,\n\t\timages,\n\t\tsource: \"extension\",\n\t});\n}\n\n/**\n * Clear all queued messages and return them.\n * Useful for restoring to editor when user aborts.\n * @returns Object with steering and followUp arrays\n */\n\nexport const agentSessionPromptMethods = {\n\tprompt,\n\t_runAgentPrompt,\n\t_runAgentContinue,\n\t_continueQueuedAgentMessages,\n\t_tryExecuteBuiltinSlashCommand,\n\t_tryExecuteExtensionCommand,\n\t_expandSkillCommand,\n\tsteer,\n\tfollowUp,\n\tsendUserMessage,\n};\n"]}
|
|
@@ -148,6 +148,11 @@ export async function _runAgentPrompt(messages) {
|
|
|
148
148
|
await this.waitForRetry();
|
|
149
149
|
await this._continueQueuedAgentMessages();
|
|
150
150
|
}
|
|
151
|
+
export async function _runAgentContinue() {
|
|
152
|
+
await this.agent.continue();
|
|
153
|
+
await this.waitForRetry();
|
|
154
|
+
await this._continueQueuedAgentMessages();
|
|
155
|
+
}
|
|
151
156
|
export async function _continueQueuedAgentMessages() {
|
|
152
157
|
await this._agentEventQueue;
|
|
153
158
|
while (this.agent.hasQueuedMessages()) {
|
|
@@ -319,6 +324,7 @@ export async function sendUserMessage(content, options) {
|
|
|
319
324
|
export const agentSessionPromptMethods = {
|
|
320
325
|
prompt,
|
|
321
326
|
_runAgentPrompt,
|
|
327
|
+
_runAgentContinue,
|
|
322
328
|
_continueQueuedAgentMessages,
|
|
323
329
|
_tryExecuteBuiltinSlashCommand,
|
|
324
330
|
_tryExecuteExtensionCommand,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-prompt.js","sourceRoot":"","sources":["../../src/core/agent-session-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport { ATOMIC_GUIDE_COMMAND_NAME, ATOMIC_GUIDE_HELP_CHOICES, atomicGuideModeForChoice, getAtomicGuideMessage, isAtomicGuideHelpChoice, normalizeAtomicGuideMode } from \"./atomic-guide-command.ts\";\nimport { formatAuthStorageLoadFailedMessage, formatNoApiKeyFoundMessage, formatNoModelSelectedMessage, formatUnresolvedModelMessage } from \"./auth-guidance.ts\";\nimport { expandPromptTemplate } from \"./prompt-templates.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { PromptOptions } from \"./agent-session-types.ts\";\n\nexport async function prompt(this: AgentSession, text: string, options?: PromptOptions): Promise<void> {\n\tconst expandPromptTemplates = options?.expandPromptTemplates ?? true;\n\tconst preflightResult = options?.preflightResult;\n\tlet messages: AgentMessage[] | undefined;\n\n\ttry {\n\t\t// Handle slash commands first (execute immediately, even during streaming).\n\t\t// Builtin and extension commands manage their own LLM interaction via custom messages.\n\t\tif (expandPromptTemplates && text.startsWith(\"/\")) {\n\t\t\tconst handledBuiltin = await this._tryExecuteBuiltinSlashCommand(text);\n\t\t\tif (handledBuiltin) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handledExtension = await this._tryExecuteExtensionCommand(text);\n\t\t\tif (handledExtension) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Emit input event for extension interception (before skill/template expansion)\n\t\tlet currentText = text;\n\t\tlet currentImages = options?.images;\n\t\tif (this._extensionRunner.hasHandlers(\"input\")) {\n\t\t\tconst inputResult = await this._extensionRunner.emitInput(\n\t\t\t\tcurrentText,\n\t\t\t\tcurrentImages,\n\t\t\t\toptions?.source ?? \"interactive\",\n\t\t\t\tthis.isStreaming ? options?.streamingBehavior : undefined,\n\t\t\t);\n\t\t\tif (inputResult.action === \"handled\") {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (inputResult.action === \"transform\") {\n\t\t\t\tcurrentText = inputResult.text;\n\t\t\t\tcurrentImages = inputResult.images ?? currentImages;\n\t\t\t}\n\t\t}\n\n\t\t// Expand skill commands (/skill:name args) and prompt templates (/template args)\n\t\tlet expandedText = currentText;\n\t\tif (expandPromptTemplates) {\n\t\t\texpandedText = this._expandSkillCommand(expandedText);\n\t\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\t\t}\n\n\t\t// If streaming, queue via steer() or followUp() based on option\n\t\tif (this.isStreaming) {\n\t\t\tif (!options?.streamingBehavior) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (options.streamingBehavior === \"followUp\") {\n\t\t\t\tawait this._queueFollowUp(expandedText, currentImages);\n\t\t\t} else {\n\t\t\t\tawait this._queueSteer(expandedText, currentImages);\n\t\t\t}\n\t\t\tpreflightResult?.(true);\n\t\t\treturn;\n\t\t}\n\n\t\t// Flush any pending bash messages before the new prompt\n\t\tthis._flushPendingBashMessages();\n\n\t\t// Validate model\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Defensive guard: a model that never resolved to a real provider\n\t\t// (for example an unknown/unresolved model id that reached this path\n\t\t// as a bare string) has no `provider`, which would otherwise fail deep\n\t\t// in auth resolution as the confusing \"No API key found for undefined\".\n\t\t// Surface a clear, accurate \"unknown model\" error instead.\n\t\tconst resolvedProvider = (this.model as { provider?: unknown }).provider;\n\t\tif (typeof resolvedProvider !== \"string\" || resolvedProvider.length === 0) {\n\t\t\tthrow new Error(formatUnresolvedModelMessage(this.model));\n\t\t}\n\n\t\tif (!this._modelRegistry.hasConfiguredAuth(this.model)) {\n\t\t\t// A failed credential-store load (for example auth.json briefly locked\n\t\t\t// by a concurrent process, or invalid JSON) leaves an empty in-memory\n\t\t\t// credential set. That would otherwise be misreported here as\n\t\t\t// \"No API key found\" even though the credentials exist on disk. Surface\n\t\t\t// the real load failure instead so configured providers are not falsely\n\t\t\t// reported as unauthenticated (issue #1431).\n\t\t\tconst authLoadError = this._modelRegistry.authStorage.getLoadError();\n\t\t\tif (authLoadError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tformatAuthStorageLoadFailedMessage(this.model.provider, authLoadError),\n\t\t\t\t\t{ cause: authLoadError },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst isOAuth = this._modelRegistry.isUsingOAuth(this.model);\n\t\t\tif (isOAuth) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Authentication failed for \"${this.model.provider}\". ` +\n\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t`Run '/login ${this.model.provider}' to re-authenticate.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(formatNoApiKeyFoundMessage(this.model.provider));\n\t\t}\n\n\t\t// Check if we need to compact before sending (catches aborted responses)\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (lastAssistant) {\n\t\t\tawait this._checkCompaction(lastAssistant, false);\n\t\t}\n\n\t\t// Build messages array (custom message if any, then user message)\n\t\tmessages = [];\n\n\t\t// Add user message\n\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: expandedText }];\n\t\tif (currentImages) {\n\t\t\tuserContent.push(...currentImages);\n\t\t}\n\t\tmessages.push({\n\t\t\trole: \"user\",\n\t\t\tcontent: userContent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\t// Inject any pending \"nextTurn\" messages as context alongside the user message\n\t\tfor (const msg of this._pendingNextTurnMessages) {\n\t\t\tmessages.push(msg);\n\t\t}\n\t\tthis._pendingNextTurnMessages = [];\n\n\t\t// Emit before_agent_start extension event\n\t\tconst result = await this._extensionRunner.emitBeforeAgentStart(\n\t\t\texpandedText,\n\t\t\tcurrentImages,\n\t\t\tthis._baseSystemPrompt,\n\t\t\tthis._baseSystemPromptOptions,\n\t\t);\n\t\t// Add all custom messages from extensions\n\t\tif (result?.messages) {\n\t\t\tfor (const msg of result.messages) {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t// Apply extension-modified system prompt, or reset to base\n\t\tif (result?.systemPrompt) {\n\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t} else {\n\t\t\t// Ensure we're using the base prompt (in case previous turn had modifications)\n\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t}\n\t} catch (error) {\n\t\tpreflightResult?.(false);\n\t\tthrow error;\n\t}\n\n\tpreflightResult?.(true);\n\tawait this._runAgentPrompt(messages);\n}\n\n\nexport async function _runAgentPrompt(this: AgentSession, messages: AgentMessage | AgentMessage[]): Promise<void> {\n\tawait this.agent.prompt(messages);\n\tawait this.waitForRetry();\n\tawait this._continueQueuedAgentMessages();\n}\n\n\nexport async function _continueQueuedAgentMessages(this: AgentSession): Promise<void> {\n\tawait this._agentEventQueue;\n\n\twhile (this.agent.hasQueuedMessages()) {\n\t\tawait this.agent.continue();\n\t\tawait this.waitForRetry();\n\t\tawait this._agentEventQueue;\n\t}\n}\n\n/**\n * Try to execute a built-in slash command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteBuiltinSlashCommand(this: AgentSession, text: string): Promise<boolean> {\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tif (commandName !== ATOMIC_GUIDE_COMMAND_NAME) return false;\n\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\tconst mode = normalizeAtomicGuideMode(args);\n\tif (mode === \"help\" && this._extensionUIContext) {\n\t\tconst choice = await this._extensionUIContext.select(\"Atomic. Select where to start:\", [\n\t\t\t...ATOMIC_GUIDE_HELP_CHOICES,\n\t\t]);\n\t\tif (!choice || !isAtomicGuideHelpChoice(choice)) return true;\n\t\tawait this.sendCustomMessage(\n\t\t\t{\n\t\t\t\tcustomType: \"atomic\",\n\t\t\t\tcontent: getAtomicGuideMessage(atomicGuideModeForChoice(choice), this._cwd),\n\t\t\t\tdisplay: true,\n\t\t\t},\n\t\t\t{ triggerTurn: false },\n\t\t);\n\t\treturn true;\n\t}\n\n\tawait this.sendCustomMessage(\n\t\t{\n\t\t\tcustomType: \"atomic\",\n\t\t\tcontent: getAtomicGuideMessage(mode, this._cwd),\n\t\t\tdisplay: true,\n\t\t},\n\t\t{ triggerTurn: false },\n\t);\n\treturn true;\n}\n\n/**\n * Try to execute an extension command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteExtensionCommand(this: AgentSession, text: string): Promise<boolean> {\n\t// Parse command name and args\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst command = this._extensionRunner.getCommand(commandName);\n\tif (!command) return false;\n\n\t// Get command context from extension runner (includes session control methods)\n\tconst ctx = this._extensionRunner.createCommandContext();\n\n\ttry {\n\t\tawait command.handler(args, ctx);\n\t\treturn true;\n\t} catch (err) {\n\t\t// Emit error via extension runner\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: `command:${commandName}`,\n\t\t\tevent: \"command\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn true;\n\t}\n}\n\n/**\n * Expand skill commands (/skill:name args) to their full content.\n * Returns the expanded text, or the original text if not a skill command or skill not found.\n * Emits errors via extension runner if file read fails.\n */\n\nexport function _expandSkillCommand(this: AgentSession, text: string): string {\n\tif (!text.startsWith(\"/skill:\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\tconst skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);\n\tif (!skill) return text; // Unknown skill, pass through\n\n\ttry {\n\t\tconst content = readFileSync(skill.filePath, \"utf-8\");\n\t\tconst body = stripFrontmatter(content).trim();\n\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t} catch (err) {\n\t\t// Emit error like extension commands do\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: skill.filePath,\n\t\t\tevent: \"skill_expansion\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn text; // Return original on error\n\t}\n}\n\n/**\n * Queue a steering message while the agent is running.\n * Delivered after the current assistant turn finishes executing its tool calls,\n * before the next LLM call.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function steer(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueSteer(expandedText, images);\n}\n\n/**\n * Queue a follow-up message to be processed after the agent finishes.\n * Delivered only when agent has no more tool calls or steering messages.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function followUp(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueFollowUp(expandedText, images);\n}\n\n/**\n * Internal: Queue a steering message (already expanded, no extension command check).\n */\n\nexport async function sendUserMessage(this: AgentSession, \n\tcontent: string | (TextContent | ImageContent)[],\n\toptions?: { deliverAs?: \"steer\" | \"followUp\" },\n): Promise<void> {\n\t// Normalize content to text string + optional images\n\tlet text: string;\n\tlet images: ImageContent[] | undefined;\n\n\tif (typeof content === \"string\") {\n\t\ttext = content;\n\t} else {\n\t\tconst textParts: string[] = [];\n\t\timages = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\") {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t} else {\n\t\t\t\timages.push(part);\n\t\t\t}\n\t\t}\n\t\ttext = textParts.join(\"\\n\");\n\t\tif (images.length === 0) images = undefined;\n\t}\n\n\t// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion\n\tawait this.prompt(text, {\n\t\texpandPromptTemplates: false,\n\t\tstreamingBehavior: options?.deliverAs,\n\t\timages,\n\t\tsource: \"extension\",\n\t});\n}\n\n/**\n * Clear all queued messages and return them.\n * Useful for restoring to editor when user aborts.\n * @returns Object with steering and followUp arrays\n */\n\nexport const agentSessionPromptMethods = {\n\tprompt,\n\t_runAgentPrompt,\n\t_continueQueuedAgentMessages,\n\t_tryExecuteBuiltinSlashCommand,\n\t_tryExecuteExtensionCommand,\n\t_expandSkillCommand,\n\tsteer,\n\tfollowUp,\n\tsendUserMessage,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"agent-session-prompt.js","sourceRoot":"","sources":["../../src/core/agent-session-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { ImageContent, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport { ATOMIC_GUIDE_COMMAND_NAME, ATOMIC_GUIDE_HELP_CHOICES, atomicGuideModeForChoice, getAtomicGuideMessage, isAtomicGuideHelpChoice, normalizeAtomicGuideMode } from \"./atomic-guide-command.ts\";\nimport { formatAuthStorageLoadFailedMessage, formatNoApiKeyFoundMessage, formatNoModelSelectedMessage, formatUnresolvedModelMessage } from \"./auth-guidance.ts\";\nimport { expandPromptTemplate } from \"./prompt-templates.ts\";\nimport { stripFrontmatter } from \"../utils/frontmatter.ts\";\nimport type { AgentSessionInternalSurface as AgentSession } from \"./agent-session-methods.ts\";\nimport type { PromptOptions } from \"./agent-session-types.ts\";\n\nexport async function prompt(this: AgentSession, text: string, options?: PromptOptions): Promise<void> {\n\tconst expandPromptTemplates = options?.expandPromptTemplates ?? true;\n\tconst preflightResult = options?.preflightResult;\n\tlet messages: AgentMessage[] | undefined;\n\n\ttry {\n\t\t// Handle slash commands first (execute immediately, even during streaming).\n\t\t// Builtin and extension commands manage their own LLM interaction via custom messages.\n\t\tif (expandPromptTemplates && text.startsWith(\"/\")) {\n\t\t\tconst handledBuiltin = await this._tryExecuteBuiltinSlashCommand(text);\n\t\t\tif (handledBuiltin) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst handledExtension = await this._tryExecuteExtensionCommand(text);\n\t\t\tif (handledExtension) {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\t// Emit input event for extension interception (before skill/template expansion)\n\t\tlet currentText = text;\n\t\tlet currentImages = options?.images;\n\t\tif (this._extensionRunner.hasHandlers(\"input\")) {\n\t\t\tconst inputResult = await this._extensionRunner.emitInput(\n\t\t\t\tcurrentText,\n\t\t\t\tcurrentImages,\n\t\t\t\toptions?.source ?? \"interactive\",\n\t\t\t\tthis.isStreaming ? options?.streamingBehavior : undefined,\n\t\t\t);\n\t\t\tif (inputResult.action === \"handled\") {\n\t\t\t\tpreflightResult?.(true);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (inputResult.action === \"transform\") {\n\t\t\t\tcurrentText = inputResult.text;\n\t\t\t\tcurrentImages = inputResult.images ?? currentImages;\n\t\t\t}\n\t\t}\n\n\t\t// Expand skill commands (/skill:name args) and prompt templates (/template args)\n\t\tlet expandedText = currentText;\n\t\tif (expandPromptTemplates) {\n\t\t\texpandedText = this._expandSkillCommand(expandedText);\n\t\t\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\t\t}\n\n\t\t// If streaming, queue via steer() or followUp() based on option\n\t\tif (this.isStreaming) {\n\t\t\tif (!options?.streamingBehavior) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (options.streamingBehavior === \"followUp\") {\n\t\t\t\tawait this._queueFollowUp(expandedText, currentImages);\n\t\t\t} else {\n\t\t\t\tawait this._queueSteer(expandedText, currentImages);\n\t\t\t}\n\t\t\tpreflightResult?.(true);\n\t\t\treturn;\n\t\t}\n\n\t\t// Flush any pending bash messages before the new prompt\n\t\tthis._flushPendingBashMessages();\n\n\t\t// Validate model\n\t\tif (!this.model) {\n\t\t\tthrow new Error(formatNoModelSelectedMessage());\n\t\t}\n\n\t\t// Defensive guard: a model that never resolved to a real provider\n\t\t// (for example an unknown/unresolved model id that reached this path\n\t\t// as a bare string) has no `provider`, which would otherwise fail deep\n\t\t// in auth resolution as the confusing \"No API key found for undefined\".\n\t\t// Surface a clear, accurate \"unknown model\" error instead.\n\t\tconst resolvedProvider = (this.model as { provider?: unknown }).provider;\n\t\tif (typeof resolvedProvider !== \"string\" || resolvedProvider.length === 0) {\n\t\t\tthrow new Error(formatUnresolvedModelMessage(this.model));\n\t\t}\n\n\t\tif (!this._modelRegistry.hasConfiguredAuth(this.model)) {\n\t\t\t// A failed credential-store load (for example auth.json briefly locked\n\t\t\t// by a concurrent process, or invalid JSON) leaves an empty in-memory\n\t\t\t// credential set. That would otherwise be misreported here as\n\t\t\t// \"No API key found\" even though the credentials exist on disk. Surface\n\t\t\t// the real load failure instead so configured providers are not falsely\n\t\t\t// reported as unauthenticated (issue #1431).\n\t\t\tconst authLoadError = this._modelRegistry.authStorage.getLoadError();\n\t\t\tif (authLoadError) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\tformatAuthStorageLoadFailedMessage(this.model.provider, authLoadError),\n\t\t\t\t\t{ cause: authLoadError },\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst isOAuth = this._modelRegistry.isUsingOAuth(this.model);\n\t\t\tif (isOAuth) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Authentication failed for \"${this.model.provider}\". ` +\n\t\t\t\t\t\t`Credentials may have expired or network is unavailable. ` +\n\t\t\t\t\t\t`Run '/login ${this.model.provider}' to re-authenticate.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow new Error(formatNoApiKeyFoundMessage(this.model.provider));\n\t\t}\n\n\t\t// Check if we need to compact before sending (catches aborted responses)\n\t\tconst lastAssistant = this._findLastAssistantMessage();\n\t\tif (lastAssistant) {\n\t\t\tawait this._checkCompaction(lastAssistant, false);\n\t\t}\n\n\t\t// Build messages array (custom message if any, then user message)\n\t\tmessages = [];\n\n\t\t// Add user message\n\t\tconst userContent: (TextContent | ImageContent)[] = [{ type: \"text\", text: expandedText }];\n\t\tif (currentImages) {\n\t\t\tuserContent.push(...currentImages);\n\t\t}\n\t\tmessages.push({\n\t\t\trole: \"user\",\n\t\t\tcontent: userContent,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\t// Inject any pending \"nextTurn\" messages as context alongside the user message\n\t\tfor (const msg of this._pendingNextTurnMessages) {\n\t\t\tmessages.push(msg);\n\t\t}\n\t\tthis._pendingNextTurnMessages = [];\n\n\t\t// Emit before_agent_start extension event\n\t\tconst result = await this._extensionRunner.emitBeforeAgentStart(\n\t\t\texpandedText,\n\t\t\tcurrentImages,\n\t\t\tthis._baseSystemPrompt,\n\t\t\tthis._baseSystemPromptOptions,\n\t\t);\n\t\t// Add all custom messages from extensions\n\t\tif (result?.messages) {\n\t\t\tfor (const msg of result.messages) {\n\t\t\t\tmessages.push({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: msg.customType,\n\t\t\t\t\tcontent: msg.content,\n\t\t\t\t\tdisplay: msg.display,\n\t\t\t\t\tdetails: msg.details,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t// Apply extension-modified system prompt, or reset to base\n\t\tif (result?.systemPrompt) {\n\t\t\tthis.agent.state.systemPrompt = result.systemPrompt;\n\t\t} else {\n\t\t\t// Ensure we're using the base prompt (in case previous turn had modifications)\n\t\t\tthis.agent.state.systemPrompt = this._baseSystemPrompt;\n\t\t}\n\t} catch (error) {\n\t\tpreflightResult?.(false);\n\t\tthrow error;\n\t}\n\n\tpreflightResult?.(true);\n\tawait this._runAgentPrompt(messages);\n}\n\n\nexport async function _runAgentPrompt(this: AgentSession, messages: AgentMessage | AgentMessage[]): Promise<void> {\n\tawait this.agent.prompt(messages);\n\tawait this.waitForRetry();\n\tawait this._continueQueuedAgentMessages();\n}\n\nexport async function _runAgentContinue(this: AgentSession): Promise<void> {\n\tawait this.agent.continue();\n\tawait this.waitForRetry();\n\tawait this._continueQueuedAgentMessages();\n}\n\n\nexport async function _continueQueuedAgentMessages(this: AgentSession): Promise<void> {\n\tawait this._agentEventQueue;\n\n\twhile (this.agent.hasQueuedMessages()) {\n\t\tawait this.agent.continue();\n\t\tawait this.waitForRetry();\n\t\tawait this._agentEventQueue;\n\t}\n}\n\n/**\n * Try to execute a built-in slash command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteBuiltinSlashCommand(this: AgentSession, text: string): Promise<boolean> {\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tif (commandName !== ATOMIC_GUIDE_COMMAND_NAME) return false;\n\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\tconst mode = normalizeAtomicGuideMode(args);\n\tif (mode === \"help\" && this._extensionUIContext) {\n\t\tconst choice = await this._extensionUIContext.select(\"Atomic. Select where to start:\", [\n\t\t\t...ATOMIC_GUIDE_HELP_CHOICES,\n\t\t]);\n\t\tif (!choice || !isAtomicGuideHelpChoice(choice)) return true;\n\t\tawait this.sendCustomMessage(\n\t\t\t{\n\t\t\t\tcustomType: \"atomic\",\n\t\t\t\tcontent: getAtomicGuideMessage(atomicGuideModeForChoice(choice), this._cwd),\n\t\t\t\tdisplay: true,\n\t\t\t},\n\t\t\t{ triggerTurn: false },\n\t\t);\n\t\treturn true;\n\t}\n\n\tawait this.sendCustomMessage(\n\t\t{\n\t\t\tcustomType: \"atomic\",\n\t\t\tcontent: getAtomicGuideMessage(mode, this._cwd),\n\t\t\tdisplay: true,\n\t\t},\n\t\t{ triggerTurn: false },\n\t);\n\treturn true;\n}\n\n/**\n * Try to execute an extension command. Returns true if command was found and executed.\n */\n\nexport async function _tryExecuteExtensionCommand(this: AgentSession, text: string): Promise<boolean> {\n\t// Parse command name and args\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst command = this._extensionRunner.getCommand(commandName);\n\tif (!command) return false;\n\n\t// Get command context from extension runner (includes session control methods)\n\tconst ctx = this._extensionRunner.createCommandContext();\n\n\ttry {\n\t\tawait command.handler(args, ctx);\n\t\treturn true;\n\t} catch (err) {\n\t\t// Emit error via extension runner\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: `command:${commandName}`,\n\t\t\tevent: \"command\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn true;\n\t}\n}\n\n/**\n * Expand skill commands (/skill:name args) to their full content.\n * Returns the expanded text, or the original text if not a skill command or skill not found.\n * Emits errors via extension runner if file read fails.\n */\n\nexport function _expandSkillCommand(this: AgentSession, text: string): string {\n\tif (!text.startsWith(\"/skill:\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);\n\tconst args = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1).trim();\n\n\tconst skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);\n\tif (!skill) return text; // Unknown skill, pass through\n\n\ttry {\n\t\tconst content = readFileSync(skill.filePath, \"utf-8\");\n\t\tconst body = stripFrontmatter(content).trim();\n\t\tconst skillBlock = `<skill name=\"${skill.name}\" location=\"${skill.filePath}\">\\nReferences are relative to ${skill.baseDir}.\\n\\n${body}\\n</skill>`;\n\t\treturn args ? `${skillBlock}\\n\\n${args}` : skillBlock;\n\t} catch (err) {\n\t\t// Emit error like extension commands do\n\t\tthis._extensionRunner.emitError({\n\t\t\textensionPath: skill.filePath,\n\t\t\tevent: \"skill_expansion\",\n\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t});\n\t\treturn text; // Return original on error\n\t}\n}\n\n/**\n * Queue a steering message while the agent is running.\n * Delivered after the current assistant turn finishes executing its tool calls,\n * before the next LLM call.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function steer(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueSteer(expandedText, images);\n}\n\n/**\n * Queue a follow-up message to be processed after the agent finishes.\n * Delivered only when agent has no more tool calls or steering messages.\n * Expands skill commands and prompt templates. Errors on extension commands.\n * @param images Optional image attachments to include with the message\n * @throws Error if text is an extension command\n */\n\nexport async function followUp(this: AgentSession, text: string, images?: ImageContent[]): Promise<void> {\n\t// Check for extension commands (cannot be queued)\n\tif (text.startsWith(\"/\")) {\n\t\tthis._throwIfExtensionCommand(text);\n\t}\n\n\t// Expand skill commands and prompt templates\n\tlet expandedText = this._expandSkillCommand(text);\n\texpandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);\n\n\tawait this._queueFollowUp(expandedText, images);\n}\n\n/**\n * Internal: Queue a steering message (already expanded, no extension command check).\n */\n\nexport async function sendUserMessage(this: AgentSession, \n\tcontent: string | (TextContent | ImageContent)[],\n\toptions?: { deliverAs?: \"steer\" | \"followUp\" },\n): Promise<void> {\n\t// Normalize content to text string + optional images\n\tlet text: string;\n\tlet images: ImageContent[] | undefined;\n\n\tif (typeof content === \"string\") {\n\t\ttext = content;\n\t} else {\n\t\tconst textParts: string[] = [];\n\t\timages = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\") {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t} else {\n\t\t\t\timages.push(part);\n\t\t\t}\n\t\t}\n\t\ttext = textParts.join(\"\\n\");\n\t\tif (images.length === 0) images = undefined;\n\t}\n\n\t// Use prompt() with expandPromptTemplates: false to skip command handling and template expansion\n\tawait this.prompt(text, {\n\t\texpandPromptTemplates: false,\n\t\tstreamingBehavior: options?.deliverAs,\n\t\timages,\n\t\tsource: \"extension\",\n\t});\n}\n\n/**\n * Clear all queued messages and return them.\n * Useful for restoring to editor when user aborts.\n * @returns Object with steering and followUp arrays\n */\n\nexport const agentSessionPromptMethods = {\n\tprompt,\n\t_runAgentPrompt,\n\t_runAgentContinue,\n\t_continueQueuedAgentMessages,\n\t_tryExecuteBuiltinSlashCommand,\n\t_tryExecuteExtensionCommand,\n\t_expandSkillCommand,\n\tsteer,\n\tfollowUp,\n\tsendUserMessage,\n};\n"]}
|
|
@@ -46,6 +46,10 @@ export type AgentSessionEvent = AgentEvent | {
|
|
|
46
46
|
aborted: boolean;
|
|
47
47
|
willRetry: false;
|
|
48
48
|
errorMessage?: string;
|
|
49
|
+
} | {
|
|
50
|
+
type: "agent_continue_error";
|
|
51
|
+
source: "post_compaction";
|
|
52
|
+
errorMessage: string;
|
|
49
53
|
} | {
|
|
50
54
|
type: "auto_retry_start";
|
|
51
55
|
attempt: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-types.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-types.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAgent,\n\tAgentEvent,\n\tAgentMessage,\n\tAgentTool,\n\tThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport type { Api, AssistantMessage, ImageContent, Model, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport type {\n\tContextUsage,\n\tExtensionCommandContextActions,\n\tExtensionErrorListener,\n\tExtensionMode,\n\tExtensionUIContext,\n\tInputSource,\n\tOrchestrationContext,\n\tSessionStartEvent,\n\tShutdownHandler,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nimport type { CustomMessage } from \"./messages.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport type { SessionManager } from \"./session-manager.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { SourceInfo } from \"./source-info.ts\";\nimport type { ContextCompactionResult } from \"./compaction/index.ts\";\n\nexport type AgentSessionEvent =\n\t| AgentEvent\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"context_compaction_start\"; reason: \"manual\" }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| {\n\t\t\ttype: \"model_changed\";\n\t\t\tmodel: Model<Api>;\n\t\t\tpreviousModel: Model<Api> | undefined;\n\t\t\tsource: \"set\" | \"cycle\" | \"restore\";\n\t }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| { type: \"context_window_changed\"; contextWindow: number }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: ContextCompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| {\n\t\t\ttype: \"context_compaction_end\";\n\t\t\treason: \"manual\";\n\t\t\tresult: ContextCompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: false;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string };\n\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\nexport type ContextWindowReplaySource = \"session\" | \"model-settings\" | \"global-settings\";\n\nexport interface ContextWindowReplayRequest {\n\tcontextWindow: number;\n\tsource: ContextWindowReplaySource;\n}\n\nexport const COPILOT_CONTEXT_WINDOW_SELECTION_OPTIONS = {\n\tallowCopilotLongContextFallback: true,\n} as const;\n\nexport interface PendingAgentMessageQueue {\n\thasItems(): boolean;\n\tdrain(): AgentMessage[];\n}\n\nexport interface AgentQueueAccess {\n\treadonly steeringQueue?: PendingAgentMessageQueue;\n\treadonly followUpQueue?: PendingAgentMessageQueue;\n}\n\nexport interface DrainedAgentQueues {\n\treadonly steering: AgentMessage[];\n\treadonly followUp: AgentMessage[];\n}\n\nexport interface InterruptQueueHold {\n\treadonly steering: AgentMessage[];\n\treadonly followUp: AgentMessage[];\n}\n\nexport function drainAgentMessageQueue(queue: PendingAgentMessageQueue | undefined): AgentMessage[] {\n\tif (!queue) return [];\n\tconst drained: AgentMessage[] = [];\n\twhile (queue.hasItems()) {\n\t\tdrained.push(...queue.drain());\n\t}\n\treturn drained;\n}\n\nexport function normalizeInterruptAbortMessage(value: string | undefined): string | undefined {\n\tconst trimmed = value?.trim();\n\treturn trimmed && trimmed.length > 0 ? trimmed : undefined;\n}\n\nexport function isGenericAbortText(value: string): boolean {\n\tconst normalized = value.trim().toLowerCase().replace(/[.!]+$/, \"\");\n\treturn (\n\t\tnormalized === \"operation aborted\" ||\n\t\tnormalized === \"the operation was aborted\" ||\n\t\tnormalized === \"request was aborted\" ||\n\t\tnormalized === \"this operation was aborted\" ||\n\t\tnormalized === \"extension custom ui aborted\"\n\t);\n}\n\nexport function isSingleGenericAbortTextContent(content: unknown): boolean {\n\treturn (\n\t\tArray.isArray(content) &&\n\t\tcontent.length === 1 &&\n\t\ttypeof content[0] === \"object\" &&\n\t\tcontent[0] !== null &&\n\t\t(content[0] as { type?: unknown }).type === \"text\" &&\n\t\ttypeof (content[0] as { text?: unknown }).text === \"string\" &&\n\t\tisGenericAbortText((content[0] as { text: string }).text)\n\t);\n}\n\nexport function replacementAbortContent(text: string): TextContent[] {\n\treturn [{ type: \"text\", text }];\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\tscopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\tresourceLoader: ResourceLoader;\n\tcustomTools?: ToolDefinition[];\n\tmodelRegistry: ModelRegistry;\n\tinitialActiveToolNames?: string[];\n\tallowedToolNames?: string[];\n\texcludedToolNames?: string[];\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\textensionRunnerRef?: { current?: import(\"./extensions/index.ts\").ExtensionRunner };\n\tsessionStartEvent?: SessionStartEvent;\n\torchestrationContext?: OrchestrationContext;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tmode?: ExtensionMode;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\nexport interface PromptOptions {\n\texpandPromptTemplates?: boolean;\n\timages?: ImageContent[];\n\tstreamingBehavior?: \"steer\" | \"followUp\";\n\tsource?: InputSource;\n\tpreflightResult?: (success: boolean) => void;\n}\n\nexport interface ModelCycleResult {\n\tmodel: Model<Api>;\n\tthinkingLevel: ThinkingLevel;\n\tisScoped: boolean;\n}\n\nexport interface SessionStats {\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t};\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\nexport interface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\nexport const THINKING_LEVELS: ThinkingLevel[] = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\nexport function customMessageExcludesContext(message: CustomMessage): boolean {\n\treturn (message as CustomMessage & { excludeFromContext?: boolean }).excludeFromContext === true;\n}\n\nexport type AssistantMessageWithError = AssistantMessage;\n"]}
|
|
1
|
+
{"version":3,"file":"agent-session-types.d.ts","sourceRoot":"","sources":["../../src/core/agent-session-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,KAAK,EACL,UAAU,EACV,YAAY,EACZ,SAAS,EACT,aAAa,EACb,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,GAAG,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC5G,OAAO,KAAK,EACX,YAAY,EACZ,8BAA8B,EAC9B,sBAAsB,EACtB,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,oBAAoB,EACpB,iBAAiB,EACjB,eAAe,EACf,cAAc,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAErE,MAAM,MAAM,iBAAiB,GAC1B,UAAU,GACV;IACA,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3B,GACD;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAA;CAAE,GACzE;IAAE,IAAI,EAAE,0BAA0B,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,GACtD;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC1D;IACA,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,aAAa,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;IACtC,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;CACnC,GACD;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,wBAAwB,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACzD;IACA,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,UAAU,CAAC;IAC5C,MAAM,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACrB,GACD;IACA,IAAI,EAAE,wBAAwB,CAAC;IAC/B,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,uBAAuB,GAAG,SAAS,CAAC;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACrB,GACD;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACzG;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtF,MAAM,MAAM,yBAAyB,GAAG,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;AAE3E,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAEzF,MAAM,WAAW,0BAA0B;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,yBAAyB,CAAC;CAClC;AAED,eAAO,MAAM,wCAAwC;aACpD,+BAA+B;CACtB,CAAC;AAEX,MAAM,WAAW,wBAAwB;IACxC,QAAQ,IAAI,OAAO,CAAC;IACpB,KAAK,IAAI,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC;IAClD,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAC;CAClD;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;CAClC;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,wBAAwB,GAAG,SAAS,GAAG,YAAY,EAAE,CAOlG;AAED,wBAAgB,8BAA8B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG5F;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CASzD;AAED,wBAAgB,+BAA+B,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAUzE;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAEnE;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,KAAK,CAAC;IACb,cAAc,EAAE,cAAc,CAAC;IAC/B,eAAe,EAAE,eAAe,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IAC3E,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,aAAa,EAAE,aAAa,CAAC;IAC7B,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC9C,kBAAkB,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,uBAAuB,EAAE,eAAe,CAAA;KAAE,CAAC;IACnF,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IACjC,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,qBAAqB,CAAC,EAAE,8BAA8B,CAAC;IACvD,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,OAAO,CAAC,EAAE,sBAAsB,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC7B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,iBAAiB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACzC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACd,CAAC;IACF,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,YAAY,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,cAAc,CAAC;IAC3B,UAAU,EAAE,UAAU,CAAC;CACvB;AAED,eAAO,MAAM,eAAe,EAAE,aAAa,EAAgD,CAAC;AAE5F,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAE5E;AAED,MAAM,MAAM,yBAAyB,GAAG,gBAAgB,CAAC","sourcesContent":["import type {\n\tAgent,\n\tAgentEvent,\n\tAgentMessage,\n\tAgentTool,\n\tThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport type { Api, AssistantMessage, ImageContent, Model, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport type {\n\tContextUsage,\n\tExtensionCommandContextActions,\n\tExtensionErrorListener,\n\tExtensionMode,\n\tExtensionUIContext,\n\tInputSource,\n\tOrchestrationContext,\n\tSessionStartEvent,\n\tShutdownHandler,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nimport type { CustomMessage } from \"./messages.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport type { SessionManager } from \"./session-manager.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { SourceInfo } from \"./source-info.ts\";\nimport type { ContextCompactionResult } from \"./compaction/index.ts\";\n\nexport type AgentSessionEvent =\n\t| AgentEvent\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"context_compaction_start\"; reason: \"manual\" }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| {\n\t\t\ttype: \"model_changed\";\n\t\t\tmodel: Model<Api>;\n\t\t\tpreviousModel: Model<Api> | undefined;\n\t\t\tsource: \"set\" | \"cycle\" | \"restore\";\n\t }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| { type: \"context_window_changed\"; contextWindow: number }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: ContextCompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| {\n\t\t\ttype: \"context_compaction_end\";\n\t\t\treason: \"manual\";\n\t\t\tresult: ContextCompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: false;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"agent_continue_error\"; source: \"post_compaction\"; errorMessage: string }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string };\n\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\nexport type ContextWindowReplaySource = \"session\" | \"model-settings\" | \"global-settings\";\n\nexport interface ContextWindowReplayRequest {\n\tcontextWindow: number;\n\tsource: ContextWindowReplaySource;\n}\n\nexport const COPILOT_CONTEXT_WINDOW_SELECTION_OPTIONS = {\n\tallowCopilotLongContextFallback: true,\n} as const;\n\nexport interface PendingAgentMessageQueue {\n\thasItems(): boolean;\n\tdrain(): AgentMessage[];\n}\n\nexport interface AgentQueueAccess {\n\treadonly steeringQueue?: PendingAgentMessageQueue;\n\treadonly followUpQueue?: PendingAgentMessageQueue;\n}\n\nexport interface DrainedAgentQueues {\n\treadonly steering: AgentMessage[];\n\treadonly followUp: AgentMessage[];\n}\n\nexport interface InterruptQueueHold {\n\treadonly steering: AgentMessage[];\n\treadonly followUp: AgentMessage[];\n}\n\nexport function drainAgentMessageQueue(queue: PendingAgentMessageQueue | undefined): AgentMessage[] {\n\tif (!queue) return [];\n\tconst drained: AgentMessage[] = [];\n\twhile (queue.hasItems()) {\n\t\tdrained.push(...queue.drain());\n\t}\n\treturn drained;\n}\n\nexport function normalizeInterruptAbortMessage(value: string | undefined): string | undefined {\n\tconst trimmed = value?.trim();\n\treturn trimmed && trimmed.length > 0 ? trimmed : undefined;\n}\n\nexport function isGenericAbortText(value: string): boolean {\n\tconst normalized = value.trim().toLowerCase().replace(/[.!]+$/, \"\");\n\treturn (\n\t\tnormalized === \"operation aborted\" ||\n\t\tnormalized === \"the operation was aborted\" ||\n\t\tnormalized === \"request was aborted\" ||\n\t\tnormalized === \"this operation was aborted\" ||\n\t\tnormalized === \"extension custom ui aborted\"\n\t);\n}\n\nexport function isSingleGenericAbortTextContent(content: unknown): boolean {\n\treturn (\n\t\tArray.isArray(content) &&\n\t\tcontent.length === 1 &&\n\t\ttypeof content[0] === \"object\" &&\n\t\tcontent[0] !== null &&\n\t\t(content[0] as { type?: unknown }).type === \"text\" &&\n\t\ttypeof (content[0] as { text?: unknown }).text === \"string\" &&\n\t\tisGenericAbortText((content[0] as { text: string }).text)\n\t);\n}\n\nexport function replacementAbortContent(text: string): TextContent[] {\n\treturn [{ type: \"text\", text }];\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\tscopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\tresourceLoader: ResourceLoader;\n\tcustomTools?: ToolDefinition[];\n\tmodelRegistry: ModelRegistry;\n\tinitialActiveToolNames?: string[];\n\tallowedToolNames?: string[];\n\texcludedToolNames?: string[];\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\textensionRunnerRef?: { current?: import(\"./extensions/index.ts\").ExtensionRunner };\n\tsessionStartEvent?: SessionStartEvent;\n\torchestrationContext?: OrchestrationContext;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tmode?: ExtensionMode;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\nexport interface PromptOptions {\n\texpandPromptTemplates?: boolean;\n\timages?: ImageContent[];\n\tstreamingBehavior?: \"steer\" | \"followUp\";\n\tsource?: InputSource;\n\tpreflightResult?: (success: boolean) => void;\n}\n\nexport interface ModelCycleResult {\n\tmodel: Model<Api>;\n\tthinkingLevel: ThinkingLevel;\n\tisScoped: boolean;\n}\n\nexport interface SessionStats {\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t};\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\nexport interface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\nexport const THINKING_LEVELS: ThinkingLevel[] = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\nexport function customMessageExcludesContext(message: CustomMessage): boolean {\n\treturn (message as CustomMessage & { excludeFromContext?: boolean }).excludeFromContext === true;\n}\n\nexport type AssistantMessageWithError = AssistantMessage;\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-session-types.js","sourceRoot":"","sources":["../../src/core/agent-session-types.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"agent-session-types.js","sourceRoot":"","sources":["../../src/core/agent-session-types.ts"],"names":[],"mappings":"AA2EA,MAAM,CAAC,MAAM,wCAAwC,GAAG;IACvD,+BAA+B,EAAE,IAAI;CAC5B,CAAC;AAsBX,MAAM,UAAU,sBAAsB,CAAC,KAA2C;IACjF,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,OAAO,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,KAAyB;IACvE,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,OAAO,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC/C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACpE,OAAO,CACN,UAAU,KAAK,mBAAmB;QAClC,UAAU,KAAK,2BAA2B;QAC1C,UAAU,KAAK,qBAAqB;QACpC,UAAU,KAAK,4BAA4B;QAC3C,UAAU,KAAK,6BAA6B,CAC5C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,+BAA+B,CAAC,OAAgB;IAC/D,OAAO,CACN,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QACtB,OAAO,CAAC,MAAM,KAAK,CAAC;QACpB,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,QAAQ;QAC9B,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI;QAClB,OAAO,CAAC,CAAC,CAAwB,CAAC,IAAI,KAAK,MAAM;QAClD,OAAQ,OAAO,CAAC,CAAC,CAAwB,CAAC,IAAI,KAAK,QAAQ;QAC3D,kBAAkB,CAAE,OAAO,CAAC,CAAC,CAAsB,CAAC,IAAI,CAAC,CACzD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,IAAY;IACnD,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AACjC,CAAC;AAkED,MAAM,CAAC,MAAM,eAAe,GAAoB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE5F,MAAM,UAAU,4BAA4B,CAAC,OAAsB;IAClE,OAAQ,OAA4D,CAAC,kBAAkB,KAAK,IAAI,CAAC;AAClG,CAAC","sourcesContent":["import type {\n\tAgent,\n\tAgentEvent,\n\tAgentMessage,\n\tAgentTool,\n\tThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport type { Api, AssistantMessage, ImageContent, Model, TextContent } from \"@earendil-works/pi-ai/compat\";\nimport type {\n\tContextUsage,\n\tExtensionCommandContextActions,\n\tExtensionErrorListener,\n\tExtensionMode,\n\tExtensionUIContext,\n\tInputSource,\n\tOrchestrationContext,\n\tSessionStartEvent,\n\tShutdownHandler,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nimport type { CustomMessage } from \"./messages.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport type { SessionManager } from \"./session-manager.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport type { SourceInfo } from \"./source-info.ts\";\nimport type { ContextCompactionResult } from \"./compaction/index.ts\";\n\nexport type AgentSessionEvent =\n\t| AgentEvent\n\t| {\n\t\t\ttype: \"queue_update\";\n\t\t\tsteering: readonly string[];\n\t\t\tfollowUp: readonly string[];\n\t }\n\t| { type: \"compaction_start\"; reason: \"manual\" | \"threshold\" | \"overflow\" }\n\t| { type: \"context_compaction_start\"; reason: \"manual\" }\n\t| { type: \"session_info_changed\"; name: string | undefined }\n\t| {\n\t\t\ttype: \"model_changed\";\n\t\t\tmodel: Model<Api>;\n\t\t\tpreviousModel: Model<Api> | undefined;\n\t\t\tsource: \"set\" | \"cycle\" | \"restore\";\n\t }\n\t| { type: \"thinking_level_changed\"; level: ThinkingLevel }\n\t| { type: \"context_window_changed\"; contextWindow: number }\n\t| {\n\t\t\ttype: \"compaction_end\";\n\t\t\treason: \"manual\" | \"threshold\" | \"overflow\";\n\t\t\tresult: ContextCompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: boolean;\n\t\t\terrorMessage?: string;\n\t }\n\t| {\n\t\t\ttype: \"context_compaction_end\";\n\t\t\treason: \"manual\";\n\t\t\tresult: ContextCompactionResult | undefined;\n\t\t\taborted: boolean;\n\t\t\twillRetry: false;\n\t\t\terrorMessage?: string;\n\t }\n\t| { type: \"agent_continue_error\"; source: \"post_compaction\"; errorMessage: string }\n\t| { type: \"auto_retry_start\"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }\n\t| { type: \"auto_retry_end\"; success: boolean; attempt: number; finalError?: string };\n\nexport type AgentSessionEventListener = (event: AgentSessionEvent) => void;\n\nexport type ContextWindowReplaySource = \"session\" | \"model-settings\" | \"global-settings\";\n\nexport interface ContextWindowReplayRequest {\n\tcontextWindow: number;\n\tsource: ContextWindowReplaySource;\n}\n\nexport const COPILOT_CONTEXT_WINDOW_SELECTION_OPTIONS = {\n\tallowCopilotLongContextFallback: true,\n} as const;\n\nexport interface PendingAgentMessageQueue {\n\thasItems(): boolean;\n\tdrain(): AgentMessage[];\n}\n\nexport interface AgentQueueAccess {\n\treadonly steeringQueue?: PendingAgentMessageQueue;\n\treadonly followUpQueue?: PendingAgentMessageQueue;\n}\n\nexport interface DrainedAgentQueues {\n\treadonly steering: AgentMessage[];\n\treadonly followUp: AgentMessage[];\n}\n\nexport interface InterruptQueueHold {\n\treadonly steering: AgentMessage[];\n\treadonly followUp: AgentMessage[];\n}\n\nexport function drainAgentMessageQueue(queue: PendingAgentMessageQueue | undefined): AgentMessage[] {\n\tif (!queue) return [];\n\tconst drained: AgentMessage[] = [];\n\twhile (queue.hasItems()) {\n\t\tdrained.push(...queue.drain());\n\t}\n\treturn drained;\n}\n\nexport function normalizeInterruptAbortMessage(value: string | undefined): string | undefined {\n\tconst trimmed = value?.trim();\n\treturn trimmed && trimmed.length > 0 ? trimmed : undefined;\n}\n\nexport function isGenericAbortText(value: string): boolean {\n\tconst normalized = value.trim().toLowerCase().replace(/[.!]+$/, \"\");\n\treturn (\n\t\tnormalized === \"operation aborted\" ||\n\t\tnormalized === \"the operation was aborted\" ||\n\t\tnormalized === \"request was aborted\" ||\n\t\tnormalized === \"this operation was aborted\" ||\n\t\tnormalized === \"extension custom ui aborted\"\n\t);\n}\n\nexport function isSingleGenericAbortTextContent(content: unknown): boolean {\n\treturn (\n\t\tArray.isArray(content) &&\n\t\tcontent.length === 1 &&\n\t\ttypeof content[0] === \"object\" &&\n\t\tcontent[0] !== null &&\n\t\t(content[0] as { type?: unknown }).type === \"text\" &&\n\t\ttypeof (content[0] as { text?: unknown }).text === \"string\" &&\n\t\tisGenericAbortText((content[0] as { text: string }).text)\n\t);\n}\n\nexport function replacementAbortContent(text: string): TextContent[] {\n\treturn [{ type: \"text\", text }];\n}\n\nexport interface AgentSessionConfig {\n\tagent: Agent;\n\tsessionManager: SessionManager;\n\tsettingsManager: SettingsManager;\n\tcwd: string;\n\tscopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\tresourceLoader: ResourceLoader;\n\tcustomTools?: ToolDefinition[];\n\tmodelRegistry: ModelRegistry;\n\tinitialActiveToolNames?: string[];\n\tallowedToolNames?: string[];\n\texcludedToolNames?: string[];\n\tbaseToolsOverride?: Record<string, AgentTool>;\n\textensionRunnerRef?: { current?: import(\"./extensions/index.ts\").ExtensionRunner };\n\tsessionStartEvent?: SessionStartEvent;\n\torchestrationContext?: OrchestrationContext;\n}\n\nexport interface ExtensionBindings {\n\tuiContext?: ExtensionUIContext;\n\tmode?: ExtensionMode;\n\tcommandContextActions?: ExtensionCommandContextActions;\n\tshutdownHandler?: ShutdownHandler;\n\tonError?: ExtensionErrorListener;\n}\n\nexport interface PromptOptions {\n\texpandPromptTemplates?: boolean;\n\timages?: ImageContent[];\n\tstreamingBehavior?: \"steer\" | \"followUp\";\n\tsource?: InputSource;\n\tpreflightResult?: (success: boolean) => void;\n}\n\nexport interface ModelCycleResult {\n\tmodel: Model<Api>;\n\tthinkingLevel: ThinkingLevel;\n\tisScoped: boolean;\n}\n\nexport interface SessionStats {\n\tsessionFile: string | undefined;\n\tsessionId: string;\n\tuserMessages: number;\n\tassistantMessages: number;\n\ttoolCalls: number;\n\ttoolResults: number;\n\ttotalMessages: number;\n\ttokens: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\ttotal: number;\n\t};\n\tcost: number;\n\tcontextUsage?: ContextUsage;\n}\n\nexport interface ToolDefinitionEntry {\n\tdefinition: ToolDefinition;\n\tsourceInfo: SourceInfo;\n}\n\nexport const THINKING_LEVELS: ThinkingLevel[] = [\"off\", \"minimal\", \"low\", \"medium\", \"high\"];\n\nexport function customMessageExcludesContext(message: CustomMessage): boolean {\n\treturn (message as CustomMessage & { excludeFromContext?: boolean }).excludeFromContext === true;\n}\n\nexport type AssistantMessageWithError = AssistantMessage;\n"]}
|
|
@@ -57,8 +57,18 @@ export declare const COPILOT_CATALOG_API_VERSION = "2026-06-01";
|
|
|
57
57
|
* for Copilot token refresh and model-policy calls, plus the dated API version.
|
|
58
58
|
*/
|
|
59
59
|
export declare const COPILOT_CATALOG_HEADERS: Readonly<Record<string, string>>;
|
|
60
|
-
/** Default
|
|
60
|
+
/** Default Copilot CAPI base URL for OAuth tokens when no route can be resolved. */
|
|
61
61
|
export declare const DEFAULT_COPILOT_API_BASE_URL = "https://api.individual.githubcopilot.com";
|
|
62
|
+
/** GitHub Copilot public routing hub for PAT-based `COPILOT_GITHUB_TOKEN` auth. */
|
|
63
|
+
export declare const COPILOT_PUBLIC_API_BASE_URL = "https://api.githubcopilot.com";
|
|
64
|
+
/** Enterprise Copilot CAPI host for GHES/non-github.com server URLs. */
|
|
65
|
+
export declare const COPILOT_ENTERPRISE_API_BASE_URL = "https://api.enterprise.githubcopilot.com";
|
|
66
|
+
export declare const COPILOT_GITHUB_TOKEN_ENV = "COPILOT_GITHUB_TOKEN";
|
|
67
|
+
export declare const COPILOT_API_TARGET_ENV = "COPILOT_API_TARGET";
|
|
68
|
+
export declare const GITHUB_COPILOT_BASE_URL_ENV = "GITHUB_COPILOT_BASE_URL";
|
|
69
|
+
export declare const GITHUB_SERVER_URL_ENV = "GITHUB_SERVER_URL";
|
|
70
|
+
type CopilotEnvironment = Record<string, string | undefined>;
|
|
71
|
+
export declare function copilotTokenFromEnvironment(env?: CopilotEnvironment): string | undefined;
|
|
62
72
|
/** Disk-cache freshness window, matching the Copilot CLI's list-models cache TTL. */
|
|
63
73
|
export declare const COPILOT_CATALOG_CACHE_TTL_MS: number;
|
|
64
74
|
/** Current on-disk cache schema version. */
|
|
@@ -67,11 +77,13 @@ export declare const COPILOT_CATALOG_CACHE_VERSION: 3;
|
|
|
67
77
|
* Resolve the Copilot CAPI base URL.
|
|
68
78
|
*
|
|
69
79
|
* Copilot access tokens embed a `proxy-ep=proxy.<host>` segment; the API host is the same host with
|
|
70
|
-
* `proxy.` swapped for `api.`.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
80
|
+
* `proxy.` swapped for `api.`. Env-token routing resolves explicit `COPILOT_API_TARGET` /
|
|
81
|
+
* `GITHUB_COPILOT_BASE_URL` overrides, then `GITHUB_SERVER_URL` (`*.ghe.com` ->
|
|
82
|
+
* `copilot-api.<tenant>.ghe.com`, other non-github.com -> `api.enterprise.githubcopilot.com`),
|
|
83
|
+
* then the public Copilot routing hub `api.githubcopilot.com` for `COPILOT_GITHUB_TOKEN`. Stored
|
|
84
|
+
* OAuth credentials still fall back to the generated individual host when no token route is known.
|
|
73
85
|
*/
|
|
74
|
-
export declare function copilotApiBaseUrlFromToken(token: string | undefined, enterpriseDomain?: string): string;
|
|
86
|
+
export declare function copilotApiBaseUrlFromToken(token: string | undefined, enterpriseDomain?: string, env?: CopilotEnvironment): string;
|
|
75
87
|
/** Raw token limits parsed from a CAPI model entry. */
|
|
76
88
|
export interface CopilotModelLimits {
|
|
77
89
|
/** `capabilities.limits.max_prompt_tokens` — maximum prompt/input budget (the hard input cap). */
|
|
@@ -147,4 +159,5 @@ export declare function copilotCatalogCachePath(agentDir: string): string;
|
|
|
147
159
|
* was applied. No-op (returns false) without a token or a host-matching cached catalog.
|
|
148
160
|
*/
|
|
149
161
|
export declare function seedActiveCopilotModelCatalogFromCache(accessToken: string | undefined, cachePath: string, now?: number): boolean;
|
|
162
|
+
export {};
|
|
150
163
|
//# sourceMappingURL=copilot-model-catalog.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copilot-model-catalog.d.ts","sourceRoot":"","sources":["../../src/core/copilot-model-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot model catalog (CAPI) — dynamic prompt-token budgets.\n *\n * GitHub's Copilot API (CAPI) exposes distinct model limits via `GET {baseUrl}/models`:\n *\n * - `capabilities.limits.max_context_window_tokens` is the model's total context capacity\n * (prompt + completion reserve).\n * - `capabilities.limits.max_prompt_tokens` is the maximum prompt/input budget Atomic can safely\n * fill before the provider must reserve output tokens.\n * - `billing.token_prices.<tier>.context_max` is a prompt-token billing/selection threshold. The\n * `default` tier is the short prompt budget (e.g. gpt-5.5 272k, Claude 200k); a\n * `long_context` tier adds a selectable larger prompt budget (e.g. gpt-5.5 922k, Claude 936k).\n *\n * Atomic shows the model's full context window for the selectable long tier (the\n * `max_context_window_tokens` total, e.g. 1_000_000/1_050_000), matching how the native `openai/*`\n * and `anthropic/*` providers advertise these models. Because GitHub enforces a lower server-side\n * prompt cap (`max_prompt_tokens`, e.g. 936k/922k) below that total, the prompt cap is retained as\n * an internal effective input budget (`CopilotModelContext.maxInputTokens`) that drives compaction\n * thresholds and the overflow-recovery guard, so the branded total can be displayed without\n * overrunning the server limit. The default (short) tier stays at the `default` billing tier's\n * prompt budget.\n *\n * This data is intentionally NOT baked into a static map: GitHub adds/removes models and retiers\n * windows over time (e.g. a model that disappears from the catalog), so a hardcoded snapshot goes\n * stale. Instead the catalog is fetched live (gated on the user actually having the GitHub Copilot\n * provider) and cached on disk for a short TTL, exactly like the Copilot CLI.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/** Resolved input-token context window(s) for a single Copilot model. */\nexport interface CopilotModelContext {\n\t/**\n\t * Base/displayed context window — shown in the footer. The default tier's `context_max`, or the\n\t * model-level `max_prompt_tokens` fallback otherwise.\n\t */\n\tcontextWindow: number;\n\t/**\n\t * Selectable windows (`[default, long]`) when the model exposes a `long_context` tier larger than\n\t * its default; absent for single-window models. The long entry is the model's full\n\t * `max_context_window_tokens` (total capacity) when advertised, matching `openai/*` and\n\t * `anthropic/*`.\n\t */\n\tcontextWindowOptions?: readonly number[];\n\t/**\n\t * Hard prompt/input cap (`max_prompt_tokens`) when it sits below the displayed long window. Used\n\t * as the effective input budget for compaction thresholds and overflow recovery so the branded\n\t * total can be shown without overrunning GitHub's server-side prompt limit. Absent when the\n\t * displayed window already equals the input cap.\n\t */\n\tmaxInputTokens?: number;\n}\n\n/** Map of model id → resolved input-token context window(s). */\nexport type CopilotModelCatalog = ReadonlyMap<string, CopilotModelContext>;\n\n/** Safety fallback when a model reports neither `max_prompt_tokens` nor `max_context_window_tokens`. */\nexport const COPILOT_CONTEXT_WINDOW_FALLBACK = 128_000;\n\nexport const COPILOT_CATALOG_API_VERSION = \"2026-06-01\";\n\n/**\n * Headers GitHub's CAPI expects for catalog reads. Mirrors the editor headers pi-ai already sends\n * for Copilot token refresh and model-policy calls, plus the dated API version.\n */\nexport const COPILOT_CATALOG_HEADERS: Readonly<Record<string, string>> = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n\t\"X-GitHub-Api-Version\": COPILOT_CATALOG_API_VERSION,\n};\n\n/** Default (non-enterprise) Copilot CAPI base URL when the token has no resolvable `proxy-ep`. */\nexport const DEFAULT_COPILOT_API_BASE_URL = \"https://api.individual.githubcopilot.com\";\n\n/** Disk-cache freshness window, matching the Copilot CLI's list-models cache TTL. */\nexport const COPILOT_CATALOG_CACHE_TTL_MS = 30 * 60 * 1000;\n\n/** Current on-disk cache schema version. */\nexport const COPILOT_CATALOG_CACHE_VERSION = 3 as const;\n\n/**\n * Resolve the Copilot CAPI base URL.\n *\n * Copilot access tokens embed a `proxy-ep=proxy.<host>` segment; the API host is the same host with\n * `proxy.` swapped for `api.`. Falls back to the enterprise host or the individual default. (pi-ai\n * exposes an equivalent helper, but its published `dist` mangles the export name, so the small,\n * stable parsing logic is reimplemented here.)\n */\nexport function copilotApiBaseUrlFromToken(token: string | undefined, enterpriseDomain?: string): string {\n\tif (token) {\n\t\tconst match = token.match(/proxy-ep=([^;]+)/);\n\t\tif (match) {\n\t\t\treturn `https://${match[1].replace(/^proxy\\./, \"api.\")}`;\n\t\t}\n\t}\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\treturn DEFAULT_COPILOT_API_BASE_URL;\n}\n\nfunction trimTrailingSlash(url: string): string {\n\treturn url.replace(/\\/+$/, \"\");\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn value && typeof value === \"object\" ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction toPositiveInt(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\n/** Raw token limits parsed from a CAPI model entry. */\nexport interface CopilotModelLimits {\n\t/** `capabilities.limits.max_prompt_tokens` — maximum prompt/input budget (the hard input cap). */\n\tmaxPromptTokens?: number;\n\t/** `capabilities.limits.max_context_window_tokens` — total context capacity (the displayed long tier). */\n\tmaxContextWindowTokens?: number;\n\t/** `capabilities.limits.max_output_tokens` — output reserve; derives the input cap when `max_prompt_tokens` is absent. */\n\tmaxOutputTokens?: number;\n\t/** `billing.token_prices.default.context_max` — default-tier prompt threshold. */\n\tdefaultContextMax?: number;\n\t/** `billing.token_prices.long_context.context_max` — long-context prompt threshold. */\n\tlongContextMax?: number;\n}\n\n/**\n * Resolve a model's input-token context window(s) from its CAPI limits.\n *\n * `contextWindow` is the model's base input budget — the default tier's `context_max` when tiered,\n * otherwise `max_prompt_tokens ?? max_context_window_tokens ?? 128_000`. A `long_context` tier that\n * is larger than the base adds a second selectable window. Returns `undefined` when the entry\n * carries no usable limit signal at all.\n */\nexport function resolveCopilotModelContext(limits: CopilotModelLimits): CopilotModelContext | undefined {\n\tconst hasSignal =\n\t\tlimits.maxPromptTokens !== undefined ||\n\t\tlimits.maxContextWindowTokens !== undefined ||\n\t\tlimits.defaultContextMax !== undefined ||\n\t\tlimits.longContextMax !== undefined;\n\tif (!hasSignal) return undefined;\n\n\tconst maxInput = limits.maxPromptTokens ?? limits.maxContextWindowTokens ?? COPILOT_CONTEXT_WINDOW_FALLBACK;\n\tconst base = limits.defaultContextMax ?? maxInput;\n\tif (limits.longContextMax !== undefined && limits.longContextMax > base) {\n\t\t// Display the model's full context window as the long tier (matching openai/* and anthropic/*)\n\t\t// when CAPI advertises it; otherwise fall back to the long-context prompt threshold for\n\t\t// older/sparse payloads.\n\t\tconst longWindow = limits.maxContextWindowTokens ?? limits.longContextMax;\n\t\t// The hard prompt/input cap GitHub enforces server-side: prefer max_prompt_tokens, else derive\n\t\t// it from total − output reserve, else fall back to the long-context prompt threshold.\n\t\tconst derivedInputCap =\n\t\t\tlimits.maxContextWindowTokens !== undefined && limits.maxOutputTokens !== undefined\n\t\t\t\t? limits.maxContextWindowTokens - limits.maxOutputTokens\n\t\t\t\t: undefined;\n\t\tconst inputCap =\n\t\t\tlimits.maxPromptTokens ??\n\t\t\t(derivedInputCap !== undefined && derivedInputCap > 0 ? derivedInputCap : undefined) ??\n\t\t\tlimits.longContextMax;\n\t\t// Only carry the cap when the displayed long window actually exceeds it (the branded-total\n\t\t// case); when they coincide there is no gap and the input budget is just the window.\n\t\treturn longWindow > inputCap\n\t\t\t? { contextWindow: base, contextWindowOptions: [base, longWindow], maxInputTokens: inputCap }\n\t\t\t: { contextWindow: base, contextWindowOptions: [base, longWindow] };\n\t}\n\treturn { contextWindow: base };\n}\n\n/**\n * Parse a raw CAPI `/models` response body into an input-token context-window catalog.\n */\nexport function parseCopilotModelCatalog(body: unknown): CopilotModelCatalog {\n\tconst catalog = new Map<string, CopilotModelContext>();\n\tconst data = asRecord(body)?.data;\n\tif (!Array.isArray(data)) return catalog;\n\n\tfor (const entry of data) {\n\t\tconst record = asRecord(entry);\n\t\tif (!record) continue;\n\t\tconst id = record.id;\n\t\tif (typeof id !== \"string\" || id.length === 0) continue;\n\n\t\tconst limits = asRecord(asRecord(record.capabilities)?.limits);\n\t\tconst prices = asRecord(asRecord(record.billing)?.token_prices);\n\t\tconst context = resolveCopilotModelContext({\n\t\t\tmaxPromptTokens: toPositiveInt(limits?.max_prompt_tokens),\n\t\t\tmaxContextWindowTokens: toPositiveInt(limits?.max_context_window_tokens),\n\t\t\tmaxOutputTokens: toPositiveInt(limits?.max_output_tokens),\n\t\t\tdefaultContextMax: toPositiveInt(asRecord(prices?.default)?.context_max),\n\t\t\tlongContextMax: toPositiveInt(asRecord(prices?.long_context)?.context_max),\n\t\t});\n\t\tif (context) catalog.set(id, context);\n\t}\n\n\treturn catalog;\n}\n\nexport interface FetchCopilotModelCatalogOptions {\n\t/** Valid Copilot CAPI bearer token (e.g. from `modelRegistry.getApiKeyForProvider`). */\n\ttoken: string;\n\t/** Override the resolved base URL; defaults to one derived from the token. */\n\tbaseUrl?: string;\n\t/** Enterprise domain, used for base-URL resolution when the token lacks a `proxy-ep`. */\n\tenterpriseDomain?: string;\n\t/** Extra/override request headers. */\n\theaders?: Record<string, string>;\n\t/** Injectable `fetch` for testing. */\n\tfetchImpl?: typeof fetch;\n\t/** Abort signal. */\n\tsignal?: AbortSignal;\n}\n\n/** Fetch and parse the live Copilot model catalog from CAPI `GET {baseUrl}/models`. */\nexport async function fetchCopilotModelCatalog(options: FetchCopilotModelCatalogOptions): Promise<CopilotModelCatalog> {\n\tconst fetchImpl = options.fetchImpl ?? fetch;\n\tconst baseUrl = options.baseUrl ?? copilotApiBaseUrlFromToken(options.token, options.enterpriseDomain);\n\tconst response = await fetchImpl(`${trimTrailingSlash(baseUrl)}/models`, {\n\t\tmethod: \"GET\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${options.token}`,\n\t\t\t...COPILOT_CATALOG_HEADERS,\n\t\t\t...options.headers,\n\t\t},\n\t\t...(options.signal ? { signal: options.signal } : {}),\n\t});\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub Copilot /models request failed: ${response.status} ${response.statusText}`);\n\t}\n\treturn parseCopilotModelCatalog(await response.json());\n}\n\n// ----------------------------------------------------------------------------\n// Active in-memory catalog (consulted by the model registry).\n//\n// Empty by default, so with no Copilot auth / no successful fetch the registry leaves Copilot\n// model context windows untouched and the picker never appears.\n// ----------------------------------------------------------------------------\n\nlet activeCatalog: CopilotModelCatalog = new Map();\n\n/** Replace the active catalog the registry derives context windows from. */\nexport function setActiveCopilotModelCatalog(catalog: CopilotModelCatalog): void {\n\tactiveCatalog = catalog;\n}\n\n/** The active catalog (empty until a successful auth-gated fetch/cache load). */\nexport function getActiveCopilotModelCatalog(): CopilotModelCatalog {\n\treturn activeCatalog;\n}\n\n/** Reset the active catalog (primarily for tests). */\nexport function clearActiveCopilotModelCatalog(): void {\n\tactiveCatalog = new Map();\n}\n\n// ----------------------------------------------------------------------------\n// Disk cache.\n// ----------------------------------------------------------------------------\n\ninterface CopilotCatalogCacheFile {\n\tversion: typeof COPILOT_CATALOG_CACHE_VERSION;\n\t/** CAPI host the catalog was fetched from; cache misses on host change (e.g. enterprise switch). */\n\thost: string;\n\t/** Epoch ms the catalog was fetched. */\n\tfetchedAt: number;\n\tmodels: Record<string, CopilotModelContext>;\n}\n\nfunction hostFromBaseUrl(baseUrl: string): string {\n\ttry {\n\t\treturn new URL(baseUrl).host;\n\t} catch {\n\t\treturn baseUrl;\n\t}\n}\n\nexport interface ReadCopilotCatalogCacheOptions {\n\t/** Expected CAPI host; a cached file from a different host is ignored. */\n\thost: string;\n\t/** Current epoch ms (injectable for tests). */\n\tnow?: number;\n\t/** Freshness window; defaults to {@link COPILOT_CATALOG_CACHE_TTL_MS}. */\n\tttlMs?: number;\n}\n\nfunction sanitizeCachedContext(value: unknown): CopilotModelContext | undefined {\n\tconst record = asRecord(value);\n\tconst contextWindow = toPositiveInt(record?.contextWindow);\n\tif (contextWindow === undefined) return undefined;\n\tconst maxInputTokens = toPositiveInt(record?.maxInputTokens);\n\tconst rawOptions = record?.contextWindowOptions;\n\tif (Array.isArray(rawOptions)) {\n\t\tconst options = rawOptions.map(toPositiveInt).filter((n): n is number => n !== undefined);\n\t\tif (options.length > 1) {\n\t\t\treturn maxInputTokens !== undefined\n\t\t\t\t? { contextWindow, contextWindowOptions: options, maxInputTokens }\n\t\t\t\t: { contextWindow, contextWindowOptions: options };\n\t\t}\n\t}\n\treturn maxInputTokens !== undefined ? { contextWindow, maxInputTokens } : { contextWindow };\n}\n\n/** Read a fresh, host-matching catalog from the cache file, or `undefined` if missing/stale/invalid. */\nexport function readCopilotCatalogCache(\n\tpath: string,\n\toptions: ReadCopilotCatalogCacheOptions,\n): CopilotModelCatalog | undefined {\n\tlet parsed: CopilotCatalogCacheFile;\n\ttry {\n\t\tif (!existsSync(path)) return undefined;\n\t\tparsed = JSON.parse(readFileSync(path, \"utf8\")) as CopilotCatalogCacheFile;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!parsed || parsed.version !== COPILOT_CATALOG_CACHE_VERSION) return undefined;\n\tif (parsed.host !== options.host) return undefined;\n\tconst now = options.now ?? Date.now();\n\tconst ttlMs = options.ttlMs ?? COPILOT_CATALOG_CACHE_TTL_MS;\n\tif (typeof parsed.fetchedAt !== \"number\" || now - parsed.fetchedAt >= ttlMs) return undefined;\n\tconst models = asRecord(parsed.models);\n\tif (!models) return undefined;\n\n\tconst catalog = new Map<string, CopilotModelContext>();\n\tfor (const [id, value] of Object.entries(models)) {\n\t\tconst context = sanitizeCachedContext(value);\n\t\tif (context) catalog.set(id, context);\n\t}\n\treturn catalog;\n}\n\n/** Write the catalog to the cache file (creating parent dirs). Best-effort; never throws. */\nexport function writeCopilotCatalogCache(\n\tpath: string,\n\tbaseUrl: string,\n\tcatalog: CopilotModelCatalog,\n\tnow?: number,\n): void {\n\tconst payload: CopilotCatalogCacheFile = {\n\t\tversion: COPILOT_CATALOG_CACHE_VERSION,\n\t\thost: hostFromBaseUrl(baseUrl),\n\t\tfetchedAt: now ?? Date.now(),\n\t\tmodels: Object.fromEntries(catalog),\n\t};\n\ttry {\n\t\tmkdirSync(dirname(path), { recursive: true });\n\t\twriteFileSync(path, JSON.stringify(payload), \"utf8\");\n\t} catch {\n\t\t// best-effort cache; ignore write failures\n\t}\n}\n\n/** Host component of a base URL, for matching {@link readCopilotCatalogCache} `host`. */\nexport function copilotCatalogCacheHost(baseUrl: string): string {\n\treturn hostFromBaseUrl(baseUrl);\n}\n\n/** Standard on-disk cache path for the Copilot model catalog under an agent directory. */\nexport function copilotCatalogCachePath(agentDir: string): string {\n\treturn join(agentDir, \"cache\", \"copilot-models.json\");\n}\n\n/**\n * Seed the active catalog synchronously from the on-disk cache, gated on a Copilot access token.\n *\n * Called at model-registry construction so a returning user's previously selected long-context\n * window is recognized before startup validation runs — otherwise the persisted choice would warn\n * (\"context window 936k is not supported…\") and reset until the async refresh completes. The cache\n * TTL is intentionally ignored here: stale-but-present windows are still valid for selection, and\n * the async loader independently refetches on its own freshness window. Returns true when a catalog\n * was applied. No-op (returns false) without a token or a host-matching cached catalog.\n */\nexport function seedActiveCopilotModelCatalogFromCache(\n\taccessToken: string | undefined,\n\tcachePath: string,\n\tnow?: number,\n): boolean {\n\tif (typeof accessToken !== \"string\" || accessToken.length === 0) return false;\n\tconst host = copilotCatalogCacheHost(copilotApiBaseUrlFromToken(accessToken));\n\tconst cached = readCopilotCatalogCache(cachePath, { host, now, ttlMs: Number.POSITIVE_INFINITY });\n\tif (!cached) return false;\n\tsetActiveCopilotModelCatalog(cached);\n\treturn true;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"copilot-model-catalog.d.ts","sourceRoot":"","sources":["../../src/core/copilot-model-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * GitHub Copilot model catalog (CAPI) — dynamic prompt-token budgets.\n *\n * GitHub's Copilot API (CAPI) exposes distinct model limits via `GET {baseUrl}/models`:\n *\n * - `capabilities.limits.max_context_window_tokens` is the model's total context capacity\n * (prompt + completion reserve).\n * - `capabilities.limits.max_prompt_tokens` is the maximum prompt/input budget Atomic can safely\n * fill before the provider must reserve output tokens.\n * - `billing.token_prices.<tier>.context_max` is a prompt-token billing/selection threshold. The\n * `default` tier is the short prompt budget (e.g. gpt-5.5 272k, Claude 200k); a\n * `long_context` tier adds a selectable larger prompt budget (e.g. gpt-5.5 922k, Claude 936k).\n *\n * Atomic shows the model's full context window for the selectable long tier (the\n * `max_context_window_tokens` total, e.g. 1_000_000/1_050_000), matching how the native `openai/*`\n * and `anthropic/*` providers advertise these models. Because GitHub enforces a lower server-side\n * prompt cap (`max_prompt_tokens`, e.g. 936k/922k) below that total, the prompt cap is retained as\n * an internal effective input budget (`CopilotModelContext.maxInputTokens`) that drives compaction\n * thresholds and the overflow-recovery guard, so the branded total can be displayed without\n * overrunning the server limit. The default (short) tier stays at the `default` billing tier's\n * prompt budget.\n *\n * This data is intentionally NOT baked into a static map: GitHub adds/removes models and retiers\n * windows over time (e.g. a model that disappears from the catalog), so a hardcoded snapshot goes\n * stale. Instead the catalog is fetched live (gated on the user actually having the GitHub Copilot\n * provider) and cached on disk for a short TTL, exactly like the Copilot CLI.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\n/** Resolved input-token context window(s) for a single Copilot model. */\nexport interface CopilotModelContext {\n\t/**\n\t * Base/displayed context window — shown in the footer. The default tier's `context_max`, or the\n\t * model-level `max_prompt_tokens` fallback otherwise.\n\t */\n\tcontextWindow: number;\n\t/**\n\t * Selectable windows (`[default, long]`) when the model exposes a `long_context` tier larger than\n\t * its default; absent for single-window models. The long entry is the model's full\n\t * `max_context_window_tokens` (total capacity) when advertised, matching `openai/*` and\n\t * `anthropic/*`.\n\t */\n\tcontextWindowOptions?: readonly number[];\n\t/**\n\t * Hard prompt/input cap (`max_prompt_tokens`) when it sits below the displayed long window. Used\n\t * as the effective input budget for compaction thresholds and overflow recovery so the branded\n\t * total can be shown without overrunning GitHub's server-side prompt limit. Absent when the\n\t * displayed window already equals the input cap.\n\t */\n\tmaxInputTokens?: number;\n}\n\n/** Map of model id → resolved input-token context window(s). */\nexport type CopilotModelCatalog = ReadonlyMap<string, CopilotModelContext>;\n\n/** Safety fallback when a model reports neither `max_prompt_tokens` nor `max_context_window_tokens`. */\nexport const COPILOT_CONTEXT_WINDOW_FALLBACK = 128_000;\n\nexport const COPILOT_CATALOG_API_VERSION = \"2026-06-01\";\n\n/**\n * Headers GitHub's CAPI expects for catalog reads. Mirrors the editor headers pi-ai already sends\n * for Copilot token refresh and model-policy calls, plus the dated API version.\n */\nexport const COPILOT_CATALOG_HEADERS: Readonly<Record<string, string>> = {\n\t\"User-Agent\": \"GitHubCopilotChat/0.35.0\",\n\t\"Editor-Version\": \"vscode/1.107.0\",\n\t\"Editor-Plugin-Version\": \"copilot-chat/0.35.0\",\n\t\"Copilot-Integration-Id\": \"vscode-chat\",\n\t\"X-GitHub-Api-Version\": COPILOT_CATALOG_API_VERSION,\n};\n\n/** Default Copilot CAPI base URL for OAuth tokens when no route can be resolved. */\nexport const DEFAULT_COPILOT_API_BASE_URL = \"https://api.individual.githubcopilot.com\";\n\n/** GitHub Copilot public routing hub for PAT-based `COPILOT_GITHUB_TOKEN` auth. */\nexport const COPILOT_PUBLIC_API_BASE_URL = \"https://api.githubcopilot.com\";\n\n/** Enterprise Copilot CAPI host for GHES/non-github.com server URLs. */\nexport const COPILOT_ENTERPRISE_API_BASE_URL = \"https://api.enterprise.githubcopilot.com\";\n\nexport const COPILOT_GITHUB_TOKEN_ENV = \"COPILOT_GITHUB_TOKEN\";\nexport const COPILOT_API_TARGET_ENV = \"COPILOT_API_TARGET\";\nexport const GITHUB_COPILOT_BASE_URL_ENV = \"GITHUB_COPILOT_BASE_URL\";\nexport const GITHUB_SERVER_URL_ENV = \"GITHUB_SERVER_URL\";\n\ntype CopilotEnvironment = Record<string, string | undefined>;\n\nfunction normalizeCopilotApiBaseUrl(value: string | undefined): string | undefined {\n\tconst trimmed = value?.trim();\n\tif (!trimmed) return undefined;\n\tconst withScheme = /^https?:\\/\\//i.test(trimmed) ? trimmed : `https://${trimmed}`;\n\ttry {\n\t\tnew URL(withScheme);\n\t} catch {\n\t\treturn undefined;\n\t}\n\treturn withScheme.replace(/\\/+$/, \"\");\n}\n\nfunction explicitCopilotApiBaseUrlFromEnvironment(env: CopilotEnvironment): string | undefined {\n\treturn normalizeCopilotApiBaseUrl(env[COPILOT_API_TARGET_ENV]) ?? normalizeCopilotApiBaseUrl(env[GITHUB_COPILOT_BASE_URL_ENV]);\n}\n\nfunction copilotApiBaseUrlFromServerUrl(serverUrl: string | undefined): string | undefined {\n\tconst trimmed = serverUrl?.trim();\n\tif (!trimmed) return undefined;\n\ttry {\n\t\tconst parsed = new URL(trimmed.includes(\"://\") ? trimmed : `https://${trimmed}`);\n\t\tconst host = parsed.hostname.toLowerCase();\n\t\tif (!host || host === \"github.com\") return undefined;\n\t\tif (host.endsWith(\".ghe.com\")) return `https://copilot-api.${host}`;\n\t\treturn COPILOT_ENTERPRISE_API_BASE_URL;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction isCopilotEnvironmentToken(token: string | undefined, env: CopilotEnvironment): boolean {\n\tconst environmentToken = copilotTokenFromEnvironment(env);\n\treturn environmentToken !== undefined && token === environmentToken;\n}\n\nexport function copilotTokenFromEnvironment(env: CopilotEnvironment = process.env): string | undefined {\n\tconst token = env[COPILOT_GITHUB_TOKEN_ENV]?.trim();\n\treturn token ? token : undefined;\n}\n\n/** Disk-cache freshness window, matching the Copilot CLI's list-models cache TTL. */\nexport const COPILOT_CATALOG_CACHE_TTL_MS = 30 * 60 * 1000;\n\n/** Current on-disk cache schema version. */\nexport const COPILOT_CATALOG_CACHE_VERSION = 3 as const;\n\n/**\n * Resolve the Copilot CAPI base URL.\n *\n * Copilot access tokens embed a `proxy-ep=proxy.<host>` segment; the API host is the same host with\n * `proxy.` swapped for `api.`. Env-token routing resolves explicit `COPILOT_API_TARGET` /\n * `GITHUB_COPILOT_BASE_URL` overrides, then `GITHUB_SERVER_URL` (`*.ghe.com` ->\n * `copilot-api.<tenant>.ghe.com`, other non-github.com -> `api.enterprise.githubcopilot.com`),\n * then the public Copilot routing hub `api.githubcopilot.com` for `COPILOT_GITHUB_TOKEN`. Stored\n * OAuth credentials still fall back to the generated individual host when no token route is known.\n */\nexport function copilotApiBaseUrlFromToken(\n\ttoken: string | undefined,\n\tenterpriseDomain?: string,\n\tenv: CopilotEnvironment = process.env,\n): string {\n\tconst explicitOverride = explicitCopilotApiBaseUrlFromEnvironment(env);\n\tif (explicitOverride) return explicitOverride;\n\tif (token) {\n\t\tconst match = token.match(/proxy-ep=([^;]+)/);\n\t\tif (match) {\n\t\t\treturn `https://${match[1].replace(/^proxy\\./, \"api.\")}`;\n\t\t}\n\t}\n\tif (enterpriseDomain) return `https://copilot-api.${enterpriseDomain}`;\n\tconst serverUrlOverride = copilotApiBaseUrlFromServerUrl(env[GITHUB_SERVER_URL_ENV]);\n\tif (serverUrlOverride) return serverUrlOverride;\n\tif (isCopilotEnvironmentToken(token, env)) return COPILOT_PUBLIC_API_BASE_URL;\n\treturn DEFAULT_COPILOT_API_BASE_URL;\n}\n\nfunction trimTrailingSlash(url: string): string {\n\treturn url.replace(/\\/+$/, \"\");\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n\treturn value && typeof value === \"object\" ? (value as Record<string, unknown>) : undefined;\n}\n\nfunction toPositiveInt(value: unknown): number | undefined {\n\treturn typeof value === \"number\" && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\n/** Raw token limits parsed from a CAPI model entry. */\nexport interface CopilotModelLimits {\n\t/** `capabilities.limits.max_prompt_tokens` — maximum prompt/input budget (the hard input cap). */\n\tmaxPromptTokens?: number;\n\t/** `capabilities.limits.max_context_window_tokens` — total context capacity (the displayed long tier). */\n\tmaxContextWindowTokens?: number;\n\t/** `capabilities.limits.max_output_tokens` — output reserve; derives the input cap when `max_prompt_tokens` is absent. */\n\tmaxOutputTokens?: number;\n\t/** `billing.token_prices.default.context_max` — default-tier prompt threshold. */\n\tdefaultContextMax?: number;\n\t/** `billing.token_prices.long_context.context_max` — long-context prompt threshold. */\n\tlongContextMax?: number;\n}\n\n/**\n * Resolve a model's input-token context window(s) from its CAPI limits.\n *\n * `contextWindow` is the model's base input budget — the default tier's `context_max` when tiered,\n * otherwise `max_prompt_tokens ?? max_context_window_tokens ?? 128_000`. A `long_context` tier that\n * is larger than the base adds a second selectable window. Returns `undefined` when the entry\n * carries no usable limit signal at all.\n */\nexport function resolveCopilotModelContext(limits: CopilotModelLimits): CopilotModelContext | undefined {\n\tconst hasSignal =\n\t\tlimits.maxPromptTokens !== undefined ||\n\t\tlimits.maxContextWindowTokens !== undefined ||\n\t\tlimits.defaultContextMax !== undefined ||\n\t\tlimits.longContextMax !== undefined;\n\tif (!hasSignal) return undefined;\n\n\tconst maxInput = limits.maxPromptTokens ?? limits.maxContextWindowTokens ?? COPILOT_CONTEXT_WINDOW_FALLBACK;\n\tconst base = limits.defaultContextMax ?? maxInput;\n\tif (limits.longContextMax !== undefined && limits.longContextMax > base) {\n\t\t// Display the model's full context window as the long tier (matching openai/* and anthropic/*)\n\t\t// when CAPI advertises it; otherwise fall back to the long-context prompt threshold for\n\t\t// older/sparse payloads.\n\t\tconst longWindow = limits.maxContextWindowTokens ?? limits.longContextMax;\n\t\t// The hard prompt/input cap GitHub enforces server-side: prefer max_prompt_tokens, else derive\n\t\t// it from total − output reserve, else fall back to the long-context prompt threshold.\n\t\tconst derivedInputCap =\n\t\t\tlimits.maxContextWindowTokens !== undefined && limits.maxOutputTokens !== undefined\n\t\t\t\t? limits.maxContextWindowTokens - limits.maxOutputTokens\n\t\t\t\t: undefined;\n\t\tconst inputCap =\n\t\t\tlimits.maxPromptTokens ??\n\t\t\t(derivedInputCap !== undefined && derivedInputCap > 0 ? derivedInputCap : undefined) ??\n\t\t\tlimits.longContextMax;\n\t\t// Only carry the cap when the displayed long window actually exceeds it (the branded-total\n\t\t// case); when they coincide there is no gap and the input budget is just the window.\n\t\treturn longWindow > inputCap\n\t\t\t? { contextWindow: base, contextWindowOptions: [base, longWindow], maxInputTokens: inputCap }\n\t\t\t: { contextWindow: base, contextWindowOptions: [base, longWindow] };\n\t}\n\treturn { contextWindow: base };\n}\n\n/**\n * Parse a raw CAPI `/models` response body into an input-token context-window catalog.\n */\nexport function parseCopilotModelCatalog(body: unknown): CopilotModelCatalog {\n\tconst catalog = new Map<string, CopilotModelContext>();\n\tconst data = asRecord(body)?.data;\n\tif (!Array.isArray(data)) return catalog;\n\n\tfor (const entry of data) {\n\t\tconst record = asRecord(entry);\n\t\tif (!record) continue;\n\t\tconst id = record.id;\n\t\tif (typeof id !== \"string\" || id.length === 0) continue;\n\n\t\tconst limits = asRecord(asRecord(record.capabilities)?.limits);\n\t\tconst prices = asRecord(asRecord(record.billing)?.token_prices);\n\t\tconst context = resolveCopilotModelContext({\n\t\t\tmaxPromptTokens: toPositiveInt(limits?.max_prompt_tokens),\n\t\t\tmaxContextWindowTokens: toPositiveInt(limits?.max_context_window_tokens),\n\t\t\tmaxOutputTokens: toPositiveInt(limits?.max_output_tokens),\n\t\t\tdefaultContextMax: toPositiveInt(asRecord(prices?.default)?.context_max),\n\t\t\tlongContextMax: toPositiveInt(asRecord(prices?.long_context)?.context_max),\n\t\t});\n\t\tif (context) catalog.set(id, context);\n\t}\n\n\treturn catalog;\n}\n\nexport interface FetchCopilotModelCatalogOptions {\n\t/** Valid Copilot CAPI bearer token (e.g. from `modelRegistry.getApiKeyForProvider`). */\n\ttoken: string;\n\t/** Override the resolved base URL; defaults to one derived from the token. */\n\tbaseUrl?: string;\n\t/** Enterprise domain, used for base-URL resolution when the token lacks a `proxy-ep`. */\n\tenterpriseDomain?: string;\n\t/** Extra/override request headers. */\n\theaders?: Record<string, string>;\n\t/** Injectable `fetch` for testing. */\n\tfetchImpl?: typeof fetch;\n\t/** Abort signal. */\n\tsignal?: AbortSignal;\n}\n\n/** Fetch and parse the live Copilot model catalog from CAPI `GET {baseUrl}/models`. */\nexport async function fetchCopilotModelCatalog(options: FetchCopilotModelCatalogOptions): Promise<CopilotModelCatalog> {\n\tconst fetchImpl = options.fetchImpl ?? fetch;\n\tconst baseUrl = options.baseUrl ?? copilotApiBaseUrlFromToken(options.token, options.enterpriseDomain);\n\tconst response = await fetchImpl(`${trimTrailingSlash(baseUrl)}/models`, {\n\t\tmethod: \"GET\",\n\t\theaders: {\n\t\t\tAccept: \"application/json\",\n\t\t\tAuthorization: `Bearer ${options.token}`,\n\t\t\t...COPILOT_CATALOG_HEADERS,\n\t\t\t...options.headers,\n\t\t},\n\t\t...(options.signal ? { signal: options.signal } : {}),\n\t});\n\tif (!response.ok) {\n\t\tthrow new Error(`GitHub Copilot /models request failed: ${response.status} ${response.statusText}`);\n\t}\n\treturn parseCopilotModelCatalog(await response.json());\n}\n\n// ----------------------------------------------------------------------------\n// Active in-memory catalog (consulted by the model registry).\n//\n// Empty by default, so with no Copilot auth / no successful fetch the registry leaves Copilot\n// model context windows untouched and the picker never appears.\n// ----------------------------------------------------------------------------\n\nlet activeCatalog: CopilotModelCatalog = new Map();\n\n/** Replace the active catalog the registry derives context windows from. */\nexport function setActiveCopilotModelCatalog(catalog: CopilotModelCatalog): void {\n\tactiveCatalog = catalog;\n}\n\n/** The active catalog (empty until a successful auth-gated fetch/cache load). */\nexport function getActiveCopilotModelCatalog(): CopilotModelCatalog {\n\treturn activeCatalog;\n}\n\n/** Reset the active catalog (primarily for tests). */\nexport function clearActiveCopilotModelCatalog(): void {\n\tactiveCatalog = new Map();\n}\n\n// ----------------------------------------------------------------------------\n// Disk cache.\n// ----------------------------------------------------------------------------\n\ninterface CopilotCatalogCacheFile {\n\tversion: typeof COPILOT_CATALOG_CACHE_VERSION;\n\t/** CAPI host the catalog was fetched from; cache misses on host change (e.g. enterprise switch). */\n\thost: string;\n\t/** Epoch ms the catalog was fetched. */\n\tfetchedAt: number;\n\tmodels: Record<string, CopilotModelContext>;\n}\n\nfunction hostFromBaseUrl(baseUrl: string): string {\n\ttry {\n\t\treturn new URL(baseUrl).host;\n\t} catch {\n\t\treturn baseUrl;\n\t}\n}\n\nexport interface ReadCopilotCatalogCacheOptions {\n\t/** Expected CAPI host; a cached file from a different host is ignored. */\n\thost: string;\n\t/** Current epoch ms (injectable for tests). */\n\tnow?: number;\n\t/** Freshness window; defaults to {@link COPILOT_CATALOG_CACHE_TTL_MS}. */\n\tttlMs?: number;\n}\n\nfunction sanitizeCachedContext(value: unknown): CopilotModelContext | undefined {\n\tconst record = asRecord(value);\n\tconst contextWindow = toPositiveInt(record?.contextWindow);\n\tif (contextWindow === undefined) return undefined;\n\tconst maxInputTokens = toPositiveInt(record?.maxInputTokens);\n\tconst rawOptions = record?.contextWindowOptions;\n\tif (Array.isArray(rawOptions)) {\n\t\tconst options = rawOptions.map(toPositiveInt).filter((n): n is number => n !== undefined);\n\t\tif (options.length > 1) {\n\t\t\treturn maxInputTokens !== undefined\n\t\t\t\t? { contextWindow, contextWindowOptions: options, maxInputTokens }\n\t\t\t\t: { contextWindow, contextWindowOptions: options };\n\t\t}\n\t}\n\treturn maxInputTokens !== undefined ? { contextWindow, maxInputTokens } : { contextWindow };\n}\n\n/** Read a fresh, host-matching catalog from the cache file, or `undefined` if missing/stale/invalid. */\nexport function readCopilotCatalogCache(\n\tpath: string,\n\toptions: ReadCopilotCatalogCacheOptions,\n): CopilotModelCatalog | undefined {\n\tlet parsed: CopilotCatalogCacheFile;\n\ttry {\n\t\tif (!existsSync(path)) return undefined;\n\t\tparsed = JSON.parse(readFileSync(path, \"utf8\")) as CopilotCatalogCacheFile;\n\t} catch {\n\t\treturn undefined;\n\t}\n\tif (!parsed || parsed.version !== COPILOT_CATALOG_CACHE_VERSION) return undefined;\n\tif (parsed.host !== options.host) return undefined;\n\tconst now = options.now ?? Date.now();\n\tconst ttlMs = options.ttlMs ?? COPILOT_CATALOG_CACHE_TTL_MS;\n\tif (typeof parsed.fetchedAt !== \"number\" || now - parsed.fetchedAt >= ttlMs) return undefined;\n\tconst models = asRecord(parsed.models);\n\tif (!models) return undefined;\n\n\tconst catalog = new Map<string, CopilotModelContext>();\n\tfor (const [id, value] of Object.entries(models)) {\n\t\tconst context = sanitizeCachedContext(value);\n\t\tif (context) catalog.set(id, context);\n\t}\n\treturn catalog;\n}\n\n/** Write the catalog to the cache file (creating parent dirs). Best-effort; never throws. */\nexport function writeCopilotCatalogCache(\n\tpath: string,\n\tbaseUrl: string,\n\tcatalog: CopilotModelCatalog,\n\tnow?: number,\n): void {\n\tconst payload: CopilotCatalogCacheFile = {\n\t\tversion: COPILOT_CATALOG_CACHE_VERSION,\n\t\thost: hostFromBaseUrl(baseUrl),\n\t\tfetchedAt: now ?? Date.now(),\n\t\tmodels: Object.fromEntries(catalog),\n\t};\n\ttry {\n\t\tmkdirSync(dirname(path), { recursive: true });\n\t\twriteFileSync(path, JSON.stringify(payload), \"utf8\");\n\t} catch {\n\t\t// best-effort cache; ignore write failures\n\t}\n}\n\n/** Host component of a base URL, for matching {@link readCopilotCatalogCache} `host`. */\nexport function copilotCatalogCacheHost(baseUrl: string): string {\n\treturn hostFromBaseUrl(baseUrl);\n}\n\n/** Standard on-disk cache path for the Copilot model catalog under an agent directory. */\nexport function copilotCatalogCachePath(agentDir: string): string {\n\treturn join(agentDir, \"cache\", \"copilot-models.json\");\n}\n\n/**\n * Seed the active catalog synchronously from the on-disk cache, gated on a Copilot access token.\n *\n * Called at model-registry construction so a returning user's previously selected long-context\n * window is recognized before startup validation runs — otherwise the persisted choice would warn\n * (\"context window 936k is not supported…\") and reset until the async refresh completes. The cache\n * TTL is intentionally ignored here: stale-but-present windows are still valid for selection, and\n * the async loader independently refetches on its own freshness window. Returns true when a catalog\n * was applied. No-op (returns false) without a token or a host-matching cached catalog.\n */\nexport function seedActiveCopilotModelCatalogFromCache(\n\taccessToken: string | undefined,\n\tcachePath: string,\n\tnow?: number,\n): boolean {\n\tif (typeof accessToken !== \"string\" || accessToken.length === 0) return false;\n\tconst host = copilotCatalogCacheHost(copilotApiBaseUrlFromToken(accessToken));\n\tconst cached = readCopilotCatalogCache(cachePath, { host, now, ttlMs: Number.POSITIVE_INFINITY });\n\tif (!cached) return false;\n\tsetActiveCopilotModelCatalog(cached);\n\treturn true;\n}\n"]}
|
|
@@ -41,8 +41,57 @@ export const COPILOT_CATALOG_HEADERS = {
|
|
|
41
41
|
"Copilot-Integration-Id": "vscode-chat",
|
|
42
42
|
"X-GitHub-Api-Version": COPILOT_CATALOG_API_VERSION,
|
|
43
43
|
};
|
|
44
|
-
/** Default
|
|
44
|
+
/** Default Copilot CAPI base URL for OAuth tokens when no route can be resolved. */
|
|
45
45
|
export const DEFAULT_COPILOT_API_BASE_URL = "https://api.individual.githubcopilot.com";
|
|
46
|
+
/** GitHub Copilot public routing hub for PAT-based `COPILOT_GITHUB_TOKEN` auth. */
|
|
47
|
+
export const COPILOT_PUBLIC_API_BASE_URL = "https://api.githubcopilot.com";
|
|
48
|
+
/** Enterprise Copilot CAPI host for GHES/non-github.com server URLs. */
|
|
49
|
+
export const COPILOT_ENTERPRISE_API_BASE_URL = "https://api.enterprise.githubcopilot.com";
|
|
50
|
+
export const COPILOT_GITHUB_TOKEN_ENV = "COPILOT_GITHUB_TOKEN";
|
|
51
|
+
export const COPILOT_API_TARGET_ENV = "COPILOT_API_TARGET";
|
|
52
|
+
export const GITHUB_COPILOT_BASE_URL_ENV = "GITHUB_COPILOT_BASE_URL";
|
|
53
|
+
export const GITHUB_SERVER_URL_ENV = "GITHUB_SERVER_URL";
|
|
54
|
+
function normalizeCopilotApiBaseUrl(value) {
|
|
55
|
+
const trimmed = value?.trim();
|
|
56
|
+
if (!trimmed)
|
|
57
|
+
return undefined;
|
|
58
|
+
const withScheme = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
59
|
+
try {
|
|
60
|
+
new URL(withScheme);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return withScheme.replace(/\/+$/, "");
|
|
66
|
+
}
|
|
67
|
+
function explicitCopilotApiBaseUrlFromEnvironment(env) {
|
|
68
|
+
return normalizeCopilotApiBaseUrl(env[COPILOT_API_TARGET_ENV]) ?? normalizeCopilotApiBaseUrl(env[GITHUB_COPILOT_BASE_URL_ENV]);
|
|
69
|
+
}
|
|
70
|
+
function copilotApiBaseUrlFromServerUrl(serverUrl) {
|
|
71
|
+
const trimmed = serverUrl?.trim();
|
|
72
|
+
if (!trimmed)
|
|
73
|
+
return undefined;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
76
|
+
const host = parsed.hostname.toLowerCase();
|
|
77
|
+
if (!host || host === "github.com")
|
|
78
|
+
return undefined;
|
|
79
|
+
if (host.endsWith(".ghe.com"))
|
|
80
|
+
return `https://copilot-api.${host}`;
|
|
81
|
+
return COPILOT_ENTERPRISE_API_BASE_URL;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function isCopilotEnvironmentToken(token, env) {
|
|
88
|
+
const environmentToken = copilotTokenFromEnvironment(env);
|
|
89
|
+
return environmentToken !== undefined && token === environmentToken;
|
|
90
|
+
}
|
|
91
|
+
export function copilotTokenFromEnvironment(env = process.env) {
|
|
92
|
+
const token = env[COPILOT_GITHUB_TOKEN_ENV]?.trim();
|
|
93
|
+
return token ? token : undefined;
|
|
94
|
+
}
|
|
46
95
|
/** Disk-cache freshness window, matching the Copilot CLI's list-models cache TTL. */
|
|
47
96
|
export const COPILOT_CATALOG_CACHE_TTL_MS = 30 * 60 * 1000;
|
|
48
97
|
/** Current on-disk cache schema version. */
|
|
@@ -51,11 +100,16 @@ export const COPILOT_CATALOG_CACHE_VERSION = 3;
|
|
|
51
100
|
* Resolve the Copilot CAPI base URL.
|
|
52
101
|
*
|
|
53
102
|
* Copilot access tokens embed a `proxy-ep=proxy.<host>` segment; the API host is the same host with
|
|
54
|
-
* `proxy.` swapped for `api.`.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
103
|
+
* `proxy.` swapped for `api.`. Env-token routing resolves explicit `COPILOT_API_TARGET` /
|
|
104
|
+
* `GITHUB_COPILOT_BASE_URL` overrides, then `GITHUB_SERVER_URL` (`*.ghe.com` ->
|
|
105
|
+
* `copilot-api.<tenant>.ghe.com`, other non-github.com -> `api.enterprise.githubcopilot.com`),
|
|
106
|
+
* then the public Copilot routing hub `api.githubcopilot.com` for `COPILOT_GITHUB_TOKEN`. Stored
|
|
107
|
+
* OAuth credentials still fall back to the generated individual host when no token route is known.
|
|
57
108
|
*/
|
|
58
|
-
export function copilotApiBaseUrlFromToken(token, enterpriseDomain) {
|
|
109
|
+
export function copilotApiBaseUrlFromToken(token, enterpriseDomain, env = process.env) {
|
|
110
|
+
const explicitOverride = explicitCopilotApiBaseUrlFromEnvironment(env);
|
|
111
|
+
if (explicitOverride)
|
|
112
|
+
return explicitOverride;
|
|
59
113
|
if (token) {
|
|
60
114
|
const match = token.match(/proxy-ep=([^;]+)/);
|
|
61
115
|
if (match) {
|
|
@@ -64,6 +118,11 @@ export function copilotApiBaseUrlFromToken(token, enterpriseDomain) {
|
|
|
64
118
|
}
|
|
65
119
|
if (enterpriseDomain)
|
|
66
120
|
return `https://copilot-api.${enterpriseDomain}`;
|
|
121
|
+
const serverUrlOverride = copilotApiBaseUrlFromServerUrl(env[GITHUB_SERVER_URL_ENV]);
|
|
122
|
+
if (serverUrlOverride)
|
|
123
|
+
return serverUrlOverride;
|
|
124
|
+
if (isCopilotEnvironmentToken(token, env))
|
|
125
|
+
return COPILOT_PUBLIC_API_BASE_URL;
|
|
67
126
|
return DEFAULT_COPILOT_API_BASE_URL;
|
|
68
127
|
}
|
|
69
128
|
function trimTrailingSlash(url) {
|