@ikarmic/forge-core 5.2.0 → 5.4.0

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 (129) hide show
  1. package/dist/agent/agent.js +324 -122
  2. package/dist/agent/cache-hints.d.ts +2 -0
  3. package/dist/agent/cache-hints.js +18 -3
  4. package/dist/agent/enhance-prompt.js +2 -1
  5. package/dist/agent/history-adapt.d.ts +23 -0
  6. package/dist/agent/history-adapt.js +114 -0
  7. package/dist/agent/prompts.d.ts +19 -2
  8. package/dist/agent/prompts.js +44 -2
  9. package/dist/agent/retry.d.ts +50 -0
  10. package/dist/agent/retry.js +192 -0
  11. package/dist/agent/shell-command.d.ts +23 -0
  12. package/dist/agent/shell-command.js +93 -0
  13. package/dist/agent/tool-repair.d.ts +8 -0
  14. package/dist/agent/tool-repair.js +16 -0
  15. package/dist/catalog/models-dev-snapshot.d.ts +1 -0
  16. package/dist/catalog/models-dev-snapshot.js +3 -0
  17. package/dist/catalog/models-dev.d.ts +45 -0
  18. package/dist/catalog/models-dev.js +155 -0
  19. package/dist/checkpoints/store.js +3 -1
  20. package/dist/context/compaction.d.ts +21 -6
  21. package/dist/context/compaction.js +94 -28
  22. package/dist/context/mask.d.ts +9 -9
  23. package/dist/context/mask.js +30 -1
  24. package/dist/format/auto-format.d.ts +22 -0
  25. package/dist/format/auto-format.js +39 -0
  26. package/dist/git/auto-commit.d.ts +4 -2
  27. package/dist/git/auto-commit.js +16 -5
  28. package/dist/github/github-tools.d.ts +1 -0
  29. package/dist/github/github-tools.js +9 -9
  30. package/dist/github/pr.d.ts +34 -0
  31. package/dist/github/pr.js +70 -0
  32. package/dist/human-text.d.ts +6 -0
  33. package/dist/human-text.js +37 -0
  34. package/dist/index.d.ts +19 -9
  35. package/dist/index.js +14 -6
  36. package/dist/journey/answers-block.d.ts +2 -4
  37. package/dist/lsp/diagnostics.d.ts +4 -0
  38. package/dist/lsp/diagnostics.js +8 -2
  39. package/dist/lsp/index.d.ts +1 -1
  40. package/dist/lsp/index.js +1 -1
  41. package/dist/mcp/index.d.ts +1 -1
  42. package/dist/mcp/index.js +1 -1
  43. package/dist/mcp/json-schema-to-zod.js +2 -4
  44. package/dist/mcp/mcp-client.d.ts +14 -0
  45. package/dist/mcp/mcp-client.js +102 -18
  46. package/dist/memory/session-digest.js +2 -1
  47. package/dist/orchestrator/specialists-registry.js +14 -1
  48. package/dist/orchestrator/specialists.js +3 -3
  49. package/dist/pricing.js +12 -0
  50. package/dist/semantic/embedder.d.ts +11 -0
  51. package/dist/semantic/embedder.js +16 -0
  52. package/dist/semantic/semantic-index.d.ts +39 -1
  53. package/dist/semantic/semantic-index.js +43 -13
  54. package/dist/semantic/vector-store.d.ts +2 -0
  55. package/dist/semantic/vector-store.js +13 -1
  56. package/dist/sessions/index.d.ts +2 -2
  57. package/dist/sessions/index.js +1 -1
  58. package/dist/sessions/search.js +2 -2
  59. package/dist/sessions/session-export.d.ts +1 -1
  60. package/dist/sessions/session-export.js +41 -33
  61. package/dist/sessions/session-store.d.ts +20 -1
  62. package/dist/sessions/session-store.js +7 -0
  63. package/dist/sessions/watch.js +4 -4
  64. package/dist/skills/activator.d.ts +6 -0
  65. package/dist/skills/activator.js +17 -0
  66. package/dist/skills/skill-tool.d.ts +12 -0
  67. package/dist/skills/skill-tool.js +28 -0
  68. package/dist/tools/background-process.d.ts +63 -0
  69. package/dist/tools/background-process.js +150 -0
  70. package/dist/tools/command-sandbox.d.ts +24 -0
  71. package/dist/tools/command-sandbox.js +73 -0
  72. package/dist/tools/edit-file.d.ts +8 -0
  73. package/dist/tools/edit-file.js +39 -16
  74. package/dist/tools/edit-match.d.ts +16 -0
  75. package/dist/tools/edit-match.js +115 -0
  76. package/dist/tools/fetch-url.d.ts +15 -1
  77. package/dist/tools/fetch-url.js +7 -47
  78. package/dist/tools/get-diagnostics.d.ts +14 -0
  79. package/dist/tools/get-diagnostics.js +44 -0
  80. package/dist/tools/glob-filter.d.ts +7 -0
  81. package/dist/tools/glob-filter.js +12 -0
  82. package/dist/tools/html-to-markdown.d.ts +13 -0
  83. package/dist/tools/html-to-markdown.js +507 -0
  84. package/dist/tools/index.d.ts +5 -1
  85. package/dist/tools/index.js +11 -1
  86. package/dist/tools/list-files.d.ts +1 -0
  87. package/dist/tools/list-files.js +17 -5
  88. package/dist/tools/list-symbols.js +2 -2
  89. package/dist/tools/network-policy.d.ts +5 -0
  90. package/dist/tools/network-policy.js +77 -0
  91. package/dist/tools/office-docs.d.ts +6 -0
  92. package/dist/tools/office-docs.js +393 -0
  93. package/dist/tools/permission-rules.js +55 -5
  94. package/dist/tools/read-file.d.ts +2 -0
  95. package/dist/tools/read-file.js +47 -10
  96. package/dist/tools/read-image.js +2 -2
  97. package/dist/tools/read-only.d.ts +1 -1
  98. package/dist/tools/read-only.js +4 -0
  99. package/dist/tools/repo-clone.d.ts +40 -0
  100. package/dist/tools/repo-clone.js +218 -0
  101. package/dist/tools/result-cap.d.ts +8 -0
  102. package/dist/tools/result-cap.js +44 -0
  103. package/dist/tools/run-command.d.ts +18 -1
  104. package/dist/tools/run-command.js +44 -9
  105. package/dist/tools/search-files.js +12 -53
  106. package/dist/tools/search-worker.d.ts +23 -0
  107. package/dist/tools/search-worker.js +86 -0
  108. package/dist/tools/shared.d.ts +14 -1
  109. package/dist/tools/shared.js +76 -4
  110. package/dist/tools/shell.d.ts +27 -0
  111. package/dist/tools/shell.js +80 -0
  112. package/dist/tools/sub-agent.d.ts +1 -1
  113. package/dist/tools/sub-agent.js +4 -1
  114. package/dist/tools/suggest-plan.d.ts +18 -0
  115. package/dist/tools/suggest-plan.js +28 -0
  116. package/dist/types.d.ts +132 -1
  117. package/dist/wakeup/wakeup-tools.d.ts +22 -0
  118. package/dist/wakeup/wakeup-tools.js +34 -0
  119. package/dist/wakeup/wakeup.d.ts +75 -0
  120. package/dist/wakeup/wakeup.js +151 -0
  121. package/dist/worktree/health.d.ts +51 -0
  122. package/dist/worktree/health.js +186 -0
  123. package/dist/worktree/index.d.ts +4 -0
  124. package/dist/worktree/index.js +2 -0
  125. package/dist/worktree/setup.d.ts +51 -0
  126. package/dist/worktree/setup.js +173 -0
  127. package/dist/worktree/worktree.d.ts +7 -0
  128. package/dist/worktree/worktree.js +9 -4
  129. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  export { Agent, applySandbox } from './agent/agent.js';
