@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,814 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Readable, Writable } from "node:stream";
3
+ import os from "node:os";
4
+ import fs from "node:fs/promises";
5
+ import * as acp from "@agentclientprotocol/sdk";
6
+ import path from "path";
7
+ import { getToolCallSignature, resolveToolExecutionStatus, } from "../core/agent/utils/toolLifecycle.js";
8
+ import { executeCommand } from "../core/agent/tools/executeCommand.js";
9
+ import { getSlashCommandDescriptors, isSlashCommandsFeatureEnabled, } from "../helpers/slashCommands.js";
10
+ import { extractTokenUsageFromChunkPayload, mergeTokenUsage, } from "../helpers/tokenUsage.js";
11
+ const DEFAULT_MAX_STEPS = 50;
12
+ const CHAT_SESSIONS_DIR = path.join(os.homedir(), ".iris", "chat-sessions");
13
+ const isSafeChatSessionId = (sessionId) => /^[A-Za-z0-9._:-]+$/.test(sessionId);
14
+ const getChatSessionPath = (sessionId) => path.join(CHAT_SESSIONS_DIR, `${sessionId}.json`);
15
+ const loadPersistedChatSession = async (sessionId) => {
16
+ if (!isSafeChatSessionId(sessionId))
17
+ return undefined;
18
+ try {
19
+ const raw = await fs.readFile(getChatSessionPath(sessionId), "utf8");
20
+ const parsed = JSON.parse(raw);
21
+ if (!Array.isArray(parsed?.messages))
22
+ return undefined;
23
+ return parsed;
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ };
29
+ const savePersistedChatSession = async (sessionId, messages, existing) => {
30
+ if (!isSafeChatSessionId(sessionId))
31
+ return;
32
+ const timestamp = Date.now();
33
+ const payload = {
34
+ id: sessionId,
35
+ title: existing?.title,
36
+ createdAt: existing?.createdAt ?? timestamp,
37
+ updatedAt: timestamp,
38
+ messages,
39
+ };
40
+ try {
41
+ await fs.mkdir(CHAT_SESSIONS_DIR, { recursive: true });
42
+ await fs.writeFile(getChatSessionPath(sessionId), JSON.stringify(payload, null, 2), "utf8");
43
+ }
44
+ catch {
45
+ // Persistence is best-effort; the in-memory session remains usable.
46
+ }
47
+ };
48
+ const readIrisMeta = (meta, params) => {
49
+ const iris = meta && typeof meta === "object" ? meta.iris : undefined;
50
+ const irisRecord = iris && typeof iris === "object"
51
+ ? iris
52
+ : undefined;
53
+ const metaRecord = meta && typeof meta === "object" ? meta : undefined;
54
+ const paramsRecord = params && typeof params === "object" ? params : undefined;
55
+ const chatSessionId = (typeof irisRecord?.chatSessionId === "string"
56
+ ? irisRecord.chatSessionId
57
+ : undefined) ||
58
+ (typeof metaRecord?.chatSessionId === "string"
59
+ ? metaRecord.chatSessionId
60
+ : undefined) ||
61
+ (typeof paramsRecord?.chatSessionId === "string"
62
+ ? paramsRecord.chatSessionId
63
+ : undefined);
64
+ const rawMaxSteps = irisRecord?.maxSteps ?? metaRecord?.maxSteps ?? paramsRecord?.maxSteps;
65
+ const maxSteps = typeof rawMaxSteps === "number" && Number.isFinite(rawMaxSteps)
66
+ ? Math.max(1, Math.floor(rawMaxSteps))
67
+ : undefined;
68
+ const rawModelId = irisRecord?.modelId ??
69
+ irisRecord?.model ??
70
+ metaRecord?.modelId ??
71
+ metaRecord?.model ??
72
+ paramsRecord?.modelId ??
73
+ paramsRecord?.model;
74
+ const modelId = typeof rawModelId === "string" && rawModelId.trim()
75
+ ? rawModelId.trim()
76
+ : undefined;
77
+ return {
78
+ chatSessionId,
79
+ maxSteps,
80
+ modelId,
81
+ model: modelId,
82
+ };
83
+ };
84
+ export function assertAcpWorkspace(params, boundWorkspaceRoot) {
85
+ const requestedWorkspace = typeof params?.workspaceRoot === "string"
86
+ ? params.workspaceRoot
87
+ : typeof params?.cwd === "string"
88
+ ? params.cwd
89
+ : undefined;
90
+ if (requestedWorkspace &&
91
+ path.resolve(requestedWorkspace) !== path.resolve(boundWorkspaceRoot)) {
92
+ throw new Error(`This ACP process is bound to '${path.resolve(boundWorkspaceRoot)}'. Close it and respawn iris-agent with --workspace '${path.resolve(requestedWorkspace)}' to switch workspaces.`);
93
+ }
94
+ }
95
+ const toPromptText = (prompt) => prompt
96
+ .map((block) => {
97
+ if (block.type === "text")
98
+ return block.text;
99
+ if (block.type === "resource_link") {
100
+ return `[Resource: ${block.name || block.uri}](${block.uri})`;
101
+ }
102
+ if (block.type === "resource" && "text" in block.resource) {
103
+ return block.resource.text;
104
+ }
105
+ return "";
106
+ })
107
+ .filter(Boolean)
108
+ .join("\n\n");
109
+ const toToolKind = (toolName) => {
110
+ const normalized = toolName.toLowerCase();
111
+ if (normalized.includes("read") || normalized.includes("view"))
112
+ return "read";
113
+ if (normalized.includes("write") || normalized.includes("edit"))
114
+ return "edit";
115
+ if (normalized.includes("delete"))
116
+ return "delete";
117
+ if (normalized.includes("move") || normalized.includes("rename"))
118
+ return "move";
119
+ if (normalized.includes("search") || normalized.includes("grep"))
120
+ return "search";
121
+ if (normalized.includes("terminal") || normalized.includes("command"))
122
+ return "execute";
123
+ if (normalized.includes("fetch") || normalized.includes("http"))
124
+ return "fetch";
125
+ return "other";
126
+ };
127
+ const extractText = (value) => {
128
+ if (typeof value === "string")
129
+ return value;
130
+ if (value && typeof value === "object") {
131
+ const record = value;
132
+ if (typeof record.text === "string")
133
+ return record.text;
134
+ if (typeof record.content === "string")
135
+ return record.content;
136
+ }
137
+ return value === undefined ? "" : JSON.stringify(value);
138
+ };
139
+ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
140
+ const boundWorkspaceRoot = workspaceRoot
141
+ ? path.resolve(workspaceRoot)
142
+ : undefined;
143
+ const sessions = new Map();
144
+ const agentCache = new Map();
145
+ const resolveAgent = async (modelId, sessionCwd) => {
146
+ if (typeof agentProvider !== "function") {
147
+ return agentProvider;
148
+ }
149
+ const targetWorkspace = sessionCwd || boundWorkspaceRoot;
150
+ const cacheKey = `${modelId || "default"}::${targetWorkspace || ""}`;
151
+ let cached = agentCache.get(cacheKey);
152
+ if (!cached) {
153
+ cached = agentProvider(modelId, targetWorkspace);
154
+ agentCache.set(cacheKey, cached);
155
+ }
156
+ return Promise.resolve(cached);
157
+ };
158
+ const cancelActiveTurn = async (activeTurn) => {
159
+ if (!activeTurn)
160
+ return;
161
+ activeTurn.abortController.abort();
162
+ await activeTurn.cancelStream?.().catch(() => undefined);
163
+ };
164
+ const cancelSessionTurn = async (sessionId) => {
165
+ await cancelActiveTurn(sessions.get(sessionId)?.activeTurn);
166
+ };
167
+ const notifyAvailableCommands = async (ctx, sessionId) => {
168
+ if (!isSlashCommandsFeatureEnabled())
169
+ return;
170
+ const descriptors = getSlashCommandDescriptors();
171
+ if (!descriptors.length)
172
+ return;
173
+ const availableCommands = descriptors.map((descriptor) => ({
174
+ name: descriptor.name,
175
+ description: descriptor.description,
176
+ }));
177
+ await ctx.client
178
+ .notify(acp.methods.client.session.update, {
179
+ sessionId,
180
+ update: {
181
+ sessionUpdate: "available_commands_update",
182
+ availableCommands,
183
+ },
184
+ })
185
+ .catch(() => undefined);
186
+ };
187
+ return acp
188
+ .agent({ name: "iris-agent" })
189
+ .onRequest(acp.methods.agent.initialize, async () => ({
190
+ protocolVersion: acp.PROTOCOL_VERSION,
191
+ agentCapabilities: {
192
+ loadSession: true,
193
+ sessionCapabilities: { close: {} },
194
+ },
195
+ agentInfo: {
196
+ name: "iris-agent",
197
+ version: "0.1.0",
198
+ },
199
+ }))
200
+ .onRequest(acp.methods.agent.session.new, async (ctx) => {
201
+ if (boundWorkspaceRoot) {
202
+ assertAcpWorkspace({ cwd: ctx.params.cwd }, boundWorkspaceRoot);
203
+ }
204
+ const irisMeta = readIrisMeta(ctx.params._meta);
205
+ const requestedModel = irisMeta.modelId ||
206
+ (typeof ctx.params.modelId === "string"
207
+ ? ctx.params.modelId
208
+ : undefined);
209
+ const sessionId = irisMeta.chatSessionId && isSafeChatSessionId(irisMeta.chatSessionId)
210
+ ? irisMeta.chatSessionId
211
+ : randomUUID();
212
+ sessions.set(sessionId, {
213
+ cwd: boundWorkspaceRoot || ctx.params.cwd,
214
+ modelId: requestedModel,
215
+ history: [],
216
+ });
217
+ await notifyAvailableCommands(ctx, sessionId);
218
+ return { sessionId };
219
+ })
220
+ .onRequest(acp.methods.agent.session.load, async (ctx) => {
221
+ const sessionId = ctx.params.sessionId;
222
+ if (boundWorkspaceRoot) {
223
+ assertAcpWorkspace({ cwd: ctx.params.cwd }, boundWorkspaceRoot);
224
+ }
225
+ const irisMeta = readIrisMeta(ctx.params._meta);
226
+ const requestedModel = irisMeta.modelId ||
227
+ (typeof ctx.params.modelId === "string"
228
+ ? ctx.params.modelId
229
+ : undefined);
230
+ const persisted = await loadPersistedChatSession(sessionId);
231
+ if (!persisted) {
232
+ throw new Error(`No persisted chat session found for '${sessionId}'`);
233
+ }
234
+ const history = persisted.messages.slice();
235
+ sessions.set(sessionId, {
236
+ cwd: boundWorkspaceRoot || ctx.params.cwd,
237
+ modelId: requestedModel,
238
+ history,
239
+ });
240
+ for (const message of history) {
241
+ await ctx.client.notify(acp.methods.client.session.update, {
242
+ sessionId,
243
+ update: {
244
+ sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
245
+ content: { type: "text", text: message.content },
246
+ },
247
+ });
248
+ }
249
+ await notifyAvailableCommands(ctx, sessionId);
250
+ return {};
251
+ })
252
+ .onRequest(acp.methods.agent.session.setConfigOption, async (ctx) => {
253
+ const sessionId = ctx.params.sessionId;
254
+ const session = sessions.get(sessionId);
255
+ if (!session) {
256
+ throw new Error(`Unknown ACP session '${sessionId}'`);
257
+ }
258
+ const configId = ctx.params.configId;
259
+ const value = ctx.params.value;
260
+ if (configId === "model" || configId === "modelId") {
261
+ if (typeof value === "string" && value.trim()) {
262
+ session.modelId = value.trim();
263
+ }
264
+ else {
265
+ session.modelId = undefined;
266
+ }
267
+ }
268
+ const currentModel = session.modelId || "default";
269
+ const configOptions = [
270
+ {
271
+ id: "model",
272
+ name: "Model",
273
+ type: "select",
274
+ category: "model",
275
+ currentValue: currentModel,
276
+ options: [
277
+ { value: currentModel, name: currentModel },
278
+ ],
279
+ },
280
+ ];
281
+ return { configOptions };
282
+ })
283
+ .onRequest(acp.methods.agent.session.prompt, async (ctx) => {
284
+ const sessionId = ctx.params.sessionId;
285
+ const session = sessions.get(sessionId);
286
+ if (!session) {
287
+ throw new Error(`Unknown ACP session '${sessionId}'`);
288
+ }
289
+ const activeTurn = { abortController: new AbortController() };
290
+ const previousTurn = session.activeTurn;
291
+ session.activeTurn = activeTurn;
292
+ await cancelActiveTurn(previousTurn);
293
+ const promptText = toPromptText(ctx.params.prompt);
294
+ const irisMeta = readIrisMeta(ctx.params._meta);
295
+ const effectiveMaxSteps = irisMeta.maxSteps ?? DEFAULT_MAX_STEPS;
296
+ const turnModelId = irisMeta.modelId ||
297
+ (typeof ctx.params.modelId === "string"
298
+ ? ctx.params.modelId
299
+ : undefined) ||
300
+ session.modelId;
301
+ const runtimeAgent = await resolveAgent(turnModelId, session.cwd);
302
+ let stepsUsed = 0;
303
+ let usage;
304
+ if (promptText) {
305
+ session.history.push({ role: "user", content: promptText });
306
+ }
307
+ const persistTurn = async (assistantText) => {
308
+ if (assistantText) {
309
+ session.history.push({ role: "assistant", content: assistantText });
310
+ }
311
+ await savePersistedChatSession(sessionId, session.history, await loadPersistedChatSession(sessionId));
312
+ };
313
+ const turnSignal = activeTurn.abortController.signal;
314
+ if (sessions.get(sessionId) !== session || turnSignal.aborted) {
315
+ return { stopReason: "cancelled" };
316
+ }
317
+ const abortedMarker = Symbol("aborted");
318
+ const raceWithAbort = async (startWork) => {
319
+ if (turnSignal.aborted) {
320
+ throw new Error("ACP session turn cancelled");
321
+ }
322
+ let onAbort;
323
+ const abortWait = new Promise((resolve) => {
324
+ onAbort = () => resolve(abortedMarker);
325
+ turnSignal.addEventListener("abort", onAbort, { once: true });
326
+ });
327
+ const cleanup = () => {
328
+ if (onAbort) {
329
+ turnSignal.removeEventListener("abort", onAbort);
330
+ onAbort = undefined;
331
+ }
332
+ };
333
+ const work = Promise.resolve().then(() => startWork());
334
+ work.then(cleanup, cleanup);
335
+ const result = await Promise.race([
336
+ work,
337
+ abortWait,
338
+ ]);
339
+ cleanup();
340
+ if (result === abortedMarker) {
341
+ throw new Error("ACP session turn cancelled");
342
+ }
343
+ return result;
344
+ };
345
+ try {
346
+ if (runtimeAgent.stream) {
347
+ const streamResult = await raceWithAbort(async () => runtimeAgent.stream(promptText, {
348
+ workspaceRoot: session.cwd,
349
+ abortSignal: turnSignal,
350
+ maxSteps: effectiveMaxSteps,
351
+ modelId: turnModelId,
352
+ }));
353
+ const reader = streamResult.fullStream.getReader();
354
+ activeTurn.cancelStream = async () => reader.cancel();
355
+ const pendingToolInvocations = new Map();
356
+ const generatedToolCallCounts = new Map();
357
+ const usedProtocolToolCallIds = new Set();
358
+ const runtimeSignaturesByToolCall = new Map();
359
+ const runtimeProtocolIdByOperation = new Map();
360
+ let emittedText = false;
361
+ const suspendedTools = [];
362
+ const normalizeToolArgs = (rawArgs) => {
363
+ if (!rawArgs || typeof rawArgs !== "object" || Array.isArray(rawArgs)) {
364
+ return {};
365
+ }
366
+ return rawArgs;
367
+ };
368
+ const hasArgumentDetails = (toolArgs) => Object.keys(toolArgs).length > 0;
369
+ const buildRuntimeCallKey = (toolName, runtimeToolCallId) => `${toolName}:${runtimeToolCallId}`;
370
+ const buildRuntimeOperationKey = (toolName, runtimeToolCallId, signature) => `${buildRuntimeCallKey(toolName, runtimeToolCallId)}:${signature}`;
371
+ const allocateGeneratedToolCallId = (toolName) => {
372
+ let occurrence = generatedToolCallCounts.get(toolName) || 0;
373
+ while (true) {
374
+ occurrence += 1;
375
+ const candidate = `generated-${toolName}-${occurrence}`;
376
+ if (!usedProtocolToolCallIds.has(candidate)) {
377
+ generatedToolCallCounts.set(toolName, occurrence);
378
+ usedProtocolToolCallIds.add(candidate);
379
+ return candidate;
380
+ }
381
+ }
382
+ };
383
+ const resolveProtocolToolCallId = (toolName, runtimeToolCallId, toolArgs) => {
384
+ if (!runtimeToolCallId) {
385
+ return allocateGeneratedToolCallId(toolName);
386
+ }
387
+ const signature = getToolCallSignature(toolName, toolArgs);
388
+ const runtimeOperationKey = buildRuntimeOperationKey(toolName, runtimeToolCallId, signature);
389
+ const existingProtocolId = runtimeProtocolIdByOperation.get(runtimeOperationKey);
390
+ if (existingProtocolId) {
391
+ usedProtocolToolCallIds.add(existingProtocolId);
392
+ return existingProtocolId;
393
+ }
394
+ const runtimeCallKey = buildRuntimeCallKey(toolName, runtimeToolCallId);
395
+ const signatures = runtimeSignaturesByToolCall.get(runtimeCallKey) || new Set();
396
+ runtimeSignaturesByToolCall.set(runtimeCallKey, signatures);
397
+ const protocolToolCallId = usedProtocolToolCallIds.has(runtimeToolCallId) ||
398
+ (signatures.size > 0 && !signatures.has(signature))
399
+ ? allocateGeneratedToolCallId(toolName)
400
+ : runtimeToolCallId;
401
+ signatures.add(signature);
402
+ runtimeProtocolIdByOperation.set(runtimeOperationKey, protocolToolCallId);
403
+ usedProtocolToolCallIds.add(protocolToolCallId);
404
+ return protocolToolCallId;
405
+ };
406
+ const resolveMappedProtocolIdForOmittedArgs = (toolName, runtimeToolCallId) => {
407
+ const runtimeCallKey = buildRuntimeCallKey(toolName, runtimeToolCallId);
408
+ const signatures = runtimeSignaturesByToolCall.get(runtimeCallKey);
409
+ if (!signatures || signatures.size !== 1) {
410
+ return undefined;
411
+ }
412
+ const [signature] = Array.from(signatures);
413
+ if (!signature) {
414
+ return undefined;
415
+ }
416
+ return runtimeProtocolIdByOperation.get(buildRuntimeOperationKey(toolName, runtimeToolCallId, signature));
417
+ };
418
+ const consumePendingInvocation = (toolName, suppliedToolCallId, rawToolArgs) => {
419
+ const pending = pendingToolInvocations.get(toolName) || [];
420
+ const toolArgs = normalizeToolArgs(rawToolArgs);
421
+ const hasArgs = hasArgumentDetails(toolArgs);
422
+ if (typeof suppliedToolCallId === "string" &&
423
+ suppliedToolCallId.length > 0) {
424
+ if (hasArgs) {
425
+ const signature = getToolCallSignature(toolName, toolArgs);
426
+ const runtimeOperationKey = buildRuntimeOperationKey(toolName, suppliedToolCallId, signature);
427
+ const matchIndex = pending.findIndex((entry) => entry.runtimeToolCallId === suppliedToolCallId &&
428
+ entry.operationKey === runtimeOperationKey);
429
+ if (matchIndex >= 0) {
430
+ const [invocation] = pending.splice(matchIndex, 1);
431
+ return {
432
+ invocation,
433
+ toolCallId: invocation.protocolToolCallId,
434
+ };
435
+ }
436
+ const mappedProtocolId = runtimeProtocolIdByOperation.get(runtimeOperationKey);
437
+ if (mappedProtocolId) {
438
+ return { toolCallId: mappedProtocolId };
439
+ }
440
+ return {
441
+ toolCallId: resolveProtocolToolCallId(toolName, suppliedToolCallId, toolArgs),
442
+ };
443
+ }
444
+ const sameIdEntries = pending.filter((entry) => entry.runtimeToolCallId === suppliedToolCallId);
445
+ if (sameIdEntries.length === 1) {
446
+ const [invocation] = sameIdEntries;
447
+ const matchIndex = pending.findIndex((entry) => entry === invocation);
448
+ if (matchIndex >= 0) {
449
+ pending.splice(matchIndex, 1);
450
+ }
451
+ return {
452
+ invocation,
453
+ toolCallId: invocation.protocolToolCallId,
454
+ };
455
+ }
456
+ const mappedProtocolId = resolveMappedProtocolIdForOmittedArgs(toolName, suppliedToolCallId);
457
+ if (mappedProtocolId) {
458
+ const matchIndex = pending.findIndex((entry) => entry.runtimeToolCallId === suppliedToolCallId &&
459
+ entry.protocolToolCallId === mappedProtocolId);
460
+ if (matchIndex >= 0) {
461
+ const [invocation] = pending.splice(matchIndex, 1);
462
+ return {
463
+ invocation,
464
+ toolCallId: invocation.protocolToolCallId,
465
+ };
466
+ }
467
+ return { toolCallId: mappedProtocolId };
468
+ }
469
+ return {
470
+ toolCallId: resolveProtocolToolCallId(toolName, suppliedToolCallId, toolArgs),
471
+ };
472
+ }
473
+ if (pending.length > 0) {
474
+ const invocation = pending.shift();
475
+ return {
476
+ invocation,
477
+ toolCallId: invocation.protocolToolCallId,
478
+ };
479
+ }
480
+ return { toolCallId: allocateGeneratedToolCallId(toolName) };
481
+ };
482
+ const finalizePendingToolInvocations = async (reason) => {
483
+ const message = reason === "cancelled"
484
+ ? "Tool call was cancelled before reporting a result."
485
+ : "Tool call did not report a result before the turn ended.";
486
+ for (const invocations of pendingToolInvocations.values()) {
487
+ for (const invocation of invocations) {
488
+ await ctx.client.notify(acp.methods.client.session.update, {
489
+ sessionId: ctx.params.sessionId,
490
+ update: {
491
+ sessionUpdate: "tool_call_update",
492
+ toolCallId: invocation.protocolToolCallId,
493
+ status: "failed",
494
+ content: [
495
+ {
496
+ type: "content",
497
+ content: {
498
+ type: "text",
499
+ text: message,
500
+ },
501
+ },
502
+ ],
503
+ },
504
+ });
505
+ }
506
+ }
507
+ pendingToolInvocations.clear();
508
+ };
509
+ while (true) {
510
+ if (activeTurn.abortController.signal.aborted) {
511
+ await reader.cancel().catch(() => undefined);
512
+ await finalizePendingToolInvocations("cancelled");
513
+ return { stopReason: "cancelled" };
514
+ }
515
+ let value;
516
+ let done = false;
517
+ try {
518
+ ({ value, done } = await raceWithAbort(() => reader.read()));
519
+ }
520
+ catch (error) {
521
+ if (activeTurn.abortController.signal.aborted) {
522
+ await reader.cancel().catch(() => undefined);
523
+ await finalizePendingToolInvocations("cancelled");
524
+ return { stopReason: "cancelled" };
525
+ }
526
+ throw error;
527
+ }
528
+ if (activeTurn.abortController.signal.aborted) {
529
+ await reader.cancel().catch(() => undefined);
530
+ await finalizePendingToolInvocations("cancelled");
531
+ return { stopReason: "cancelled" };
532
+ }
533
+ if (done)
534
+ break;
535
+ if (!value?.type)
536
+ continue;
537
+ if (value.type === "text-delta" || value.type === "reasoning-delta") {
538
+ const text = String(value.payload?.text || "");
539
+ const chunkUsage = extractTokenUsageFromChunkPayload(value.payload);
540
+ if (chunkUsage) {
541
+ usage = mergeTokenUsage(usage, chunkUsage);
542
+ }
543
+ if (!text)
544
+ continue;
545
+ if (value.type === "text-delta")
546
+ emittedText = true;
547
+ await ctx.client.notify(acp.methods.client.session.update, {
548
+ sessionId: ctx.params.sessionId,
549
+ update: {
550
+ sessionUpdate: value.type === "reasoning-delta"
551
+ ? "agent_thought_chunk"
552
+ : "agent_message_chunk",
553
+ content: { type: "text", text },
554
+ },
555
+ });
556
+ continue;
557
+ }
558
+ if (value.type === "tool-call") {
559
+ stepsUsed += 1;
560
+ const toolName = String(value.payload?.toolName || "tool");
561
+ const toolArgs = normalizeToolArgs(value.payload?.args);
562
+ const suppliedToolCallId = value.payload?.toolCallId;
563
+ const runtimeToolCallId = typeof suppliedToolCallId === "string" &&
564
+ suppliedToolCallId.length > 0
565
+ ? suppliedToolCallId
566
+ : undefined;
567
+ const toolCallId = resolveProtocolToolCallId(toolName, runtimeToolCallId, toolArgs);
568
+ const pending = pendingToolInvocations.get(toolName) || [];
569
+ const signature = getToolCallSignature(toolName, toolArgs);
570
+ pending.push({
571
+ protocolToolCallId: toolCallId,
572
+ runtimeToolCallId,
573
+ operationKey: runtimeToolCallId
574
+ ? buildRuntimeOperationKey(toolName, runtimeToolCallId, signature)
575
+ : `${toolCallId}:${signature}`,
576
+ });
577
+ pendingToolInvocations.set(toolName, pending);
578
+ await ctx.client.notify(acp.methods.client.session.update, {
579
+ sessionId: ctx.params.sessionId,
580
+ update: {
581
+ sessionUpdate: "tool_call",
582
+ toolCallId,
583
+ title: toolName,
584
+ name: toolName,
585
+ kind: toToolKind(toolName),
586
+ status: "in_progress",
587
+ rawInput: value.payload?.args,
588
+ },
589
+ });
590
+ continue;
591
+ }
592
+ if (value.type === "tool-result") {
593
+ const toolName = String(value.payload?.toolName || "tool");
594
+ const suppliedToolCallId = value.payload?.toolCallId;
595
+ const consumed = consumePendingInvocation(toolName, suppliedToolCallId, value.payload?.args);
596
+ const toolCallId = consumed.toolCallId;
597
+ const hasMatchingCall = Boolean(consumed.invocation);
598
+ if (!hasMatchingCall) {
599
+ await ctx.client.notify(acp.methods.client.session.update, {
600
+ sessionId: ctx.params.sessionId,
601
+ update: {
602
+ sessionUpdate: "tool_call",
603
+ toolCallId,
604
+ title: toolName,
605
+ name: toolName,
606
+ kind: toToolKind(toolName),
607
+ status: "in_progress",
608
+ },
609
+ });
610
+ }
611
+ let output = value.payload?.result ??
612
+ value.payload?.output ??
613
+ value.payload?.content ??
614
+ value.payload?.data;
615
+ if (resolveToolExecutionStatus(output) === "pending_confirmation") {
616
+ const permissionResponse = await ctx.client.request(acp.methods.client.session.requestPermission, {
617
+ sessionId: ctx.params.sessionId,
618
+ toolCall: {
619
+ toolCallId,
620
+ title: toolName,
621
+ kind: toToolKind(toolName),
622
+ status: "pending",
623
+ rawInput: value.payload?.args,
624
+ },
625
+ options: [
626
+ { optionId: "allow_once", name: "Allow", kind: "allow_once" },
627
+ { optionId: "reject_once", name: "Reject", kind: "reject_once" },
628
+ ],
629
+ });
630
+ const outcome = permissionResponse?.outcome;
631
+ const approved = outcome?.outcome === "selected" &&
632
+ outcome.optionId === "allow_once";
633
+ if (approved) {
634
+ const toolArgs = normalizeToolArgs(value.payload?.args);
635
+ try {
636
+ output = await executeCommand({
637
+ command: typeof toolArgs.command === "string"
638
+ ? toolArgs.command
639
+ : String(toolArgs.command || ""),
640
+ cwd: session.cwd,
641
+ workspaceRoot: session.cwd,
642
+ skipConfirmation: true,
643
+ ...toolArgs,
644
+ });
645
+ }
646
+ catch (error) {
647
+ output = {
648
+ status: "failed",
649
+ error: error instanceof Error ? error.message : String(error),
650
+ };
651
+ }
652
+ }
653
+ else {
654
+ output = {
655
+ status: "failed",
656
+ error: "Tool call was rejected by the user.",
657
+ };
658
+ }
659
+ }
660
+ await ctx.client.notify(acp.methods.client.session.update, {
661
+ sessionId: ctx.params.sessionId,
662
+ update: {
663
+ sessionUpdate: "tool_call_update",
664
+ toolCallId,
665
+ status: resolveToolExecutionStatus(output) === "failed"
666
+ ? "failed"
667
+ : "completed",
668
+ rawOutput: output,
669
+ content: [
670
+ {
671
+ type: "content",
672
+ content: { type: "text", text: extractText(output) },
673
+ },
674
+ ],
675
+ },
676
+ });
677
+ continue;
678
+ }
679
+ if (value.type === "tool-suspended" || value.type === "tool_suspended") {
680
+ const toolName = String(value.payload?.toolName || "tool");
681
+ const suppliedToolCallId = value.payload?.toolCallId;
682
+ const consumed = consumePendingInvocation(toolName, suppliedToolCallId, undefined);
683
+ const toolCallId = consumed.toolCallId;
684
+ const suspendPayload = value.payload?.suspendPayload &&
685
+ typeof value.payload.suspendPayload === "object"
686
+ ? value.payload.suspendPayload
687
+ : undefined;
688
+ const question = extractText(suspendPayload) || "Waiting for input to continue.";
689
+ await ctx.client.notify(acp.methods.client.session.update, {
690
+ sessionId: ctx.params.sessionId,
691
+ update: {
692
+ sessionUpdate: "tool_call_update",
693
+ toolCallId,
694
+ status: "in_progress",
695
+ rawOutput: suspendPayload,
696
+ content: [
697
+ {
698
+ type: "content",
699
+ content: { type: "text", text: question },
700
+ },
701
+ ],
702
+ },
703
+ });
704
+ await ctx.client.notify(acp.methods.client.session.update, {
705
+ sessionId: ctx.params.sessionId,
706
+ update: {
707
+ sessionUpdate: "agent_message_chunk",
708
+ content: { type: "text", text: question },
709
+ },
710
+ });
711
+ suspendedTools.push({ toolName, toolCallId, suspendPayload });
712
+ continue;
713
+ }
714
+ }
715
+ await finalizePendingToolInvocations("incomplete");
716
+ const finalText = await raceWithAbort(() => streamResult.text);
717
+ if (activeTurn.abortController.signal.aborted) {
718
+ return { stopReason: "cancelled" };
719
+ }
720
+ if (!emittedText && finalText) {
721
+ await ctx.client.notify(acp.methods.client.session.update, {
722
+ sessionId: ctx.params.sessionId,
723
+ update: {
724
+ sessionUpdate: "agent_message_chunk",
725
+ content: { type: "text", text: finalText },
726
+ },
727
+ });
728
+ }
729
+ if (activeTurn.abortController.signal.aborted) {
730
+ return { stopReason: "cancelled" };
731
+ }
732
+ await persistTurn(finalText || "");
733
+ const resolvedUsage = usage &&
734
+ typeof usage.totalTokens === "number" &&
735
+ typeof usage.inputTokens === "number" &&
736
+ typeof usage.outputTokens === "number"
737
+ ? {
738
+ totalTokens: usage.totalTokens,
739
+ inputTokens: usage.inputTokens,
740
+ outputTokens: usage.outputTokens,
741
+ }
742
+ : undefined;
743
+ const stopReason = !finalText && stepsUsed >= effectiveMaxSteps
744
+ ? "max_turn_requests"
745
+ : "end_turn";
746
+ return {
747
+ stopReason,
748
+ ...(resolvedUsage ? { usage: resolvedUsage } : {}),
749
+ ...(suspendedTools.length
750
+ ? { _meta: { iris: { suspendedTools } } }
751
+ : {}),
752
+ };
753
+ }
754
+ const generated = await raceWithAbort(async () => runtimeAgent.generate
755
+ ? runtimeAgent.generate(promptText, {
756
+ workspaceRoot: session.cwd,
757
+ abortSignal: turnSignal,
758
+ maxSteps: effectiveMaxSteps,
759
+ modelId: turnModelId,
760
+ })
761
+ : runtimeAgent.chat
762
+ ? runtimeAgent.chat({
763
+ messages: [{ role: "user", content: promptText }],
764
+ signal: turnSignal,
765
+ abortSignal: turnSignal,
766
+ })
767
+ : Promise.resolve(undefined));
768
+ if (activeTurn.abortController.signal.aborted) {
769
+ return { stopReason: "cancelled" };
770
+ }
771
+ const responseText = extractText(generated);
772
+ if (responseText) {
773
+ if (activeTurn.abortController.signal.aborted) {
774
+ return { stopReason: "cancelled" };
775
+ }
776
+ await ctx.client.notify(acp.methods.client.session.update, {
777
+ sessionId: ctx.params.sessionId,
778
+ update: {
779
+ sessionUpdate: "agent_message_chunk",
780
+ content: { type: "text", text: responseText },
781
+ },
782
+ });
783
+ }
784
+ await persistTurn(responseText || "");
785
+ return { stopReason: "end_turn" };
786
+ }
787
+ catch (error) {
788
+ if (activeTurn.abortController.signal.aborted) {
789
+ return { stopReason: "cancelled" };
790
+ }
791
+ throw error;
792
+ }
793
+ finally {
794
+ if (session.activeTurn === activeTurn) {
795
+ session.activeTurn = undefined;
796
+ }
797
+ }
798
+ })
799
+ .onRequest(acp.methods.agent.session.close, async (ctx) => {
800
+ await cancelActiveTurn(sessions.get(ctx.params.sessionId)?.activeTurn);
801
+ sessions.delete(ctx.params.sessionId);
802
+ return {};
803
+ })
804
+ .onNotification(acp.methods.agent.session.cancel, async (ctx) => {
805
+ await cancelSessionTurn(ctx.params.sessionId);
806
+ });
807
+ };
808
+ export async function startAcpServer(runtimeAgentOrFactory, workspaceRoot) {
809
+ const output = Writable.toWeb(process.stdout);
810
+ const input = Readable.toWeb(process.stdin);
811
+ const connection = createAcpAgentApp(runtimeAgentOrFactory, workspaceRoot).connect(acp.ndJsonStream(output, input));
812
+ console.error("ACP agent ready: iris-agent@0.1.0 (stdio)");
813
+ await connection.closed;
814
+ }