@bastani/atomic 0.8.30-alpha.3 → 0.8.31-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +7 -1
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  5. package/dist/builtin/intercom/package.json +2 -2
  6. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  7. package/dist/builtin/mcp/package.json +3 -3
  8. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  9. package/dist/builtin/subagents/package.json +4 -4
  10. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  11. package/dist/builtin/web-access/package.json +2 -2
  12. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  13. package/dist/builtin/workflows/builtin/deep-research-codebase.ts +1 -1
  14. package/dist/builtin/workflows/builtin/goal.ts +2 -2
  15. package/dist/builtin/workflows/builtin/open-claude-design.ts +1 -1
  16. package/dist/builtin/workflows/builtin/ralph.ts +61 -11
  17. package/dist/builtin/workflows/package.json +2 -2
  18. package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -1
  19. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +5 -0
  20. package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +95 -8
  21. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +11 -0
  22. package/dist/cli/args.d.ts +1 -0
  23. package/dist/cli/args.d.ts.map +1 -1
  24. package/dist/cli/args.js +20 -0
  25. package/dist/cli/args.js.map +1 -1
  26. package/dist/cli/list-models.d.ts.map +1 -1
  27. package/dist/cli/list-models.js +2 -1
  28. package/dist/cli/list-models.js.map +1 -1
  29. package/dist/core/agent-session-services.d.ts +2 -0
  30. package/dist/core/agent-session-services.d.ts.map +1 -1
  31. package/dist/core/agent-session-services.js +2 -0
  32. package/dist/core/agent-session-services.js.map +1 -1
  33. package/dist/core/agent-session.d.ts +17 -0
  34. package/dist/core/agent-session.d.ts.map +1 -1
  35. package/dist/core/agent-session.js +161 -18
  36. package/dist/core/agent-session.js.map +1 -1
  37. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  38. package/dist/core/compaction/branch-summarization.js +20 -5
  39. package/dist/core/compaction/branch-summarization.js.map +1 -1
  40. package/dist/core/compaction/context-compaction.d.ts.map +1 -1
  41. package/dist/core/compaction/context-compaction.js +14 -3
  42. package/dist/core/compaction/context-compaction.js.map +1 -1
  43. package/dist/core/context-window.d.ts +29 -0
  44. package/dist/core/context-window.d.ts.map +1 -0
  45. package/dist/core/context-window.js +86 -0
  46. package/dist/core/context-window.js.map +1 -0
  47. package/dist/core/copilot-errors.d.ts +9 -0
  48. package/dist/core/copilot-errors.d.ts.map +1 -0
  49. package/dist/core/copilot-errors.js +32 -0
  50. package/dist/core/copilot-errors.js.map +1 -0
  51. package/dist/core/copilot-model-catalog.d.ts +132 -0
  52. package/dist/core/copilot-model-catalog.d.ts.map +1 -0
  53. package/dist/core/copilot-model-catalog.js +254 -0
  54. package/dist/core/copilot-model-catalog.js.map +1 -0
  55. package/dist/core/export-html/template.js +10 -1
  56. package/dist/core/extensions/types.d.ts +3 -1
  57. package/dist/core/extensions/types.d.ts.map +1 -1
  58. package/dist/core/extensions/types.js.map +1 -1
  59. package/dist/core/model-registry.d.ts +10 -0
  60. package/dist/core/model-registry.d.ts.map +1 -1
  61. package/dist/core/model-registry.js +107 -4
  62. package/dist/core/model-registry.js.map +1 -1
  63. package/dist/core/model-resolver.d.ts.map +1 -1
  64. package/dist/core/model-resolver.js +4 -0
  65. package/dist/core/model-resolver.js.map +1 -1
  66. package/dist/core/provider-attribution.d.ts.map +1 -1
  67. package/dist/core/provider-attribution.js +17 -7
  68. package/dist/core/provider-attribution.js.map +1 -1
  69. package/dist/core/sdk.d.ts +8 -0
  70. package/dist/core/sdk.d.ts.map +1 -1
  71. package/dist/core/sdk.js +47 -0
  72. package/dist/core/sdk.js.map +1 -1
  73. package/dist/core/session-manager.d.ts +8 -1
  74. package/dist/core/session-manager.d.ts.map +1 -1
  75. package/dist/core/session-manager.js +19 -3
  76. package/dist/core/session-manager.js.map +1 -1
  77. package/dist/core/settings-manager.d.ts +6 -0
  78. package/dist/core/settings-manager.d.ts.map +1 -1
  79. package/dist/core/settings-manager.js +69 -0
  80. package/dist/core/settings-manager.js.map +1 -1
  81. package/dist/index.d.ts +2 -1
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. package/dist/index.js.map +1 -1
  85. package/dist/main.d.ts.map +1 -1
  86. package/dist/main.js +24 -1
  87. package/dist/main.js.map +1 -1
  88. package/dist/modes/index.d.ts +1 -1
  89. package/dist/modes/index.d.ts.map +1 -1
  90. package/dist/modes/index.js.map +1 -1
  91. package/dist/modes/interactive/components/context-window-selector.d.ts +53 -0
  92. package/dist/modes/interactive/components/context-window-selector.d.ts.map +1 -0
  93. package/dist/modes/interactive/components/context-window-selector.js +136 -0
  94. package/dist/modes/interactive/components/context-window-selector.js.map +1 -0
  95. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  96. package/dist/modes/interactive/components/tree-selector.js +7 -0
  97. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  98. package/dist/modes/interactive/interactive-mode.d.ts +5 -0
  99. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  100. package/dist/modes/interactive/interactive-mode.js +91 -1
  101. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  102. package/dist/modes/rpc/rpc-client.d.ts +14 -2
  103. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  104. package/dist/modes/rpc/rpc-client.js +23 -3
  105. package/dist/modes/rpc/rpc-client.js.map +1 -1
  106. package/dist/modes/rpc/rpc-mode.d.ts +1 -1
  107. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  108. package/dist/modes/rpc/rpc-mode.js +30 -1
  109. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  110. package/dist/modes/rpc/rpc-types.d.ts +23 -0
  111. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  112. package/dist/modes/rpc/rpc-types.js.map +1 -1
  113. package/docs/custom-provider.md +4 -1
  114. package/docs/json.md +3 -1
  115. package/docs/models.md +78 -2
  116. package/docs/providers.md +3 -0
  117. package/docs/rpc.md +80 -1
  118. package/docs/sdk.md +23 -3
  119. package/docs/session-format.md +15 -1
  120. package/docs/sessions.md +1 -1
  121. package/docs/settings.md +7 -2
  122. package/docs/workflows.md +26 -4
  123. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"model-resolver.d.ts","sourceRoot":"","sources":["../../src/core/model-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model resolution, scoping, and initial selection\n */\n\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport chalk from \"chalk\";\nimport { minimatch } from \"minimatch\";\nimport { isValidThinkingLevel } from \"../cli/args.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\n\n/** Default model IDs for each known provider */\nexport const defaultModelPerProvider: Record<string, string> = {\n \"amazon-bedrock\": \"us.anthropic.claude-opus-4-6-v1\",\n anthropic: \"claude-opus-4-8\",\n openai: \"gpt-5.4\",\n \"azure-openai-responses\": \"gpt-5.4\",\n \"openai-codex\": \"gpt-5.5\",\n deepseek: \"deepseek-v4-pro\",\n google: \"gemini-3.1-pro-preview\",\n \"google-vertex\": \"gemini-3.1-pro-preview\",\n \"github-copilot\": \"gpt-5.4\",\n cursor: \"composer-2\",\n openrouter: \"moonshotai/kimi-k2.6\",\n \"vercel-ai-gateway\": \"zai/glm-5.1\",\n xai: \"grok-4.20-0309-reasoning\",\n groq: \"openai/gpt-oss-120b\",\n cerebras: \"zai-glm-4.7\",\n \"ant-ling\": \"Ring-2.6-1T\",\n zai: \"glm-5.1\",\n mistral: \"devstral-medium-latest\",\n minimax: \"MiniMax-M2.7\",\n \"minimax-cn\": \"MiniMax-M2.7\",\n moonshotai: \"kimi-k2.6\",\n \"moonshotai-cn\": \"kimi-k2.6\",\n huggingface: \"moonshotai/Kimi-K2.6\",\n fireworks: \"accounts/fireworks/models/kimi-k2p6\",\n together: \"moonshotai/Kimi-K2.6\",\n opencode: \"kimi-k2.6\",\n \"opencode-go\": \"kimi-k2.6\",\n \"kimi-coding\": \"kimi-for-coding\",\n \"cloudflare-workers-ai\": \"@cf/moonshotai/kimi-k2.6\",\n \"cloudflare-ai-gateway\": \"workers-ai/@cf/moonshotai/kimi-k2.6\",\n xiaomi: \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-cn\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-ams\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-sgp\": \"mimo-v2.5-pro\",\n};\n\nexport interface ScopedModel {\n model: Model<Api>;\n /** Thinking level if explicitly specified in pattern (e.g., \"model:high\"), undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n}\n\n/**\n * Helper to check if a model ID looks like an alias (no date suffix)\n * Dates are typically in format: -20241022 or -20250929\n */\nfunction isAlias(id: string): boolean {\n // Check if ID ends with -latest\n if (id.endsWith(\"-latest\")) return true;\n\n // Check if ID ends with a date pattern (-YYYYMMDD)\n const datePattern = /-\\d{8}$/;\n return !datePattern.test(id);\n}\n\n/**\n * Find an exact model reference match.\n * Supports either a bare model id or a canonical provider/modelId reference.\n * When matching by bare id, ambiguous matches across providers are rejected.\n */\nexport function findExactModelReferenceMatch(\n modelReference: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const trimmedReference = modelReference.trim();\n if (!trimmedReference) {\n return undefined;\n }\n\n const normalizedReference = trimmedReference.toLowerCase();\n\n const canonicalMatches = availableModels.filter(\n (model) =>\n `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n );\n if (canonicalMatches.length === 1) {\n return canonicalMatches[0];\n }\n if (canonicalMatches.length > 1) {\n return undefined;\n }\n\n const slashIndex = trimmedReference.indexOf(\"/\");\n if (slashIndex !== -1) {\n const provider = trimmedReference.substring(0, slashIndex).trim();\n const modelId = trimmedReference.substring(slashIndex + 1).trim();\n if (provider && modelId) {\n const providerMatches = availableModels.filter(\n (model) =>\n model.provider.toLowerCase() === provider.toLowerCase() &&\n model.id.toLowerCase() === modelId.toLowerCase(),\n );\n if (providerMatches.length === 1) {\n return providerMatches[0];\n }\n if (providerMatches.length > 1) {\n return undefined;\n }\n }\n }\n\n const idMatches = availableModels.filter(\n (model) => model.id.toLowerCase() === normalizedReference,\n );\n return idMatches.length === 1 ? idMatches[0] : undefined;\n}\n\n/**\n * Try to match a pattern to a model from the available models list.\n * Returns the matched model or undefined if no match found.\n */\nfunction tryMatchModel(\n modelPattern: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const exactMatch = findExactModelReferenceMatch(\n modelPattern,\n availableModels,\n );\n if (exactMatch) {\n return exactMatch;\n }\n\n // No exact match - fall back to partial matching\n const matches = availableModels.filter(\n (m) =>\n m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n m.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n );\n\n if (matches.length === 0) {\n return undefined;\n }\n\n // Separate into aliases and dated versions\n const aliases = matches.filter((m) => isAlias(m.id));\n const datedVersions = matches.filter((m) => !isAlias(m.id));\n\n if (aliases.length > 0) {\n // Prefer alias - if multiple aliases, pick the one that sorts highest\n aliases.sort((a, b) => b.id.localeCompare(a.id));\n return aliases[0];\n } else {\n // No alias found, pick latest dated version\n datedVersions.sort((a, b) => b.id.localeCompare(a.id));\n return datedVersions[0];\n }\n}\n\nexport interface ParsedModelResult {\n model: Model<Api> | undefined;\n /** Thinking level if explicitly specified in pattern, undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n}\n\nfunction buildFallbackModel(\n provider: string,\n modelId: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const providerModels = availableModels.filter((m) => m.provider === provider);\n if (providerModels.length === 0) return undefined;\n\n const defaultId = defaultModelPerProvider[provider];\n const baseModel = defaultId\n ? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0])\n : providerModels[0];\n\n return {\n ...baseModel,\n id: modelId,\n name: modelId,\n };\n}\n\nasync function buildConfiguredProviderFallbackModel(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n return buildFallbackModel(provider, modelId, await modelRegistry.getAvailable());\n}\n\nexport async function resolveSavedModelReference(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n const found = modelRegistry.find(provider, modelId);\n if (found) return found;\n return buildConfiguredProviderFallbackModel(provider, modelId, modelRegistry);\n}\n\n/**\n * Parse a pattern to extract model and thinking level.\n * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).\n *\n * Algorithm:\n * 1. Try to match full pattern as a model\n * 2. If found, return it with \"off\" thinking level\n * 3. If not found and has colons, split on last colon:\n * - If suffix is valid thinking level, use it and recurse on prefix\n * - If suffix is invalid, warn and recurse on prefix with \"off\"\n *\n * @internal Exported for testing\n */\nexport function parseModelPattern(\n pattern: string,\n availableModels: Model<Api>[],\n options?: { allowInvalidThinkingLevelFallback?: boolean },\n): ParsedModelResult {\n // Try exact match first\n const exactMatch = tryMatchModel(pattern, availableModels);\n if (exactMatch) {\n return { model: exactMatch, thinkingLevel: undefined, warning: undefined };\n }\n\n // No match - try splitting on last colon if present\n const lastColonIndex = pattern.lastIndexOf(\":\");\n if (lastColonIndex === -1) {\n // No colons, pattern simply doesn't match any model\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n const prefix = pattern.substring(0, lastColonIndex);\n const suffix = pattern.substring(lastColonIndex + 1);\n\n if (isValidThinkingLevel(suffix)) {\n // Valid thinking level - recurse on prefix and use this level\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n // Only use this thinking level if no warning from inner recursion\n return {\n model: result.model,\n thinkingLevel: result.warning ? undefined : suffix,\n warning: result.warning,\n };\n }\n return result;\n } else {\n // Invalid suffix\n const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;\n if (!allowFallback) {\n // In strict mode (CLI --model parsing), treat it as part of the model id and fail.\n // This avoids accidentally resolving to a different model.\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n // Scope mode: recurse on prefix and warn\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n return {\n model: result.model,\n thinkingLevel: undefined,\n warning: `Invalid thinking level \"${suffix}\" in pattern \"${pattern}\". Using default instead.`,\n };\n }\n return result;\n }\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n *\n * Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).\n * The algorithm tries to match the full pattern first, then progressively\n * strips colon-suffixes to find a match.\n */\nexport async function resolveModelScope(\n patterns: string[],\n modelRegistry: ModelRegistry,\n): Promise<ScopedModel[]> {\n const availableModels = await modelRegistry.getAvailable();\n const scopedModels: ScopedModel[] = [];\n\n for (const pattern of patterns) {\n // Check if pattern contains glob characters\n if (\n pattern.includes(\"*\") ||\n pattern.includes(\"?\") ||\n pattern.includes(\"[\")\n ) {\n // Extract optional thinking level suffix (e.g., \"provider/*:high\")\n const colonIdx = pattern.lastIndexOf(\":\");\n let globPattern = pattern;\n let thinkingLevel: ThinkingLevel | undefined;\n\n if (colonIdx !== -1) {\n const suffix = pattern.substring(colonIdx + 1);\n if (isValidThinkingLevel(suffix)) {\n thinkingLevel = suffix;\n globPattern = pattern.substring(0, colonIdx);\n }\n }\n\n // Match against \"provider/modelId\" format OR just model ID\n // This allows \"*sonnet*\" to match without requiring \"anthropic/*sonnet*\"\n const matchingModels = availableModels.filter((m) => {\n const fullId = `${m.provider}/${m.id}`;\n return (\n minimatch(fullId, globPattern, { nocase: true }) ||\n minimatch(m.id, globPattern, { nocase: true })\n );\n });\n\n if (matchingModels.length === 0) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n for (const model of matchingModels) {\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n continue;\n }\n\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n availableModels,\n );\n\n if (warning) {\n console.warn(chalk.yellow(`Warning: ${warning}`));\n }\n\n if (!model) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n // Avoid duplicates\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n\n return scopedModels;\n}\n\nexport interface ResolveCliModelResult {\n model: Model<Api> | undefined;\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n /**\n * Error message suitable for CLI display.\n * When set, model will be undefined.\n */\n error: string | undefined;\n}\n\n/**\n * Resolve a single model from CLI flags.\n *\n * Supports:\n * - --provider <provider> --model <pattern>\n * - --model <provider>/<pattern>\n * - Fuzzy matching (same rules as model scoping: exact id, then partial id/name)\n *\n * Note: This does not apply the thinking level by itself, but it may *parse* and\n * return a thinking level from \"<pattern>:<thinking>\" so the caller can apply it.\n */\nexport function resolveCliModel(options: {\n cliProvider?: string;\n cliModel?: string;\n modelRegistry: ModelRegistry;\n}): ResolveCliModelResult {\n const { cliProvider, cliModel, modelRegistry } = options;\n\n if (!cliModel) {\n return { model: undefined, warning: undefined, error: undefined };\n }\n\n // Important: use *all* models here, not just models with pre-configured auth.\n // This allows \"--api-key\" to be used for first-time setup.\n const availableModels = modelRegistry.getAll();\n if (availableModels.length === 0) {\n return {\n model: undefined,\n warning: undefined,\n error:\n \"No models available. Check your installation or add models to models.json.\",\n };\n }\n\n // Build canonical provider lookup (case-insensitive)\n const providerMap = new Map<string, string>();\n for (const m of availableModels) {\n providerMap.set(m.provider.toLowerCase(), m.provider);\n }\n\n let provider = cliProvider\n ? providerMap.get(cliProvider.toLowerCase())\n : undefined;\n if (cliProvider && !provider) {\n return {\n model: undefined,\n warning: undefined,\n error: `Unknown provider \"${cliProvider}\". Use --list-models to see available providers/models.`,\n };\n }\n\n // If no explicit --provider, try to interpret \"provider/model\" format first.\n // When the prefix before the first slash matches a known provider, prefer that\n // interpretation over matching models whose IDs literally contain slashes\n // (e.g. \"zai/glm-5\" should resolve to provider=zai, model=glm-5, not to a\n // vercel-ai-gateway model with id \"zai/glm-5\").\n let pattern = cliModel;\n let inferredProvider = false;\n\n if (!provider) {\n const slashIndex = cliModel.indexOf(\"/\");\n if (slashIndex !== -1) {\n const maybeProvider = cliModel.substring(0, slashIndex);\n const canonical = providerMap.get(maybeProvider.toLowerCase());\n if (canonical) {\n provider = canonical;\n pattern = cliModel.substring(slashIndex + 1);\n inferredProvider = true;\n }\n }\n }\n\n // If no provider was inferred from the slash, try exact matches without provider inference.\n // This handles models whose IDs naturally contain slashes (e.g. OpenRouter-style IDs).\n if (!provider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n }\n\n if (cliProvider && provider) {\n // If both were provided, tolerate --model <provider>/<pattern> by stripping the provider prefix\n const prefix = `${provider}/`;\n if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {\n pattern = cliModel.substring(prefix.length);\n }\n }\n\n const candidates = provider\n ? availableModels.filter((m) => m.provider === provider)\n : availableModels;\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n candidates,\n {\n allowInvalidThinkingLevelFallback: false,\n },\n );\n\n if (model) {\n return { model, thinkingLevel, warning, error: undefined };\n }\n\n // If we inferred a provider from the slash but found no match within that provider,\n // fall back to matching the full input as a raw model id across all models.\n // This handles OpenRouter-style IDs like \"openai/gpt-4o:extended\" where \"openai\"\n // looks like a provider but the full string is actually a model id on openrouter.\n if (inferredProvider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n // Also try parseModelPattern on the full input against all models\n const fallback = parseModelPattern(cliModel, availableModels, {\n allowInvalidThinkingLevelFallback: false,\n });\n if (fallback.model) {\n return {\n model: fallback.model,\n thinkingLevel: fallback.thinkingLevel,\n warning: fallback.warning,\n error: undefined,\n };\n }\n }\n\n if (provider) {\n const fallbackModel = buildFallbackModel(\n provider,\n pattern,\n availableModels,\n );\n if (fallbackModel) {\n const fallbackWarning = warning\n ? `${warning} Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`\n : `Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`;\n return {\n model: fallbackModel,\n thinkingLevel: undefined,\n warning: fallbackWarning,\n error: undefined,\n };\n }\n }\n\n const display = provider ? `${provider}/${pattern}` : cliModel;\n return {\n model: undefined,\n thinkingLevel: undefined,\n warning,\n error: `Model \"${display}\" not found. Use --list-models to see available models.`,\n };\n}\n\nexport interface InitialModelResult {\n model: Model<Api> | undefined;\n thinkingLevel: ThinkingLevel;\n fallbackMessage: string | undefined;\n}\n\n/**\n * Find the initial model to use based on priority:\n * 1. CLI args (provider + model)\n * 2. First model from scoped models (if not continuing/resuming)\n * 3. Restored from session (if continuing/resuming)\n * 4. Saved default from settings\n * 5. First available model with valid API key\n */\nexport async function findInitialModel(options: {\n cliProvider?: string;\n cliModel?: string;\n scopedModels: ScopedModel[];\n isContinuing: boolean;\n defaultProvider?: string;\n defaultModelId?: string;\n defaultThinkingLevel?: ThinkingLevel;\n modelRegistry: ModelRegistry;\n}): Promise<InitialModelResult> {\n const {\n cliProvider,\n cliModel,\n scopedModels,\n isContinuing,\n defaultProvider,\n defaultModelId,\n defaultThinkingLevel,\n modelRegistry,\n } = options;\n\n let model: Model<Api> | undefined;\n let thinkingLevel: ThinkingLevel = DEFAULT_THINKING_LEVEL;\n\n // 1. CLI args take priority\n if (cliProvider && cliModel) {\n const resolved = resolveCliModel({\n cliProvider,\n cliModel,\n modelRegistry,\n });\n if (resolved.error) {\n console.error(chalk.red(resolved.error));\n process.exit(1);\n }\n if (resolved.model) {\n return {\n model: resolved.model,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // 2. Use first model from scoped models (skip if continuing/resuming)\n if (scopedModels.length > 0 && !isContinuing) {\n return {\n model: scopedModels[0].model,\n thinkingLevel:\n scopedModels[0].thinkingLevel ??\n defaultThinkingLevel ??\n DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 3. Try saved default from settings\n if (defaultProvider && defaultModelId) {\n const found = await resolveSavedModelReference(\n defaultProvider,\n defaultModelId,\n modelRegistry,\n );\n if (found) {\n model = found;\n if (defaultThinkingLevel) {\n thinkingLevel = defaultThinkingLevel;\n }\n return { model, thinkingLevel, fallbackMessage: undefined };\n }\n }\n\n // 4. Try first available model with valid API key\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n return {\n model: match,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // If no default found, use first available\n return {\n model: availableModels[0],\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 5. No model found\n return {\n model: undefined,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n}\n\n/**\n * Restore model from session, with fallback to available models\n */\nexport async function restoreModelFromSession(\n savedProvider: string,\n savedModelId: string,\n currentModel: Model<Api> | undefined,\n shouldPrintMessages: boolean,\n modelRegistry: ModelRegistry,\n): Promise<{\n model: Model<Api> | undefined;\n fallbackMessage: string | undefined;\n}> {\n const exactRestoredModel = modelRegistry.find(savedProvider, savedModelId);\n const restoredModel = exactRestoredModel && modelRegistry.hasConfiguredAuth(exactRestoredModel)\n ? exactRestoredModel\n : await buildConfiguredProviderFallbackModel(savedProvider, savedModelId, modelRegistry);\n\n if (restoredModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`),\n );\n }\n return { model: restoredModel, fallbackMessage: undefined };\n }\n\n // Model not found or no API key - fall back\n const reason = !exactRestoredModel\n ? \"model no longer exists\"\n : \"no auth configured\";\n\n if (shouldPrintMessages) {\n console.error(\n chalk.yellow(\n `Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`,\n ),\n );\n }\n\n // If we already have a model, use it as fallback\n if (currentModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${currentModel.provider}/${currentModel.id}`,\n ),\n );\n }\n return {\n model: currentModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,\n };\n }\n\n // Try to find any available model\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n let fallbackModel: Model<Api> | undefined;\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n fallbackModel = match;\n break;\n }\n }\n\n // If no default found, use first available\n if (!fallbackModel) {\n fallbackModel = availableModels[0];\n }\n\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`,\n ),\n );\n }\n\n return {\n model: fallbackModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,\n };\n }\n\n // No models available\n return { model: undefined, fallbackMessage: undefined };\n}\n"]}