2
2
  export { LoopGuard } from './agent/loop-guard.js';
3
3
  export { enhancePrompt } from './agent/enhance-prompt.js';
4
+ export { generateShellCommand, shellKindOf, commandRisk } from './agent/shell-command.js';
5
+ export type { ShellKind } from './agent/shell-command.js';
4
6
  export { stripThinkTags } from './agent/think-tags.js';
5
- export { createGitHubCreateTool, createGitHubReadTool, createGitHubTools, githubFromEnv, parseRemote, type GitHubConfig, type PullRequestReport, type RepoRef, } from './github/github-tools.js';
7
+ export { createGitHubCreateTool, createGitHubReadTool, createGitHubTools, githubFromEnv, parseRemote, type GitHubConfig, type PullRequestReport, repoFor, type RepoRef, } from './github/github-tools.js';
8
+ export { defaultBranch, findPullRequest, openPullRequest, type NewPullRequest, type PullRequestSummary } from './github/pr.js';
6
9
  export { createWebSearchTool, searchWeb, WEB_SEARCH_PROVIDERS, webSearchFromEnv, webSearchProblem, type WebSearchConfig, type WebSearchProvider, type WebSearchResult, } from './tools/web-search.js';
7
10
  export { attachmentKind, attachmentBytes, attachmentParts, attachmentProblem, describeAttachment, detectMediaType, loadAttachment, makeAttachment, mediaPartTokens, MAX_ATTACHMENT_BYTES, MAX_IMAGE_ATTACHMENT_BYTES, type Attachment, type AttachmentKind, } from './attachments.js';
8
11
  export type { CredentialStore } from './credentials/types.js';
@@ -33,17 +36,18 @@ export type { StaleOptions } from './memory/stale.js';
33
36
  export type { LoadedMemory, InjectOptions, MemoryInjection, MemorySection, MemorySource, MemoryType, SectionMeta, } from './memory/memory-store.js';
34
37
  export { MAX_BODY_CHARS } from './tools/memory.js';
35
38
  export type { MemoryForgetResult, MemoryReadResult, MemoryWriteResult } from './tools/memory.js';
36
- export { buildLlmSummarizer, compactMessages, estimateMessageTokens, estimateTokens, getModelWindowTokens, shouldCompact, } from './context/compaction.js';
39
+ export { buildLlmSummarizer, COMPACTION_SUMMARY_PREFIX, compactMessages, estimateMessageTokens, isCompactionSummary, estimateTokens, getModelWindowTokens, windowIsGuess, shouldCompact, } from './context/compaction.js';
37
40
  export type { CompactionResult, Summarizer } from './context/compaction.js';
38
41
  export { defaultModelFor, detectProvider, envVarForProvider, fastDefaultFor, hasProviderKey, readModelConfigFromEnv, resolveApiKey, SUPPORTED_PROVIDERS, } from './config.js';
39
- export { loadMcpConfig, parseMcpConfig, connectAll, connectOne, closeAll, mcpResultToValue, mcpInstructionsBlock, createMcpResourceTools, jsonSchemaToZod, } from './mcp/index.js';
42
+ export { loadMcpConfig, parseMcpConfig, connectAll, connectOne, closeAll, mcpResultToValue, mcpInstructionsBlock, mcpToolName, mcpToolPrefix, createMcpResourceTools, jsonSchemaToZod, } from './mcp/index.js';
40
43
  export type { McpConfigFile, McpServerSpec, ConnectedMcpServer, } from './mcp/index.js';
41
44
  export { createLanguageModel, formatModelId, pickAvailableProvider, resolveModelFromEnv, resolveModelFromEnvAsync } from './providers.js';
42
- export { defaultSessionDir, generateSessionId, saveSession, loadSession, deleteSession, listSessions, findLatestSession, formatSessionMarkdown, formatSessionJson, appendedSince, summarizeMessage, watchSession, searchSessions, sessionText, pruneSessions, pruneByRetention, } from './sessions/index.js';
43
- export type { Session, ListEntry, FormatOptions, SessionUpdate, WatchSessionOptions, SessionMatch, SearchSessionsOptions, SessionPruneOptions, SessionPruneResult } from './sessions/index.js';
45
+ export { defaultSessionDir, generateSessionId, saveSession, loadSession, deleteSession, displayMessages, listSessions, findLatestSession, formatSessionMarkdown, formatSessionJson, appendedSince, summarizeMessage, watchSession, searchSessions, sessionText, pruneSessions, pruneByRetention, } from './sessions/index.js';
46
+ export type { Session, ListEntry, MessageFeedback, FormatOptions, SessionUpdate, WatchSessionOptions, SessionMatch, SearchSessionsOptions, SessionPruneOptions, SessionPruneResult } from './sessions/index.js';
44
47
  export { commandDirs, expandCustomCommand, formatCustomCommands, loadCustomCommands, parseCommandFile, type CustomCommand, } from './commands/custom-commands.js';
45
48
  export { BUILTIN_TOOLS, editFileTool, fetchUrlTool, findSymbolTool, gitDiffTool, dependencyAuditTool, listFilesTool, memoryReadTool, memoryForgetTool, memoryWriteTool, readFileTool, runCommandTool, searchFilesTool, subAgentTool, writeFileTool, checkPolicyViolation, globToRegex, mergeToolSandbox, normalizePathPattern, pathMatchesAny, redact, countRedactions, buildRules, ToolResultCache, buildCacheKey, cachedExecute, } from './tools/index.js';
