@4onstudios/iris-agent 0.1.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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +690 -0
  3. package/dist/api/acp/acpServer.d.ts +23 -0
  4. package/dist/api/acp/acpServer.js +814 -0
  5. package/dist/api/acp/index.d.ts +2 -0
  6. package/dist/api/acp/index.js +2 -0
  7. package/dist/api/acp/irisClient.d.ts +58 -0
  8. package/dist/api/acp/irisClient.js +214 -0
  9. package/dist/api/agent.d.ts +14 -0
  10. package/dist/api/agent.js +4171 -0
  11. package/dist/api/backendService.d.ts +4 -0
  12. package/dist/api/backendService.js +14 -0
  13. package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
  14. package/dist/api/core/agent/host/AgentContract.js +1 -0
  15. package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
  16. package/dist/api/core/agent/host/AgentRegistry.js +125 -0
  17. package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
  18. package/dist/api/core/agent/host/defaultRegistry.js +46 -0
  19. package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
  20. package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
  21. package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
  22. package/dist/api/core/agent/host/hostSessionManager.js +352 -0
  23. package/dist/api/core/agent/host/index.d.ts +5 -0
  24. package/dist/api/core/agent/host/index.js +5 -0
  25. package/dist/api/core/agent/index.d.ts +87 -0
  26. package/dist/api/core/agent/index.js +1024 -0
  27. package/dist/api/core/agent/publicContracts.d.ts +35 -0
  28. package/dist/api/core/agent/publicContracts.js +103 -0
  29. package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
  30. package/dist/api/core/agent/tools/applyDiff.js +131 -0
  31. package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
  32. package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
  33. package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
  34. package/dist/api/core/agent/tools/createDirectory.js +77 -0
  35. package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
  36. package/dist/api/core/agent/tools/deleteFile.js +66 -0
  37. package/dist/api/core/agent/tools/editFile.d.ts +73 -0
  38. package/dist/api/core/agent/tools/editFile.js +173 -0
  39. package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
  40. package/dist/api/core/agent/tools/executeCommand.js +250 -0
  41. package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
  42. package/dist/api/core/agent/tools/fileContent.js +227 -0
  43. package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
  44. package/dist/api/core/agent/tools/findDefinition.js +109 -0
  45. package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
  46. package/dist/api/core/agent/tools/findReferences.js +135 -0
  47. package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
  48. package/dist/api/core/agent/tools/formatDocument.js +90 -0
  49. package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
  50. package/dist/api/core/agent/tools/getCodeActions.js +97 -0
  51. package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
  52. package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
  53. package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
  54. package/dist/api/core/agent/tools/getCodeContext.js +282 -0
  55. package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
  56. package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
  57. package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
  58. package/dist/api/core/agent/tools/getSymbols.js +164 -0
  59. package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
  60. package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
  61. package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
  62. package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
  63. package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
  64. package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
  65. package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
  66. package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
  67. package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
  68. package/dist/api/core/agent/tools/grepSearch.js +211 -0
  69. package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
  70. package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
  71. package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
  72. package/dist/api/core/agent/tools/listDirectory.js +161 -0
  73. package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
  74. package/dist/api/core/agent/tools/mcpTools.js +442 -0
  75. package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
  76. package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
  77. package/dist/api/core/agent/tools/readFile.d.ts +92 -0
  78. package/dist/api/core/agent/tools/readFile.js +337 -0
  79. package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
  80. package/dist/api/core/agent/tools/renameFile.js +86 -0
  81. package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
  82. package/dist/api/core/agent/tools/renameSymbol.js +106 -0
  83. package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
  84. package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
  85. package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
  86. package/dist/api/core/agent/tools/searchFiles.js +280 -0
  87. package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
  88. package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
  89. package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
  90. package/dist/api/core/agent/tools/treeTraversal.js +217 -0
  91. package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
  92. package/dist/api/core/agent/tools/webSearch.js +80 -0
  93. package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
  94. package/dist/api/core/agent/tools/writeFile.js +97 -0
  95. package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
  96. package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
  97. package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
  98. package/dist/api/core/agent/utils/diffUtils.js +39 -0
  99. package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
  100. package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
  101. package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
  102. package/dist/api/core/agent/utils/errorRecovery.js +229 -0
  103. package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
  104. package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
  105. package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
  106. package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
  107. package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
  108. package/dist/api/core/agent/utils/pathRecovery.js +29 -0
  109. package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
  110. package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
  111. package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
  112. package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
  113. package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
  114. package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
  115. package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
  116. package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
  117. package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
  118. package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
  119. package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
  120. package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
  121. package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
  122. package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
  123. package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
  124. package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
  125. package/dist/api/core/library/BrowserManager.d.ts +11 -0
  126. package/dist/api/core/library/BrowserManager.js +66 -0
  127. package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
  128. package/dist/api/core/library/desktopWorkspace.js +3 -0
  129. package/dist/api/core/library/extensionManager.d.ts +10 -0
  130. package/dist/api/core/library/extensionManager.js +27 -0
  131. package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
  132. package/dist/api/core/library/knowledgeGraph.js +989 -0
  133. package/dist/api/core/library/languageModelTools.d.ts +141 -0
  134. package/dist/api/core/library/languageModelTools.js +207 -0
  135. package/dist/api/core/library/localRuntime.d.ts +23 -0
  136. package/dist/api/core/library/localRuntime.js +144 -0
  137. package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
  138. package/dist/api/core/library/lsp/coreLsp.js +2076 -0
  139. package/dist/api/core/library/lsp/protocol.d.ts +25 -0
  140. package/dist/api/core/library/lsp/protocol.js +1 -0
  141. package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
  142. package/dist/api/core/library/lsp/serverManager.js +427 -0
  143. package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
  144. package/dist/api/core/library/mcpServerProvider.js +152 -0
  145. package/dist/api/core/library/mcpSettings.d.ts +20 -0
  146. package/dist/api/core/library/mcpSettings.js +131 -0
  147. package/dist/api/core/library/patternMatcher.d.ts +66 -0
  148. package/dist/api/core/library/patternMatcher.js +284 -0
  149. package/dist/api/core/library/regexEscape.d.ts +4 -0
  150. package/dist/api/core/library/regexEscape.js +4 -0
  151. package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
  152. package/dist/api/core/library/runtimeEventBus.js +78 -0
  153. package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
  154. package/dist/api/core/library/safetyMiddleware.js +215 -0
  155. package/dist/api/core/library/tauri.d.ts +71 -0
  156. package/dist/api/core/library/tauri.js +222 -0
  157. package/dist/api/core/library/tauriImport.d.ts +5 -0
  158. package/dist/api/core/library/tauriImport.js +7 -0
  159. package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
  160. package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
  161. package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
  162. package/dist/api/core/library/workspaceIdentity.js +49 -0
  163. package/dist/api/core/library/workspaceSummary.d.ts +19 -0
  164. package/dist/api/core/library/workspaceSummary.js +35 -0
  165. package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
  166. package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
  167. package/dist/api/core/skills/create-pr/SKILL.md +32 -0
  168. package/dist/api/core/skills/dev-server/SKILL.md +20 -0
  169. package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
  170. package/dist/api/core/skills/github-search/SKILL.md +21 -0
  171. package/dist/api/core/skills/github-summary/SKILL.md +25 -0
  172. package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
  173. package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
  174. package/dist/api/core/skills/project-setup/SKILL.md +20 -0
  175. package/dist/api/core/skills/search-integration/SKILL.md +19 -0
  176. package/dist/api/core/skills/search-results/SKILL.md +19 -0
  177. package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
  178. package/dist/api/data/runStore.d.ts +46 -0
  179. package/dist/api/data/runStore.js +244 -0
  180. package/dist/api/helpers/agentUtils.d.ts +11 -0
  181. package/dist/api/helpers/agentUtils.js +23 -0
  182. package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
  183. package/dist/api/helpers/modelTokenLimits.js +158 -0
  184. package/dist/api/helpers/observationalMemory.d.ts +10 -0
  185. package/dist/api/helpers/observationalMemory.js +36 -0
  186. package/dist/api/helpers/promptBudget.d.ts +30 -0
  187. package/dist/api/helpers/promptBudget.js +216 -0
  188. package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
  189. package/dist/api/helpers/resolveImageMessageParts.js +140 -0
  190. package/dist/api/helpers/slashCommands.d.ts +31 -0
  191. package/dist/api/helpers/slashCommands.js +129 -0
  192. package/dist/api/helpers/tokenUsage.d.ts +19 -0
  193. package/dist/api/helpers/tokenUsage.js +81 -0
  194. package/dist/api/routes/fileRoutes.d.ts +2 -0
  195. package/dist/api/routes/fileRoutes.js +108 -0
  196. package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
  197. package/dist/api/routes/lspDocumentRoutes.js +84 -0
  198. package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
  199. package/dist/api/routes/lspHierarchyRoutes.js +122 -0
  200. package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
  201. package/dist/api/routes/lspPositionRoutes.js +263 -0
  202. package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
  203. package/dist/api/routes/lspQueryRoutes.js +679 -0
  204. package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
  205. package/dist/api/routes/lspResolveRoutes.js +143 -0
  206. package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
  207. package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
  208. package/dist/api/routes/semanticRoutes.d.ts +2 -0
  209. package/dist/api/routes/semanticRoutes.js +126 -0
  210. package/dist/cli.d.ts +8 -0
  211. package/dist/cli.js +160 -0
  212. package/dist/index.d.ts +1 -0
  213. package/dist/index.js +1 -0
  214. package/dist/server.d.ts +2 -0
  215. package/dist/server.js +38 -0
  216. package/package.json +100 -0