1
+ {"version":3,"file":"model-resolver.d.ts","sourceRoot":"","sources":["../../src/core/model-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model resolution, scoping, and initial selection\n */\n\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport chalk from \"chalk\";\nimport { minimatch } from \"minimatch\";\nimport { isValidThinkingLevel } from \"../cli/args.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\n\n/** Default model IDs for each known provider */\nexport const defaultModelPerProvider: Record<string, string> = {\n \"amazon-bedrock\": \"us.anthropic.claude-opus-4-6-v1\",\n anthropic: \"claude-opus-4-8\",\n openai: \"gpt-5.4\",\n \"azure-openai-responses\": \"gpt-5.4\",\n \"openai-codex\": \"gpt-5.5\",\n deepseek: \"deepseek-v4-pro\",\n google: \"gemini-3.1-pro-preview\",\n \"google-vertex\": \"gemini-3.1-pro-preview\",\n \"github-copilot\": \"gpt-5.4\",\n cursor: \"composer-2\",\n openrouter: \"moonshotai/kimi-k2.6\",\n \"vercel-ai-gateway\": \"zai/glm-5.1\",\n xai: \"grok-4.20-0309-reasoning\",\n groq: \"openai/gpt-oss-120b\",\n cerebras: \"zai-glm-4.7\",\n \"ant-ling\": \"Ring-2.6-1T\",\n zai: \"glm-5.1\",\n mistral: \"devstral-medium-latest\",\n minimax: \"MiniMax-M2.7\",\n \"minimax-cn\": \"MiniMax-M2.7\",\n moonshotai: \"kimi-k2.6\",\n \"moonshotai-cn\": \"kimi-k2.6\",\n huggingface: \"moonshotai/Kimi-K2.6\",\n fireworks: \"accounts/fireworks/models/kimi-k2p6\",\n together: \"moonshotai/Kimi-K2.6\",\n opencode: \"kimi-k2.6\",\n \"opencode-go\": \"kimi-k2.6\",\n \"kimi-coding\": \"kimi-for-coding\",\n \"cloudflare-workers-ai\": \"@cf/moonshotai/kimi-k2.6\",\n \"cloudflare-ai-gateway\": \"workers-ai/@cf/moonshotai/kimi-k2.6\",\n xiaomi: \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-cn\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-ams\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-sgp\": \"mimo-v2.5-pro\",\n};\n\nexport interface ScopedModel {\n model: Model<Api>;\n /** Thinking level if explicitly specified in pattern (e.g., \"model:high\"), undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n}\n\n/**\n * Helper to check if a model ID looks like an alias (no date suffix)\n * Dates are typically in format: -20241022 or -20250929\n */\nfunction isAlias(id: string): boolean {\n // Check if ID ends with -latest\n if (id.endsWith(\"-latest\")) return true;\n\n // Check if ID ends with a date pattern (-YYYYMMDD)\n const datePattern = /-\\d{8}$/;\n return !datePattern.test(id);\n}\n\n/**\n * Find an exact model reference match.\n * Supports either a bare model id or a canonical provider/modelId reference.\n * When matching by bare id, ambiguous matches across providers are rejected.\n */\nexport function findExactModelReferenceMatch(\n modelReference: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const trimmedReference = modelReference.trim();\n if (!trimmedReference) {\n return undefined;\n }\n\n const normalizedReference = trimmedReference.toLowerCase();\n\n const canonicalMatches = availableModels.filter(\n (model) =>\n `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n );\n if (canonicalMatches.length === 1) {\n return canonicalMatches[0];\n }\n if (canonicalMatches.length > 1) {\n return undefined;\n }\n\n const slashIndex = trimmedReference.indexOf(\"/\");\n if (slashIndex !== -1) {\n const provider = trimmedReference.substring(0, slashIndex).trim();\n const modelId = trimmedReference.substring(slashIndex + 1).trim();\n if (provider && modelId) {\n const providerMatches = availableModels.filter(\n (model) =>\n model.provider.toLowerCase() === provider.toLowerCase() &&\n model.id.toLowerCase() === modelId.toLowerCase(),\n );\n if (providerMatches.length === 1) {\n return providerMatches[0];\n }\n if (providerMatches.length > 1) {\n return undefined;\n }\n }\n }\n\n const idMatches = availableModels.filter(\n (model) => model.id.toLowerCase() === normalizedReference,\n );\n return idMatches.length === 1 ? idMatches[0] : undefined;\n}\n\n/**\n * Try to match a pattern to a model from the available models list.\n * Returns the matched model or undefined if no match found.\n */\nfunction tryMatchModel(\n modelPattern: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const exactMatch = findExactModelReferenceMatch(\n modelPattern,\n availableModels,\n );\n if (exactMatch) {\n return exactMatch;\n }\n\n // No exact match - fall back to partial matching\n const matches = availableModels.filter(\n (m) =>\n m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n m.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n );\n\n if (matches.length === 0) {\n return undefined;\n }\n\n // Separate into aliases and dated versions\n const aliases = matches.filter((m) => isAlias(m.id));\n const datedVersions = matches.filter((m) => !isAlias(m.id));\n\n if (aliases.length > 0) {\n // Prefer alias - if multiple aliases, pick the one that sorts highest\n aliases.sort((a, b) => b.id.localeCompare(a.id));\n return aliases[0];\n } else {\n // No alias found, pick latest dated version\n datedVersions.sort((a, b) => b.id.localeCompare(a.id));\n return datedVersions[0];\n }\n}\n\nexport interface ParsedModelResult {\n model: Model<Api> | undefined;\n /** Thinking level if explicitly specified in pattern, undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n}\n\nfunction buildFallbackModel(\n provider: string,\n modelId: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const providerModels = availableModels.filter((m) => m.provider === provider);\n if (providerModels.length === 0) return undefined;\n\n const defaultId = defaultModelPerProvider[provider];\n const baseModel = defaultId\n ? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0])\n : providerModels[0];\n\n const fallbackContextWindow = baseModel.contextWindow;\n\n return {\n ...baseModel,\n id: modelId,\n name: modelId,\n contextWindow: fallbackContextWindow,\n defaultContextWindow: fallbackContextWindow,\n contextWindowOptions: undefined,\n };\n}\n\nasync function buildConfiguredProviderFallbackModel(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n return buildFallbackModel(provider, modelId, await modelRegistry.getAvailable());\n}\n\nexport async function resolveSavedModelReference(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n const found = modelRegistry.find(provider, modelId);\n if (found) return found;\n return buildConfiguredProviderFallbackModel(provider, modelId, modelRegistry);\n}\n\n/**\n * Parse a pattern to extract model and thinking level.\n * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).\n *\n * Algorithm:\n * 1. Try to match full pattern as a model\n * 2. If found, return it with \"off\" thinking level\n * 3. If not found and has colons, split on last colon:\n * - If suffix is valid thinking level, use it and recurse on prefix\n * - If suffix is invalid, warn and recurse on prefix with \"off\"\n *\n * @internal Exported for testing\n */\nexport function parseModelPattern(\n pattern: string,\n availableModels: Model<Api>[],\n options?: { allowInvalidThinkingLevelFallback?: boolean },\n): ParsedModelResult {\n // Try exact match first\n const exactMatch = tryMatchModel(pattern, availableModels);\n if (exactMatch) {\n return { model: exactMatch, thinkingLevel: undefined, warning: undefined };\n }\n\n // No match - try splitting on last colon if present\n const lastColonIndex = pattern.lastIndexOf(\":\");\n if (lastColonIndex === -1) {\n // No colons, pattern simply doesn't match any model\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n const prefix = pattern.substring(0, lastColonIndex);\n const suffix = pattern.substring(lastColonIndex + 1);\n\n if (isValidThinkingLevel(suffix)) {\n // Valid thinking level - recurse on prefix and use this level\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n // Only use this thinking level if no warning from inner recursion\n return {\n model: result.model,\n thinkingLevel: result.warning ? undefined : suffix,\n warning: result.warning,\n };\n }\n return result;\n } else {\n // Invalid suffix\n const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;\n if (!allowFallback) {\n // In strict mode (CLI --model parsing), treat it as part of the model id and fail.\n // This avoids accidentally resolving to a different model.\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n // Scope mode: recurse on prefix and warn\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n return {\n model: result.model,\n thinkingLevel: undefined,\n warning: `Invalid thinking level \"${suffix}\" in pattern \"${pattern}\". Using default instead.`,\n };\n }\n return result;\n }\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n *\n * Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).\n * The algorithm tries to match the full pattern first, then progressively\n * strips colon-suffixes to find a match.\n */\nexport async function resolveModelScope(\n patterns: string[],\n modelRegistry: ModelRegistry,\n): Promise<ScopedModel[]> {\n const availableModels = await modelRegistry.getAvailable();\n const scopedModels: ScopedModel[] = [];\n\n for (const pattern of patterns) {\n // Check if pattern contains glob characters\n if (\n pattern.includes(\"*\") ||\n pattern.includes(\"?\") ||\n pattern.includes(\"[\")\n ) {\n // Extract optional thinking level suffix (e.g., \"provider/*:high\")\n const colonIdx = pattern.lastIndexOf(\":\");\n let globPattern = pattern;\n let thinkingLevel: ThinkingLevel | undefined;\n\n if (colonIdx !== -1) {\n const suffix = pattern.substring(colonIdx + 1);\n if (isValidThinkingLevel(suffix)) {\n thinkingLevel = suffix;\n globPattern = pattern.substring(0, colonIdx);\n }\n }\n\n // Match against \"provider/modelId\" format OR just model ID\n // This allows \"*sonnet*\" to match without requiring \"anthropic/*sonnet*\"\n const matchingModels = availableModels.filter((m) => {\n const fullId = `${m.provider}/${m.id}`;\n return (\n minimatch(fullId, globPattern, { nocase: true }) ||\n minimatch(m.id, globPattern, { nocase: true })\n );\n });\n\n if (matchingModels.length === 0) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n for (const model of matchingModels) {\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n continue;\n }\n\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n availableModels,\n );\n\n if (warning) {\n console.warn(chalk.yellow(`Warning: ${warning}`));\n }\n\n if (!model) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n // Avoid duplicates\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n\n return scopedModels;\n}\n\nexport interface ResolveCliModelResult {\n model: Model<Api> | undefined;\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n /**\n * Error message suitable for CLI display.\n * When set, model will be undefined.\n */\n error: string | undefined;\n}\n\n/**\n * Resolve a single model from CLI flags.\n *\n * Supports:\n * - --provider <provider> --model <pattern>\n * - --model <provider>/<pattern>\n * - Fuzzy matching (same rules as model scoping: exact id, then partial id/name)\n *\n * Note: This does not apply the thinking level by itself, but it may *parse* and\n * return a thinking level from \"<pattern>:<thinking>\" so the caller can apply it.\n */\nexport function resolveCliModel(options: {\n cliProvider?: string;\n cliModel?: string;\n modelRegistry: ModelRegistry;\n}): ResolveCliModelResult {\n const { cliProvider, cliModel, modelRegistry } = options;\n\n if (!cliModel) {\n return { model: undefined, warning: undefined, error: undefined };\n }\n\n // Important: use *all* models here, not just models with pre-configured auth.\n // This allows \"--api-key\" to be used for first-time setup.\n const availableModels = modelRegistry.getAll();\n if (availableModels.length === 0) {\n return {\n model: undefined,\n warning: undefined,\n error:\n \"No models available. Check your installation or add models to models.json.\",\n };\n }\n\n // Build canonical provider lookup (case-insensitive)\n const providerMap = new Map<string, string>();\n for (const m of availableModels) {\n providerMap.set(m.provider.toLowerCase(), m.provider);\n }\n\n let provider = cliProvider\n ? providerMap.get(cliProvider.toLowerCase())\n : undefined;\n if (cliProvider && !provider) {\n return {\n model: undefined,\n warning: undefined,\n error: `Unknown provider \"${cliProvider}\". Use --list-models to see available providers/models.`,\n };\n }\n\n // If no explicit --provider, try to interpret \"provider/model\" format first.\n // When the prefix before the first slash matches a known provider, prefer that\n // interpretation over matching models whose IDs literally contain slashes\n // (e.g. \"zai/glm-5\" should resolve to provider=zai, model=glm-5, not to a\n // vercel-ai-gateway model with id \"zai/glm-5\").\n let pattern = cliModel;\n let inferredProvider = false;\n\n if (!provider) {\n const slashIndex = cliModel.indexOf(\"/\");\n if (slashIndex !== -1) {\n const maybeProvider = cliModel.substring(0, slashIndex);\n const canonical = providerMap.get(maybeProvider.toLowerCase());\n if (canonical) {\n provider = canonical;\n pattern = cliModel.substring(slashIndex + 1);\n inferredProvider = true;\n }\n }\n }\n\n // If no provider was inferred from the slash, try exact matches without provider inference.\n // This handles models whose IDs naturally contain slashes (e.g. OpenRouter-style IDs).\n if (!provider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n }\n\n if (cliProvider && provider) {\n // If both were provided, tolerate --model <provider>/<pattern> by stripping the provider prefix\n const prefix = `${provider}/`;\n if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {\n pattern = cliModel.substring(prefix.length);\n }\n }\n\n const candidates = provider\n ? availableModels.filter((m) => m.provider === provider)\n : availableModels;\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n candidates,\n {\n allowInvalidThinkingLevelFallback: false,\n },\n );\n\n if (model) {\n return { model, thinkingLevel, warning, error: undefined };\n }\n\n // If we inferred a provider from the slash but found no match within that provider,\n // fall back to matching the full input as a raw model id across all models.\n // This handles OpenRouter-style IDs like \"openai/gpt-4o:extended\" where \"openai\"\n // looks like a provider but the full string is actually a model id on openrouter.\n if (inferredProvider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n // Also try parseModelPattern on the full input against all models\n const fallback = parseModelPattern(cliModel, availableModels, {\n allowInvalidThinkingLevelFallback: false,\n });\n if (fallback.model) {\n return {\n model: fallback.model,\n thinkingLevel: fallback.thinkingLevel,\n warning: fallback.warning,\n error: undefined,\n };\n }\n }\n\n if (provider) {\n const fallbackModel = buildFallbackModel(\n provider,\n pattern,\n availableModels,\n );\n if (fallbackModel) {\n const fallbackWarning = warning\n ? `${warning} Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`\n : `Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`;\n return {\n model: fallbackModel,\n thinkingLevel: undefined,\n warning: fallbackWarning,\n error: undefined,\n };\n }\n }\n\n const display = provider ? `${provider}/${pattern}` : cliModel;\n return {\n model: undefined,\n thinkingLevel: undefined,\n warning,\n error: `Model \"${display}\" not found. Use --list-models to see available models.`,\n };\n}\n\nexport interface InitialModelResult {\n model: Model<Api> | undefined;\n thinkingLevel: ThinkingLevel;\n fallbackMessage: string | undefined;\n}\n\n/**\n * Find the initial model to use based on priority:\n * 1. CLI args (provider + model)\n * 2. First model from scoped models (if not continuing/resuming)\n * 3. Restored from session (if continuing/resuming)\n * 4. Saved default from settings\n * 5. First available model with valid API key\n */\nexport async function findInitialModel(options: {\n cliProvider?: string;\n cliModel?: string;\n scopedModels: ScopedModel[];\n isContinuing: boolean;\n defaultProvider?: string;\n defaultModelId?: string;\n defaultThinkingLevel?: ThinkingLevel;\n modelRegistry: ModelRegistry;\n}): Promise<InitialModelResult> {\n const {\n cliProvider,\n cliModel,\n scopedModels,\n isContinuing,\n defaultProvider,\n defaultModelId,\n defaultThinkingLevel,\n modelRegistry,\n } = options;\n\n let model: Model<Api> | undefined;\n let thinkingLevel: ThinkingLevel = DEFAULT_THINKING_LEVEL;\n\n // 1. CLI args take priority\n if (cliProvider && cliModel) {\n const resolved = resolveCliModel({\n cliProvider,\n cliModel,\n modelRegistry,\n });\n if (resolved.error) {\n console.error(chalk.red(resolved.error));\n process.exit(1);\n }\n if (resolved.model) {\n return {\n model: resolved.model,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // 2. Use first model from scoped models (skip if continuing/resuming)\n if (scopedModels.length > 0 && !isContinuing) {\n return {\n model: scopedModels[0].model,\n thinkingLevel:\n scopedModels[0].thinkingLevel ??\n defaultThinkingLevel ??\n DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 3. Try saved default from settings\n if (defaultProvider && defaultModelId) {\n const found = await resolveSavedModelReference(\n defaultProvider,\n defaultModelId,\n modelRegistry,\n );\n if (found) {\n model = found;\n if (defaultThinkingLevel) {\n thinkingLevel = defaultThinkingLevel;\n }\n return { model, thinkingLevel, fallbackMessage: undefined };\n }\n }\n\n // 4. Try first available model with valid API key\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n return {\n model: match,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // If no default found, use first available\n return {\n model: availableModels[0],\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 5. No model found\n return {\n model: undefined,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n}\n\n/**\n * Restore model from session, with fallback to available models\n */\nexport async function restoreModelFromSession(\n savedProvider: string,\n savedModelId: string,\n currentModel: Model<Api> | undefined,\n shouldPrintMessages: boolean,\n modelRegistry: ModelRegistry,\n): Promise<{\n model: Model<Api> | undefined;\n fallbackMessage: string | undefined;\n}> {\n const exactRestoredModel = modelRegistry.find(savedProvider, savedModelId);\n const restoredModel = exactRestoredModel && modelRegistry.hasConfiguredAuth(exactRestoredModel)\n ? exactRestoredModel\n : await buildConfiguredProviderFallbackModel(savedProvider, savedModelId, modelRegistry);\n\n if (restoredModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`),\n );\n }\n return { model: restoredModel, fallbackMessage: undefined };\n }\n\n // Model not found or no API key - fall back\n const reason = !exactRestoredModel\n ? \"model no longer exists\"\n : \"no auth configured\";\n\n if (shouldPrintMessages) {\n console.error(\n chalk.yellow(\n `Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`,\n ),\n );\n }\n\n // If we already have a model, use it as fallback\n if (currentModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${currentModel.provider}/${currentModel.id}`,\n ),\n );\n }\n return {\n model: currentModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,\n };\n }\n\n // Try to find any available model\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n let fallbackModel: Model<Api> | undefined;\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n fallbackModel = match;\n break;\n }\n }\n\n // If no default found, use first available\n if (!fallbackModel) {\n fallbackModel = availableModels[0];\n }\n\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`,\n ),\n );\n }\n\n return {\n model: fallbackModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,\n };\n }\n\n // No models available\n return { model: undefined, fallbackMessage: undefined };\n}\n"]}
@@ -128,10 +128,14 @@ function buildFallbackModel(provider, modelId, availableModels) {
128
128
  const baseModel = defaultId
129
129
  ? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0])