46
- export type { AgentConfig, AgentEvent, AgentRunOptions, AgentRunReason, AgentRunResult, AnyLanguageModel, ApprovalDecision, CommandRunner, ModelRouter, VerifyConfig, ApprovalRequest, LanguageModelFactory, ModelConfig, PricePerMillionTokens, ProviderName, RedactOptions, TodoItem, ToolContext, UserQuestion, ToolDefinition, ToolPolicy, ToolSandbox, } from './types.js';
49
+ export type { AgentConfig, AgentEvent, ApprovalSource, AgentRunOptions, AgentRunReason, AgentRunResult, AnyLanguageModel, ApprovalDecision, CommandRunner, ModelRouter, VerifyConfig, ApprovalRequest, LanguageModelFactory, ModelConfig, PricePerMillionTokens, ProviderName, RedactOptions, TodoItem, ToolContext, UserQuestion, ToolDefinition, ToolPolicy, ToolSandbox, } from './types.js';
50
+ export { humanize, runReasonLabel, toolLabel } from './human-text.js';
47
51
  export type { CacheKeyParts, CacheStats } from './tools/index.js';
48
52
  export type { RedactionRule, RedactionResult } from './tools/index.js';
49
53
  export { buildSkillsBlock, builtinSkills, composeSkillsPrompt, discoverSkills, fetchSkillsCatalog, installSkillFromCatalog, loadSkillsRegistry, loadSkillsRegistryAsync, parseSkill, parseSkillsDir, resolveSkillRoots, scoreSkill, searchSkills, selectSkills, skillsRegistryFromList, DEFAULT_SKILL_CATALOG_URL, GLOBAL_SKILLS_DIR, LOCAL_SKILLS_DIR, PROJECT_SKILLS_DIR, SKILL_CATALOG_VERSION, } from './skills/index.js';
