@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,129 @@
1
+ const REGISTERED_SLASH_COMMANDS = [
2
+ {
3
+ name: "help",
4
+ description: "Show available slash commands.",
5
+ executeImmediately: true,
6
+ kind: "builtin",
7
+ },
8
+ {
9
+ name: "compact",
10
+ description: "Summarize recent conversation context into a compact digest.",
11
+ executeImmediately: true,
12
+ kind: "builtin",
13
+ },
14
+ {
15
+ name: "run",
16
+ description: "Run a shell command. Example: /run npm test",
17
+ executeImmediately: false,
18
+ kind: "shell",
19
+ aliases: ["sh", "shell"],
20
+ },
21
+ ];
22
+ const getRegisteredSlashCommand = (name) => REGISTERED_SLASH_COMMANDS.find((command) => command.name === name ||
23
+ Boolean(command.aliases?.includes(name)));
24
+ export const getSlashCommandDescriptors = () => REGISTERED_SLASH_COMMANDS.map((command) => ({
25
+ name: command.name,
26
+ description: command.description,
27
+ executeImmediately: command.executeImmediately,
28
+ kind: command.kind,
29
+ aliases: command.aliases || [],
30
+ }));
31
+ export const parseSlashCommandRequest = (input) => {
32
+ if (!input)
33
+ return null;
34
+ const trimmed = input.trim();
35
+ if (!trimmed.startsWith("/"))
36
+ return null;
37
+ if (trimmed.startsWith("//"))
38
+ return null;
39
+ const rawCommand = trimmed.slice(1).trim();
40
+ if (!rawCommand)
41
+ return null;
42
+ const [commandName, ...argParts] = rawCommand.split(/\s+/);
43
+ const name = (commandName || "").trim().toLowerCase();
44
+ const args = argParts.join(" ").trim();
45
+ if (!name)
46
+ return null;
47
+ const resolvedCommand = getRegisteredSlashCommand(name);
48
+ const shellCommand = resolvedCommand?.kind === "shell" ? args : rawCommand;
49
+ return {
50
+ name,
51
+ shellCommand,
52
+ };
53
+ };
54
+ export const isSlashCommandsFeatureEnabled = () => {
55
+ const raw = process.env.IRIS_ENABLE_SLASH_COMMANDS;
56
+ return raw === undefined
57
+ ? true
58
+ : !["0", "false", "off", "no"].includes(raw.trim().toLowerCase());
59
+ };
60
+ const formatSlashCommandsHelp = () => {
61
+ const lines = ["Available slash commands:"];
62
+ for (const command of REGISTERED_SLASH_COMMANDS) {
63
+ const aliasSuffix = command.aliases && command.aliases.length > 0
64
+ ? ` (aliases: ${command.aliases.map((alias) => `/${alias}`).join(", ")})`
65
+ : "";
66
+ lines.push(`- /${command.name}${aliasSuffix}: ${command.description}`);
67
+ }
68
+ return lines.join("\n");
69
+ };
70
+ const buildCompactDigest = (conversationHistory, contextSummary) => {
71
+ const recentMessages = (conversationHistory || [])
72
+ .filter((message) => message?.content)
73
+ .slice(-8);
74
+ const recentUserMessages = recentMessages
75
+ .filter((message) => message.role === "user")
76
+ .slice(-3)
77
+ .map((message) => message.content.replace(/\s+/g, " ").trim())
78
+ .filter(Boolean)
79
+ .map((text) => (text.length > 140 ? `${text.slice(0, 137)}...` : text));
80
+ const contextItems = (contextSummary || [])
81
+ .slice(-5)
82
+ .map((item) => item.preview.trim())
83
+ .filter(Boolean)
84
+ .map((text) => (text.length > 120 ? `${text.slice(0, 117)}...` : text));
85
+ const lines = [
86
+ "Conversation compact digest:",
87
+ `- Recent messages: ${recentMessages.length}`,
88
+ `- Context snippets: ${contextItems.length}`,
89
+ ];
90
+ if (recentUserMessages.length > 0) {
91
+ lines.push("- Recent user intents:");
92
+ for (const message of recentUserMessages) {
93
+ lines.push(` - ${message}`);
94
+ }
95
+ }
96
+ if (contextItems.length > 0) {
97
+ lines.push("- Context summary excerpts:");
98
+ for (const item of contextItems) {
99
+ lines.push(` - ${item}`);
100
+ }
101
+ }
102
+ if (recentUserMessages.length === 0 && contextItems.length === 0) {
103
+ lines.push("- No prior conversation context is available yet.");
104
+ }
105
+ return lines.join("\n");
106
+ };
107
+ export const executeRegisteredSlashCommand = ({ parsedCommand, conversationHistory, contextSummary, }) => {
108
+ const command = getRegisteredSlashCommand(parsedCommand.name);
109
+ if (!command || command.kind !== "builtin") {
110
+ return null;
111
+ }
112
+ if (command.name === "help") {
113
+ return {
114
+ success: true,
115
+ response: formatSlashCommandsHelp(),
116
+ toolCalls: [],
117
+ executedToolResults: [],
118
+ };
119
+ }
120
+ if (command.name === "compact") {
121
+ return {
122
+ success: true,
123
+ response: buildCompactDigest(conversationHistory, contextSummary),
124
+ toolCalls: [],
125
+ executedToolResults: [],
126
+ };
127
+ }
128
+ return null;
129
+ };
@@ -0,0 +1,19 @@
1
+ export type TokenUsageSummary = {
2
+ inputTokens?: number;
3
+ outputTokens?: number;
4
+ totalTokens?: number;
5
+ };
6
+ export declare const normalizeTokenUsage: (raw: unknown) => TokenUsageSummary | undefined;
7
+ export declare const mergeTokenUsage: (base: TokenUsageSummary | undefined, incoming: TokenUsageSummary | undefined) => TokenUsageSummary | undefined;
8
+ export declare const extractTokenUsageFromChunkPayload: (payload: unknown) => TokenUsageSummary | undefined;
9
+ export declare const isTokenUsageSourceDebugEnabled: () => boolean;
10
+ export declare const logTokenUsageSource: ({ mode, source, usage, modelId, }: {
11
+ mode: "stream" | "non_stream";
12
+ source: "final" | "stream" | "none";
13
+ usage: TokenUsageSummary | undefined;
14
+ modelId: string;
15
+ }) => void;
16
+ export declare const buildTokenUsageDebug: ({ mode, source, }: {
17
+ mode: "stream" | "non_stream";
18
+ source: "final" | "stream" | "none";
19
+ }) => Record<string, unknown> | undefined;
@@ -0,0 +1,81 @@
1
+ const asFiniteNonNegativeInt = (value) => {
2
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3
+ return undefined;
4
+ }
5
+ if (value < 0) {
6
+ return undefined;
7
+ }
8
+ return Math.round(value);
9
+ };
10
+ export const normalizeTokenUsage = (raw) => {
11
+ if (!raw || typeof raw !== "object") {
12
+ return undefined;
13
+ }
14
+ const record = raw;
15
+ const inputTokens = asFiniteNonNegativeInt(record.inputTokens) ??
16
+ asFiniteNonNegativeInt(record.promptTokens) ??
17
+ asFiniteNonNegativeInt(record.prompt_tokens);
18
+ const outputTokens = asFiniteNonNegativeInt(record.outputTokens) ??
19
+ asFiniteNonNegativeInt(record.completionTokens) ??
20
+ asFiniteNonNegativeInt(record.completion_tokens);
21
+ let totalTokens = asFiniteNonNegativeInt(record.totalTokens) ??
22
+ asFiniteNonNegativeInt(record.total_tokens);
23
+ if (totalTokens === undefined && inputTokens !== undefined && outputTokens !== undefined) {
24
+ totalTokens = inputTokens + outputTokens;
25
+ }
26
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined) {
27
+ return undefined;
28
+ }
29
+ return {
30
+ ...(inputTokens !== undefined ? { inputTokens } : {}),
31
+ ...(outputTokens !== undefined ? { outputTokens } : {}),
32
+ ...(totalTokens !== undefined ? { totalTokens } : {}),
33
+ };
34
+ };
35
+ export const mergeTokenUsage = (base, incoming) => {
36
+ if (!base && !incoming) {
37
+ return undefined;
38
+ }
39
+ return {
40
+ ...(base || {}),
41
+ ...(incoming || {}),
42
+ };
43
+ };
44
+ export const extractTokenUsageFromChunkPayload = (payload) => {
45
+ if (!payload || typeof payload !== "object") {
46
+ return undefined;
47
+ }
48
+ const payloadRecord = payload;
49
+ const nested = normalizeTokenUsage(payloadRecord.usage);
50
+ if (nested) {
51
+ return nested;
52
+ }
53
+ return normalizeTokenUsage(payload);
54
+ };
55
+ export const isTokenUsageSourceDebugEnabled = () => {
56
+ const raw = process.env.IRIS_DEBUG_TOKEN_USAGE_SOURCE;
57
+ if (!raw) {
58
+ return false;
59
+ }
60
+ return ["1", "true", "yes", "on"].includes(raw.trim().toLowerCase());
61
+ };
62
+ export const logTokenUsageSource = ({ mode, source, usage, modelId, }) => {
63
+ if (!isTokenUsageSourceDebugEnabled()) {
64
+ return;
65
+ }
66
+ console.log("📊 Token usage source", {
67
+ mode,
68
+ source,
69
+ modelId,
70
+ usage,
71
+ });
72
+ };
73
+ export const buildTokenUsageDebug = ({ mode, source, }) => {
74
+ if (!isTokenUsageSourceDebugEnabled()) {
75
+ return undefined;
76
+ }
77
+ return {
78
+ mode,
79
+ source,
80
+ };
81
+ };
@@ -0,0 +1,2 @@
1
+ import type { RequestHandler, Router } from "express";
2
+ export declare const registerFileRoutes: (router: Router, requireDesktopAuth: RequestHandler) => void;
@@ -0,0 +1,108 @@
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ export const registerFileRoutes = (router, requireDesktopAuth) => {
4
+ // POST /api/agent/write-file - Write file content (for revert/undo operations)
5
+ router.post("/write-file", requireDesktopAuth, async (req, res) => {
6
+ try {
7
+ const { filePath, content, workspaceRoot } = req.body;
8
+ if (!filePath) {
9
+ return res.status(400).json({ error: "File path is required" });
10
+ }
11
+ if (content === undefined || content === null) {
12
+ return res.status(400).json({ error: "Content is required" });
13
+ }
14
+ const workspacePath = workspaceRoot || process.cwd();
15
+ // Resolve path and guard against traversal outside workspace
16
+ const absolutePath = path.isAbsolute(filePath)
17
+ ? filePath
18
+ : path.join(workspacePath, filePath);
19
+ const resolvedWorkspace = path.resolve(workspacePath);
20
+ const resolvedTarget = path.resolve(absolutePath);
21
+ if (!resolvedTarget.startsWith(resolvedWorkspace + path.sep) &&
22
+ resolvedTarget !== resolvedWorkspace) {
23
+ return res.status(400).json({ error: "Path outside workspace" });
24
+ }
25
+ await fs.writeFile(resolvedTarget, content, "utf8");
26
+ return res.json({ success: true, path: filePath });
27
+ }
28
+ catch (error) {
29
+ const err = error;
30
+ console.error("Write file error:", error);
31
+ return res
32
+ .status(500)
33
+ .json({ success: false, error: err.message || "Failed to write file" });
34
+ }
35
+ });
36
+ // POST /api/agent/delete-file - Delete file (for undoing newly created files)
37
+ router.post("/delete-file", requireDesktopAuth, async (req, res) => {
38
+ try {
39
+ const { filePath, workspaceRoot } = req.body;
40
+ if (!filePath) {
41
+ return res.status(400).json({ error: "File path is required" });
42
+ }
43
+ const workspacePath = workspaceRoot || process.cwd();
44
+ // Resolve path and guard against traversal outside workspace
45
+ const absolutePath = path.isAbsolute(filePath)
46
+ ? filePath
47
+ : path.join(workspacePath, filePath);
48
+ const resolvedWorkspace = path.resolve(workspacePath);
49
+ const resolvedTarget = path.resolve(absolutePath);
50
+ if (!resolvedTarget.startsWith(resolvedWorkspace + path.sep) &&
51
+ resolvedTarget !== resolvedWorkspace) {
52
+ return res.status(400).json({ error: "Path outside workspace" });
53
+ }
54
+ try {
55
+ await fs.unlink(resolvedTarget);
56
+ }
57
+ catch (error) {
58
+ const err = error;
59
+ // If already missing, treat as success for idempotent undo behavior.
60
+ if (err.code !== "ENOENT") {
61
+ throw error;
62
+ }
63
+ }
64
+ return res.json({ success: true, path: filePath });
65
+ }
66
+ catch (error) {
67
+ const err = error;
68
+ console.error("Delete file error:", error);
69
+ return res
70
+ .status(500)
71
+ .json({ success: false, error: err.message || "Failed to delete file" });
72
+ }
73
+ });
74
+ // POST /api/agent/read-file - Read file content (for web workspace)
75
+ router.post("/read-file", async (req, res) => {
76
+ try {
77
+ const { filePath, workspaceRoot } = req.body;
78
+ if (!filePath) {
79
+ return res.status(400).json({ error: "File path is required" });
80
+ }
81
+ const workspacePath = workspaceRoot || process.cwd();
82
+ const isWebWorkspace = workspacePath.startsWith("/workspace/");
83
+ if (isWebWorkspace) {
84
+ return res.status(400).json({
85
+ error: "Cannot read files from web workspace. File content must be provided by the client.",
86
+ });
87
+ }
88
+ // Resolve file path relative to workspace
89
+ const absolutePath = path.isAbsolute(filePath)
90
+ ? filePath
91
+ : path.join(workspacePath, filePath);
92
+ const content = await fs.readFile(absolutePath, "utf8");
93
+ return res.json({
94
+ success: true,
95
+ content,
96
+ path: filePath,
97
+ });
98
+ }
99
+ catch (error) {
100
+ const err = error;
101
+ console.error("Read file error:", error);
102
+ return res.status(500).json({
103
+ success: false,
104
+ error: err.message || "Failed to read file",
105
+ });
106
+ }
107
+ });
108
+ };
@@ -0,0 +1,4 @@
1
+ import type { NextFunction, Request, Response, Router } from "express";
2
+ type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void;
3
+ export declare const registerLspDocumentRoutes: (router: Router, requireDesktopAuth: AuthMiddleware) => void;
4
+ export {};
@@ -0,0 +1,84 @@
1
+ import path from "path";
2
+ import { coreLsp } from "../core/library/lsp/coreLsp.js";
3
+ const resolveAbsolutePath = (workspacePath, filePath) => path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
4
+ export const registerLspDocumentRoutes = (router, requireDesktopAuth) => {
5
+ router.post("/lsp/document/open", requireDesktopAuth, async (req, res) => {
6
+ try {
7
+ const { filePath, workspaceRoot, text } = req.body;
8
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
9
+ return res.status(400).json({ success: false, error: "filePath is required" });
10
+ }
11
+ const workspacePath = workspaceRoot || process.cwd();
12
+ const absolutePath = resolveAbsolutePath(workspacePath, filePath);
13
+ coreLsp.setRootPath(workspacePath);
14
+ const opened = await coreLsp.openDocument(absolutePath, typeof text === "string" ? text : undefined);
15
+ return res.json({ success: opened });
16
+ }
17
+ catch (error) {
18
+ const err = error;
19
+ return res
20
+ .status(500)
21
+ .json({ success: false, error: err.message || "Failed to open LSP document" });
22
+ }
23
+ });
24
+ router.post("/lsp/document/change", requireDesktopAuth, async (req, res) => {
25
+ try {
26
+ const { filePath, workspaceRoot, text } = req.body;
27
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
28
+ return res.status(400).json({ success: false, error: "filePath is required" });
29
+ }
30
+ if (typeof text !== "string") {
31
+ return res.status(400).json({ success: false, error: "text is required" });
32
+ }
33
+ const workspacePath = workspaceRoot || process.cwd();
34
+ const absolutePath = resolveAbsolutePath(workspacePath, filePath);
35
+ coreLsp.setRootPath(workspacePath);
36
+ const changed = await coreLsp.changeDocument(absolutePath, text);
37
+ return res.json({ success: changed });
38
+ }
39
+ catch (error) {
40
+ const err = error;
41
+ return res
42
+ .status(500)
43
+ .json({ success: false, error: err.message || "Failed to change LSP document" });
44
+ }
45
+ });
46
+ router.post("/lsp/document/save", requireDesktopAuth, async (req, res) => {
47
+ try {
48
+ const { filePath, workspaceRoot } = req.body;
49
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
50
+ return res.status(400).json({ success: false, error: "filePath is required" });
51
+ }
52
+ const workspacePath = workspaceRoot || process.cwd();
53
+ const absolutePath = resolveAbsolutePath(workspacePath, filePath);
54
+ coreLsp.setRootPath(workspacePath);
55
+ const saved = await coreLsp.saveDocument(absolutePath);
56
+ return res.json({ success: saved });
57
+ }
58
+ catch (error) {
59
+ const err = error;
60
+ return res
61
+ .status(500)
62
+ .json({ success: false, error: err.message || "Failed to save LSP document" });
63
+ }
64
+ });
65
+ router.post("/lsp/document/close", requireDesktopAuth, async (req, res) => {
66
+ try {
67
+ const { filePath, workspaceRoot } = req.body;
68
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
69
+ return res.status(400).json({ success: false, error: "filePath is required" });
70
+ }
71
+ const workspacePath = workspaceRoot || process.cwd();
72
+ const absolutePath = resolveAbsolutePath(workspacePath, filePath);
73
+ coreLsp.setRootPath(workspacePath);
74
+ const closed = await coreLsp.closeDocument(absolutePath);
75
+ return res.json({ success: closed });
76
+ }
77
+ catch (error) {
78
+ const err = error;
79
+ return res
80
+ .status(500)
81
+ .json({ success: false, error: err.message || "Failed to close LSP document" });
82
+ }
83
+ });
84
+ };
@@ -0,0 +1,4 @@
1
+ import type { NextFunction, Request, Response, Router } from "express";
2
+ type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void;
3
+ export declare const registerLspHierarchyRoutes: (router: Router, requireDesktopAuth: AuthMiddleware) => void;
4
+ export {};
@@ -0,0 +1,122 @@
1
+ import path from "path";
2
+ import { coreLsp } from "../core/library/lsp/coreLsp.js";
3
+ const resolveAbsolutePath = (workspacePath, filePath) => path.isAbsolute(filePath) ? filePath : path.join(workspacePath, filePath);
4
+ export const registerLspHierarchyRoutes = (router, requireDesktopAuth) => {
5
+ router.post("/lsp/call-hierarchy/prepare", requireDesktopAuth, async (req, res) => {
6
+ try {
7
+ const { filePath, workspaceRoot, line, character } = req.body;
8
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
9
+ return res.status(400).json({ success: false, error: "filePath is required" });
10
+ }
11
+ if (typeof line !== "number" || typeof character !== "number") {
12
+ return res.status(400).json({ success: false, error: "line and character are required" });
13
+ }
14
+ const workspacePath = workspaceRoot || process.cwd();
15
+ const absolutePath = resolveAbsolutePath(workspacePath, filePath);
16
+ coreLsp.setRootPath(workspacePath);
17
+ const items = await coreLsp.prepareCallHierarchy(absolutePath, line, character);
18
+ return res.json({ success: true, items: Array.isArray(items) ? items : [] });
19
+ }
20
+ catch (error) {
21
+ const err = error;
22
+ return res
23
+ .status(500)
24
+ .json({ success: false, error: err.message || "Failed to prepare call hierarchy" });
25
+ }
26
+ });
27
+ router.post("/lsp/call-hierarchy/incoming", requireDesktopAuth, async (req, res) => {
28
+ try {
29
+ const { workspaceRoot, item } = req.body;
30
+ if (!item || typeof item !== "object") {
31
+ return res.status(400).json({ success: false, error: "item is required" });
32
+ }
33
+ const workspacePath = workspaceRoot || process.cwd();
34
+ coreLsp.setRootPath(workspacePath);
35
+ const calls = await coreLsp.getIncomingCalls(item);
36
+ return res.json({ success: true, calls: Array.isArray(calls) ? calls : [] });
37
+ }
38
+ catch (error) {
39
+ const err = error;
40
+ return res
41
+ .status(500)
42
+ .json({ success: false, error: err.message || "Failed to get incoming calls" });
43
+ }
44
+ });
45
+ router.post("/lsp/call-hierarchy/outgoing", requireDesktopAuth, async (req, res) => {
46
+ try {
47
+ const { workspaceRoot, item } = req.body;
48
+ if (!item || typeof item !== "object") {
49
+ return res.status(400).json({ success: false, error: "item is required" });
50
+ }
51
+ const workspacePath = workspaceRoot || process.cwd();
52
+ coreLsp.setRootPath(workspacePath);
53
+ const calls = await coreLsp.getOutgoingCalls(item);
54
+ return res.json({ success: true, calls: Array.isArray(calls) ? calls : [] });
55
+ }
56
+ catch (error) {
57
+ const err = error;
58
+ return res
59
+ .status(500)
60
+ .json({ success: false, error: err.message || "Failed to get outgoing calls" });
61
+ }
62
+ });
63
+ router.post("/lsp/type-hierarchy/prepare", requireDesktopAuth, async (req, res) => {
64
+ try {
65
+ const { filePath, workspaceRoot, line, character } = req.body;
66
+ if (typeof filePath !== "string" || filePath.trim().length === 0) {
67
+ return res.status(400).json({ success: false, error: "filePath is required" });
68
+ }
69
+ if (typeof line !== "number" || typeof character !== "number") {
70
+ return res.status(400).json({ success: false, error: "line and character are required" });
71
+ }
72
+ const workspacePath = workspaceRoot || process.cwd();
73
+ const absolutePath = resolveAbsolutePath(workspacePath, filePath);
74
+ coreLsp.setRootPath(workspacePath);
75
+ const items = await coreLsp.prepareTypeHierarchy(absolutePath, line, character);
76
+ return res.json({ success: true, items: Array.isArray(items) ? items : [] });
77
+ }
78
+ catch (error) {
79
+ const err = error;
80
+ return res
81
+ .status(500)
82
+ .json({ success: false, error: err.message || "Failed to prepare type hierarchy" });
83
+ }
84
+ });
85
+ router.post("/lsp/type-hierarchy/supertypes", requireDesktopAuth, async (req, res) => {
86
+ try {
87
+ const { workspaceRoot, item } = req.body;
88
+ if (!item || typeof item !== "object") {
89
+ return res.status(400).json({ success: false, error: "item is required" });
90
+ }
91
+ const workspacePath = workspaceRoot || process.cwd();
92
+ coreLsp.setRootPath(workspacePath);
93
+ const items = await coreLsp.getTypeHierarchySupertypes(item);
94
+ return res.json({ success: true, items: Array.isArray(items) ? items : [] });
95
+ }
96
+ catch (error) {
97
+ const err = error;
98
+ return res.status(500).json({
99
+ success: false,
100
+ error: err.message || "Failed to get type hierarchy supertypes",
101
+ });
102
+ }
103
+ });
104
+ router.post("/lsp/type-hierarchy/subtypes", requireDesktopAuth, async (req, res) => {
105
+ try {
106
+ const { workspaceRoot, item } = req.body;
107
+ if (!item || typeof item !== "object") {
108
+ return res.status(400).json({ success: false, error: "item is required" });
109
+ }
110
+ const workspacePath = workspaceRoot || process.cwd();
111
+ coreLsp.setRootPath(workspacePath);
112
+ const items = await coreLsp.getTypeHierarchySubtypes(item);
113
+ return res.json({ success: true, items: Array.isArray(items) ? items : [] });
114
+ }
115
+ catch (error) {
116
+ const err = error;
117
+ return res
118
+ .status(500)
119
+ .json({ success: false, error: err.message || "Failed to get type hierarchy subtypes" });
120
+ }
121
+ });
122
+ };
@@ -0,0 +1,4 @@
1
+ import type { NextFunction, Request, Response, Router } from "express";
2
+ type AuthMiddleware = (req: Request, res: Response, next: NextFunction) => void;
3
+ export declare const registerLspPositionRoutes: (router: Router, requireDesktopAuth: AuthMiddleware) => void;
4
+ export {};