@@ -0,0 +1,158 @@
1
+ const MODEL_INPUT_TOKEN_LIMITS = {
2
+ "openrouter/anthropic/claude-sonnet-latest": 1_000_000,
3
+ "openrouter/google/gemini-2.5-pro": 1_000_000,
4
+ "openrouter/deepseek/deepseek-chat": 164_000,
5
+ "openrouter/xiaomi/mimo-v2.5": 128_000,
6
+ "openrouter/minimax/minimax-m3": 128_000,
7
+ "openrouter/deepseek/deepseek-v4-flash": 128_000,
8
+ "openrouter/openai/gpt-5.5": 1_000_000,
9
+ "openrouter/openai/gpt-5.5-pro": 1_000_000,
10
+ "openrouter/openai/gpt-5.1-codex-mini": 400_000,
11
+ "openrouter/openai/gpt-5.1-codex": 400_000,
12
+ "openrouter/openai/gpt-5.1-codex-max": 400_000,
13
+ "openrouter/openai/gpt-5.2-codex": 400_000,
14
+ // GPT-5.3 Codex sessions frequently include large tool schemas + long
15
+ // conversation state; keep a high effective budget to avoid hard drops where
16
+ // no message fits in the limiter window.
17
+ "openrouter/openai/gpt-5.3-codex": 400_000,
18
+ "openrouter/anthropic/claude-haiku-4.5": 200_000,
19
+ "openrouter/anthropic/claude-opus-4.1": 1_000_000,
20
+ "openrouter/anthropic/claude-fable-5": 1_000_000,
21
+ "openrouter/moonshotai/kimi-k2.5": 256_000,
22
+ "openrouter/moonshotai/kimi-k3": 256_000,
23
+ "openrouter/google/gemini-2.5-flash": 1_000_000,
24
+ "openrouter/z-ai/glm-5.2": 128_000,
25
+ "openrouter/openai/gpt-oss-20b:free": 128_000,
26
+ "openrouter/openai/gpt-oss-120b:free": 128_000,
27
+ "openrouter/z-ai/glm-4.5-air:free": 128_000,
28
+ "openrouter/poolside/laguna-xs.2:free": 64_000,
29
+ "gemini-2.5-pro": 173_000,
30
+ "gemini-3-flash-preview": 173_000,
31
+ "gemini-3.1-pro-preview": 1_000_000,
32
+ "gemini-3.5-flash": 1_000_000,
33
+ "gpt-5-mini": 192_000,
34
+ "gpt-5.2": 192_000,
35
+ "gpt-5.2-codex": 400_000,
36
+ "gpt-5.3-codex": 400_000,
37
+ "gpt-5.4": 1_000_000,
38
+ "gpt-5.4-mini": 400_000,
39
+ "raptor-mini-preview": 264_000,
40
+ "claude-haiku-4.5-other": 160_000,
41
+ "claude-sonnet-4.5-other": 160_000,
42
+ "claude-sonnet-4.6-other": 1_000_000,
43
+ "huggingface/moonshotai/Kimi-K2.7-Code:fastest": 128_000,
44
+ "huggingface/meta-llama/Llama-4-Maverick-17B-128E-Instruct:fastest": 1_000_000,
45
+ "huggingface/Qwen/Qwen3-235B-A22B:fastest": 128_000,
46
+ "huggingface/deepseek-ai/DeepSeek-V3-0324:fastest": 128_000,
47
+ "huggingface/mistralai/Mistral-Small-3.2-24B-Instruct-2506:fastest": 128_000,
48
+ "claude-haiku-4-5": 200_000,
49
+ "claude-opus-4-8": 1_000_000,
50
+ "claude-fable-5": 1_000_000,
51
+ "claude-sonnet-4-6": 1_000_000,
52
+ };
53
+ const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
54
+ const getEnvTokenLimitOverride = () => {
55
+ const raw = process.env.IRIS_AGENT_INPUT_TOKEN_LIMIT;
56
+ if (!raw)
57
+ return null;
58
+ const parsed = Number.parseInt(raw, 10);
59
+ if (!Number.isFinite(parsed))
60
+ return null;
61
+ return clamp(parsed, 4_096, 1_000_000);
62
+ };
63
+ const inferModelLimitByPattern = (modelId) => {
64
+ const normalized = modelId.toLowerCase();
65
+ if (normalized.includes("gemini") || normalized.includes("1m"))
66
+ return 1_000_000;
67
+ if (normalized.includes("claude") && normalized.includes("haiku"))
68
+ return 200_000;
69
+ if (normalized.includes("claude") && normalized.includes("sonnet"))
70
+ return 1_000_000;
71
+ if (normalized.includes("claude") && normalized.includes("opus"))
72
+ return 1_000_000;
73
+ if (normalized.includes("gpt-5.3-codex"))
74
+ return 400_000;
75
+ if (normalized.includes("gpt-5.2-codex"))
76
+ return 400_000;
77
+ if (normalized.includes("gpt-5"))
78
+ return 192_000;
79
+ if (normalized.includes("gpt-4o"))
80
+ return 128_000;
81
+ if (normalized.includes("deepseek") || normalized.includes("qwen") || normalized.includes("mistral"))
82
+ return 128_000;
83
+ if (normalized.includes("laguna"))
84
+ return 64_000;
85
+ return null;
86
+ };
87
+ // Models known to accept image content parts at the provider/endpoint level.
88
+ // Keep this exact-match metadata in sync with selectable model metadata rather
89
+ // than inferring from provider/family substrings.
90
+ const VISION_MODEL_IDS = new Set([
91
+ "openrouter/openai/gpt-5.3-codex",
92
+ "openrouter/moonshotai/kimi-k2.5",
93
+ "openrouter/moonshotai/kimi-k3",
94
+ "openrouter/z-ai/glm-5.3-flash",
95
+ "openrouter/google/gemini-2.5-pro",
96
+ "openrouter/google/gemini-2.5-flash",
97
+ "gemini-2.5-pro",
98
+ "gemini-3-flash-preview",
99
+ "gemini-3.1-pro-preview",
100
+ "gemini-3.5-flash",
101
+ "gpt-5.2",
102
+ "gpt-5.2-codex",
103
+ "gpt-5.3-codex",
104
+ "gpt-5.4",
105
+ "gpt-4o",
106
+ "gpt-4o-mini",
107
+ "claude-haiku-4-5",
108
+ "claude-opus-4-8",
109
+ "claude-fable-5",
110
+ "claude-sonnet-4-6",
111
+ ]);
112
+ const stripProviderSuffix = (modelId) => {
113
+ return modelId.endsWith("-other")
114
+ ? modelId.slice(0, -"-other".length)
115
+ : modelId;
116
+ };
117
+ const getVisionModelIdCandidates = (modelId) => {
118
+ const normalized = stripProviderSuffix((modelId || "").trim().toLowerCase());
119
+ if (!normalized)
120
+ return [];
121
+ const candidates = new Set([normalized]);
122
+ if (normalized.startsWith("openrouter/")) {
123
+ candidates.add(normalized.slice("openrouter/".length));
124
+ }
125
+ if (normalized.startsWith("openai/")) {
126
+ candidates.add(normalized.slice("openai/".length));
127
+ }
128
+ if (normalized.startsWith("google/")) {
129
+ candidates.add(normalized.slice("google/".length));
130
+ }
131
+ if (normalized.startsWith("anthropic/")) {
132
+ candidates.add(normalized.slice("anthropic/".length));
133
+ }
134
+ if (normalized.includes("/") &&
135
+ !normalized.startsWith("openrouter/") &&
136
+ !normalized.startsWith("ollama/") &&
137
+ !normalized.startsWith("local/") &&
138
+ !normalized.startsWith("huggingface/")) {
139
+ candidates.add(`openrouter/${normalized}`);
140
+ }
141
+ return Array.from(candidates);
142
+ };
143
+ export const resolveModelSupportsVision = (modelId) => {
144
+ return getVisionModelIdCandidates(modelId).some((candidate) => VISION_MODEL_IDS.has(candidate));
145
+ };
146
+ export const resolveModelInputTokenLimit = (modelId, fallback = 64_000) => {
147
+ const envOverride = getEnvTokenLimitOverride();
148
+ if (envOverride)
149
+ return envOverride;
150
+ const normalized = (modelId || "").trim();
151
+ if (MODEL_INPUT_TOKEN_LIMITS[normalized]) {
152
+ return MODEL_INPUT_TOKEN_LIMITS[normalized];
153
+ }
154
+ const inferred = inferModelLimitByPattern(normalized);
155
+ if (inferred)
156
+ return inferred;
157
+ return clamp(fallback, 4_096, 1_000_000);
158
+ };
@@ -0,0 +1,10 @@
1
+ export type ObservationalMemorySettingsPayload = {
2
+ model?: string;
3
+ scope?: "resource" | "thread";
4
+ observationMessageTokens?: number;
5
+ reflectionObservationTokens?: number;
6
+ observationPreviousTokens?: number;
7
+ activateAfterIdle?: string;
8
+ activateOnProviderChange?: boolean;
9
+ };
10
+ export declare const sanitizeObservationalMemorySettings: (input: unknown) => ObservationalMemorySettingsPayload | undefined;
@@ -0,0 +1,36 @@
1
+ const asBoundedInteger = (input, fallback, min, max) => {
2
+ const parsed = typeof input === "number"
3
+ ? input
4
+ : typeof input === "string"
5
+ ? Number.parseInt(input, 10)
6
+ : Number.NaN;
7
+ if (!Number.isFinite(parsed))
8
+ return fallback;
9
+ return Math.max(min, Math.min(max, Math.trunc(parsed)));
10
+ };
11
+ export const sanitizeObservationalMemorySettings = (input) => {
12
+ if (!input || typeof input !== "object") {
13
+ return undefined;
14
+ }
15
+ const raw = input;
16
+ const model = typeof raw.model === "string" && raw.model.trim().length > 0
17
+ ? raw.model.trim()
18
+ : "google/gemini-2.5-flash";
19
+ const scope = raw.scope === "thread" ? "thread" : "resource";
20
+ const observationMessageTokens = asBoundedInteger(raw.observationMessageTokens, 4000, 4000, 200000);
21
+ const reflectionObservationTokens = asBoundedInteger(raw.reflectionObservationTokens, 8000, 8000, 400000);
22
+ const observationPreviousTokens = asBoundedInteger(raw.observationPreviousTokens, 0, 0, 100000);
23
+ const activateAfterIdle = typeof raw.activateAfterIdle === "string" ? raw.activateAfterIdle.trim() : "";
24
+ const activateOnProviderChange = typeof raw.activateOnProviderChange === "boolean"
25
+ ? raw.activateOnProviderChange
26
+ : true;
27
+ return {
28
+ model,
29
+ scope,
30
+ observationMessageTokens,
31
+ reflectionObservationTokens,
32
+ observationPreviousTokens,
33
+ activateAfterIdle,
34
+ activateOnProviderChange,
35
+ };
36
+ };
@@ -0,0 +1,30 @@
1
+ export type ConversationMessageLike = {
2
+ content?: string;
3
+ role?: string;
4
+ continuationType?: string;
5
+ };
6
+ export { resolveModelInputTokenLimit, resolveModelSupportsVision, } from "./modelTokenLimits.js";
7
+ type PromptBudgetBuildOptions<T extends ConversationMessageLike> = {
8
+ effectiveMessage: string;
9
+ conversationHistory?: T[];
10
+ contextInfo: string;
11
+ maxPromptTokens: number;
12
+ maxConversationMessages: number;
13
+ maxConversationMessageTokens: number;
14
+ continuationInstruction?: string;
15
+ };
16
+ type PromptBudgetBuildResult<T extends ConversationMessageLike> = {
17
+ prompt: string;
18
+ budgetedContextInfo: string;
19
+ budgetedConversationHistory: T[];
20
+ promptEstimatedTokens: number;
21
+ };
22
+ export declare const truncateText: (value: string, maxChars: number) => string;
23
+ export declare const truncateMiddle: (value: string, maxChars: number) => string;
24
+ export declare const estimateTokensFromChars: (value: string) => number;
25
+ export declare const truncateTextByTokens: (value: string, maxTokens: number) => string;
26
+ export declare const truncateHeadByTokens: (value: string, maxTokens: number) => string;
27
+ export declare const truncateMiddleByTokens: (value: string, maxTokens: number) => string;
28
+ export declare const budgetConversationHistory: <T extends ConversationMessageLike>(history: T[] | undefined, maxMessages: number, maxCharsPerMessage: number) => T[];
29
+ export declare const budgetConversationHistoryByTokens: <T extends ConversationMessageLike>(history: T[] | undefined, maxMessages: number, maxTokensPerMessage: number, maxTotalTokens: number) => T[];
30
+ export declare const buildPromptWithinTokenBudget: <T extends ConversationMessageLike>(options: PromptBudgetBuildOptions<T>) => PromptBudgetBuildResult<T>;
@@ -0,0 +1,216 @@
1
+ export { resolveModelInputTokenLimit, resolveModelSupportsVision, } from "./modelTokenLimits.js";
2
+ const TOKEN_TO_CHAR_RATIO = 4;
3
+ const MIN_SECTION_TOKENS = 64;
4
+ const MESSAGE_OVERHEAD_TOKENS = 10;
5
+ const SUMMARY_MESSAGE_LIMIT = 8;
6
+ const SUMMARY_MESSAGE_CHAR_LIMIT = 220;
7
+ export const truncateText = (value, maxChars) => {
8
+ if (value.length <= maxChars)
9
+ return value;
10
+ return `${value.slice(0, Math.max(0, maxChars - 31))}\n\n[truncated: prompt budget exceeded]`;
11
+ };
12
+ export const truncateMiddle = (value, maxChars) => {
13
+ if (value.length <= maxChars)
14
+ return value;
15
+ const marker = "\n\n[truncated middle content for prompt budget]\n\n";
16
+ if (maxChars <= marker.length + 2) {
17
+ return truncateText(value, maxChars);
18
+ }
19
+ const remaining = maxChars - marker.length;
20
+ const head = Math.floor(remaining * 0.7);
21
+ const tail = remaining - head;
22
+ return `${value.slice(0, head)}${marker}${value.slice(Math.max(0, value.length - tail))}`;
23
+ };
24
+ export const estimateTokensFromChars = (value) => Math.ceil((value || "").length / TOKEN_TO_CHAR_RATIO);
25
+ export const truncateTextByTokens = (value, maxTokens) => {
26
+ const safeMaxTokens = Math.max(0, maxTokens);
27
+ const maxChars = safeMaxTokens * TOKEN_TO_CHAR_RATIO;
28
+ return truncateText(value || "", maxChars);
29
+ };
30
+ export const truncateHeadByTokens = (value, maxTokens) => {
31
+ const safeMaxTokens = Math.max(0, maxTokens);
32
+ const maxChars = safeMaxTokens * TOKEN_TO_CHAR_RATIO;
33
+ if (value.length <= maxChars)
34
+ return value;
35
+ const keepChars = Math.max(0, maxChars - 31);
36
+ return `\n\n[truncated: prompt budget exceeded]\n${value.slice(Math.max(0, value.length - keepChars))}`;
37
+ };
38
+ export const truncateMiddleByTokens = (value, maxTokens) => {
39
+ const safeMaxTokens = Math.max(0, maxTokens);
40
+ const maxChars = safeMaxTokens * TOKEN_TO_CHAR_RATIO;
41
+ return truncateMiddle(value || "", maxChars);
42
+ };
43
+ export const budgetConversationHistory = (history, maxMessages, maxCharsPerMessage) => {
44
+ if (!history || history.length === 0)
45
+ return [];
46
+ return history
47
+ .slice(-maxMessages)
48
+ .map((message) => ({
49
+ ...message,
50
+ content: truncateText(message.content || "", maxCharsPerMessage),
51
+ }));
52
+ };
53
+ export const budgetConversationHistoryByTokens = (history, maxMessages, maxTokensPerMessage, maxTotalTokens) => {
54
+ if (!history || history.length === 0)
55
+ return [];
56
+ const safeMaxMessages = Math.max(1, maxMessages);
57
+ const safeMaxTokensPerMessage = Math.max(0, maxTokensPerMessage);
58
+ const safeMaxTotalTokens = Math.max(0, maxTotalTokens);
59
+ const windowed = history.slice(-safeMaxMessages);
60
+ const droppedByWindow = history.slice(0, Math.max(0, history.length - windowed.length));
61
+ const selected = [];
62
+ let remainingTokens = safeMaxTotalTokens;
63
+ let selectedStartIndex = windowed.length;
64
+ for (let i = windowed.length - 1; i >= 0; i -= 1) {
65
+ if (remainingTokens <= MESSAGE_OVERHEAD_TOKENS)
66
+ break;
67
+ const message = windowed[i];
68
+ const content = typeof message.content === "string" ? message.content : "";
69
+ if (!content)
70
+ continue;
71
+ const isToolResultsContinuation = message.continuationType === "tool_results";
72
+ const perMessageBudget = Math.min(isToolResultsContinuation
73
+ ? Math.max(safeMaxTokensPerMessage, safeMaxTotalTokens)
74
+ : safeMaxTokensPerMessage, remainingTokens - MESSAGE_OVERHEAD_TOKENS);
75
+ if (perMessageBudget <= 0)
76
+ break;
77
+ const truncatedContent = truncateTextByTokens(content, perMessageBudget);
78
+ const estimatedTokens = estimateTokensFromChars(truncatedContent) + MESSAGE_OVERHEAD_TOKENS;
79
+ if (estimatedTokens > remainingTokens)
80
+ break;
81
+ selected.unshift({
82
+ ...message,
83
+ content: truncatedContent,
84
+ });
85
+ selectedStartIndex = i;
86
+ remainingTokens -= estimatedTokens;
87
+ }
88
+ const omittedMessages = [...droppedByWindow, ...windowed.slice(0, selectedStartIndex)];
89
+ if (omittedMessages.length === 0) {
90
+ if (selected.length > 0) {
91
+ return selected;
92
+ }
93
+ const fallbackMessage = windowed[windowed.length - 1];
94
+ const fallbackBudget = Math.min(safeMaxTokensPerMessage, safeMaxTotalTokens - MESSAGE_OVERHEAD_TOKENS);
95
+ if (fallbackBudget <= 0) {
96
+ return [];
97
+ }
98
+ return [
99
+ {
100
+ ...fallbackMessage,
101
+ content: truncateTextByTokens(fallbackMessage.content || "", fallbackBudget),
102
+ },
103
+ ];
104
+ }
105
+ const summaryBudget = Math.min(Math.max(1, Math.floor(safeMaxTotalTokens * 0.25)), Math.max(0, safeMaxTotalTokens - MESSAGE_OVERHEAD_TOKENS));
106
+ if (summaryBudget <= 0) {
107
+ return selected;
108
+ }
109
+ const summaryText = truncateTextByTokens(buildConversationSummary(omittedMessages), summaryBudget);
110
+ const summaryTokenUsage = estimateTokensFromChars(summaryText) + MESSAGE_OVERHEAD_TOKENS;
111
+ const selectedWithTokens = selected.map((message) => ({
112
+ message,
113
+ tokenUsage: estimateTokensFromChars(typeof message.content === "string" ? message.content : "") +
114
+ MESSAGE_OVERHEAD_TOKENS,
115
+ }));
116
+ let selectedTokenUsage = selectedWithTokens.reduce((total, entry) => total + entry.tokenUsage, 0);
117
+ while (selectedTokenUsage + summaryTokenUsage > safeMaxTotalTokens && selectedWithTokens.length > 0) {
118
+ const removed = selectedWithTokens.shift();
119
+ if (!removed)
120
+ break;
121
+ selectedTokenUsage -= removed.tokenUsage;
122
+ }
123
+ const summarizedConversation = {
124
+ role: "system",
125
+ content: summaryText,
126
+ };
127
+ if (selectedWithTokens.length > 0) {
128
+ return [summarizedConversation, ...selectedWithTokens.map((entry) => entry.message)];
129
+ }
130
+ return [
131
+ summarizedConversation,
132
+ ];
133
+ };
134
+ const formatRoleLabel = (role) => {
135
+ if (role === "user")
136
+ return "User";
137
+ if (role === "assistant")
138
+ return "Assistant";
139
+ if (role === "tool")
140
+ return "Tool";
141
+ if (role === "system")
142
+ return "System";
143
+ return "Assistant";
144
+ };
145
+ const buildConversationSummary = (history) => {
146
+ if (history.length === 0)
147
+ return "";
148
+ const recentEntries = history.slice(-SUMMARY_MESSAGE_LIMIT);
149
+ const parts = recentEntries.map((message) => {
150
+ const role = formatRoleLabel(message.role);
151
+ const content = truncateText((message.content || "").replace(/\s+/g, " ").trim(), SUMMARY_MESSAGE_CHAR_LIMIT).trim();
152
+ return content ? `${role}: ${content}` : role;
153
+ });
154
+ return `Conversation state: ${parts.join("; ")}`;
155
+ };
156
+ const formatConversationTranscript = (history) => history
157
+ .map((message) => `**${formatRoleLabel(message.role)}:** ${message.content || ""}`)
158
+ .join("\n\n");
159
+ export const buildPromptWithinTokenBudget = (options) => {
160
+ const { effectiveMessage, conversationHistory, contextInfo, maxPromptTokens, maxConversationMessages, maxConversationMessageTokens, continuationInstruction, } = options;
161
+ const safeMaxPromptTokens = Math.max(512, maxPromptTokens);
162
+ const safeContinuation = typeof continuationInstruction === "string"
163
+ ? continuationInstruction.trim()
164
+ : "";
165
+ const continuationTokens = safeContinuation
166
+ ? estimateTokensFromChars(`\n\n${safeContinuation}`)
167
+ : 0;
168
+ const baseBudget = Math.max(256, safeMaxPromptTokens - continuationTokens);
169
+ const hasHistory = Array.isArray(conversationHistory) && conversationHistory.length > 0;
170
+ const historyBudget = hasHistory ? Math.max(MIN_SECTION_TOKENS, Math.floor(baseBudget * 0.6)) : 0;
171
+ const contextBudget = hasHistory
172
+ ? Math.max(MIN_SECTION_TOKENS, baseBudget - historyBudget)
173
+ : Math.max(MIN_SECTION_TOKENS, Math.floor(baseBudget * 0.7));
174
+ const budgetedConversationHistory = hasHistory
175
+ ? budgetConversationHistoryByTokens(conversationHistory, maxConversationMessages, maxConversationMessageTokens, historyBudget)
176
+ : [];
177
+ const budgetedContextInfo = truncateMiddleByTokens(contextInfo || "", contextBudget);
178
+ let prompt = "";
179
+ if (budgetedConversationHistory.length > 0) {
180
+ const transcript = formatConversationTranscript(budgetedConversationHistory);
181
+ prompt = `${transcript}\n\n${budgetedContextInfo}`;
182
+ }
183
+ else {
184
+ const messageBudget = Math.max(MIN_SECTION_TOKENS, baseBudget - contextBudget);
185
+ const budgetedMessage = truncateTextByTokens(effectiveMessage || "", messageBudget);
186
+ prompt = `${budgetedMessage}\n\n${budgetedContextInfo}`;
187
+ }
188
+ if (safeContinuation.length > 0) {
189
+ const promptWithContinuation = `${prompt}\n\n${safeContinuation}`;
190
+ if (estimateTokensFromChars(promptWithContinuation) <= safeMaxPromptTokens) {
191
+ prompt = promptWithContinuation;
192
+ }
193
+ else {
194
+ const compressedPrompt = truncateMiddleByTokens(prompt, Math.max(MIN_SECTION_TOKENS, safeMaxPromptTokens - continuationTokens));
195
+ prompt = `${compressedPrompt}\n\n${safeContinuation}`;
196
+ }
197
+ }
198
+ const promptEstimatedTokens = estimateTokensFromChars(prompt);
199
+ if (promptEstimatedTokens > safeMaxPromptTokens) {
200
+ const hardLimitedPrompt = budgetedConversationHistory.some((message) => message.continuationType === "tool_results")
201
+ ? truncateHeadByTokens(prompt, safeMaxPromptTokens)
202
+ : truncateMiddleByTokens(prompt, safeMaxPromptTokens);
203
+ return {
204
+ prompt: hardLimitedPrompt,
205
+ budgetedContextInfo,
206
+ budgetedConversationHistory,
207
+ promptEstimatedTokens: estimateTokensFromChars(hardLimitedPrompt),
208
+ };
209
+ }
210
+ return {
211
+ prompt,
212
+ budgetedContextInfo,
213
+ budgetedConversationHistory,
214
+ promptEstimatedTokens,
215
+ };
216
+ };
@@ -0,0 +1,9 @@
1
+ type ImageContextFile = Record<string, any>;
2
+ export type ResolvedImageMessagePart = {
3
+ type: "image";
4
+ image: string;
5
+ mediaType?: string;
6
+ };
7
+ export declare const isLikelyImageFile: (file: ImageContextFile) => boolean;
8
+ export declare const resolveImageMessageParts: (filesInContext: ImageContextFile[], workspacePath: string, isWebWorkspace: boolean, allowOutOfWorkspace?: boolean) => Promise<ResolvedImageMessagePart[]>;
9
+ export {};
@@ -0,0 +1,140 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ const IMAGE_EXT_TO_MIME = {
4
+ png: "image/png",
5
+ jpg: "image/jpeg",
6
+ jpeg: "image/jpeg",
7
+ webp: "image/webp",
8
+ gif: "image/gif",
9
+ bmp: "image/bmp",
10
+ svg: "image/svg+xml",
11
+ avif: "image/avif",
12
+ };
13
+ const MAX_IMAGE_FILE_SIZE_BYTES = 5 * 1024 * 1024;
14
+ const MAX_IMAGES_PER_REQUEST = 1;
15
+ const isPathWithin = (basePath, targetPath) => {
16
+ const relative = path.relative(basePath, targetPath);
17
+ return !(path.isAbsolute(relative) ||
18
+ relative === ".." ||
19
+ relative.startsWith(`..${path.sep}`));
20
+ };
21
+ const resolveImageMediaType = (file) => {
22
+ const rawType = typeof file?.type === "string" ? file.type.trim().toLowerCase() : "";
23
+ if (rawType.startsWith("image/")) {
24
+ return rawType;
25
+ }
26
+ const ext = typeof file?.name === "string"
27
+ ? file.name.split(".").pop()?.toLowerCase() || ""
28
+ : "";
29
+ return IMAGE_EXT_TO_MIME[ext] || undefined;
30
+ };
31
+ export const isLikelyImageFile = (file) => {
32
+ const mediaType = resolveImageMediaType(file);
33
+ if (mediaType) {
34
+ return true;
35
+ }
36
+ const pathValue = typeof file?.path === "string" ? file.path.toLowerCase() : "";
37
+ return /\.(png|jpe?g|webp|gif|bmp|svg|avif)$/.test(pathValue);
38
+ };
39
+ const toDataUrlFromBuffer = (bytes, mediaType) => `data:${mediaType};base64,${bytes.toString("base64")}`;
40
+ const getErrorMessage = (err) => {
41
+ if (err instanceof Error) {
42
+ return err.message;
43
+ }
44
+ if (typeof err === "string") {
45
+ return err;
46
+ }
47
+ if (err === null || err === undefined) {
48
+ return String(err);
49
+ }
50
+ return String(err);
51
+ };
52
+ export const resolveImageMessageParts = async (filesInContext, workspacePath, isWebWorkspace, allowOutOfWorkspace = false) => {
53
+ const parts = [];
54
+ const resolvedWorkspace = path.resolve(workspacePath);
55
+ const realWorkspace = await fs
56
+ .realpath(resolvedWorkspace)
57
+ .catch(() => resolvedWorkspace);
58
+ let imagesIncluded = 0;
59
+ for (const file of filesInContext) {
60
+ if (!isLikelyImageFile(file)) {
61
+ continue;
62
+ }
63
+ if (imagesIncluded >= MAX_IMAGES_PER_REQUEST) {
64
+ console.warn("Skipping image: reached maximum images per request limit");
65
+ continue;
66
+ }
67
+ const mediaType = resolveImageMediaType(file) || "image/png";
68
+ const encodedFromClient = typeof file?.imageDataUrl === "string" ? file.imageDataUrl.trim() : "";
69
+ if (encodedFromClient.startsWith("data:image/")) {
70
+ parts.push({
71
+ type: "image",
72
+ image: encodedFromClient,
73
+ mediaType,
74
+ });
75
+ imagesIncluded++;
76
+ continue;
77
+ }
78
+ const directUrlCandidates = [file?.imageUrl, file?.previewUrl, file?.path]
79
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
80
+ .map((value) => value.trim());
81
+ const httpsUrl = directUrlCandidates.find((value) => /^https?:\/\//i.test(value));
82
+ if (httpsUrl) {
83
+ parts.push({
84
+ type: "image",
85
+ image: httpsUrl,
86
+ mediaType,
87
+ });
88
+ imagesIncluded++;
89
+ continue;
90
+ }
91
+ if (isWebWorkspace) {
92
+ continue;
93
+ }
94
+ const filePathValue = typeof file?.path === "string" && file.path.trim().length > 0
95
+ ? file.path.trim()
96
+ : "";
97
+ if (!filePathValue || /^blob:/i.test(filePathValue)) {
98
+ continue;
99
+ }
100
+ const absolutePath = path.isAbsolute(filePathValue)
101
+ ? path.resolve(filePathValue)
102
+ : path.resolve(path.join(resolvedWorkspace, filePathValue));
103
+ if (!path.isAbsolute(filePathValue)) {
104
+ if (!isPathWithin(resolvedWorkspace, absolutePath)) {
105
+ console.warn("Skipping path-traversing relative image path:", filePathValue);
106
+ continue;
107
+ }
108
+ }
109
+ try {
110
+ let readablePath = absolutePath;
111
+ if (!allowOutOfWorkspace) {
112
+ const realAbsolutePath = await fs.realpath(absolutePath);
113
+ if (!isPathWithin(realWorkspace, realAbsolutePath)) {
114
+ console.warn("Skipping out-of-workspace image path:", filePathValue);
115
+ continue;
116
+ }
117
+ readablePath = realAbsolutePath;
118
+ }
119
+ const stats = await fs.stat(readablePath);
120
+ if (!stats.isFile()) {
121
+ continue;
122
+ }
123
+ if (stats.size > MAX_IMAGE_FILE_SIZE_BYTES) {
124
+ console.warn("Skipping oversized image:", filePathValue, `${stats.size} bytes exceeds limit of ${MAX_IMAGE_FILE_SIZE_BYTES} bytes`);
125
+ continue;
126
+ }
127
+ const bytes = await fs.readFile(readablePath);
128
+ parts.push({
129
+ type: "image",
130
+ image: toDataUrlFromBuffer(bytes, mediaType),
131
+ mediaType,
132
+ });
133
+ imagesIncluded++;
134
+ }
135
+ catch (err) {
136
+ console.warn("Failed to read image:", filePathValue, getErrorMessage(err));
137
+ }
138
+ }
139
+ return parts;
140
+ };
@@ -0,0 +1,31 @@
1
+ export type ConversationMessageLike = {
2
+ role: "user" | "assistant" | "system" | "tool";
3
+ content: string;
4
+ };
5
+ export type ContextSummaryItemLike = {
6
+ preview: string;
7
+ };
8
+ export type ParsedSlashCommand = {
9
+ name: string;
10
+ shellCommand: string;
11
+ };
12
+ export type SlashCommandDescriptor = {
13
+ name: string;
14
+ description: string;
15
+ executeImmediately: boolean;
16
+ kind: "builtin" | "shell";
17
+ aliases: string[];
18
+ };
19
+ export declare const getSlashCommandDescriptors: () => SlashCommandDescriptor[];
20
+ export declare const parseSlashCommandRequest: (input?: string) => ParsedSlashCommand | null;
21
+ export declare const isSlashCommandsFeatureEnabled: () => boolean;
22
+ export declare const executeRegisteredSlashCommand: ({ parsedCommand, conversationHistory, contextSummary, }: {
23
+ parsedCommand: ParsedSlashCommand;
24
+ conversationHistory?: ConversationMessageLike[];
25
+ contextSummary?: ContextSummaryItemLike[];
26
+ }) => {
27
+ success: boolean;
28
+ response: string;
29
+ toolCalls: any[];
30
+ executedToolResults: any[];
31
+ };