130
130
  : providerModels[0];
131
+ const fallbackContextWindow = baseModel.contextWindow;
131
132
  return {
132
133
  ...baseModel,
133
134
  id: modelId,
134
135
  name: modelId,
136
+ contextWindow: fallbackContextWindow,
137
+ defaultContextWindow: fallbackContextWindow,
138
+ contextWindowOptions: undefined,
135
139
  };
136
140
  }
137
141
  async function buildConfiguredProviderFallbackModel(provider, modelId, modelRegistry) {
@@ -1 +1 @@
1
- {"version":3,"file":"model-resolver.js","sourceRoot":"","sources":["../../src/core/model-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model resolution, scoping, and initial selection\n */\n\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport chalk from \"chalk\";\nimport { minimatch } from \"minimatch\";\nimport { isValidThinkingLevel } from \"../cli/args.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\n\n/** Default model IDs for each known provider */\nexport const defaultModelPerProvider: Record<string, string> = {\n \"amazon-bedrock\": \"us.anthropic.claude-opus-4-6-v1\",\n anthropic: \"claude-opus-4-8\",\n openai: \"gpt-5.4\",\n \"azure-openai-responses\": \"gpt-5.4\",\n \"openai-codex\": \"gpt-5.5\",\n deepseek: \"deepseek-v4-pro\",\n google: \"gemini-3.1-pro-preview\",\n \"google-vertex\": \"gemini-3.1-pro-preview\",\n \"github-copilot\": \"gpt-5.4\",\n cursor: \"composer-2\",\n openrouter: \"moonshotai/kimi-k2.6\",\n \"vercel-ai-gateway\": \"zai/glm-5.1\",\n xai: \"grok-4.20-0309-reasoning\",\n groq: \"openai/gpt-oss-120b\",\n cerebras: \"zai-glm-4.7\",\n \"ant-ling\": \"Ring-2.6-1T\",\n zai: \"glm-5.1\",\n mistral: \"devstral-medium-latest\",\n minimax: \"MiniMax-M2.7\",\n \"minimax-cn\": \"MiniMax-M2.7\",\n moonshotai: \"kimi-k2.6\",\n \"moonshotai-cn\": \"kimi-k2.6\",\n huggingface: \"moonshotai/Kimi-K2.6\",\n fireworks: \"accounts/fireworks/models/kimi-k2p6\",\n together: \"moonshotai/Kimi-K2.6\",\n opencode: \"kimi-k2.6\",\n \"opencode-go\": \"kimi-k2.6\",\n \"kimi-coding\": \"kimi-for-coding\",\n \"cloudflare-workers-ai\": \"@cf/moonshotai/kimi-k2.6\",\n \"cloudflare-ai-gateway\": \"workers-ai/@cf/moonshotai/kimi-k2.6\",\n xiaomi: \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-cn\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-ams\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-sgp\": \"mimo-v2.5-pro\",\n};\n\nexport interface ScopedModel {\n model: Model<Api>;\n /** Thinking level if explicitly specified in pattern (e.g., \"model:high\"), undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n}\n\n/**\n * Helper to check if a model ID looks like an alias (no date suffix)\n * Dates are typically in format: -20241022 or -20250929\n */\nfunction isAlias(id: string): boolean {\n // Check if ID ends with -latest\n if (id.endsWith(\"-latest\")) return true;\n\n // Check if ID ends with a date pattern (-YYYYMMDD)\n const datePattern = /-\\d{8}$/;\n return !datePattern.test(id);\n}\n\n/**\n * Find an exact model reference match.\n * Supports either a bare model id or a canonical provider/modelId reference.\n * When matching by bare id, ambiguous matches across providers are rejected.\n */\nexport function findExactModelReferenceMatch(\n modelReference: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const trimmedReference = modelReference.trim();\n if (!trimmedReference) {\n return undefined;\n }\n\n const normalizedReference = trimmedReference.toLowerCase();\n\n const canonicalMatches = availableModels.filter(\n (model) =>\n `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n );\n if (canonicalMatches.length === 1) {\n return canonicalMatches[0];\n }\n if (canonicalMatches.length > 1) {\n return undefined;\n }\n\n const slashIndex = trimmedReference.indexOf(\"/\");\n if (slashIndex !== -1) {\n const provider = trimmedReference.substring(0, slashIndex).trim();\n const modelId = trimmedReference.substring(slashIndex + 1).trim();\n if (provider && modelId) {\n const providerMatches = availableModels.filter(\n (model) =>\n model.provider.toLowerCase() === provider.toLowerCase() &&\n model.id.toLowerCase() === modelId.toLowerCase(),\n );\n if (providerMatches.length === 1) {\n return providerMatches[0];\n }\n if (providerMatches.length > 1) {\n return undefined;\n }\n }\n }\n\n const idMatches = availableModels.filter(\n (model) => model.id.toLowerCase() === normalizedReference,\n );\n return idMatches.length === 1 ? idMatches[0] : undefined;\n}\n\n/**\n * Try to match a pattern to a model from the available models list.\n * Returns the matched model or undefined if no match found.\n */\nfunction tryMatchModel(\n modelPattern: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const exactMatch = findExactModelReferenceMatch(\n modelPattern,\n availableModels,\n );\n if (exactMatch) {\n return exactMatch;\n }\n\n // No exact match - fall back to partial matching\n const matches = availableModels.filter(\n (m) =>\n m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n m.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n );\n\n if (matches.length === 0) {\n return undefined;\n }\n\n // Separate into aliases and dated versions\n const aliases = matches.filter((m) => isAlias(m.id));\n const datedVersions = matches.filter((m) => !isAlias(m.id));\n\n if (aliases.length > 0) {\n // Prefer alias - if multiple aliases, pick the one that sorts highest\n aliases.sort((a, b) => b.id.localeCompare(a.id));\n return aliases[0];\n } else {\n // No alias found, pick latest dated version\n datedVersions.sort((a, b) => b.id.localeCompare(a.id));\n return datedVersions[0];\n }\n}\n\nexport interface ParsedModelResult {\n model: Model<Api> | undefined;\n /** Thinking level if explicitly specified in pattern, undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n}\n\nfunction buildFallbackModel(\n provider: string,\n modelId: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const providerModels = availableModels.filter((m) => m.provider === provider);\n if (providerModels.length === 0) return undefined;\n\n const defaultId = defaultModelPerProvider[provider];\n const baseModel = defaultId\n ? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0])\n : providerModels[0];\n\n return {\n ...baseModel,\n id: modelId,\n name: modelId,\n };\n}\n\nasync function buildConfiguredProviderFallbackModel(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n return buildFallbackModel(provider, modelId, await modelRegistry.getAvailable());\n}\n\nexport async function resolveSavedModelReference(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n const found = modelRegistry.find(provider, modelId);\n if (found) return found;\n return buildConfiguredProviderFallbackModel(provider, modelId, modelRegistry);\n}\n\n/**\n * Parse a pattern to extract model and thinking level.\n * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).\n *\n * Algorithm:\n * 1. Try to match full pattern as a model\n * 2. If found, return it with \"off\" thinking level\n * 3. If not found and has colons, split on last colon:\n * - If suffix is valid thinking level, use it and recurse on prefix\n * - If suffix is invalid, warn and recurse on prefix with \"off\"\n *\n * @internal Exported for testing\n */\nexport function parseModelPattern(\n pattern: string,\n availableModels: Model<Api>[],\n options?: { allowInvalidThinkingLevelFallback?: boolean },\n): ParsedModelResult {\n // Try exact match first\n const exactMatch = tryMatchModel(pattern, availableModels);\n if (exactMatch) {\n return { model: exactMatch, thinkingLevel: undefined, warning: undefined };\n }\n\n // No match - try splitting on last colon if present\n const lastColonIndex = pattern.lastIndexOf(\":\");\n if (lastColonIndex === -1) {\n // No colons, pattern simply doesn't match any model\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n const prefix = pattern.substring(0, lastColonIndex);\n const suffix = pattern.substring(lastColonIndex + 1);\n\n if (isValidThinkingLevel(suffix)) {\n // Valid thinking level - recurse on prefix and use this level\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n // Only use this thinking level if no warning from inner recursion\n return {\n model: result.model,\n thinkingLevel: result.warning ? undefined : suffix,\n warning: result.warning,\n };\n }\n return result;\n } else {\n // Invalid suffix\n const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;\n if (!allowFallback) {\n // In strict mode (CLI --model parsing), treat it as part of the model id and fail.\n // This avoids accidentally resolving to a different model.\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n // Scope mode: recurse on prefix and warn\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n return {\n model: result.model,\n thinkingLevel: undefined,\n warning: `Invalid thinking level \"${suffix}\" in pattern \"${pattern}\". Using default instead.`,\n };\n }\n return result;\n }\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n *\n * Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).\n * The algorithm tries to match the full pattern first, then progressively\n * strips colon-suffixes to find a match.\n */\nexport async function resolveModelScope(\n patterns: string[],\n modelRegistry: ModelRegistry,\n): Promise<ScopedModel[]> {\n const availableModels = await modelRegistry.getAvailable();\n const scopedModels: ScopedModel[] = [];\n\n for (const pattern of patterns) {\n // Check if pattern contains glob characters\n if (\n pattern.includes(\"*\") ||\n pattern.includes(\"?\") ||\n pattern.includes(\"[\")\n ) {\n // Extract optional thinking level suffix (e.g., \"provider/*:high\")\n const colonIdx = pattern.lastIndexOf(\":\");\n let globPattern = pattern;\n let thinkingLevel: ThinkingLevel | undefined;\n\n if (colonIdx !== -1) {\n const suffix = pattern.substring(colonIdx + 1);\n if (isValidThinkingLevel(suffix)) {\n thinkingLevel = suffix;\n globPattern = pattern.substring(0, colonIdx);\n }\n }\n\n // Match against \"provider/modelId\" format OR just model ID\n // This allows \"*sonnet*\" to match without requiring \"anthropic/*sonnet*\"\n const matchingModels = availableModels.filter((m) => {\n const fullId = `${m.provider}/${m.id}`;\n return (\n minimatch(fullId, globPattern, { nocase: true }) ||\n minimatch(m.id, globPattern, { nocase: true })\n );\n });\n\n if (matchingModels.length === 0) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n for (const model of matchingModels) {\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n continue;\n }\n\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n availableModels,\n );\n\n if (warning) {\n console.warn(chalk.yellow(`Warning: ${warning}`));\n }\n\n if (!model) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n // Avoid duplicates\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n\n return scopedModels;\n}\n\nexport interface ResolveCliModelResult {\n model: Model<Api> | undefined;\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n /**\n * Error message suitable for CLI display.\n * When set, model will be undefined.\n */\n error: string | undefined;\n}\n\n/**\n * Resolve a single model from CLI flags.\n *\n * Supports:\n * - --provider <provider> --model <pattern>\n * - --model <provider>/<pattern>\n * - Fuzzy matching (same rules as model scoping: exact id, then partial id/name)\n *\n * Note: This does not apply the thinking level by itself, but it may *parse* and\n * return a thinking level from \"<pattern>:<thinking>\" so the caller can apply it.\n */\nexport function resolveCliModel(options: {\n cliProvider?: string;\n cliModel?: string;\n modelRegistry: ModelRegistry;\n}): ResolveCliModelResult {\n const { cliProvider, cliModel, modelRegistry } = options;\n\n if (!cliModel) {\n return { model: undefined, warning: undefined, error: undefined };\n }\n\n // Important: use *all* models here, not just models with pre-configured auth.\n // This allows \"--api-key\" to be used for first-time setup.\n const availableModels = modelRegistry.getAll();\n if (availableModels.length === 0) {\n return {\n model: undefined,\n warning: undefined,\n error:\n \"No models available. Check your installation or add models to models.json.\",\n };\n }\n\n // Build canonical provider lookup (case-insensitive)\n const providerMap = new Map<string, string>();\n for (const m of availableModels) {\n providerMap.set(m.provider.toLowerCase(), m.provider);\n }\n\n let provider = cliProvider\n ? providerMap.get(cliProvider.toLowerCase())\n : undefined;\n if (cliProvider && !provider) {\n return {\n model: undefined,\n warning: undefined,\n error: `Unknown provider \"${cliProvider}\". Use --list-models to see available providers/models.`,\n };\n }\n\n // If no explicit --provider, try to interpret \"provider/model\" format first.\n // When the prefix before the first slash matches a known provider, prefer that\n // interpretation over matching models whose IDs literally contain slashes\n // (e.g. \"zai/glm-5\" should resolve to provider=zai, model=glm-5, not to a\n // vercel-ai-gateway model with id \"zai/glm-5\").\n let pattern = cliModel;\n let inferredProvider = false;\n\n if (!provider) {\n const slashIndex = cliModel.indexOf(\"/\");\n if (slashIndex !== -1) {\n const maybeProvider = cliModel.substring(0, slashIndex);\n const canonical = providerMap.get(maybeProvider.toLowerCase());\n if (canonical) {\n provider = canonical;\n pattern = cliModel.substring(slashIndex + 1);\n inferredProvider = true;\n }\n }\n }\n\n // If no provider was inferred from the slash, try exact matches without provider inference.\n // This handles models whose IDs naturally contain slashes (e.g. OpenRouter-style IDs).\n if (!provider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n }\n\n if (cliProvider && provider) {\n // If both were provided, tolerate --model <provider>/<pattern> by stripping the provider prefix\n const prefix = `${provider}/`;\n if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {\n pattern = cliModel.substring(prefix.length);\n }\n }\n\n const candidates = provider\n ? availableModels.filter((m) => m.provider === provider)\n : availableModels;\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n candidates,\n {\n allowInvalidThinkingLevelFallback: false,\n },\n );\n\n if (model) {\n return { model, thinkingLevel, warning, error: undefined };\n }\n\n // If we inferred a provider from the slash but found no match within that provider,\n // fall back to matching the full input as a raw model id across all models.\n // This handles OpenRouter-style IDs like \"openai/gpt-4o:extended\" where \"openai\"\n // looks like a provider but the full string is actually a model id on openrouter.\n if (inferredProvider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n // Also try parseModelPattern on the full input against all models\n const fallback = parseModelPattern(cliModel, availableModels, {\n allowInvalidThinkingLevelFallback: false,\n });\n if (fallback.model) {\n return {\n model: fallback.model,\n thinkingLevel: fallback.thinkingLevel,\n warning: fallback.warning,\n error: undefined,\n };\n }\n }\n\n if (provider) {\n const fallbackModel = buildFallbackModel(\n provider,\n pattern,\n availableModels,\n );\n if (fallbackModel) {\n const fallbackWarning = warning\n ? `${warning} Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`\n : `Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`;\n return {\n model: fallbackModel,\n thinkingLevel: undefined,\n warning: fallbackWarning,\n error: undefined,\n };\n }\n }\n\n const display = provider ? `${provider}/${pattern}` : cliModel;\n return {\n model: undefined,\n thinkingLevel: undefined,\n warning,\n error: `Model \"${display}\" not found. Use --list-models to see available models.`,\n };\n}\n\nexport interface InitialModelResult {\n model: Model<Api> | undefined;\n thinkingLevel: ThinkingLevel;\n fallbackMessage: string | undefined;\n}\n\n/**\n * Find the initial model to use based on priority:\n * 1. CLI args (provider + model)\n * 2. First model from scoped models (if not continuing/resuming)\n * 3. Restored from session (if continuing/resuming)\n * 4. Saved default from settings\n * 5. First available model with valid API key\n */\nexport async function findInitialModel(options: {\n cliProvider?: string;\n cliModel?: string;\n scopedModels: ScopedModel[];\n isContinuing: boolean;\n defaultProvider?: string;\n defaultModelId?: string;\n defaultThinkingLevel?: ThinkingLevel;\n modelRegistry: ModelRegistry;\n}): Promise<InitialModelResult> {\n const {\n cliProvider,\n cliModel,\n scopedModels,\n isContinuing,\n defaultProvider,\n defaultModelId,\n defaultThinkingLevel,\n modelRegistry,\n } = options;\n\n let model: Model<Api> | undefined;\n let thinkingLevel: ThinkingLevel = DEFAULT_THINKING_LEVEL;\n\n // 1. CLI args take priority\n if (cliProvider && cliModel) {\n const resolved = resolveCliModel({\n cliProvider,\n cliModel,\n modelRegistry,\n });\n if (resolved.error) {\n console.error(chalk.red(resolved.error));\n process.exit(1);\n }\n if (resolved.model) {\n return {\n model: resolved.model,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // 2. Use first model from scoped models (skip if continuing/resuming)\n if (scopedModels.length > 0 && !isContinuing) {\n return {\n model: scopedModels[0].model,\n thinkingLevel:\n scopedModels[0].thinkingLevel ??\n defaultThinkingLevel ??\n DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 3. Try saved default from settings\n if (defaultProvider && defaultModelId) {\n const found = await resolveSavedModelReference(\n defaultProvider,\n defaultModelId,\n modelRegistry,\n );\n if (found) {\n model = found;\n if (defaultThinkingLevel) {\n thinkingLevel = defaultThinkingLevel;\n }\n return { model, thinkingLevel, fallbackMessage: undefined };\n }\n }\n\n // 4. Try first available model with valid API key\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n return {\n model: match,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // If no default found, use first available\n return {\n model: availableModels[0],\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 5. No model found\n return {\n model: undefined,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n}\n\n/**\n * Restore model from session, with fallback to available models\n */\nexport async function restoreModelFromSession(\n savedProvider: string,\n savedModelId: string,\n currentModel: Model<Api> | undefined,\n shouldPrintMessages: boolean,\n modelRegistry: ModelRegistry,\n): Promise<{\n model: Model<Api> | undefined;\n fallbackMessage: string | undefined;\n}> {\n const exactRestoredModel = modelRegistry.find(savedProvider, savedModelId);\n const restoredModel = exactRestoredModel && modelRegistry.hasConfiguredAuth(exactRestoredModel)\n ? exactRestoredModel\n : await buildConfiguredProviderFallbackModel(savedProvider, savedModelId, modelRegistry);\n\n if (restoredModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`),\n );\n }\n return { model: restoredModel, fallbackMessage: undefined };\n }\n\n // Model not found or no API key - fall back\n const reason = !exactRestoredModel\n ? \"model no longer exists\"\n : \"no auth configured\";\n\n if (shouldPrintMessages) {\n console.error(\n chalk.yellow(\n `Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`,\n ),\n );\n }\n\n // If we already have a model, use it as fallback\n if (currentModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${currentModel.provider}/${currentModel.id}`,\n ),\n );\n }\n return {\n model: currentModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,\n };\n }\n\n // Try to find any available model\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n let fallbackModel: Model<Api> | undefined;\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n fallbackModel = match;\n break;\n }\n }\n\n // If no default found, use first available\n if (!fallbackModel) {\n fallbackModel = availableModels[0];\n }\n\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`,\n ),\n );\n }\n\n return {\n model: fallbackModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,\n };\n }\n\n // No models available\n return { model: undefined, fallbackMessage: undefined };\n}\n"]}
1
+ {"version":3,"file":"model-resolver.js","sourceRoot":"","sources":["../../src/core/model-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Model resolution, scoping, and initial selection\n */\n\nimport type { ThinkingLevel } from \"@earendil-works/pi-agent-core\";\nimport { type Api, type Model, modelsAreEqual } from \"@earendil-works/pi-ai\";\nimport chalk from \"chalk\";\nimport { minimatch } from \"minimatch\";\nimport { isValidThinkingLevel } from \"../cli/args.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\n\n/** Default model IDs for each known provider */\nexport const defaultModelPerProvider: Record<string, string> = {\n \"amazon-bedrock\": \"us.anthropic.claude-opus-4-6-v1\",\n anthropic: \"claude-opus-4-8\",\n openai: \"gpt-5.4\",\n \"azure-openai-responses\": \"gpt-5.4\",\n \"openai-codex\": \"gpt-5.5\",\n deepseek: \"deepseek-v4-pro\",\n google: \"gemini-3.1-pro-preview\",\n \"google-vertex\": \"gemini-3.1-pro-preview\",\n \"github-copilot\": \"gpt-5.4\",\n cursor: \"composer-2\",\n openrouter: \"moonshotai/kimi-k2.6\",\n \"vercel-ai-gateway\": \"zai/glm-5.1\",\n xai: \"grok-4.20-0309-reasoning\",\n groq: \"openai/gpt-oss-120b\",\n cerebras: \"zai-glm-4.7\",\n \"ant-ling\": \"Ring-2.6-1T\",\n zai: \"glm-5.1\",\n mistral: \"devstral-medium-latest\",\n minimax: \"MiniMax-M2.7\",\n \"minimax-cn\": \"MiniMax-M2.7\",\n moonshotai: \"kimi-k2.6\",\n \"moonshotai-cn\": \"kimi-k2.6\",\n huggingface: \"moonshotai/Kimi-K2.6\",\n fireworks: \"accounts/fireworks/models/kimi-k2p6\",\n together: \"moonshotai/Kimi-K2.6\",\n opencode: \"kimi-k2.6\",\n \"opencode-go\": \"kimi-k2.6\",\n \"kimi-coding\": \"kimi-for-coding\",\n \"cloudflare-workers-ai\": \"@cf/moonshotai/kimi-k2.6\",\n \"cloudflare-ai-gateway\": \"workers-ai/@cf/moonshotai/kimi-k2.6\",\n xiaomi: \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-cn\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-ams\": \"mimo-v2.5-pro\",\n \"xiaomi-token-plan-sgp\": \"mimo-v2.5-pro\",\n};\n\nexport interface ScopedModel {\n model: Model<Api>;\n /** Thinking level if explicitly specified in pattern (e.g., \"model:high\"), undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n}\n\n/**\n * Helper to check if a model ID looks like an alias (no date suffix)\n * Dates are typically in format: -20241022 or -20250929\n */\nfunction isAlias(id: string): boolean {\n // Check if ID ends with -latest\n if (id.endsWith(\"-latest\")) return true;\n\n // Check if ID ends with a date pattern (-YYYYMMDD)\n const datePattern = /-\\d{8}$/;\n return !datePattern.test(id);\n}\n\n/**\n * Find an exact model reference match.\n * Supports either a bare model id or a canonical provider/modelId reference.\n * When matching by bare id, ambiguous matches across providers are rejected.\n */\nexport function findExactModelReferenceMatch(\n modelReference: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const trimmedReference = modelReference.trim();\n if (!trimmedReference) {\n return undefined;\n }\n\n const normalizedReference = trimmedReference.toLowerCase();\n\n const canonicalMatches = availableModels.filter(\n (model) =>\n `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n );\n if (canonicalMatches.length === 1) {\n return canonicalMatches[0];\n }\n if (canonicalMatches.length > 1) {\n return undefined;\n }\n\n const slashIndex = trimmedReference.indexOf(\"/\");\n if (slashIndex !== -1) {\n const provider = trimmedReference.substring(0, slashIndex).trim();\n const modelId = trimmedReference.substring(slashIndex + 1).trim();\n if (provider && modelId) {\n const providerMatches = availableModels.filter(\n (model) =>\n model.provider.toLowerCase() === provider.toLowerCase() &&\n model.id.toLowerCase() === modelId.toLowerCase(),\n );\n if (providerMatches.length === 1) {\n return providerMatches[0];\n }\n if (providerMatches.length > 1) {\n return undefined;\n }\n }\n }\n\n const idMatches = availableModels.filter(\n (model) => model.id.toLowerCase() === normalizedReference,\n );\n return idMatches.length === 1 ? idMatches[0] : undefined;\n}\n\n/**\n * Try to match a pattern to a model from the available models list.\n * Returns the matched model or undefined if no match found.\n */\nfunction tryMatchModel(\n modelPattern: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const exactMatch = findExactModelReferenceMatch(\n modelPattern,\n availableModels,\n );\n if (exactMatch) {\n return exactMatch;\n }\n\n // No exact match - fall back to partial matching\n const matches = availableModels.filter(\n (m) =>\n m.id.toLowerCase().includes(modelPattern.toLowerCase()) ||\n m.name?.toLowerCase().includes(modelPattern.toLowerCase()),\n );\n\n if (matches.length === 0) {\n return undefined;\n }\n\n // Separate into aliases and dated versions\n const aliases = matches.filter((m) => isAlias(m.id));\n const datedVersions = matches.filter((m) => !isAlias(m.id));\n\n if (aliases.length > 0) {\n // Prefer alias - if multiple aliases, pick the one that sorts highest\n aliases.sort((a, b) => b.id.localeCompare(a.id));\n return aliases[0];\n } else {\n // No alias found, pick latest dated version\n datedVersions.sort((a, b) => b.id.localeCompare(a.id));\n return datedVersions[0];\n }\n}\n\nexport interface ParsedModelResult {\n model: Model<Api> | undefined;\n /** Thinking level if explicitly specified in pattern, undefined otherwise */\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n}\n\nfunction buildFallbackModel(\n provider: string,\n modelId: string,\n availableModels: Model<Api>[],\n): Model<Api> | undefined {\n const providerModels = availableModels.filter((m) => m.provider === provider);\n if (providerModels.length === 0) return undefined;\n\n const defaultId = defaultModelPerProvider[provider];\n const baseModel = defaultId\n ? (providerModels.find((m) => m.id === defaultId) ?? providerModels[0])\n : providerModels[0];\n\n const fallbackContextWindow = baseModel.contextWindow;\n\n return {\n ...baseModel,\n id: modelId,\n name: modelId,\n contextWindow: fallbackContextWindow,\n defaultContextWindow: fallbackContextWindow,\n contextWindowOptions: undefined,\n };\n}\n\nasync function buildConfiguredProviderFallbackModel(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n return buildFallbackModel(provider, modelId, await modelRegistry.getAvailable());\n}\n\nexport async function resolveSavedModelReference(\n provider: string,\n modelId: string,\n modelRegistry: ModelRegistry,\n): Promise<Model<Api> | undefined> {\n const found = modelRegistry.find(provider, modelId);\n if (found) return found;\n return buildConfiguredProviderFallbackModel(provider, modelId, modelRegistry);\n}\n\n/**\n * Parse a pattern to extract model and thinking level.\n * Handles models with colons in their IDs (e.g., OpenRouter's :exacto suffix).\n *\n * Algorithm:\n * 1. Try to match full pattern as a model\n * 2. If found, return it with \"off\" thinking level\n * 3. If not found and has colons, split on last colon:\n * - If suffix is valid thinking level, use it and recurse on prefix\n * - If suffix is invalid, warn and recurse on prefix with \"off\"\n *\n * @internal Exported for testing\n */\nexport function parseModelPattern(\n pattern: string,\n availableModels: Model<Api>[],\n options?: { allowInvalidThinkingLevelFallback?: boolean },\n): ParsedModelResult {\n // Try exact match first\n const exactMatch = tryMatchModel(pattern, availableModels);\n if (exactMatch) {\n return { model: exactMatch, thinkingLevel: undefined, warning: undefined };\n }\n\n // No match - try splitting on last colon if present\n const lastColonIndex = pattern.lastIndexOf(\":\");\n if (lastColonIndex === -1) {\n // No colons, pattern simply doesn't match any model\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n const prefix = pattern.substring(0, lastColonIndex);\n const suffix = pattern.substring(lastColonIndex + 1);\n\n if (isValidThinkingLevel(suffix)) {\n // Valid thinking level - recurse on prefix and use this level\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n // Only use this thinking level if no warning from inner recursion\n return {\n model: result.model,\n thinkingLevel: result.warning ? undefined : suffix,\n warning: result.warning,\n };\n }\n return result;\n } else {\n // Invalid suffix\n const allowFallback = options?.allowInvalidThinkingLevelFallback ?? true;\n if (!allowFallback) {\n // In strict mode (CLI --model parsing), treat it as part of the model id and fail.\n // This avoids accidentally resolving to a different model.\n return { model: undefined, thinkingLevel: undefined, warning: undefined };\n }\n\n // Scope mode: recurse on prefix and warn\n const result = parseModelPattern(prefix, availableModels, options);\n if (result.model) {\n return {\n model: result.model,\n thinkingLevel: undefined,\n warning: `Invalid thinking level \"${suffix}\" in pattern \"${pattern}\". Using default instead.`,\n };\n }\n return result;\n }\n}\n\n/**\n * Resolve model patterns to actual Model objects with optional thinking levels\n * Format: \"pattern:level\" where :level is optional\n * For each pattern, finds all matching models and picks the best version:\n * 1. Prefer alias (e.g., claude-sonnet-4-5) over dated versions (claude-sonnet-4-5-20250929)\n * 2. If no alias, pick the latest dated version\n *\n * Supports models with colons in their IDs (e.g., OpenRouter's model:exacto).\n * The algorithm tries to match the full pattern first, then progressively\n * strips colon-suffixes to find a match.\n */\nexport async function resolveModelScope(\n patterns: string[],\n modelRegistry: ModelRegistry,\n): Promise<ScopedModel[]> {\n const availableModels = await modelRegistry.getAvailable();\n const scopedModels: ScopedModel[] = [];\n\n for (const pattern of patterns) {\n // Check if pattern contains glob characters\n if (\n pattern.includes(\"*\") ||\n pattern.includes(\"?\") ||\n pattern.includes(\"[\")\n ) {\n // Extract optional thinking level suffix (e.g., \"provider/*:high\")\n const colonIdx = pattern.lastIndexOf(\":\");\n let globPattern = pattern;\n let thinkingLevel: ThinkingLevel | undefined;\n\n if (colonIdx !== -1) {\n const suffix = pattern.substring(colonIdx + 1);\n if (isValidThinkingLevel(suffix)) {\n thinkingLevel = suffix;\n globPattern = pattern.substring(0, colonIdx);\n }\n }\n\n // Match against \"provider/modelId\" format OR just model ID\n // This allows \"*sonnet*\" to match without requiring \"anthropic/*sonnet*\"\n const matchingModels = availableModels.filter((m) => {\n const fullId = `${m.provider}/${m.id}`;\n return (\n minimatch(fullId, globPattern, { nocase: true }) ||\n minimatch(m.id, globPattern, { nocase: true })\n );\n });\n\n if (matchingModels.length === 0) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n for (const model of matchingModels) {\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n continue;\n }\n\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n availableModels,\n );\n\n if (warning) {\n console.warn(chalk.yellow(`Warning: ${warning}`));\n }\n\n if (!model) {\n console.warn(\n chalk.yellow(`Warning: No models match pattern \"${pattern}\"`),\n );\n continue;\n }\n\n // Avoid duplicates\n if (!scopedModels.find((sm) => modelsAreEqual(sm.model, model))) {\n scopedModels.push({ model, thinkingLevel });\n }\n }\n\n return scopedModels;\n}\n\nexport interface ResolveCliModelResult {\n model: Model<Api> | undefined;\n thinkingLevel?: ThinkingLevel;\n warning: string | undefined;\n /**\n * Error message suitable for CLI display.\n * When set, model will be undefined.\n */\n error: string | undefined;\n}\n\n/**\n * Resolve a single model from CLI flags.\n *\n * Supports:\n * - --provider <provider> --model <pattern>\n * - --model <provider>/<pattern>\n * - Fuzzy matching (same rules as model scoping: exact id, then partial id/name)\n *\n * Note: This does not apply the thinking level by itself, but it may *parse* and\n * return a thinking level from \"<pattern>:<thinking>\" so the caller can apply it.\n */\nexport function resolveCliModel(options: {\n cliProvider?: string;\n cliModel?: string;\n modelRegistry: ModelRegistry;\n}): ResolveCliModelResult {\n const { cliProvider, cliModel, modelRegistry } = options;\n\n if (!cliModel) {\n return { model: undefined, warning: undefined, error: undefined };\n }\n\n // Important: use *all* models here, not just models with pre-configured auth.\n // This allows \"--api-key\" to be used for first-time setup.\n const availableModels = modelRegistry.getAll();\n if (availableModels.length === 0) {\n return {\n model: undefined,\n warning: undefined,\n error:\n \"No models available. Check your installation or add models to models.json.\",\n };\n }\n\n // Build canonical provider lookup (case-insensitive)\n const providerMap = new Map<string, string>();\n for (const m of availableModels) {\n providerMap.set(m.provider.toLowerCase(), m.provider);\n }\n\n let provider = cliProvider\n ? providerMap.get(cliProvider.toLowerCase())\n : undefined;\n if (cliProvider && !provider) {\n return {\n model: undefined,\n warning: undefined,\n error: `Unknown provider \"${cliProvider}\". Use --list-models to see available providers/models.`,\n };\n }\n\n // If no explicit --provider, try to interpret \"provider/model\" format first.\n // When the prefix before the first slash matches a known provider, prefer that\n // interpretation over matching models whose IDs literally contain slashes\n // (e.g. \"zai/glm-5\" should resolve to provider=zai, model=glm-5, not to a\n // vercel-ai-gateway model with id \"zai/glm-5\").\n let pattern = cliModel;\n let inferredProvider = false;\n\n if (!provider) {\n const slashIndex = cliModel.indexOf(\"/\");\n if (slashIndex !== -1) {\n const maybeProvider = cliModel.substring(0, slashIndex);\n const canonical = providerMap.get(maybeProvider.toLowerCase());\n if (canonical) {\n provider = canonical;\n pattern = cliModel.substring(slashIndex + 1);\n inferredProvider = true;\n }\n }\n }\n\n // If no provider was inferred from the slash, try exact matches without provider inference.\n // This handles models whose IDs naturally contain slashes (e.g. OpenRouter-style IDs).\n if (!provider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n }\n\n if (cliProvider && provider) {\n // If both were provided, tolerate --model <provider>/<pattern> by stripping the provider prefix\n const prefix = `${provider}/`;\n if (cliModel.toLowerCase().startsWith(prefix.toLowerCase())) {\n pattern = cliModel.substring(prefix.length);\n }\n }\n\n const candidates = provider\n ? availableModels.filter((m) => m.provider === provider)\n : availableModels;\n const { model, thinkingLevel, warning } = parseModelPattern(\n pattern,\n candidates,\n {\n allowInvalidThinkingLevelFallback: false,\n },\n );\n\n if (model) {\n return { model, thinkingLevel, warning, error: undefined };\n }\n\n // If we inferred a provider from the slash but found no match within that provider,\n // fall back to matching the full input as a raw model id across all models.\n // This handles OpenRouter-style IDs like \"openai/gpt-4o:extended\" where \"openai\"\n // looks like a provider but the full string is actually a model id on openrouter.\n if (inferredProvider) {\n const lower = cliModel.toLowerCase();\n const exact = availableModels.find(\n (m) =>\n m.id.toLowerCase() === lower ||\n `${m.provider}/${m.id}`.toLowerCase() === lower,\n );\n if (exact) {\n return {\n model: exact,\n warning: undefined,\n thinkingLevel: undefined,\n error: undefined,\n };\n }\n // Also try parseModelPattern on the full input against all models\n const fallback = parseModelPattern(cliModel, availableModels, {\n allowInvalidThinkingLevelFallback: false,\n });\n if (fallback.model) {\n return {\n model: fallback.model,\n thinkingLevel: fallback.thinkingLevel,\n warning: fallback.warning,\n error: undefined,\n };\n }\n }\n\n if (provider) {\n const fallbackModel = buildFallbackModel(\n provider,\n pattern,\n availableModels,\n );\n if (fallbackModel) {\n const fallbackWarning = warning\n ? `${warning} Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`\n : `Model \"${pattern}\" not found for provider \"${provider}\". Using custom model id.`;\n return {\n model: fallbackModel,\n thinkingLevel: undefined,\n warning: fallbackWarning,\n error: undefined,\n };\n }\n }\n\n const display = provider ? `${provider}/${pattern}` : cliModel;\n return {\n model: undefined,\n thinkingLevel: undefined,\n warning,\n error: `Model \"${display}\" not found. Use --list-models to see available models.`,\n };\n}\n\nexport interface InitialModelResult {\n model: Model<Api> | undefined;\n thinkingLevel: ThinkingLevel;\n fallbackMessage: string | undefined;\n}\n\n/**\n * Find the initial model to use based on priority:\n * 1. CLI args (provider + model)\n * 2. First model from scoped models (if not continuing/resuming)\n * 3. Restored from session (if continuing/resuming)\n * 4. Saved default from settings\n * 5. First available model with valid API key\n */\nexport async function findInitialModel(options: {\n cliProvider?: string;\n cliModel?: string;\n scopedModels: ScopedModel[];\n isContinuing: boolean;\n defaultProvider?: string;\n defaultModelId?: string;\n defaultThinkingLevel?: ThinkingLevel;\n modelRegistry: ModelRegistry;\n}): Promise<InitialModelResult> {\n const {\n cliProvider,\n cliModel,\n scopedModels,\n isContinuing,\n defaultProvider,\n defaultModelId,\n defaultThinkingLevel,\n modelRegistry,\n } = options;\n\n let model: Model<Api> | undefined;\n let thinkingLevel: ThinkingLevel = DEFAULT_THINKING_LEVEL;\n\n // 1. CLI args take priority\n if (cliProvider && cliModel) {\n const resolved = resolveCliModel({\n cliProvider,\n cliModel,\n modelRegistry,\n });\n if (resolved.error) {\n console.error(chalk.red(resolved.error));\n process.exit(1);\n }\n if (resolved.model) {\n return {\n model: resolved.model,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // 2. Use first model from scoped models (skip if continuing/resuming)\n if (scopedModels.length > 0 && !isContinuing) {\n return {\n model: scopedModels[0].model,\n thinkingLevel:\n scopedModels[0].thinkingLevel ??\n defaultThinkingLevel ??\n DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 3. Try saved default from settings\n if (defaultProvider && defaultModelId) {\n const found = await resolveSavedModelReference(\n defaultProvider,\n defaultModelId,\n modelRegistry,\n );\n if (found) {\n model = found;\n if (defaultThinkingLevel) {\n thinkingLevel = defaultThinkingLevel;\n }\n return { model, thinkingLevel, fallbackMessage: undefined };\n }\n }\n\n // 4. Try first available model with valid API key\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n return {\n model: match,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n }\n\n // If no default found, use first available\n return {\n model: availableModels[0],\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n }\n\n // 5. No model found\n return {\n model: undefined,\n thinkingLevel: DEFAULT_THINKING_LEVEL,\n fallbackMessage: undefined,\n };\n}\n\n/**\n * Restore model from session, with fallback to available models\n */\nexport async function restoreModelFromSession(\n savedProvider: string,\n savedModelId: string,\n currentModel: Model<Api> | undefined,\n shouldPrintMessages: boolean,\n modelRegistry: ModelRegistry,\n): Promise<{\n model: Model<Api> | undefined;\n fallbackMessage: string | undefined;\n}> {\n const exactRestoredModel = modelRegistry.find(savedProvider, savedModelId);\n const restoredModel = exactRestoredModel && modelRegistry.hasConfiguredAuth(exactRestoredModel)\n ? exactRestoredModel\n : await buildConfiguredProviderFallbackModel(savedProvider, savedModelId, modelRegistry);\n\n if (restoredModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(`Restored model: ${savedProvider}/${savedModelId}`),\n );\n }\n return { model: restoredModel, fallbackMessage: undefined };\n }\n\n // Model not found or no API key - fall back\n const reason = !exactRestoredModel\n ? \"model no longer exists\"\n : \"no auth configured\";\n\n if (shouldPrintMessages) {\n console.error(\n chalk.yellow(\n `Warning: Could not restore model ${savedProvider}/${savedModelId} (${reason}).`,\n ),\n );\n }\n\n // If we already have a model, use it as fallback\n if (currentModel) {\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${currentModel.provider}/${currentModel.id}`,\n ),\n );\n }\n return {\n model: currentModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${currentModel.provider}/${currentModel.id}.`,\n };\n }\n\n // Try to find any available model\n const availableModels = await modelRegistry.getAvailable();\n\n if (availableModels.length > 0) {\n // Try to find a default model from known providers\n let fallbackModel: Model<Api> | undefined;\n for (const [provider, defaultId] of Object.entries(\n defaultModelPerProvider,\n )) {\n const match = availableModels.find(\n (m) => m.provider === provider && m.id === defaultId,\n );\n if (match) {\n fallbackModel = match;\n break;\n }\n }\n\n // If no default found, use first available\n if (!fallbackModel) {\n fallbackModel = availableModels[0];\n }\n\n if (shouldPrintMessages) {\n console.log(\n chalk.dim(\n `Falling back to: ${fallbackModel.provider}/${fallbackModel.id}`,\n ),\n );\n }\n\n return {\n model: fallbackModel,\n fallbackMessage: `Could not restore model ${savedProvider}/${savedModelId} (${reason}). Using ${fallbackModel.provider}/${fallbackModel.id}.`,\n };\n }\n\n // No models available\n return { model: undefined, fallbackMessage: undefined };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provider-attribution.d.ts","sourceRoot":"","sources":["../../src/core/provider-attribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA6E7D,wBAAgB,+BAA+B,CAC9C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,GAAG,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,GACzD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAapC","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { APP_NAME } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { isInstallTelemetryEnabled } from \"./telemetry.ts\";\n\nconst OPENROUTER_HOST = \"openrouter.ai\";\nconst NVIDIA_NIM_HOST = \"integrate.api.nvidia.com\";\nconst CLOUDFLARE_API_HOST = \"api.cloudflare.com\";\nconst CLOUDFLARE_AI_GATEWAY_HOST = \"gateway.ai.cloudflare.com\";\nconst OPENCODE_HOST = \"opencode.ai\";\n\nfunction matchesHost(baseUrl: string, expectedHost: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl).hostname === expectedHost;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isOpenRouterModel(model: Model<Api>): boolean {\n\treturn model.provider === \"openrouter\" || matchesHost(model.baseUrl, OPENROUTER_HOST);\n}\n\nfunction isNvidiaNimModel(model: Model<Api>): boolean {\n\treturn model.provider === \"nvidia\" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);\n}\n\nfunction isCloudflareModel(model: Model<Api>): boolean {\n\treturn (\n\t\tmodel.provider === \"cloudflare-workers-ai\" ||\n\t\tmodel.provider === \"cloudflare-ai-gateway\" ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST)\n\t);\n}\n\nfunction getDefaultAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n): Record<string, string> | undefined {\n\tif (!isInstallTelemetryEnabled(settingsManager)) {\n\t\treturn undefined;\n\t}\n\n\tif (isOpenRouterModel(model)) {\n\t\treturn {\n\t\t\t\"HTTP-Referer\": \"https://atomic.sh\",\n\t\t\t\"X-OpenRouter-Title\": APP_NAME,\n\t\t\t\"X-OpenRouter-Categories\": \"cli-agent\",\n\t\t};\n\t}\n\n\tif (isNvidiaNimModel(model)) {\n\t\treturn {\n\t\t\t\"X-BILLING-INVOKE-ORIGIN\": \"Atomic\",\n\t\t};\n\t}\n\n\tif (isCloudflareModel(model)) {\n\t\treturn {\n\t\t\t\"User-Agent\": APP_NAME,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n\nfunction getSessionHeaders(model: Model<Api>, sessionId: string | undefined): Record<string, string> | undefined {\n\tif (!sessionId) return undefined;\n\tif (\n\t\tmodel.provider !== \"opencode\" &&\n\t\tmodel.provider !== \"opencode-go\" &&\n\t\t!matchesHost(model.baseUrl, OPENCODE_HOST)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { \"x-opencode-session\": sessionId, \"x-opencode-client\": APP_NAME };\n}\n\nexport function mergeProviderAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n\tsessionId: string | undefined,\n\t...headerSources: Array<Record<string, string> | undefined>\n): Record<string, string> | undefined {\n\tconst merged = {\n\t\t...getSessionHeaders(model, sessionId),\n\t\t...getDefaultAttributionHeaders(model, settingsManager),\n\t};\n\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n}\n"]}
1
+ {"version":3,"file":"provider-attribution.d.ts","sourceRoot":"","sources":["../../src/core/provider-attribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA6F7D,wBAAgB,+BAA+B,CAC9C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,GAAG,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,GACzD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAUpC","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { APP_NAME } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { isInstallTelemetryEnabled } from \"./telemetry.ts\";\n\nconst OPENROUTER_HOST = \"openrouter.ai\";\nconst NVIDIA_NIM_HOST = \"integrate.api.nvidia.com\";\nconst CLOUDFLARE_API_HOST = \"api.cloudflare.com\";\nconst CLOUDFLARE_AI_GATEWAY_HOST = \"gateway.ai.cloudflare.com\";\nconst OPENCODE_HOST = \"opencode.ai\";\n\nfunction matchesHost(baseUrl: string, expectedHost: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl).hostname === expectedHost;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isOpenRouterModel(model: Model<Api>): boolean {\n\treturn model.provider === \"openrouter\" || matchesHost(model.baseUrl, OPENROUTER_HOST);\n}\n\nfunction isNvidiaNimModel(model: Model<Api>): boolean {\n\treturn model.provider === \"nvidia\" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);\n}\n\nfunction isCloudflareModel(model: Model<Api>): boolean {\n\treturn (\n\t\tmodel.provider === \"cloudflare-workers-ai\" ||\n\t\tmodel.provider === \"cloudflare-ai-gateway\" ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST)\n\t);\n}\n\nfunction getDefaultAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n): Record<string, string> | undefined {\n\tif (!isInstallTelemetryEnabled(settingsManager)) {\n\t\treturn undefined;\n\t}\n\n\tif (isOpenRouterModel(model)) {\n\t\treturn {\n\t\t\t\"HTTP-Referer\": \"https://atomic.sh\",\n\t\t\t\"X-OpenRouter-Title\": APP_NAME,\n\t\t\t\"X-OpenRouter-Categories\": \"cli-agent\",\n\t\t};\n\t}\n\n\tif (isNvidiaNimModel(model)) {\n\t\treturn {\n\t\t\t\"X-BILLING-INVOKE-ORIGIN\": \"Atomic\",\n\t\t};\n\t}\n\n\tif (isCloudflareModel(model)) {\n\t\treturn {\n\t\t\t\"User-Agent\": APP_NAME,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n\nfunction getSessionHeaders(model: Model<Api>, sessionId: string | undefined): Record<string, string> | undefined {\n\tif (!sessionId) return undefined;\n\tif (\n\t\tmodel.provider !== \"opencode\" &&\n\t\tmodel.provider !== \"opencode-go\" &&\n\t\t!matchesHost(model.baseUrl, OPENCODE_HOST)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { \"x-opencode-session\": sessionId, \"x-opencode-client\": APP_NAME };\n}\n\nfunction mergeHeaderSource(\n\tmerged: Record<string, string>,\n\theaders: Record<string, string> | undefined,\n): void {\n\tif (!headers) return;\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tconst normalizedKey = key.toLowerCase();\n\t\tfor (const existingKey of Object.keys(merged)) {\n\t\t\tif (existingKey.toLowerCase() === normalizedKey) {\n\t\t\t\tdelete merged[existingKey];\n\t\t\t}\n\t\t}\n\t\tmerged[key] = value;\n\t}\n}\n\nexport function mergeProviderAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n\tsessionId: string | undefined,\n\t...headerSources: Array<Record<string, string> | undefined>\n): Record<string, string> | undefined {\n\tconst merged: Record<string, string> = {};\n\tmergeHeaderSource(merged, getSessionHeaders(model, sessionId));\n\tmergeHeaderSource(merged, getDefaultAttributionHeaders(model, settingsManager));\n\n\tfor (const headers of headerSources) {\n\t\tmergeHeaderSource(merged, headers);\n\t}\n\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n}\n"]}
@@ -58,15 +58,25 @@ function getSessionHeaders(model, sessionId) {
58
58
  }
59
59
  return { "x-opencode-session": sessionId, "x-opencode-client": APP_NAME };
60
60
  }
61
+ function mergeHeaderSource(merged, headers) {
62
+ if (!headers)
63
+ return;
64
+ for (const [key, value] of Object.entries(headers)) {
65
+ const normalizedKey = key.toLowerCase();
66
+ for (const existingKey of Object.keys(merged)) {
67
+ if (existingKey.toLowerCase() === normalizedKey) {
68
+ delete merged[existingKey];
69
+ }
70
+ }
71
+ merged[key] = value;
72
+ }
73
+ }
61
74
  export function mergeProviderAttributionHeaders(model, settingsManager, sessionId, ...headerSources) {
62
- const merged = {
63
- ...getSessionHeaders(model, sessionId),
64
- ...getDefaultAttributionHeaders(model, settingsManager),
65
- };
75
+ const merged = {};
76
+ mergeHeaderSource(merged, getSessionHeaders(model, sessionId));
77
+ mergeHeaderSource(merged, getDefaultAttributionHeaders(model, settingsManager));
66
78
  for (const headers of headerSources) {
67
- if (headers) {
68
- Object.assign(merged, headers);
69
- }
79
+ mergeHeaderSource(merged, headers);
70
80
  }
71
81
  return Object.keys(merged).length > 0 ? merged : undefined;
72
82
  }
@@ -1 +1 @@
1
- {"version":3,"file":"provider-attribution.js","sourceRoot":"","sources":["../../src/core/provider-attribution.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { APP_NAME } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { isInstallTelemetryEnabled } from \"./telemetry.ts\";\n\nconst OPENROUTER_HOST = \"openrouter.ai\";\nconst NVIDIA_NIM_HOST = \"integrate.api.nvidia.com\";\nconst CLOUDFLARE_API_HOST = \"api.cloudflare.com\";\nconst CLOUDFLARE_AI_GATEWAY_HOST = \"gateway.ai.cloudflare.com\";\nconst OPENCODE_HOST = \"opencode.ai\";\n\nfunction matchesHost(baseUrl: string, expectedHost: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl).hostname === expectedHost;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isOpenRouterModel(model: Model<Api>): boolean {\n\treturn model.provider === \"openrouter\" || matchesHost(model.baseUrl, OPENROUTER_HOST);\n}\n\nfunction isNvidiaNimModel(model: Model<Api>): boolean {\n\treturn model.provider === \"nvidia\" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);\n}\n\nfunction isCloudflareModel(model: Model<Api>): boolean {\n\treturn (\n\t\tmodel.provider === \"cloudflare-workers-ai\" ||\n\t\tmodel.provider === \"cloudflare-ai-gateway\" ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST)\n\t);\n}\n\nfunction getDefaultAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n): Record<string, string> | undefined {\n\tif (!isInstallTelemetryEnabled(settingsManager)) {\n\t\treturn undefined;\n\t}\n\n\tif (isOpenRouterModel(model)) {\n\t\treturn {\n\t\t\t\"HTTP-Referer\": \"https://atomic.sh\",\n\t\t\t\"X-OpenRouter-Title\": APP_NAME,\n\t\t\t\"X-OpenRouter-Categories\": \"cli-agent\",\n\t\t};\n\t}\n\n\tif (isNvidiaNimModel(model)) {\n\t\treturn {\n\t\t\t\"X-BILLING-INVOKE-ORIGIN\": \"Atomic\",\n\t\t};\n\t}\n\n\tif (isCloudflareModel(model)) {\n\t\treturn {\n\t\t\t\"User-Agent\": APP_NAME,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n\nfunction getSessionHeaders(model: Model<Api>, sessionId: string | undefined): Record<string, string> | undefined {\n\tif (!sessionId) return undefined;\n\tif (\n\t\tmodel.provider !== \"opencode\" &&\n\t\tmodel.provider !== \"opencode-go\" &&\n\t\t!matchesHost(model.baseUrl, OPENCODE_HOST)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { \"x-opencode-session\": sessionId, \"x-opencode-client\": APP_NAME };\n}\n\nexport function mergeProviderAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n\tsessionId: string | undefined,\n\t...headerSources: Array<Record<string, string> | undefined>\n): Record<string, string> | undefined {\n\tconst merged = {\n\t\t...getSessionHeaders(model, sessionId),\n\t\t...getDefaultAttributionHeaders(model, settingsManager),\n\t};\n\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n}\n"]}
1
+ {"version":3,"file":"provider-attribution.js","sourceRoot":"","sources":["../../src/core/provider-attribution.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { APP_NAME } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { isInstallTelemetryEnabled } from \"./telemetry.ts\";\n\nconst OPENROUTER_HOST = \"openrouter.ai\";\nconst NVIDIA_NIM_HOST = \"integrate.api.nvidia.com\";\nconst CLOUDFLARE_API_HOST = \"api.cloudflare.com\";\nconst CLOUDFLARE_AI_GATEWAY_HOST = \"gateway.ai.cloudflare.com\";\nconst OPENCODE_HOST = \"opencode.ai\";\n\nfunction matchesHost(baseUrl: string, expectedHost: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl).hostname === expectedHost;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isOpenRouterModel(model: Model<Api>): boolean {\n\treturn model.provider === \"openrouter\" || matchesHost(model.baseUrl, OPENROUTER_HOST);\n}\n\nfunction isNvidiaNimModel(model: Model<Api>): boolean {\n\treturn model.provider === \"nvidia\" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);\n}\n\nfunction isCloudflareModel(model: Model<Api>): boolean {\n\treturn (\n\t\tmodel.provider === \"cloudflare-workers-ai\" ||\n\t\tmodel.provider === \"cloudflare-ai-gateway\" ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST)\n\t);\n}\n\nfunction getDefaultAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n): Record<string, string> | undefined {\n\tif (!isInstallTelemetryEnabled(settingsManager)) {\n\t\treturn undefined;\n\t}\n\n\tif (isOpenRouterModel(model)) {\n\t\treturn {\n\t\t\t\"HTTP-Referer\": \"https://atomic.sh\",\n\t\t\t\"X-OpenRouter-Title\": APP_NAME,\n\t\t\t\"X-OpenRouter-Categories\": \"cli-agent\",\n\t\t};\n\t}\n\n\tif (isNvidiaNimModel(model)) {\n\t\treturn {\n\t\t\t\"X-BILLING-INVOKE-ORIGIN\": \"Atomic\",\n\t\t};\n\t}\n\n\tif (isCloudflareModel(model)) {\n\t\treturn {\n\t\t\t\"User-Agent\": APP_NAME,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n\nfunction getSessionHeaders(model: Model<Api>, sessionId: string | undefined): Record<string, string> | undefined {\n\tif (!sessionId) return undefined;\n\tif (\n\t\tmodel.provider !== \"opencode\" &&\n\t\tmodel.provider !== \"opencode-go\" &&\n\t\t!matchesHost(model.baseUrl, OPENCODE_HOST)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { \"x-opencode-session\": sessionId, \"x-opencode-client\": APP_NAME };\n}\n\nfunction mergeHeaderSource(\n\tmerged: Record<string, string>,\n\theaders: Record<string, string> | undefined,\n): void {\n\tif (!headers) return;\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tconst normalizedKey = key.toLowerCase();\n\t\tfor (const existingKey of Object.keys(merged)) {\n\t\t\tif (existingKey.toLowerCase() === normalizedKey) {\n\t\t\t\tdelete merged[existingKey];\n\t\t\t}\n\t\t}\n\t\tmerged[key] = value;\n\t}\n}\n\nexport function mergeProviderAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n\tsessionId: string | undefined,\n\t...headerSources: Array<Record<string, string> | undefined>\n): Record<string, string> | undefined {\n\tconst merged: Record<string, string> = {};\n\tmergeHeaderSource(merged, getSessionHeaders(model, sessionId));\n\tmergeHeaderSource(merged, getDefaultAttributionHeaders(model, settingsManager));\n\n\tfor (const headers of headerSources) {\n\t\tmergeHeaderSource(merged, headers);\n\t}\n\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n}\n"]}
@@ -21,6 +21,10 @@ export interface CreateAgentSessionOptions {
21
21
  model?: Model<Api>;
22
22
  /** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */
23
23
  thinkingLevel?: ThinkingLevel;
24
+ /** Context window token count. Default: model scalar contextWindow, or settings/session override when supported. */
25
+ contextWindow?: number;
26
+ /** Treat unsupported contextWindow as an error instead of a warning/fallback. */
27
+ contextWindowStrict?: boolean;
24
28
  /** Models available for cycling (Ctrl+P in interactive mode) */
25
29
  scopedModels?: Array<{
26
30
  model: Model<Api>;
@@ -74,6 +78,10 @@ export interface CreateAgentSessionResult {
74
78
  extensionsResult: LoadExtensionsResult;
75
79
  /** Warning if session was restored with a different model than saved */
76
80
  modelFallbackMessage?: string;
81
+ /** Warning if a saved/default context window could not be applied to the selected model. */
82
+ contextWindowWarning?: string;
83
+ /** Error if an explicit strict context-window selection is unsupported. */
84
+ contextWindowError?: string;
77
85
  }
78
86
  export * from "./agent-session-runtime.ts";
79
87
  export type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionFactory, SlashCommandInfo, SlashCommandSource, ToolDefinition, } from "./extensions/index.ts";
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport {\n Agent,\n type AgentMessage,\n type ThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport {\n clampThinkingLevel,\n type Api,\n type Message,\n type Model,\n streamSimple,\n} from \"@earendil-works/pi-ai\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport {\n shouldApplyCodexFastMode,\n streamWithCodexFastMode,\n withCodexFastModePayload,\n withCodexFastModeStreamOptions,\n} from \"./codex-fast-mode.ts\";\nimport { restoreAnthropicReplayThinkingBlocks } from \"./anthropic-thinking-guard.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type {\n ExtensionRunner,\n LoadExtensionsResult,\n OrchestrationContext,\n SessionStartEvent,\n ToolDefinition,\n} from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport { findInitialModel, resolveSavedModelReference } from \"./model-resolver.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport { time } from \"./timings.ts\";\nimport {\n createBashTool,\n createCodingTools,\n createEditTool,\n createFindTool,\n createGrepTool,\n createLsTool,\n createReadOnlyTools,\n createReadTool,\n STRUCTURED_OUTPUT_TOOL_NAME,\n createStructuredOutputCapture,\n createStructuredOutputTool,\n createWriteTool,\n defaultToolNames,\n withFileMutationQueue,\n type BashCommandPolicy,\n} from \"./tools/index.ts\";\n\nexport interface CreateAgentSessionOptions {\n /** Working directory for project-local discovery. Default: process.cwd() */\n cwd?: string;\n /** Global config directory. Default: ~/.atomic/agent */\n agentDir?: string;\n\n /** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */\n authStorage?: AuthStorage;\n /** Model registry. Default: ModelRegistry.create(authStorage, agentDir/models.json) */\n modelRegistry?: ModelRegistry;\n\n /** Model to use. Default: from settings, else first available */\n model?: Model<Api>;\n /** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n thinkingLevel?: ThinkingLevel;\n /** Models available for cycling (Ctrl+P in interactive mode) */\n scopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\n /**\n * Optional default tool suppression mode when no explicit allowlist is provided.\n *\n * - \"all\": start with no tools enabled\n * - \"builtin\": disable the default built-in tools (read, bash, edit, write,\n * ask_user_question, todo) but keep extension/custom tools enabled\n */\n noTools?: \"all\" | \"builtin\";\n /**\n * Optional allowlist of tool names.\n *\n * When omitted, pi enables the default built-in tools (read, bash, edit, write,\n * ask_user_question, todo) and leaves extension/custom tools enabled unless\n * `noTools` changes that default.\n * When provided, only the listed tool names are enabled, minus any names in\n * `excludedTools`.\n */\n tools?: string[];\n /**\n * Optional blocklist of tool names.\n *\n * Matching built-in, extension, and SDK custom tools are omitted from the\n * final session tool registry and active tool set. Unknown names are ignored.\n */\n excludedTools?: string[];\n /** Custom tools to register (in addition to built-in tools). */\n customTools?: ToolDefinition[];\n /** Optional command-level policy for the built-in bash tool. Does not expose bash by itself. */\n bashPolicy?: BashCommandPolicy;\n\n /** Resource loader. When omitted, DefaultResourceLoader is used. */\n resourceLoader?: ResourceLoader;\n\n /** Session manager. Default: SessionManager.create(cwd) */\n sessionManager?: SessionManager;\n\n /** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n settingsManager?: SettingsManager;\n /** Session start event metadata for extension runtime startup. */\n sessionStartEvent?: SessionStartEvent;\n /** Session-scoped orchestration policy exposed to extension/tool handlers. */\n orchestrationContext?: OrchestrationContext;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n /** The created session */\n session: AgentSession;\n /** Extensions result (for UI context setup in interactive mode) */\n extensionsResult: LoadExtensionsResult;\n /** Warning if session was restored with a different model than saved */\n modelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n ExtensionAPI,\n ExtensionCommandContext,\n ExtensionContext,\n ExtensionFactory,\n SlashCommandInfo,\n SlashCommandSource,\n ToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type {\n BashCommandParseError,\n BashCommandParseResult,\n BashCommandPolicy,\n BashCommandPolicyDecision,\n BashCommandPolicyMatchMode,\n BashCommandPolicyRejection,\n BashCommandRule,\n BashCommandSegment,\n BashCommandSegmentSource,\n JsonObject,\n JsonPrimitive,\n JsonValue,\n StructuredOutputCapture,\n StructuredOutputFileCapture,\n StructuredOutputToolOptions,\n Tool,\n} from \"./tools/index.ts\";\n\nexport {\n withFileMutationQueue,\n STRUCTURED_OUTPUT_TOOL_NAME,\n // Tool factories (for custom cwd)\n createCodingTools,\n createReadOnlyTools,\n createReadTool,\n createBashTool,\n createEditTool,\n createWriteTool,\n createGrepTool,\n createFindTool,\n createLsTool,\n createStructuredOutputCapture,\n createStructuredOutputTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n return getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(\n options: CreateAgentSessionOptions = {},\n): Promise<CreateAgentSessionResult> {\n const cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n const agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n let resourceLoader = options.resourceLoader;\n\n // Use provided or create AuthStorage and ModelRegistry\n const authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n const modelsPath = options.agentDir\n ? join(agentDir, \"models.json\")\n : undefined;\n const authStorage = options.authStorage ?? AuthStorage.create(authPath);\n const modelRegistry =\n options.modelRegistry ?? ModelRegistry.create(authStorage, modelsPath);\n\n const settingsManager =\n options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n const sessionManager =\n options.sessionManager ??\n SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n if (!resourceLoader) {\n resourceLoader = new DefaultResourceLoader({\n cwd,\n agentDir,\n settingsManager,\n });\n await resourceLoader.reload();\n time(\"resourceLoader.reload\");\n }\n\n // Check if session has existing data to restore\n const existingSession = sessionManager.buildSessionContext();\n const hasExistingSession = existingSession.messages.length > 0;\n const hasThinkingEntry = sessionManager\n .getBranch()\n .some((entry) => entry.type === \"thinking_level_change\");\n\n let model = options.model;\n let modelFallbackMessage: string | undefined;\n\n // If session has data, try to restore model from it\n if (!model && hasExistingSession && existingSession.model) {\n const restoredModel = await resolveSavedModelReference(\n existingSession.model.provider,\n existingSession.model.modelId,\n modelRegistry,\n );\n if (restoredModel && modelRegistry.hasConfiguredAuth(restoredModel)) {\n model = restoredModel;\n }\n if (!model) {\n modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n }\n }\n\n // If still no model, use findInitialModel (checks settings default, then provider defaults)\n if (!model) {\n const result = await findInitialModel({\n scopedModels: [],\n isContinuing: hasExistingSession,\n defaultProvider: settingsManager.getDefaultProvider(),\n defaultModelId: settingsManager.getDefaultModel(),\n defaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n modelRegistry,\n });\n model = result.model;\n if (!model) {\n modelFallbackMessage = formatNoModelsAvailableMessage();\n } else if (modelFallbackMessage) {\n modelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n }\n }\n\n let thinkingLevel = options.thinkingLevel;\n\n // If session has data, restore thinking level from it\n if (thinkingLevel === undefined && hasExistingSession) {\n thinkingLevel = hasThinkingEntry\n ? (existingSession.thinkingLevel as ThinkingLevel)\n : (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n }\n\n // Fall back to settings default\n if (thinkingLevel === undefined) {\n thinkingLevel =\n settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n }\n\n // Clamp to model capabilities\n if (!model) {\n thinkingLevel = \"off\";\n } else {\n thinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n }\n\n const allowedToolNames =\n options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n const initialActiveToolNames: string[] = options.tools\n ? [...options.tools]\n : options.noTools\n ? []\n : [...defaultToolNames];\n\n let agent: Agent;\n\n let lastConvertedLlmMessages: Message[] | undefined;\n\n // Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n const converted = convertToLlm(messages);\n // Check setting dynamically so mid-session changes take effect\n if (!settingsManager.getBlockImages()) {\n lastConvertedLlmMessages = converted;\n return converted;\n }\n // Filter out ImageContent from all messages, replacing with text placeholder\n const filtered = converted.map((msg) => {\n if (msg.role === \"user\" || msg.role === \"toolResult\") {\n const content = msg.content;\n if (Array.isArray(content)) {\n const hasImages = content.some((c) => c.type === \"image\");\n if (hasImages) {\n const filteredContent = content\n .map((c) =>\n c.type === \"image\"\n ? {\n type: \"text\" as const,\n text: \"Image reading is disabled.\",\n }\n : c,\n )\n .filter(\n (c, i, arr) =>\n // Dedupe consecutive \"Image reading is disabled.\" texts\n !(\n c.type === \"text\" &&\n c.text === \"Image reading is disabled.\" &&\n i > 0 &&\n arr[i - 1].type === \"text\" &&\n (arr[i - 1] as { type: \"text\"; text: string }).text ===\n \"Image reading is disabled.\"\n ),\n );\n return { ...msg, content: filteredContent };\n }\n }\n }\n return msg;\n });\n lastConvertedLlmMessages = filtered;\n return filtered;\n };\n\n const extensionRunnerRef: { current?: ExtensionRunner } = {};\n const isCodexFastModeEnabled = (requestModel: Model<Api>): boolean =>\n shouldApplyCodexFastMode(\n requestModel,\n settingsManager.getCodexFastModeSettings(),\n options.orchestrationContext,\n );\n\n agent = new Agent({\n initialState: {\n systemPrompt: \"\",\n model,\n thinkingLevel,\n tools: [],\n },\n convertToLlm: convertToLlmWithBlockImages,\n streamFn: async (model, context, streamOptions) => {\n const auth = await modelRegistry.getApiKeyAndHeaders(model);\n if (!auth.ok) {\n throw new Error(auth.error);\n }\n const providerRetrySettings = settingsManager.getProviderRetrySettings();\n const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n // SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n // Use max int32 to effectively disable the timeout.\n const effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n const timeoutMs = streamOptions?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n const websocketConnectTimeoutMs =\n streamOptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n const attributionHeaders = mergeProviderAttributionHeaders(\n model,\n settingsManager,\n streamOptions?.sessionId,\n auth.headers,\n streamOptions?.headers,\n );\n const fastModeEnabled = isCodexFastModeEnabled(model);\n const codexFastModeStreamOptions = withCodexFastModeStreamOptions(\n {\n ...streamOptions,\n apiKey: auth.apiKey,\n timeoutMs,\n websocketConnectTimeoutMs,\n maxRetries: streamOptions?.maxRetries ?? providerRetrySettings.maxRetries,\n maxRetryDelayMs:\n streamOptions?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n headers: attributionHeaders,\n },\n fastModeEnabled,\n );\n if (modelRegistry.hasRegisteredStreamSimpleForApi(model.api)) {\n return streamSimple(model, context, codexFastModeStreamOptions);\n }\n return streamWithCodexFastMode(model, context, codexFastModeStreamOptions);\n },\n onPayload: async (payload, model) => {\n const fastModeEnabled = isCodexFastModeEnabled(model);\n const guardedPayload = withCodexFastModePayload(payload, fastModeEnabled);\n const sourceMessages = lastConvertedLlmMessages;\n const replayGuardedPayload = sourceMessages\n ? restoreAnthropicReplayThinkingBlocks(guardedPayload, sourceMessages, model)\n : guardedPayload;\n const runner = extensionRunnerRef.current;\n if (!runner?.hasHandlers(\"before_provider_request\")) {\n return replayGuardedPayload;\n }\n const extensionPayload = await runner.emitBeforeProviderRequest(\n replayGuardedPayload,\n );\n return sourceMessages\n ? restoreAnthropicReplayThinkingBlocks(extensionPayload, sourceMessages, model)\n : extensionPayload;\n },\n onResponse: async (response, _model) => {\n const runner = extensionRunnerRef.current;\n if (!runner?.hasHandlers(\"after_provider_response\")) {\n return;\n }\n await runner.emit({\n type: \"after_provider_response\",\n status: response.status,\n headers: response.headers,\n });\n },\n sessionId: sessionManager.getSessionId(),\n transformContext: async (messages) => {\n const runner = extensionRunnerRef.current;\n if (!runner) return messages;\n return runner.emitContext(messages);\n },\n steeringMode: settingsManager.getSteeringMode(),\n followUpMode: settingsManager.getFollowUpMode(),\n transport: settingsManager.getTransport(),\n thinkingBudgets: settingsManager.getThinkingBudgets(),\n maxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n });\n\n // Restore messages if session has existing data\n if (hasExistingSession) {\n agent.state.messages = existingSession.messages;\n if (!hasThinkingEntry) {\n sessionManager.appendThinkingLevelChange(thinkingLevel);\n }\n } else {\n // Save initial model and thinking level for new sessions so they can be restored on resume\n if (model) {\n sessionManager.appendModelChange(model.provider, model.id);\n }\n sessionManager.appendThinkingLevelChange(thinkingLevel);\n }\n\n const session = new AgentSession({\n agent,\n sessionManager,\n settingsManager,\n cwd,\n scopedModels: options.scopedModels,\n resourceLoader,\n customTools: options.customTools,\n bashPolicy: options.bashPolicy,\n modelRegistry,\n initialActiveToolNames,\n allowedToolNames,\n excludedToolNames: options.excludedTools,\n extensionRunnerRef,\n sessionStartEvent: options.sessionStartEvent,\n orchestrationContext: options.orchestrationContext,\n });\n const extensionsResult = resourceLoader.getExtensions();\n\n return {\n session,\n extensionsResult,\n modelFallbackMessage,\n };\n}\n"]}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport {\n Agent,\n type AgentMessage,\n type ThinkingLevel,\n} from \"@earendil-works/pi-agent-core\";\nimport {\n clampThinkingLevel,\n type Api,\n type Message,\n type Model,\n streamSimple,\n} from \"@earendil-works/pi-ai\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport {\n shouldApplyCodexFastMode,\n streamWithCodexFastMode,\n withCodexFastModePayload,\n withCodexFastModeStreamOptions,\n} from \"./codex-fast-mode.ts\";\nimport { restoreAnthropicReplayThinkingBlocks } from \"./anthropic-thinking-guard.ts\";\nimport { getModelDefaultContextWindow, getSupportedContextWindows, selectContextWindow } from \"./context-window.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport type {\n ExtensionRunner,\n LoadExtensionsResult,\n OrchestrationContext,\n SessionStartEvent,\n ToolDefinition,\n} from \"./extensions/index.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { ModelRegistry } from \"./model-registry.ts\";\nimport { findInitialModel, resolveSavedModelReference } from \"./model-resolver.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport { time } from \"./timings.ts\";\nimport {\n createBashTool,\n createCodingTools,\n createEditTool,\n createFindTool,\n createGrepTool,\n createLsTool,\n createReadOnlyTools,\n createReadTool,\n STRUCTURED_OUTPUT_TOOL_NAME,\n createStructuredOutputCapture,\n createStructuredOutputTool,\n createWriteTool,\n defaultToolNames,\n withFileMutationQueue,\n type BashCommandPolicy,\n} from \"./tools/index.ts\";\n\nexport interface CreateAgentSessionOptions {\n /** Working directory for project-local discovery. Default: process.cwd() */\n cwd?: string;\n /** Global config directory. Default: ~/.atomic/agent */\n agentDir?: string;\n\n /** Auth storage for credentials. Default: AuthStorage.create(agentDir/auth.json) */\n authStorage?: AuthStorage;\n /** Model registry. Default: ModelRegistry.create(authStorage, agentDir/models.json) */\n modelRegistry?: ModelRegistry;\n\n /** Model to use. Default: from settings, else first available */\n model?: Model<Api>;\n /** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n thinkingLevel?: ThinkingLevel;\n /** Context window token count. Default: model scalar contextWindow, or settings/session override when supported. */\n contextWindow?: number;\n /** Treat unsupported contextWindow as an error instead of a warning/fallback. */\n contextWindowStrict?: boolean;\n /** Models available for cycling (Ctrl+P in interactive mode) */\n scopedModels?: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }>;\n\n /**\n * Optional default tool suppression mode when no explicit allowlist is provided.\n *\n * - \"all\": start with no tools enabled\n * - \"builtin\": disable the default built-in tools (read, bash, edit, write,\n * ask_user_question, todo) but keep extension/custom tools enabled\n */\n noTools?: \"all\" | \"builtin\";\n /**\n * Optional allowlist of tool names.\n *\n * When omitted, pi enables the default built-in tools (read, bash, edit, write,\n * ask_user_question, todo) and leaves extension/custom tools enabled unless\n * `noTools` changes that default.\n * When provided, only the listed tool names are enabled, minus any names in\n * `excludedTools`.\n */\n tools?: string[];\n /**\n * Optional blocklist of tool names.\n *\n * Matching built-in, extension, and SDK custom tools are omitted from the\n * final session tool registry and active tool set. Unknown names are ignored.\n */\n excludedTools?: string[];\n /** Custom tools to register (in addition to built-in tools). */\n customTools?: ToolDefinition[];\n /** Optional command-level policy for the built-in bash tool. Does not expose bash by itself. */\n bashPolicy?: BashCommandPolicy;\n\n /** Resource loader. When omitted, DefaultResourceLoader is used. */\n resourceLoader?: ResourceLoader;\n\n /** Session manager. Default: SessionManager.create(cwd) */\n sessionManager?: SessionManager;\n\n /** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n settingsManager?: SettingsManager;\n /** Session start event metadata for extension runtime startup. */\n sessionStartEvent?: SessionStartEvent;\n /** Session-scoped orchestration policy exposed to extension/tool handlers. */\n orchestrationContext?: OrchestrationContext;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n /** The created session */\n session: AgentSession;\n /** Extensions result (for UI context setup in interactive mode) */\n extensionsResult: LoadExtensionsResult;\n /** Warning if session was restored with a different model than saved */\n modelFallbackMessage?: string;\n /** Warning if a saved/default context window could not be applied to the selected model. */\n contextWindowWarning?: string;\n /** Error if an explicit strict context-window selection is unsupported. */\n contextWindowError?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n ExtensionAPI,\n ExtensionCommandContext,\n ExtensionContext,\n ExtensionFactory,\n SlashCommandInfo,\n SlashCommandSource,\n ToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type {\n BashCommandParseError,\n BashCommandParseResult,\n BashCommandPolicy,\n BashCommandPolicyDecision,\n BashCommandPolicyMatchMode,\n BashCommandPolicyRejection,\n BashCommandRule,\n BashCommandSegment,\n BashCommandSegmentSource,\n JsonObject,\n JsonPrimitive,\n JsonValue,\n StructuredOutputCapture,\n StructuredOutputFileCapture,\n StructuredOutputToolOptions,\n Tool,\n} from \"./tools/index.ts\";\n\nexport {\n withFileMutationQueue,\n STRUCTURED_OUTPUT_TOOL_NAME,\n // Tool factories (for custom cwd)\n createCodingTools,\n createReadOnlyTools,\n createReadTool,\n createBashTool,\n createEditTool,\n createWriteTool,\n createGrepTool,\n createFindTool,\n createLsTool,\n createStructuredOutputCapture,\n createStructuredOutputTool,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n return getAgentDir();\n}\n\nfunction getAlreadyAppliedContextWindow(model: Model<Api>): number | undefined {\n const defaultContextWindow = getModelDefaultContextWindow(model);\n if (model.contextWindow === defaultContextWindow) {\n return undefined;\n }\n\n return getSupportedContextWindows(model).includes(model.contextWindow)\n ? model.contextWindow\n : undefined;\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\nexport async function createAgentSession(\n options: CreateAgentSessionOptions = {},\n): Promise<CreateAgentSessionResult> {\n const cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n const agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n let resourceLoader = options.resourceLoader;\n\n // Use provided or create AuthStorage and ModelRegistry\n const authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n const modelsPath = options.agentDir\n ? join(agentDir, \"models.json\")\n : undefined;\n const authStorage = options.authStorage ?? AuthStorage.create(authPath);\n const modelRegistry =\n options.modelRegistry ?? ModelRegistry.create(authStorage, modelsPath);\n\n const settingsManager =\n options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n const sessionManager =\n options.sessionManager ??\n SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\n if (!resourceLoader) {\n resourceLoader = new DefaultResourceLoader({\n cwd,\n agentDir,\n settingsManager,\n });\n await resourceLoader.reload();\n time(\"resourceLoader.reload\");\n }\n\n // Check if session has existing data to restore\n const existingSession = sessionManager.buildSessionContext();\n const hasExistingSession = existingSession.messages.length > 0;\n const hasThinkingEntry = sessionManager\n .getBranch()\n .some((entry) => entry.type === \"thinking_level_change\");\n\n let model = options.model;\n let modelFallbackMessage: string | undefined;\n\n // If session has data, try to restore model from it\n if (!model && hasExistingSession && existingSession.model) {\n const restoredModel = await resolveSavedModelReference(\n existingSession.model.provider,\n existingSession.model.modelId,\n modelRegistry,\n );\n if (restoredModel && modelRegistry.hasConfiguredAuth(restoredModel)) {\n model = restoredModel;\n }\n if (!model) {\n modelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n }\n }\n\n // If still no model, use findInitialModel (checks settings default, then provider defaults)\n if (!model) {\n const result = await findInitialModel({\n scopedModels: [],\n isContinuing: hasExistingSession,\n defaultProvider: settingsManager.getDefaultProvider(),\n defaultModelId: settingsManager.getDefaultModel(),\n defaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n modelRegistry,\n });\n model = result.model;\n if (!model) {\n modelFallbackMessage = formatNoModelsAvailableMessage();\n } else if (modelFallbackMessage) {\n modelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n }\n }\n\n let thinkingLevel = options.thinkingLevel;\n\n // If session has data, restore thinking level from it\n if (thinkingLevel === undefined && hasExistingSession) {\n thinkingLevel = hasThinkingEntry\n ? (existingSession.thinkingLevel as ThinkingLevel)\n : (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n }\n\n // Fall back to settings default\n if (thinkingLevel === undefined) {\n thinkingLevel =\n settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n }\n\n // Clamp to model capabilities\n if (!model) {\n thinkingLevel = \"off\";\n } else {\n thinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n }\n\n let selectedContextWindow: number | undefined;\n let contextWindowWarning: string | undefined;\n let contextWindowError: string | undefined;\n const explicitContextWindowSelection = options.contextWindow !== undefined;\n const incomingModelContextWindow =\n model && options.model ? getAlreadyAppliedContextWindow(model) : undefined;\n const requestedContextWindow =\n options.contextWindow ??\n incomingModelContextWindow ??\n (hasExistingSession ? existingSession.contextWindow : undefined) ??\n settingsManager.getDefaultContextWindow();\n if (model && requestedContextWindow !== undefined) {\n const selected = selectContextWindow(model, requestedContextWindow);\n if (\"error\" in selected) {\n if (options.contextWindowStrict) {\n contextWindowError = selected.error;\n } else {\n contextWindowWarning = selected.error;\n }\n } else {\n model = selected.model;\n selectedContextWindow = selected.contextWindow;\n }\n }\n\n const allowedToolNames =\n options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n const initialActiveToolNames: string[] = options.tools\n ? [...options.tools]\n : options.noTools\n ? []\n : [...defaultToolNames];\n\n let agent: Agent;\n\n let lastConvertedLlmMessages: Message[] | undefined;\n\n // Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n const converted = convertToLlm(messages);\n // Check setting dynamically so mid-session changes take effect\n if (!settingsManager.getBlockImages()) {\n lastConvertedLlmMessages = converted;\n return converted;\n }\n // Filter out ImageContent from all messages, replacing with text placeholder\n const filtered = converted.map((msg) => {\n if (msg.role === \"user\" || msg.role === \"toolResult\") {\n const content = msg.content;\n if (Array.isArray(content)) {\n const hasImages = content.some((c) => c.type === \"image\");\n if (hasImages) {\n const filteredContent = content\n .map((c) =>\n c.type === \"image\"\n ? {\n type: \"text\" as const,\n text: \"Image reading is disabled.\",\n }\n : c,\n )\n .filter(\n (c, i, arr) =>\n // Dedupe consecutive \"Image reading is disabled.\" texts\n !(\n c.type === \"text\" &&\n c.text === \"Image reading is disabled.\" &&\n i > 0 &&\n arr[i - 1].type === \"text\" &&\n (arr[i - 1] as { type: \"text\"; text: string }).text ===\n \"Image reading is disabled.\"\n ),\n );\n return { ...msg, content: filteredContent };\n }\n }\n }\n return msg;\n });\n lastConvertedLlmMessages = filtered;\n return filtered;\n };\n\n const extensionRunnerRef: { current?: ExtensionRunner } = {};\n const isCodexFastModeEnabled = (requestModel: Model<Api>): boolean =>\n shouldApplyCodexFastMode(\n requestModel,\n settingsManager.getCodexFastModeSettings(),\n options.orchestrationContext,\n );\n\n agent = new Agent({\n initialState: {\n systemPrompt: \"\",\n model,\n thinkingLevel,\n tools: [],\n },\n convertToLlm: convertToLlmWithBlockImages,\n streamFn: async (model, context, streamOptions) => {\n const auth = await modelRegistry.getApiKeyAndHeaders(model);\n if (!auth.ok) {\n throw new Error(auth.error);\n }\n const providerRetrySettings = settingsManager.getProviderRetrySettings();\n const httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n // SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n // Use max int32 to effectively disable the timeout.\n const effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n const timeoutMs = streamOptions?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n const websocketConnectTimeoutMs =\n streamOptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n const attributionHeaders = mergeProviderAttributionHeaders(\n model,\n settingsManager,\n streamOptions?.sessionId,\n auth.headers,\n streamOptions?.headers,\n );\n const fastModeEnabled = isCodexFastModeEnabled(model);\n const codexFastModeStreamOptions = withCodexFastModeStreamOptions(\n {\n ...streamOptions,\n apiKey: auth.apiKey,\n timeoutMs,\n websocketConnectTimeoutMs,\n maxRetries: streamOptions?.maxRetries ?? providerRetrySettings.maxRetries,\n maxRetryDelayMs:\n streamOptions?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n headers: attributionHeaders,\n },\n fastModeEnabled,\n );\n if (modelRegistry.hasRegisteredStreamSimpleForApi(model.api)) {\n return streamSimple(model, context, codexFastModeStreamOptions);\n }\n return streamWithCodexFastMode(model, context, codexFastModeStreamOptions);\n },\n onPayload: async (payload, model) => {\n const fastModeEnabled = isCodexFastModeEnabled(model);\n const guardedPayload = withCodexFastModePayload(payload, fastModeEnabled);\n const sourceMessages = lastConvertedLlmMessages;\n const replayGuardedPayload = sourceMessages\n ? restoreAnthropicReplayThinkingBlocks(guardedPayload, sourceMessages, model)\n : guardedPayload;\n const runner = extensionRunnerRef.current;\n if (!runner?.hasHandlers(\"before_provider_request\")) {\n return replayGuardedPayload;\n }\n const extensionPayload = await runner.emitBeforeProviderRequest(\n replayGuardedPayload,\n );\n return sourceMessages\n ? restoreAnthropicReplayThinkingBlocks(extensionPayload, sourceMessages, model)\n : extensionPayload;\n },\n onResponse: async (response, _model) => {\n const runner = extensionRunnerRef.current;\n if (!runner?.hasHandlers(\"after_provider_response\")) {\n return;\n }\n await runner.emit({\n type: \"after_provider_response\",\n status: response.status,\n headers: response.headers,\n });\n },\n sessionId: sessionManager.getSessionId(),\n transformContext: async (messages) => {\n const runner = extensionRunnerRef.current;\n if (!runner) return messages;\n return runner.emitContext(messages);\n },\n steeringMode: settingsManager.getSteeringMode(),\n followUpMode: settingsManager.getFollowUpMode(),\n transport: settingsManager.getTransport(),\n thinkingBudgets: settingsManager.getThinkingBudgets(),\n maxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n });\n\n // Restore messages if session has existing data\n if (hasExistingSession) {\n agent.state.messages = existingSession.messages;\n const transcriptContextWindow = model\n ? (existingSession.contextWindow ?? getModelDefaultContextWindow(model))\n : undefined;\n if (\n selectedContextWindow !== undefined &&\n (explicitContextWindowSelection || selectedContextWindow !== transcriptContextWindow)\n ) {\n sessionManager.appendContextWindowChange(selectedContextWindow);\n }\n if (!hasThinkingEntry) {\n sessionManager.appendThinkingLevelChange(thinkingLevel);\n }\n } else {\n // Save initial model and thinking level for new sessions so they can be restored on resume\n if (model) {\n sessionManager.appendModelChange(model.provider, model.id);\n if (\n selectedContextWindow !== undefined &&\n (explicitContextWindowSelection || selectedContextWindow !== getModelDefaultContextWindow(model))\n ) {\n sessionManager.appendContextWindowChange(selectedContextWindow);\n }\n }\n sessionManager.appendThinkingLevelChange(thinkingLevel);\n }\n\n const session = new AgentSession({\n agent,\n sessionManager,\n settingsManager,\n cwd,\n scopedModels: options.scopedModels,\n resourceLoader,\n customTools: options.customTools,\n bashPolicy: options.bashPolicy,\n modelRegistry,\n initialActiveToolNames,\n allowedToolNames,\n excludedToolNames: options.excludedTools,\n extensionRunnerRef,\n sessionStartEvent: options.sessionStartEvent,\n orchestrationContext: options.orchestrationContext,\n });\n const extensionsResult = resourceLoader.getExtensions();\n\n return {\n session,\n extensionsResult,\n modelFallbackMessage,\n contextWindowWarning,\n contextWindowError,\n };\n}\n"]}