@@ -89,8 +93,8 @@ export { computeUnifiedDiff, computeDiffSummary, type DiffOptions, type DiffResu
89
93
  export { defaultRetryPrompt, formatZodError, isStructuredOutputError, loadSchemaFile, runStructuredAgent, StructuredOutputError, validateFinalText, } from './structured/index.js';
90
94
  export type { StructuredRunOptions, StructuredRunResult } from './structured/index.js';
91
95
  export type { ZodTypeAny } from './structured/index.js';
92
- export { branchChangedFiles, branchDiff, commitAllInWorktree, compareWorktreeContexts, deleteBranch, mergeBranch, snapshotWorktreeContext, WorktreeError, WorktreeManager, } from './worktree/index.js';
93
- export type { MergeResult, WorktreeCommit, CreateOptions, RemoveOptions, RemoveResult, WorktreeContextSnapshot, WorktreeInfo, WorktreeMismatchKind, WorktreeMismatchResult, } from './worktree/index.js';
96
+ export { branchChangedFiles, branchDiff, commitAllInWorktree, compareWorktreeContexts, copyIntoWorktree, deleteBranch, doctorWorktrees, inspectWorktrees, mergeBranch, pruneMissingWorktrees, removeStrayFolder, runSetupScript, setupWorktree, snapshotWorktreeContext, worktreeStats, WorktreeError, WorktreeManager, } from './worktree/index.js';
97
+ export type { MergeResult, WorktreeCommit, CreateOptions, RemoveOptions, RemoveResult, WorktreeContextSnapshot, WorktreeInfo, WorktreeMismatchKind, WorktreeMismatchResult, HealthFinding, ListedWorktree, WorktreeInspection, WorktreeStats, CopyOptions, CopyResult, ScriptOptions, ScriptResult, SetupOptions, SetupOutcome, } from './worktree/index.js';
94
98
  export { formatSteeringLine, isSafeSteeringName, loadSteering, overrideFilePath, parseSteering, pathMatchesGlob, readOverride, selectAtStart, selectOnToolTouch, setSteeringDisabled, stripSteering, wrapSteering, } from './steering/index.js';
95
99
  export type { StartSelection, SteeringConfig, SteeringDoc, SteeringInclusion, SteeringLoadResult, SteeringSkip, } from './steering/index.js';
96
100
  export { CONFIG_KEY_COVERAGE, FORGE_CONFIG_SCHEMA } from './config-schema.js';
@@ -100,9 +104,15 @@ export { detectStackFacts, stackBlock, MAX_STACK_LIBRARIES, type StackFacts } fr
100
104
  export { pickAuditors, type AuditFinding, type AuditorPlan } from './tools/dependency-audit.js';
101
105
  export { shellEnv } from './tools/shell-env.js';
102
106
  export { previewPatch } from './tools/apply-patch.js';
107
+ export { applyTextEdit } from './tools/edit-file.js';
108
+ export { catalogEntry, loadModelCatalog, type CatalogEntry, type CatalogSource } from './catalog/models-dev.js';
109
+ export { MAX_HORIZON_MS, MAX_PER_SESSION as MAX_WAKEUPS, MIN_DELAY_MS as MIN_WAKEUP_DELAY_MS, WakeupScheduler, resolveWakeupTime, wakeupMessage } from './wakeup/wakeup.js';
110
+ export type { Wakeup, WakeupSchedulerOptions, WakeupStore } from './wakeup/wakeup.js';
111
+ export { isContextOverflowError, retryDecision, withModelRetry } from './agent/retry.js';
103
112
  export type { PatchPreview } from './tools/apply-patch.js';
104
113
  export { applyPatchTool, parsePatch, patchedPaths } from './tools/apply-patch.js';
105
- export { buildEmbedder, type EmbedderSpec } from './semantic/embedder.js';
114
+ export { buildEmbedder, embedderDestination, type EmbedderSpec } from './semantic/embedder.js';
115
+ export { deleteSemanticIndex, SemanticConsentError, type SemanticStatus } from './semantic/semantic-index.js';
106
116
  export { parseExtensions, type SemanticOptions } from './semantic/semantic-index.js';
107
117
  export { cleanDigest, generateSessionDigest, shouldDigest, type DigestItem } from './memory/session-digest.js';
108
118
  export { buildExtraInstructionsBlock } from './steering/extra-instructions.js';
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  export { Agent, applySandbox } from './agent/agent.js';
2
2
  export { LoopGuard } from './agent/loop-guard.js';
3
3
  export { enhancePrompt } from './agent/enhance-prompt.js';
4
+ export { generateShellCommand, shellKindOf, commandRisk } from './agent/shell-command.js';
4
5
  export { stripThinkTags } from './agent/think-tags.js';
5
- export { createGitHubCreateTool, createGitHubReadTool, createGitHubTools, githubFromEnv, parseRemote, } from './github/github-tools.js';
6
+ export { createGitHubCreateTool, createGitHubReadTool, createGitHubTools, githubFromEnv, parseRemote, repoFor, } from './github/github-tools.js';
7
+ export { defaultBranch, findPullRequest, openPullRequest } from './github/pr.js';
6
8
  export { createWebSearchTool, searchWeb, WEB_SEARCH_PROVIDERS, webSearchFromEnv, webSearchProblem, } from './tools/web-search.js';
7
9
  export { attachmentKind, attachmentBytes, attachmentParts, attachmentProblem, describeAttachment, detectMediaType, loadAttachment, makeAttachment, mediaPartTokens, MAX_ATTACHMENT_BYTES, MAX_IMAGE_ATTACHMENT_BYTES, } from './attachments.js';
8
10
  export { CREDENTIAL_SERVICE_NAME, credentialAccountKey } from './credentials/index.js';
@@ -20,13 +22,14 @@ export { buildMemoryBlock, composeSectionUpdate, findProjectMemoryRoot, globalMe
20
22
  export { scoreSections, tokenize } from './memory/rank.js';
21
23
  export { checkStale, staleKey } from './memory/stale.js';
22
24
  export { MAX_BODY_CHARS } from './tools/memory.js';
23
- export { buildLlmSummarizer, compactMessages, estimateMessageTokens, estimateTokens, getModelWindowTokens, shouldCompact, } from './context/compaction.js';
25
+ export { buildLlmSummarizer, COMPACTION_SUMMARY_PREFIX, compactMessages, estimateMessageTokens, isCompactionSummary, estimateTokens, getModelWindowTokens, windowIsGuess, shouldCompact, } from './context/compaction.js';
24
26
  export { defaultModelFor, detectProvider, envVarForProvider, fastDefaultFor, hasProviderKey, readModelConfigFromEnv, resolveApiKey, SUPPORTED_PROVIDERS, } from './config.js';
25
- export { loadMcpConfig, parseMcpConfig, connectAll, connectOne, closeAll, mcpResultToValue, mcpInstructionsBlock, createMcpResourceTools, jsonSchemaToZod, } from './mcp/index.js';
27
+ export { loadMcpConfig, parseMcpConfig, connectAll, connectOne, closeAll, mcpResultToValue, mcpInstructionsBlock, mcpToolName, mcpToolPrefix, createMcpResourceTools, jsonSchemaToZod, } from './mcp/index.js';
26
28
  export { createLanguageModel, formatModelId, pickAvailableProvider, resolveModelFromEnv, resolveModelFromEnvAsync } from './providers.js';
27
- export { defaultSessionDir, generateSessionId, saveSession, loadSession, deleteSession, listSessions, findLatestSession, formatSessionMarkdown, formatSessionJson, appendedSince, summarizeMessage, watchSession, searchSessions, sessionText, pruneSessions, pruneByRetention, } from './sessions/index.js';
29
+ export { defaultSessionDir, generateSessionId, saveSession, loadSession, deleteSession, displayMessages, listSessions, findLatestSession, formatSessionMarkdown, formatSessionJson, appendedSince, summarizeMessage, watchSession, searchSessions, sessionText, pruneSessions, pruneByRetention, } from './sessions/index.js';
28
30
  export { commandDirs, expandCustomCommand, formatCustomCommands, loadCustomCommands, parseCommandFile, } from './commands/custom-commands.js';
29
31
  export { BUILTIN_TOOLS, editFileTool, fetchUrlTool, findSymbolTool, gitDiffTool, dependencyAuditTool, listFilesTool, memoryReadTool, memoryForgetTool, memoryWriteTool, readFileTool, runCommandTool, searchFilesTool, subAgentTool, writeFileTool, checkPolicyViolation, globToRegex, mergeToolSandbox, normalizePathPattern, pathMatchesAny, redact, countRedactions, buildRules, ToolResultCache, buildCacheKey, cachedExecute, } from './tools/index.js';
32
+ export { humanize, runReasonLabel, toolLabel } from './human-text.js';
30
33
  // Skills system
31
34
  export { buildSkillsBlock, builtinSkills, composeSkillsPrompt, discoverSkills, fetchSkillsCatalog, installSkillFromCatalog, loadSkillsRegistry, loadSkillsRegistryAsync, parseSkill, parseSkillsDir, resolveSkillRoots, scoreSkill, searchSkills, selectSkills, skillsRegistryFromList, DEFAULT_SKILL_CATALOG_URL, GLOBAL_SKILLS_DIR, LOCAL_SKILLS_DIR, PROJECT_SKILLS_DIR, SKILL_CATALOG_VERSION, } from './skills/index.js';
32
35
  export { cosine, embedderFromEnv, hashingEmbedder, openAiCompatibleEmbedder, } from './semantic/embedder.js';
@@ -70,7 +73,7 @@ export { defaultRetryPrompt, formatZodError, isStructuredOutputError, loadSchema
70
73
  // Lets child agents work in isolated git worktrees so parallel work
71
74
  // doesn't clobber the main checkout. Lifecycle: create → use → remove.
72
75
  // `pruneStale()` cleans up worktrees left behind by crashed sessions.
73
- export { branchChangedFiles, branchDiff, commitAllInWorktree, compareWorktreeContexts, deleteBranch, mergeBranch, snapshotWorktreeContext, WorktreeError, WorktreeManager, } from './worktree/index.js';
76
+ export { branchChangedFiles, branchDiff, commitAllInWorktree, compareWorktreeContexts, copyIntoWorktree, deleteBranch, doctorWorktrees, inspectWorktrees, mergeBranch, pruneMissingWorktrees, removeStrayFolder, runSetupScript, setupWorktree, snapshotWorktreeContext, worktreeStats, WorktreeError, WorktreeManager, } from './worktree/index.js';
74
77
  // Steering files (SP3a)
75
78
  export { formatSteeringLine, isSafeSteeringName, loadSteering, overrideFilePath, parseSteering, pathMatchesGlob, readOverride, selectAtStart, selectOnToolTouch, setSteeringDisabled, stripSteering, wrapSteering, } from './steering/index.js';
76
79
  export { CONFIG_KEY_COVERAGE, FORGE_CONFIG_SCHEMA } from './config-schema.js';
@@ -80,8 +83,13 @@ export { detectStackFacts, stackBlock, MAX_STACK_LIBRARIES } from './agent/stack
80
83
  export { pickAuditors } from './tools/dependency-audit.js';
81
84
  export { shellEnv } from './tools/shell-env.js';
82
85
  export { previewPatch } from './tools/apply-patch.js';
86
+ export { applyTextEdit } from './tools/edit-file.js';
87
+ export { catalogEntry, loadModelCatalog } from './catalog/models-dev.js';
88
+ export { MAX_HORIZON_MS, MAX_PER_SESSION as MAX_WAKEUPS, MIN_DELAY_MS as MIN_WAKEUP_DELAY_MS, WakeupScheduler, resolveWakeupTime, wakeupMessage } from './wakeup/wakeup.js';
89
+ export { isContextOverflowError, retryDecision, withModelRetry } from './agent/retry.js';
83
90
  export { applyPatchTool, parsePatch, patchedPaths } from './tools/apply-patch.js';
84
- export { buildEmbedder } from './semantic/embedder.js';
91
+ export { buildEmbedder, embedderDestination } from './semantic/embedder.js';
92
+ export { deleteSemanticIndex, SemanticConsentError } from './semantic/semantic-index.js';
85
93
  export { parseExtensions } from './semantic/semantic-index.js';
86
94
  export { cleanDigest, generateSessionDigest, shouldDigest } from './memory/session-digest.js';
87
95
  export { buildExtraInstructionsBlock } from './steering/extra-instructions.js';
@@ -8,10 +8,8 @@
8
8
  * model scaffolds MySQL with invoicing. In the system message it cannot be
9
9
  * forgotten.
10
10
  *
11
- * Why the interview must finish before the first agent run:
12
- * `buildInitialMessages` assembles the blocks only when no history is passed
13
- * (`agent.ts:231`), so this block enters the prompt once, on a session's first
14
- * turn, and rides inside `history[0]` from then on.
11
+ * The system message is rebuilt on every turn (`buildSystemContent`, RB-57), so this block always carries the
12
+ * answers given so far.
15
13
  */
16
14
  import type { Answer } from './types.js';
17
15
  export declare function renderAnswersBlock(answers: readonly Answer[]): string;
@@ -4,6 +4,8 @@ export interface LspConfig {
4
4
  servers?: Record<string, LspServerSpec>;
5
5
  /** How long to wait for a server to report on a file. Default 10s. */
6
6
  waitMs?: number;
7
+ /** Also report warnings, not only errors (the verify loop wants errors only). */
8
+ includeWarnings?: boolean;
7
9
  }
8
10
  export interface FileDiagnostics {
9
11
  file: string;
@@ -15,6 +17,8 @@ export interface LspReport {
15
17
  /** Extensions whose server could not be started (not installed, crashed). */
16
18
  skipped: string[];
17
19
  }
20
+ /** Whether a file's type has a language server configured (built-in table or an override). */
21
+ export declare function hasServerFor(file: string, config?: LspConfig): boolean;
18
22
  /** Runs the matching language server over `files` (paths relative to `cwd` or absolute). */
19
23
  export declare function collectErrors(files: string[], cwd: string, config?: LspConfig): Promise<LspReport>;
20
24
  /** Plain-text rendering fed to the model, in `file:line:col: message` form. */
@@ -23,6 +23,11 @@ const DEFAULT_LANGUAGES = {
23
23
  const DEFAULT_WAIT_MS = 10_000;
24
24
  const START_TIMEOUT_MS = 15_000;
25
25
  const MAX_PER_FILE = 20;
26
+ /** Whether a file's type has a language server configured (built-in table or an override). */
27
+ export function hasServerFor(file, config = {}) {
28
+ const ext = path.extname(file).toLowerCase();
29
+ return Boolean(config.servers?.[ext] ?? DEFAULT_LANGUAGES[ext]);
30
+ }
26
31
  /** Runs the matching language server over `files` (paths relative to `cwd` or absolute). */
27
32
  export async function collectErrors(files, cwd, config = {}) {
28
33
  const waitMs = config.waitMs ?? DEFAULT_WAIT_MS;
@@ -54,7 +59,7 @@ export async function collectErrors(files, cwd, config = {}) {
54
59
  const text = await readFile(f.abs, 'utf8').catch(() => null);
55
60
  if (text === null)
56
61
  continue;
57
- const diagnostics = (await client.diagnose(pathToFileURL(f.abs).href, f.languageId, text, waitMs)).filter((d) => d.severity === 'error');
62
+ const diagnostics = (await client.diagnose(pathToFileURL(f.abs).href, f.languageId, text, waitMs)).filter((d) => d.severity === 'error' || (config.includeWarnings === true && d.severity === 'warning'));
58
63
  if (diagnostics.length > 0)
59
64
  report.errors.push({ file: path.relative(cwd, f.abs) || f.abs, diagnostics });
60
65
  }
@@ -71,7 +76,8 @@ export function formatErrors(errors) {
71
76
  .map(({ file, diagnostics }) => {
72
77
  const lines = diagnostics.slice(0, MAX_PER_FILE).map((d) => {
73
78
  const tag = [d.source, d.code].filter((x) => x !== undefined && x !== '').join(' ');
74
- return `${file}:${d.line}:${d.column}: ${d.message}${tag ? ` (${tag})` : ''}`;
79
+ const level = d.severity === 'error' ? '' : `${d.severity}: `;
80
+ return `${file}:${d.line}:${d.column}: ${level}${d.message}${tag ? ` (${tag})` : ''}`;
75
81
  });
76
82
  if (diagnostics.length > MAX_PER_FILE)
77
83
  lines.push(`... ${diagnostics.length - MAX_PER_FILE} more in ${file}`);
@@ -1,4 +1,4 @@
1
1
  export { LspClient } from './client.js';
2
2
  export type { LspDiagnostic, LspServerSpec, LspSeverity } from './client.js';
3
- export { collectErrors, formatErrors } from './diagnostics.js';
3
+ export { collectErrors, formatErrors, hasServerFor } from './diagnostics.js';
4
4
  export type { FileDiagnostics, LspConfig, LspReport } from './diagnostics.js';
package/dist/lsp/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { LspClient } from './client.js';
2
- export { collectErrors, formatErrors } from './diagnostics.js';
2
+ export { collectErrors, formatErrors, hasServerFor } from './diagnostics.js';
@@ -1,7 +1,7 @@
1
1
  export { loadMcpConfig, parseMcpConfig } from './mcp-config.js';
2
2
  export type { McpConfigFile, McpServerSpec } from './mcp-config.js';
3
3
  export { jsonSchemaToZod } from './json-schema-to-zod.js';
4
- export { connectAll, connectOne, closeAll, mcpInstructionsBlock, mcpResultToValue, McpAuthRequiredError } from './mcp-client.js';
4
+ export { connectAll, connectOne, closeAll, mcpInstructionsBlock, mcpResultToValue, mcpToolName, mcpToolPrefix, McpAuthRequiredError } from './mcp-client.js';
5
5
  export type { ConnectOptions } from './mcp-client.js';
6
6
  export { forgetSavedAuth, defaultMcpAuthDir } from './oauth.js';
7
7
  export type { McpOAuthOptions } from './oauth.js';
package/dist/mcp/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { loadMcpConfig, parseMcpConfig } from './mcp-config.js';
2
2
  export { jsonSchemaToZod } from './json-schema-to-zod.js';
3
- export { connectAll, connectOne, closeAll, mcpInstructionsBlock, mcpResultToValue, McpAuthRequiredError } from './mcp-client.js';
3
+ export { connectAll, connectOne, closeAll, mcpInstructionsBlock, mcpResultToValue, mcpToolName, mcpToolPrefix, McpAuthRequiredError } from './mcp-client.js';
4
4
  export { forgetSavedAuth, defaultMcpAuthDir } from './oauth.js';
5
5
  export { createMcpResourceTools } from './resource-tools.js';
6
6
  export { BUILTIN_MCP_CATALOG, loadMcpCatalog } from './catalog.js';
@@ -68,11 +68,9 @@ export function jsonSchemaToZod(schema) {
68
68
  .pipe(objectZod)
69
69
  : objectZod;
70
70
  }
71
- else if (s.additionalProperties === true) {
72
- inner = z.record(z.string(), z.any());
73
- }
74
71
  else {
75
- inner = z.record(z.string(), z.any());
72
+ // Any object. Not z.record(): its JSON Schema carries `propertyNames`, a keyword Gemini's schema input does not list.
73
+ inner = z.looseObject({});
76
74
  }
77
75
  break;
78
76
  }
@@ -26,6 +26,20 @@ export declare function connectOne(name: string, spec: McpServerSpec, options?:
26
26
  export declare function mcpInstructionsBlock(servers: readonly ConnectedMcpServer[]): string;
27
27
  /** Close all clients and their underlying transports. */
28
28
  export declare function closeAll(servers: ConnectedMcpServer[]): Promise<void>;
29
+ /** The start of every tool name of one server. Names a provider accepts only: letters, digits, `_` and `-`. */
30
+ export declare function mcpToolPrefix(serverName: string): string;
31
+ /**
32
+ * The name the model sees for a server's tool. OpenAI and Anthropic refuse a tool name with any other character or
33
+ * over 64 characters, and one refused name fails the whole request. A name that needed cleaning or cutting gets a
34
+ * short hash of the original pair so two different tools never become one; the call itself still uses the server's
35
+ * own tool name.
36
+ */
37
+ export declare function mcpToolName(serverName: string, toolName: string): string;
38
+ export declare function mcpToolToForgeTool(serverName: string, mcpTool: {
39
+ name: string;
40
+ description?: string;
41
+ inputSchema?: unknown;
42
+ }, getClient: () => Client, timeout?: number, reconnect?: () => Promise<Client>): ToolDefinition;
29
43
  /**
30
44
  * Reduce an MCP `CallToolResult` to a model-friendly value.
31
45
  *
@@ -21,6 +21,7 @@
21
21
  * - **streamable-http**: `url` + optional `headers`. The 2025-03-26 modern
22
22
  * transport. Uses the SDK's `StreamableHTTPClientTransport`.
23
23
  */
24
+ import { createHash } from 'node:crypto';
24
25
  import { readFileSync } from 'node:fs';
25
26
  import { dirname, join } from 'node:path';
26
27
  import { fileURLToPath } from 'node:url';
@@ -32,6 +33,7 @@ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/
32
33
  import { filterMcpServers, getOrgPolicy } from '../policy/policy.js';
33
34
  import { jsonSchemaToZod } from './json-schema-to-zod.js';
34
35
  import { ForgeOAuthProvider, McpAuthRequiredError, defaultMcpAuthDir, readSavedAuth } from './oauth.js';
36
+ import { clampOutput } from '../tools/run-command.js';
35
37
  /**
36
38
  * Self-identifying info we send to every MCP server during the `initialize`
37
39
  * handshake. Version is read from our own package.json so it stays in sync
@@ -90,6 +92,32 @@ function isUnauthorized(err) {
90
92
  }
91
93
  /** Connect to a single server and pull its tools. */
92
94
  export async function connectOne(name, spec, options = {}) {
95
+ let current = await openClient(name, spec, options);
96
+ // One restart at a time: calls that find the server gone together share one new process (G7).
97
+ let restarting;
98
+ const reconnect = () => {
99
+ restarting ??= (async () => {
100
+ await current.close().catch(() => undefined);
101
+ current = await openClient(name, spec, options);
102
+ return current;
103
+ })().finally(() => {
104
+ restarting = undefined;
105
+ });
106
+ return restarting;
107
+ };
108
+ const { tools: mcpTools } = await current.listTools(undefined, spec.timeout ? { timeout: spec.timeout } : undefined);
109
+ const tools = mcpTools.map((t) => mcpToolToForgeTool(name, t, () => current, spec.timeout, reconnect));
110
+ return {
111
+ name,
112
+ get client() {
113
+ return current;
114
+ },
115
+ tools,
116
+ instructions: current.getInstructions()?.trim() || undefined,
117
+ };
118
+ }
119
+ /** Start the transport, run the handshake (and the sign-in when the server wants one) and return the live client. */
120
+ async function openClient(name, spec, options) {
93
121
  const timeout = spec.timeout ? { timeout: spec.timeout } : undefined;
94
122
  let client = new Client(CLIENT_INFO, { capabilities: {} });
95
123
  const oauth = options.oauth;
@@ -134,9 +162,12 @@ export async function connectOne(name, spec, options = {}) {
134
162
  await client.connect(transport, timeout);
135
163
  }
136
164
  }
137
- const { tools: mcpTools } = await client.listTools(undefined, spec.timeout ? { timeout: spec.timeout } : undefined);
138
- const tools = mcpTools.map((t) => mcpToolToForgeTool(name, t, client, spec.timeout));
139
- return { name, client, tools, instructions: client.getInstructions()?.trim() || undefined };
165
+ return client;
166
+ }
167
+ /** A call that failed because the server's process or connection is gone, not because the call itself was bad. */
168
+ function isConnectionLost(err) {
169
+ const e = err;
170
+ return e?.code === -32000 || (typeof e?.message === 'string' && /connection closed|not connected/i.test(e.message));
140
171
  }
141
172
  /**
142
173
  * Pick the right SDK transport for the server spec. The transport type is
@@ -193,8 +224,36 @@ export async function closeAll(servers) {
193
224
  * namespace of the built-in tools and tells the model which server a tool
194
225
  * came from at a glance.
195
226
  */
196
- function mcpToolToForgeTool(serverName, mcpTool, client, timeout) {
197
- const fullName = `mcp__${serverName}__${mcpTool.name}`;
227
+ const MAX_TOOL_NAME = 64;
228
+ const MAX_SERVER_PART = 30;
229
+ function cleanNamePart(s) {
230
+ return s.replace(/[^a-zA-Z0-9_-]/g, '_');
231
+ }
232
+ function shortHash(s) {
233
+ return createHash('sha256').update(s).digest('hex').slice(0, 8);
234
+ }
235
+ /** The start of every tool name of one server. Names a provider accepts only: letters, digits, `_` and `-`. */
236
+ export function mcpToolPrefix(serverName) {
237
+ return `mcp__${cleanNamePart(serverName).slice(0, MAX_SERVER_PART)}__`;
238
+ }
239
+ /**
240
+ * The name the model sees for a server's tool. OpenAI and Anthropic refuse a tool name with any other character or
241
+ * over 64 characters, and one refused name fails the whole request. A name that needed cleaning or cutting gets a
242
+ * short hash of the original pair so two different tools never become one; the call itself still uses the server's
243
+ * own tool name.
244
+ */
245
+ export function mcpToolName(serverName, toolName) {
246
+ const prefix = mcpToolPrefix(serverName);
247
+ const body = cleanNamePart(toolName);
248
+ const name = prefix + body;
249
+ const untouched = prefix === `mcp__${serverName}__` && body === toolName && name.length <= MAX_TOOL_NAME;
250
+ if (untouched)
251
+ return name;
252
+ const hash = shortHash(`${serverName}\u0000${toolName}`);
253
+ return `${name.slice(0, MAX_TOOL_NAME - hash.length - 1)}_${hash}`;
254
+ }
255
+ export function mcpToolToForgeTool(serverName, mcpTool, getClient, timeout, reconnect) {
256
+ const fullName = mcpToolName(serverName, mcpTool.name);
198
257
  const description = [
199
258
  mcpTool.description ?? '',
200
259
  `(MCP server: ${serverName})`,
@@ -210,8 +269,24 @@ function mcpToolToForgeTool(serverName, mcpTool, client, timeout) {
210
269
  // destructive so the user gets a confirmation prompt unless --auto.
211
270
  destructive: true,
212
271
  execute: async (args) => {
213
- const result = await client.callTool({ name: mcpTool.name, arguments: args }, undefined, timeout ? { timeout } : undefined);
214
- return mcpResultToValue(result);
272
+ const call = (client) => client.callTool({ name: mcpTool.name, arguments: args }, undefined, timeout ? { timeout } : undefined);
273
+ let result;
274
+ try {
275
+ result = await call(getClient());
276
+ }
277
+ catch (err) {
278
+ // A server that exited is started again for this call, once; any other failure goes to the caller as it is.
279
+ if (!reconnect || !isConnectionLost(err))
280
+ throw err;
281
+ result = await call(await reconnect());
282
+ }
283
+ const value = mcpResultToValue(result);
284
+ // An MCP server reports a failed call as a normal result with isError set: the model must see a failure.
285
+ if (result?.isError === true) {
286
+ const said = typeof value === 'string' ? value : value?.text;
287
+ throw new Error(said?.trim() || `MCP server "${serverName}" reported an error for ${mcpTool.name}.`);
288
+ }
289
+ return value;
215
290
  },
216
291
  };
217
292
  }
@@ -229,16 +304,25 @@ export function mcpResultToValue(result) {
229
304
  if (!Array.isArray(r.content)) {
230
305
  return { ...r };
231
306
  }
232
- const text = r.content
233
- .filter((c) => typeof c === 'object' && c !== null && c.type === 'text')
234
- .map((c) => c.text)
235
- .join('\n');
236
- const other = r.content.filter((c) => {
237
- const t = typeof c === 'object' && c !== null ? c.type : undefined;
238
- return t !== 'text';
239
- });
240
- if (other.length === 0) {
241
- return text;
307
+ const texts = [];
308
+ let image;
309
+ let omitted = 0;
310
+ for (const c of r.content) {
311
+ const b = (typeof c === 'object' && c !== null ? c : {});
312
+ if (b.type === 'text' && typeof b.text === 'string')
313
+ texts.push(b.text);
314
+ else if (b.type === 'resource' && typeof b.resource?.text === 'string')
315
+ texts.push(b.resource.text);
316
+ // The first image goes to the model as media (like `read_image`); more images, audio and binary resources do not.
317
+ else if (b.type === 'image' && !image && typeof b.data === 'string' && b.mimeType?.startsWith('image/'))
318
+ image = { data: b.data, mime: b.mimeType };
319
+ else
320
+ omitted++;
242
321
  }
243
- return { text, content: r.content, isError: r.isError };
322
+ if (omitted > 0)
323
+ texts.push(`[${omitted} non-text ${omitted === 1 ? 'item' : 'items'} omitted]`);
324
+ const text = clampOutput(texts.join('\n'));
325
+ if (!image)
326
+ return text;
327
+ return { text, dataUrl: `data:${image.mime};base64,${image.data}`, mime: image.mime, bytes: Math.floor((image.data.length * 3) / 4), path: 'MCP tool result' };
244
328
  }
@@ -1,4 +1,5 @@
1
1
  import { generateText } from 'ai';
2
+ import { withModelRetry } from '../agent/retry.js';
2
3
  import { stripThinkTags } from '../agent/think-tags.js';
3
4
  import { createLanguageModel } from '../providers.js';
4
5
  const MAX_DIGEST_CHARS = 600;
@@ -38,7 +39,7 @@ export async function generateSessionDigest(items, model, modelFactory = createL
38
39
  if (!model || !shouldDigest(items))
39
40
  return undefined;
40
41
  try {
41
- const result = await generateText({ model: modelFactory(model), prompt: `${INSTRUCTION}\n\nConversation:\n${transcript(items)}` });
42
+ const result = await withModelRetry(() => generateText({ model: modelFactory(model), prompt: `${INSTRUCTION}\n\nConversation:\n${transcript(items)}`, maxRetries: 0 }));
42
43
  return cleanDigest(result.text);
43
44
  }
44
45
  catch {
@@ -134,6 +134,9 @@ export function loadSpecialistsFromConfig(configPath, options = {}) {
134
134
  out.warnings.push({ message: 'Skipped non-object entry' });
135
135
  continue;
136
136
  }
137
+ // A word-for-word copy would shadow the built-in and keep it at the version that was copied (RB-54).
138
+ if (isBuiltinCopy(entry))
139
+ continue;
137
140
  try {
138
141
  registerSpecialist(entry, { override: options.override ?? true, configDir });
139
142
  out.registered.push(entry.id);
@@ -166,9 +169,19 @@ export async function saveSpecialistsToConfig(entries, configPath) {
166
169
  for (const entry of entries) {
167
170
  validateSpecialistConfig(entry, configDir);
168
171
  }
169
- const file = { version: 1, specialists: entries };
172
+ // A word-for-word copy of a built-in would shadow it and freeze it at this version (RB-54). Drop it, and say
173
+ // where the call came from: nothing in Forge is meant to write one, so the stack names whoever does.
174
+ const copies = entries.filter(isBuiltinCopy);
175
+ if (copies.length > 0) {
176
+ console.warn(`forge: not saving ${copies.map((c) => c.id).join(', ')}: word-for-word copies of built-in agents.\n${new Error().stack ?? ''}`);
177
+ }
178
+ const file = { version: 1, specialists: entries.filter((e) => !isBuiltinCopy(e)) };
170
179
  await updateJsonFile(path, () => file);
171
180
  }
181
+ /** Same id and same prompt as a built-in: a copy, not the person's own agent. */
182
+ function isBuiltinCopy(entry) {
183
+ return BUILTIN_SPECIALISTS.some((b) => b.id === entry.id && b.systemPrompt === entry.systemPrompt);
184
+ }
172
185
  /**
173
186
  * Pure validator: returns a normalized {@link SpecialistConfig}.
174
187
  * Throws on any rule violation with a descriptive message. Library
@@ -49,7 +49,7 @@ Stop when: you have read enough to name every file and function in the plan. Do
49
49
 
50
50
  Never: write or edit files, run commands, or plan around code you have not read. If the request is unclear and the answer would change the plan, put it under Questions instead of guessing.`,
51
51
  allowedTools: ['read_file', 'list_files', 'search_files', 'find_symbol', 'find_references', 'list_symbols', 'git_diff', 'memory_read', 'todo_write'],
52
- forbiddenTools: ['write_file', 'edit_file', 'apply_patch', 'edit_notebook', 'run_command', 'sub_agent'],
52
+ forbiddenTools: ['write_file', 'edit_file', 'apply_patch', 'edit_notebook', 'run_command', 'bg_start', 'repo_clone', 'suggest_plan', 'sub_agent'],
53
53
  maxIterations: 25,
54
54
  auto: false,
55
55
  },
@@ -129,7 +129,7 @@ Output:
129
129
 
130
130
  Never: change files, run commands, or state something about this code that you did not read.`,
131
131
  allowedTools: ['read_file', 'list_files', 'search_files', 'find_symbol', 'find_references', 'list_symbols', 'fetch_url', 'memory_read'],
132
- forbiddenTools: ['write_file', 'edit_file', 'apply_patch', 'edit_notebook', 'run_command', 'sub_agent'],
132
+ forbiddenTools: ['write_file', 'edit_file', 'apply_patch', 'edit_notebook', 'run_command', 'bg_start', 'repo_clone', 'suggest_plan', 'sub_agent'],
133
133
  maxIterations: 15,
134
134
  auto: false,
135
135
  },
@@ -156,7 +156,7 @@ Report at most ten findings. End with one line: how many hunks you reviewed and
156
156
 
157
157
  Never: change files, run commands, or report something you did not confirm in the code.`,
158
158
  allowedTools: ['read_file', 'list_files', 'search_files', 'find_symbol', 'find_references', 'list_symbols', 'git_diff', 'memory_read'],
159
- forbiddenTools: ['write_file', 'edit_file', 'apply_patch', 'edit_notebook', 'run_command', 'sub_agent'],
159
+ forbiddenTools: ['write_file', 'edit_file', 'apply_patch', 'edit_notebook', 'run_command', 'bg_start', 'repo_clone', 'suggest_plan', 'sub_agent'],
160
160
  maxIterations: 20,
161
161
  auto: false,
162
162
  },
package/dist/pricing.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { catalogEntry } from './catalog/models-dev.js';
1
2
  import { overrideCost } from './model-overrides.js';
2
3
  /**
3
4
  * Pricing table. Keys are matched with case-insensitive substring matching
@@ -120,6 +121,17 @@ export function lookupPricing(model, override) {
120
121
  return { ...cache, ...configured, known: true };
121
122
  if (typeof model !== 'string' && LOCAL_PROVIDERS.has(model.provider))
122
123
  return { input: 0, output: 0, known: true };
124
+ const listed = typeof model === 'string' ? undefined : catalogEntry(model)?.cost;
125
+ if (listed && listed.input > 0) {
126
+ return {
127
+ ...cache,
128
+ input: listed.input,
129
+ output: listed.output,
130
+ ...(listed.cacheRead !== undefined ? { cacheReadMultiplier: listed.cacheRead / listed.input } : {}),
131
+ ...(listed.cacheWrite !== undefined ? { cacheWriteMultiplier: listed.cacheWrite / listed.input } : {}),
132
+ known: true,
133
+ };
134
+ }
123
135
  const id = (typeof model === 'string' ? model : model.model).toLowerCase();
124
136
  // Strip only the OpenRouter-style 'models/' prefix so 'models/gemini-1.5-pro'
125
137
  // resolves the same way as 'gemini-1.5-pro'. We do NOT strip provider prefixes
@@ -12,7 +12,18 @@ export interface Embedder {
12
12
  /** Stable identity. Vectors from different ids are never compared; a change rebuilds the index. */
13
13
  id: string;
14
14
  embed(texts: string[]): Promise<number[][]>;
15
+ /** Where the text goes, when it leaves this process: absent for the offline embedder. */
16
+ remote?: {
17
+ host: string;
18
+ model: string;
19
+ local: boolean;
20
+ };
15
21
  }
22
+ /** The host and model that would receive chunks of code, or undefined when nothing leaves this machine (F12). */
23
+ export declare function embedderDestination(e: Embedder): {
24
+ host: string;
25
+ model: string;
26
+ } | undefined;
16
27
  export declare function cosine(a: number[], b: number[]): number;
17
28
  /** Lowercased sub-tokens: splits camelCase, snake_case, digits and punctuation. */
18
29
  export declare function tokenize(text: string): string[];
@@ -8,6 +8,21 @@
8
8
  * `/v1`, LM Studio, vLLM...). Opt-in via `embedderFromEnv`; chunk text is sent
9
9
  * to that endpoint (after secret redaction).
10
10
  */
11
+ /** The host and model that would receive chunks of code, or undefined when nothing leaves this machine (F12). */
12
+ export function embedderDestination(e) {
13
+ return e.remote && !e.remote.local ? { host: e.remote.host, model: e.remote.model } : undefined;
14
+ }
15
+ function remoteOf(baseUrl, model) {
16
+ let host = baseUrl;
17
+ try {
18
+ host = new URL(baseUrl).hostname.replace(/^\[|\]$/g, '');
19
+ }
20
+ catch {
21
+ // an address that is not a URL: report it as written, and treat it as remote
22
+ }
23
+ const local = host === 'localhost' || host === '::1' || /^127\./.test(host);
24
+ return { host, model, local };
25
+ }
11
26
  export function cosine(a, b) {
12
27
  let dot = 0;
13
28
  let na = 0;
@@ -62,6 +77,7 @@ export function openAiCompatibleEmbedder(options) {
62
77
  const batch = options.batchSize ?? 64;
63
78
  return {
64
79
  id: `openai-compat:${base}:${options.model}`,
80
+ remote: remoteOf(base, options.model),
65
81
  async embed(texts) {
66
82
  const out = [];
67
83
  for (let i = 0; i < texts.length; i += batch) {