@waishnav/devspace 1.0.2 → 1.0.4

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 (115) hide show
  1. package/README.md +43 -7
  2. package/dist/apply-patch.js +402 -0
  3. package/dist/cli.js +256 -4
  4. package/dist/config.js +17 -22
  5. package/dist/db/migrations.js +34 -0
  6. package/dist/db/schema.js +19 -0
  7. package/dist/local-agent-adapters.js +653 -0
  8. package/dist/local-agent-availability.js +119 -0
  9. package/dist/local-agent-path.js +24 -0
  10. package/dist/local-agent-profiles.js +135 -0
  11. package/dist/local-agent-runtime.js +48 -0
  12. package/dist/local-agent-store.js +145 -0
  13. package/dist/local-agent-targets.js +80 -0
  14. package/dist/process-platform.js +49 -0
  15. package/dist/process-sessions.js +337 -0
  16. package/dist/roots.js +5 -2
  17. package/dist/server.js +521 -243
  18. package/dist/skills.js +48 -4
  19. package/dist/ui/.vite/manifest.json +281 -281
  20. package/dist/ui/assets/{angular-html-Dj4LysSE.js → angular-html-CKeFzaPY.js} +1 -1
  21. package/dist/ui/assets/{angular-ts-CxuC_FFh.js → angular-ts-0wcM_Zxf.js} +1 -1
  22. package/dist/ui/assets/{apl-RwuvNFFo.js → apl-C7UdkNb2.js} +1 -1
  23. package/dist/ui/assets/{astro-ncoaUbsO.js → astro-DMqju4MJ.js} +1 -1
  24. package/dist/ui/assets/{blade-Dw8-Lzux.js → blade-DHanNgN5.js} +1 -1
  25. package/dist/ui/assets/{c-Dpbbo_f2.js → c-_iPKYPcx.js} +1 -1
  26. package/dist/ui/assets/{cobol-4ddX3THy.js → cobol-CagdLJsE.js} +1 -1
  27. package/dist/ui/assets/{coffee-Bn-a5KqK.js → coffee-CkNGriPi.js} +1 -1
  28. package/dist/ui/assets/{cpp-DoRrlM_T.js → cpp-CqmKOWIQ.js} +1 -1
  29. package/dist/ui/assets/{crystal-DiZfCxn-.js → crystal-DbAbDEO1.js} +1 -1
  30. package/dist/ui/assets/{css-DiQoSjDH.js → css-xwljL6Kw.js} +1 -1
  31. package/dist/ui/assets/{edge-wEhCUoEt.js → edge-CX3sYz15.js} +1 -1
  32. package/dist/ui/assets/{elixir-C3obhm0H.js → elixir-B3qL9hqd.js} +1 -1
  33. package/dist/ui/assets/{elm-DyTBfh1L.js → elm-CH4vrjBp.js} +1 -1
  34. package/dist/ui/assets/{erb-f4TnXRuY.js → erb-CupHqynN.js} +1 -1
  35. package/dist/ui/assets/{git-rebase-vhZbWNfp.js → git-rebase-CDxsU2LC.js} +1 -1
  36. package/dist/ui/assets/{glimmer-js-BG7puL6Y.js → glimmer-js-emkga3g1.js} +1 -1
  37. package/dist/ui/assets/{glimmer-ts-CNCfLLL1.js → glimmer-ts-D8kQDnUw.js} +1 -1
  38. package/dist/ui/assets/{glsl-DhT76AL5.js → glsl-BtfTaPhi.js} +1 -1
  39. package/dist/ui/assets/{graphql-CJX08w8y.js → graphql-DEus8iSO.js} +1 -1
  40. package/dist/ui/assets/{hack-DY4NGCCL.js → hack-DI8qpBqE.js} +1 -1
  41. package/dist/ui/assets/{haml-DwSkmVcG.js → haml-Br1i1gmi.js} +1 -1
  42. package/dist/ui/assets/{handlebars-CQ_1dEmi.js → handlebars-Ce23mu9x.js} +1 -1
  43. package/dist/ui/assets/{heavy-payload-2rGBiitA.js → heavy-payload-BcZ_nGfF.js} +1 -1
  44. package/dist/ui/assets/{html-B_e3Njif.js → html-RXVmfixf.js} +1 -1
  45. package/dist/ui/assets/{html-derivative-_nHZ_Bk-.js → html-derivative-D1aqeobb.js} +1 -1
  46. package/dist/ui/assets/{http-CfcvPIru.js → http-BebG00CE.js} +1 -1
  47. package/dist/ui/assets/{hurl-9vWDQIMO.js → hurl-kHMuvenL.js} +1 -1
  48. package/dist/ui/assets/{java-C4S5r6L5.js → java-DYc2V5RJ.js} +1 -1
  49. package/dist/ui/assets/{javascript-BRwqbMMC.js → javascript-BcfzTzr3.js} +1 -1
  50. package/dist/ui/assets/{jinja-DHxzMMc5.js → jinja-D2RhihUZ.js} +1 -1
  51. package/dist/ui/assets/{jison-B1DqeWXb.js → jison-BuoFEePN.js} +1 -1
  52. package/dist/ui/assets/{json-DcFTkEdd.js → json-BIDkCuJA.js} +1 -1
  53. package/dist/ui/assets/{jsx-DgfmYa9f.js → jsx-DHCL2HA0.js} +1 -1
  54. package/dist/ui/assets/{julia-3M8SJ8iF.js → julia-CvUQXscF.js} +1 -1
  55. package/dist/ui/assets/{just-UyH7_DNz.js → just-etc9R2qw.js} +1 -1
  56. package/dist/ui/assets/{latex-wUbha6EX.js → latex-BB3zRnxC.js} +1 -1
  57. package/dist/ui/assets/{liquid-DqNTB9kN.js → liquid-CIi5Mvpv.js} +1 -1
  58. package/dist/ui/assets/{lua-DrEQ1QTW.js → lua-Cox7XDiW.js} +1 -1
  59. package/dist/ui/assets/{marko-B3GJw9Md.js → marko-ruBIRV0n.js} +1 -1
  60. package/dist/ui/assets/{mdc-BnThk82m.js → mdc-CcbvB9U5.js} +1 -1
  61. package/dist/ui/assets/{nginx-B5IeSsOX.js → nginx-DtWz6ZYE.js} +1 -1
  62. package/dist/ui/assets/{nim-BJraatnS.js → nim-BV5BR4P7.js} +1 -1
  63. package/dist/ui/assets/{perl-BAhrR4gt.js → perl-BvzoSaJ4.js} +1 -1
  64. package/dist/ui/assets/{php-BKRCk_wX.js → php-C-clrAOy.js} +1 -1
  65. package/dist/ui/assets/{pug-B8gmSV7z.js → pug-E07_GXAr.js} +1 -1
  66. package/dist/ui/assets/{qml-DKrxIjHR.js → qml-dqt33dti.js} +1 -1
  67. package/dist/ui/assets/{r-YASBZvTH.js → r-BcVQxsAw.js} +1 -1
  68. package/dist/ui/assets/{razor-P-XFZqh6.js → razor-BW5T_UZ-.js} +1 -1
  69. package/dist/ui/assets/{regexp-CCpiH6nO.js → regexp-Ed_DEsuU.js} +1 -1
  70. package/dist/ui/assets/{review-payload-BLYwg6t8.js → review-payload-Dsmx33pY.js} +1 -1
  71. package/dist/ui/assets/{rst-CFAzckbn.js → rst-BkcGqLs8.js} +1 -1
  72. package/dist/ui/assets/{ruby-Dyp_Rx-S.js → ruby-Sf_X_YID.js} +1 -1
  73. package/dist/ui/assets/{sas-YkWZIXxT.js → sas-COsombsA.js} +1 -1
  74. package/dist/ui/assets/{scss-Cf89idGl.js → scss-K6Ylfa26.js} +1 -1
  75. package/dist/ui/assets/{shellscript-BFMb52q8.js → shellscript-D2EdqkgV.js} +1 -1
  76. package/dist/ui/assets/{shellsession-DuthW-5v.js → shellsession-jRXgXrnI.js} +1 -1
  77. package/dist/ui/assets/{soy-BohVVlJU.js → soy-BGspKQWW.js} +1 -1
  78. package/dist/ui/assets/{sql-CnK9R2Gt.js → sql-C4lAmYnM.js} +1 -1
  79. package/dist/ui/assets/{stata-D-sV4Vbr.js → stata-DQWvRqxp.js} +1 -1
  80. package/dist/ui/assets/{surrealql-ChAy0gHv.js → surrealql-OmKt9XJ_.js} +1 -1
  81. package/dist/ui/assets/{svelte-3HBVqG-H.js → svelte-D0rUmwDI.js} +1 -1
  82. package/dist/ui/assets/{templ-BixlV4Cp.js → templ-B-U0RMc0.js} +1 -1
  83. package/dist/ui/assets/{tex-DWoJKHvD.js → tex-BIHD-btS.js} +1 -1
  84. package/dist/ui/assets/{ts-tags-DLA8eH13.js → ts-tags-CGuDWfPC.js} +1 -1
  85. package/dist/ui/assets/{tsx-BYWa5JUz.js → tsx-B0-h_HlD.js} +1 -1
  86. package/dist/ui/assets/{twig-CnY65vAo.js → twig-BN2qDJWN.js} +1 -1
  87. package/dist/ui/assets/{typescript-ByInUsro.js → typescript-BRnwPzY3.js} +1 -1
  88. package/dist/ui/assets/{useFileDiffInstance-Dg0pWcJV.js → useFileDiffInstance-5ivdkUYO.js} +3 -3
  89. package/dist/ui/assets/{vue-Qngt2rkz.js → vue-DekNU1qt.js} +1 -1
  90. package/dist/ui/assets/{vue-html-DDue9upr.js → vue-html-Bjlv-uvH.js} +1 -1
  91. package/dist/ui/assets/{vue-vine-DBcAKsA6.js → vue-vine-C2Doytjq.js} +1 -1
  92. package/dist/ui/assets/{workspace-app-DGD9R89D.css → workspace-app-DASlX26z.css} +1 -1
  93. package/dist/ui/assets/{workspace-app-DuzpU4Hf.js → workspace-app-DYi7VJam.js} +5 -5
  94. package/dist/ui/assets/{xml-CRLR5jiL.js → xml-pXbxxgI3.js} +1 -1
  95. package/dist/ui/assets/{xsl-CmcwyXbm.js → xsl-BEAlgDI2.js} +1 -1
  96. package/dist/ui/assets/{yaml-Dpe6Eg-k.js → yaml-C1O-To71.js} +1 -1
  97. package/dist/ui/workspace-app.html +2 -2
  98. package/dist/user-config.js +21 -1
  99. package/dist/workspaces.js +3 -0
  100. package/docs/agent-profile-schema.md +165 -0
  101. package/docs/chatgpt-coding-workflow.md +46 -12
  102. package/docs/configuration.md +47 -12
  103. package/docs/gotchas.md +24 -5
  104. package/docs/setup.md +1 -1
  105. package/examples/agents/claude-implementer.md +30 -0
  106. package/examples/agents/codex-explorer.md +27 -0
  107. package/examples/agents/codex-qa-tester.md +29 -0
  108. package/examples/agents/codex-worker.md +25 -0
  109. package/examples/agents/copilot-reviewer.md +25 -0
  110. package/examples/agents/cursor-agent-worker.md +26 -0
  111. package/examples/agents/opencode-explorer.md +27 -0
  112. package/examples/agents/pi-reviewer.md +27 -0
  113. package/package.json +18 -5
  114. package/scripts/fix-node-pty-permissions.mjs +22 -0
  115. package/skills/subagent-delegation/SKILL.md +132 -0
@@ -0,0 +1,653 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { Readable, Writable } from "node:stream";
3
+ import { removeDevspaceNodeModulesBinFromPath } from "./local-agent-path.js";
4
+ import { createCodexSdkLocalAgentRuntime, } from "./local-agent-runtime.js";
5
+ const ACP_COMMANDS = {
6
+ cursor: ["cursor-agent", "acp"],
7
+ copilot: ["copilot", "--acp"],
8
+ };
9
+ const PI_AGENT_TIMEOUT_MS = 120_000;
10
+ export async function runLocalAgentProvider(provider, input) {
11
+ return createLocalAgentAdapter(provider).run(input);
12
+ }
13
+ export function createLocalAgentAdapter(provider) {
14
+ switch (provider) {
15
+ case "codex":
16
+ return new CodexLocalAgentAdapter();
17
+ case "claude":
18
+ return new ClaudeLocalAgentAdapter();
19
+ case "opencode":
20
+ return new OpencodeLocalAgentAdapter();
21
+ case "pi":
22
+ return new PiRpcLocalAgentAdapter();
23
+ case "cursor":
24
+ case "copilot":
25
+ return new AcpLocalAgentAdapter(provider, ACP_COMMANDS[provider]);
26
+ }
27
+ }
28
+ class CodexLocalAgentAdapter {
29
+ provider = "codex";
30
+ async run(input) {
31
+ const runtime = await createCodexSdkLocalAgentRuntime();
32
+ return runtime.run(input);
33
+ }
34
+ }
35
+ class ClaudeLocalAgentAdapter {
36
+ provider = "claude";
37
+ async run(input) {
38
+ const { query } = await import("@anthropic-ai/claude-agent-sdk");
39
+ const claudeExecutable = process.env.CLAUDE_COMMAND ?? resolveExecutable("claude");
40
+ const messages = query({
41
+ prompt: input.prompt,
42
+ options: {
43
+ cwd: input.workspace,
44
+ model: input.model,
45
+ ...(input.thinking ? { thinking: { type: "adaptive" }, effort: input.thinking } : {}),
46
+ resume: input.providerSessionId,
47
+ permissionMode: "bypassPermissions",
48
+ allowDangerouslySkipPermissions: true,
49
+ env: claudeCommandEnvironment(process.env),
50
+ ...(claudeExecutable ? { pathToClaudeCodeExecutable: claudeExecutable } : {}),
51
+ },
52
+ });
53
+ let providerSessionId = input.providerSessionId ?? null;
54
+ let finalResponse = "";
55
+ const items = [];
56
+ for await (const message of messages) {
57
+ items.push(message);
58
+ const record = message;
59
+ if (typeof record.session_id === "string")
60
+ providerSessionId = record.session_id;
61
+ if (record.type === "result" && typeof record.result === "string") {
62
+ const resultError = claudeResultError(record);
63
+ if (resultError)
64
+ throw new Error(resultError);
65
+ finalResponse = record.result;
66
+ }
67
+ }
68
+ finalResponse = requireFinalResponse("Claude", finalResponse);
69
+ return {
70
+ provider: this.provider,
71
+ providerSessionId,
72
+ finalResponse,
73
+ items,
74
+ };
75
+ }
76
+ }
77
+ function claudeResultError(record) {
78
+ const subtype = typeof record.subtype === "string" ? record.subtype : undefined;
79
+ const isError = record.is_error === true || subtype?.startsWith("error");
80
+ if (!isError)
81
+ return undefined;
82
+ const message = directString(record.error) ??
83
+ directString(record.message) ??
84
+ directString(record.result) ??
85
+ subtype ??
86
+ "Claude returned an error result.";
87
+ return `Claude returned an error result: ${message}`;
88
+ }
89
+ function directString(value) {
90
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
91
+ }
92
+ function resolveExecutable(command) {
93
+ const result = spawnSync(process.platform === "win32" ? "where.exe" : "command", [
94
+ ...(process.platform === "win32" ? [command] : ["-v", command]),
95
+ ], {
96
+ encoding: "utf8",
97
+ shell: process.platform !== "win32",
98
+ });
99
+ const executable = result.stdout?.split(/\r?\n/).find((line) => line.trim());
100
+ return executable?.trim() || undefined;
101
+ }
102
+ export function claudeCommandEnvironment(env) {
103
+ const next = { ...env };
104
+ for (const key of [
105
+ "CLAUDECODE",
106
+ "CLAUDE_CODE_ENTRYPOINT",
107
+ "CLAUDE_CODE_SSE_PORT",
108
+ "CLAUDE_AGENT_SDK_VERSION",
109
+ ]) {
110
+ delete next[key];
111
+ }
112
+ return next;
113
+ }
114
+ class OpencodeLocalAgentAdapter {
115
+ provider = "opencode";
116
+ async run(input) {
117
+ const { createOpencode } = await import("@opencode-ai/sdk/v2");
118
+ const { client, server } = await createOpencode();
119
+ try {
120
+ const sessionId = input.providerSessionId ?? await createOpencodeSession(client, input);
121
+ const promptResult = await promptOpencodeSession(client, sessionId, input);
122
+ await waitForOpencodeSession(client, sessionId);
123
+ const messages = await readOpencodeMessages(client, sessionId);
124
+ const finalResponse = requireFinalResponse("OpenCode", extractOpenCodeFinalResponse(messages) || extractOpenCodeFinalResponse(promptResult));
125
+ return {
126
+ provider: this.provider,
127
+ providerSessionId: sessionId,
128
+ finalResponse,
129
+ items: [promptResult, messages],
130
+ };
131
+ }
132
+ finally {
133
+ server.close();
134
+ }
135
+ }
136
+ }
137
+ class AcpLocalAgentAdapter {
138
+ provider;
139
+ command;
140
+ constructor(provider, command) {
141
+ this.provider = provider;
142
+ this.command = command;
143
+ }
144
+ async run(input) {
145
+ const { client } = await import("@agentclientprotocol/sdk");
146
+ const { methods } = await import("@agentclientprotocol/sdk");
147
+ const { ndJsonStream } = await import("@agentclientprotocol/sdk");
148
+ const [command, ...args] = this.command;
149
+ const child = spawn(command, args, {
150
+ cwd: input.workspace,
151
+ env: process.env,
152
+ stdio: ["pipe", "pipe", "pipe"],
153
+ windowsHide: true,
154
+ });
155
+ assertPipedChild(child);
156
+ let stderr = "";
157
+ child.stderr.on("data", (chunk) => {
158
+ stderr += chunk.toString("utf8");
159
+ });
160
+ const stream = ndJsonStream(Writable.toWeb(child.stdin), Readable.toWeb(child.stdout));
161
+ try {
162
+ let providerSessionId = input.providerSessionId ?? null;
163
+ const finalResponse = await client({ name: "DevSpace" })
164
+ .onRequest(methods.client.session.requestPermission, (context) => {
165
+ const selected = selectAcpAllowPermissionOption(context.params.options);
166
+ return selected
167
+ ? { outcome: { outcome: "selected", optionId: selected.optionId } }
168
+ : { outcome: { outcome: "cancelled" } };
169
+ })
170
+ .connectWith(stream, async (context) => {
171
+ const session = await context.buildSession(input.workspace).start();
172
+ providerSessionId = session.sessionId;
173
+ try {
174
+ if (input.model) {
175
+ const config = resolveAcpModelConfigUpdate(session, input.model, this.provider);
176
+ await context.request(methods.agent.session.setConfigOption, config);
177
+ }
178
+ if (input.thinking) {
179
+ const config = resolveAcpThinkingConfigUpdate(session, input.thinking, this.provider);
180
+ await context.request(methods.agent.session.setConfigOption, config);
181
+ }
182
+ const prompt = session.prompt(input.prompt);
183
+ const textParts = [];
184
+ for (;;) {
185
+ const message = await session.nextUpdate();
186
+ if (message.kind === "stop") {
187
+ await prompt;
188
+ return textParts.join("").trim();
189
+ }
190
+ const update = message.update;
191
+ if (update.sessionUpdate !== "agent_message_chunk")
192
+ continue;
193
+ const content = update.content;
194
+ if (content.type === "text")
195
+ textParts.push(content.text);
196
+ }
197
+ }
198
+ finally {
199
+ session.dispose();
200
+ }
201
+ });
202
+ return {
203
+ provider: this.provider,
204
+ providerSessionId,
205
+ finalResponse: finalResponse.trim(),
206
+ items: [],
207
+ };
208
+ }
209
+ catch (error) {
210
+ throw new Error(`${this.provider} ACP run failed: ${errorMessage(error)}${stderr ? `\n${stderr.trim()}` : ""}`);
211
+ }
212
+ finally {
213
+ child.kill();
214
+ }
215
+ }
216
+ }
217
+ export function resolveAcpModelConfigUpdate(session, model, provider) {
218
+ return resolveAcpSelectConfigUpdate(session, {
219
+ category: "model",
220
+ label: "model",
221
+ provider,
222
+ value: model,
223
+ });
224
+ }
225
+ export function resolveAcpThinkingConfigUpdate(session, thinking, provider) {
226
+ return resolveAcpSelectConfigUpdate(session, {
227
+ category: "thought_level",
228
+ label: "thinking option",
229
+ provider,
230
+ value: thinking,
231
+ });
232
+ }
233
+ function resolveAcpSelectConfigUpdate(session, options) {
234
+ const record = asRecord(session);
235
+ if (!record)
236
+ throw new Error(`${options.provider} ACP session did not return session metadata.`);
237
+ const sessionId = typeof record?.sessionId === "string" ? record.sessionId : undefined;
238
+ if (!sessionId)
239
+ throw new Error(`${options.provider} ACP session did not return a session id.`);
240
+ const response = asRecord(record.newSessionResponse);
241
+ const configOptions = response ? readArray(response, "configOptions") ?? [] : [];
242
+ const config = configOptions
243
+ .map(asRecord)
244
+ .find((option) => option?.type === "select" && option.category === options.category);
245
+ if (!config) {
246
+ throw new Error(`${options.provider} ACP server does not expose a ${options.label}.`);
247
+ }
248
+ const configId = directString(config.id);
249
+ if (!configId)
250
+ throw new Error(`${options.provider} ACP ${options.label} is missing an id.`);
251
+ const available = flattenAcpSelectValues(config);
252
+ if (!available.includes(options.value)) {
253
+ const suffix = available.length > 0 ? ` Available values: ${available.join(", ")}.` : "";
254
+ throw new Error(`${options.provider} ACP ${options.label} does not support '${options.value}'.${suffix}`);
255
+ }
256
+ return { sessionId, configId, value: options.value };
257
+ }
258
+ function flattenAcpSelectValues(option) {
259
+ const values = [];
260
+ for (const item of readArray(option, "options") ?? []) {
261
+ const record = asRecord(item);
262
+ const value = directString(record?.value);
263
+ if (value) {
264
+ values.push(value);
265
+ continue;
266
+ }
267
+ for (const nested of readArray(record, "options") ?? []) {
268
+ const nestedValue = directString(asRecord(nested)?.value);
269
+ if (nestedValue)
270
+ values.push(nestedValue);
271
+ }
272
+ }
273
+ return values;
274
+ }
275
+ function selectAcpAllowPermissionOption(options) {
276
+ return (options.find((option) => option.kind === "allow_once") ??
277
+ options.find((option) => option.kind === "allow_always"));
278
+ }
279
+ class PiRpcLocalAgentAdapter {
280
+ provider = "pi";
281
+ async run(input) {
282
+ const args = ["--mode", "rpc"];
283
+ if (input.model)
284
+ args.push("--model", input.model);
285
+ if (input.thinking)
286
+ args.push("--thinking", input.thinking);
287
+ if (input.providerSessionId)
288
+ args.push("--session", input.providerSessionId);
289
+ const child = spawn(process.env.PI_COMMAND ?? "pi", args, {
290
+ cwd: input.workspace,
291
+ env: piCommandEnvironment(process.env),
292
+ stdio: ["pipe", "pipe", "pipe"],
293
+ windowsHide: true,
294
+ });
295
+ assertPipedChild(child);
296
+ const rpc = new JsonLineRpc(child);
297
+ const events = [];
298
+ rpc.onEvent((event) => events.push(event));
299
+ try {
300
+ const state = await rpc.request({ type: "get_state" });
301
+ const providerSessionId = readNestedString(state, ["sessionId"]) ?? input.providerSessionId ?? null;
302
+ const done = rpc.waitForEvent((event) => asRecord(event)?.type === "agent_end", PI_AGENT_TIMEOUT_MS);
303
+ await rpc.request({ type: "prompt", message: input.prompt });
304
+ const agentEnd = await done;
305
+ const sessionMessages = await rpc.request({ type: "get_messages" });
306
+ const finalResponse = extractPiFinalResponse(agentEnd) ||
307
+ extractPiFinalResponse(sessionMessages) ||
308
+ extractPiStreamingText(events);
309
+ if (!finalResponse) {
310
+ const providerError = extractPiProviderError(agentEnd) ||
311
+ extractPiProviderError(sessionMessages) ||
312
+ extractPiProviderError(events);
313
+ if (providerError)
314
+ throw new Error(`Pi returned an error: ${providerError}`);
315
+ }
316
+ requireFinalResponse("Pi", finalResponse);
317
+ return {
318
+ provider: this.provider,
319
+ providerSessionId,
320
+ finalResponse,
321
+ items: [...events, sessionMessages],
322
+ };
323
+ }
324
+ finally {
325
+ child.kill();
326
+ }
327
+ }
328
+ }
329
+ export function piCommandEnvironment(env) {
330
+ if (env.PI_COMMAND)
331
+ return env;
332
+ const path = env.PATH;
333
+ if (!path)
334
+ return env;
335
+ return {
336
+ ...env,
337
+ PATH: removeDevspaceNodeModulesBinFromPath(path),
338
+ };
339
+ }
340
+ class JsonLineRpc {
341
+ child;
342
+ pending = new Map();
343
+ eventSubscribers = new Set();
344
+ buffer = "";
345
+ nextId = 1;
346
+ stderr = "";
347
+ fatalError;
348
+ constructor(child) {
349
+ this.child = child;
350
+ child.stdout.on("data", (chunk) => this.handleStdout(chunk.toString("utf8")));
351
+ child.stderr.on("data", (chunk) => {
352
+ this.stderr += chunk.toString("utf8");
353
+ });
354
+ child.on("exit", (code, signal) => {
355
+ this.failAll(new Error(`Pi RPC process exited with code ${code ?? "null"} and signal ${signal ?? "null"}\n${this.stderr}`.trim()));
356
+ });
357
+ }
358
+ request(command) {
359
+ if (this.fatalError) {
360
+ return Promise.reject(this.fatalError);
361
+ }
362
+ const id = `req_${this.nextId}`;
363
+ this.nextId += 1;
364
+ return new Promise((resolve, reject) => {
365
+ this.pending.set(id, { resolve, reject });
366
+ this.child.stdin.write(`${JSON.stringify({ ...command, id })}\n`);
367
+ });
368
+ }
369
+ onEvent(callback) {
370
+ this.eventSubscribers.add(callback);
371
+ return () => this.eventSubscribers.delete(callback);
372
+ }
373
+ waitForEvent(predicate, timeoutMs) {
374
+ return new Promise((resolve, reject) => {
375
+ const timer = setTimeout(() => {
376
+ unsubscribe();
377
+ reject(new Error(`Pi RPC timed out waiting for agent completion\n${this.stderr}`.trim()));
378
+ }, timeoutMs);
379
+ const unsubscribe = this.onEvent((event) => {
380
+ if (!predicate(event))
381
+ return;
382
+ clearTimeout(timer);
383
+ unsubscribe();
384
+ resolve(event);
385
+ });
386
+ });
387
+ }
388
+ handleStdout(chunk) {
389
+ this.buffer += chunk;
390
+ for (;;) {
391
+ const newline = this.buffer.indexOf("\n");
392
+ if (newline === -1)
393
+ return;
394
+ const line = this.buffer.slice(0, newline).trim();
395
+ this.buffer = this.buffer.slice(newline + 1);
396
+ if (!line)
397
+ continue;
398
+ let message;
399
+ try {
400
+ message = JSON.parse(line);
401
+ }
402
+ catch {
403
+ this.stderr += `${line}\n`;
404
+ this.failAll(new Error(`Pi RPC emitted malformed JSON on stdout: ${line}`));
405
+ return;
406
+ }
407
+ if (message.type !== "response") {
408
+ for (const subscriber of this.eventSubscribers)
409
+ subscriber(message);
410
+ continue;
411
+ }
412
+ const id = typeof message.id === "string" ? message.id : undefined;
413
+ if (!id)
414
+ continue;
415
+ const pending = this.pending.get(id);
416
+ if (!pending)
417
+ continue;
418
+ this.pending.delete(id);
419
+ if (message.success === false || message.error) {
420
+ pending.reject(new Error(errorMessage(message.error ?? `Pi RPC request failed: ${message.command ?? id}`)));
421
+ }
422
+ else {
423
+ pending.resolve(message.data ?? message.result ?? message);
424
+ }
425
+ }
426
+ }
427
+ failAll(error) {
428
+ this.fatalError = error;
429
+ for (const pending of this.pending.values()) {
430
+ pending.reject(error);
431
+ }
432
+ this.pending.clear();
433
+ }
434
+ }
435
+ async function createOpencodeSession(client, input) {
436
+ const sessionClient = client;
437
+ const result = await sessionClient.session.create({
438
+ directory: input.workspace,
439
+ location: { directory: input.workspace },
440
+ ...(input.model ? { model: parseOpencodeModel(input.model) } : {}),
441
+ }, { throwOnError: true });
442
+ const id = readNestedString(result, ["id"]) ??
443
+ readNestedString(result, ["data", "id"]) ??
444
+ readNestedString(result, ["session", "id"]) ??
445
+ readNestedString(result, ["data", "session", "id"]);
446
+ if (typeof id !== "string") {
447
+ throw new Error("OpenCode did not return a session id.");
448
+ }
449
+ return id;
450
+ }
451
+ async function promptOpencodeSession(client, sessionId, input) {
452
+ const session = client.session;
453
+ const promptInput = {
454
+ sessionID: sessionId,
455
+ directory: input.workspace,
456
+ prompt: { parts: [{ type: "text", text: input.prompt }] },
457
+ parts: [{ type: "text", text: input.prompt }],
458
+ ...(input.model ? { model: parseOpencodeModel(input.model) } : {}),
459
+ ...(input.thinking ? { variant: input.thinking } : {}),
460
+ };
461
+ return session.prompt(promptInput, { throwOnError: true });
462
+ }
463
+ async function waitForOpencodeSession(client, sessionId) {
464
+ const session = client.session;
465
+ if (!session?.wait)
466
+ return;
467
+ await session.wait({ sessionID: sessionId }, { throwOnError: true });
468
+ }
469
+ async function readOpencodeMessages(client, sessionId) {
470
+ const session = client.session;
471
+ if (!session?.messages)
472
+ return undefined;
473
+ return session.messages({ sessionID: sessionId, order: "asc", limit: 100 }, { throwOnError: true });
474
+ }
475
+ function parseOpencodeModel(model) {
476
+ const separator = model.indexOf("/");
477
+ if (separator === -1)
478
+ return { providerID: "opencode", modelID: model };
479
+ return {
480
+ providerID: model.slice(0, separator),
481
+ modelID: model.slice(separator + 1),
482
+ };
483
+ }
484
+ export function extractLocalAgentResponseText(value) {
485
+ return extractOpenCodeFinalResponse(value) || extractPiFinalResponse(value);
486
+ }
487
+ function assertPipedChild(child) {
488
+ if (!child.stdin || !child.stdout || !child.stderr) {
489
+ throw new Error("Agent process did not expose stdio pipes.");
490
+ }
491
+ }
492
+ export function extractOpenCodeFinalResponse(value) {
493
+ const root = unwrapProviderPayload(value);
494
+ const messages = Array.isArray(root) ? root : readArray(root, "messages");
495
+ if (messages)
496
+ return extractLastOpenCodeAssistantMessageText(messages);
497
+ return extractOpenCodeAssistantMessageText(root);
498
+ }
499
+ export function extractPiFinalResponse(value) {
500
+ const root = unwrapProviderPayload(value);
501
+ const messages = Array.isArray(root) ? root : readArray(root, "messages");
502
+ if (!messages)
503
+ return "";
504
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
505
+ const message = asRecord(messages[index]);
506
+ if (!message || message.role !== "assistant")
507
+ continue;
508
+ const text = extractPiAssistantMessageText(message);
509
+ if (text)
510
+ return text;
511
+ }
512
+ return "";
513
+ }
514
+ export function extractPiStreamingText(events) {
515
+ return events
516
+ .map((event) => {
517
+ const record = asRecord(event);
518
+ if (!record || record.type !== "message_update")
519
+ return "";
520
+ const update = asRecord(record.assistantMessageEvent);
521
+ if (!update || update.type !== "text_delta")
522
+ return "";
523
+ return typeof update.delta === "string" ? update.delta : "";
524
+ })
525
+ .filter(Boolean)
526
+ .join("")
527
+ .trim();
528
+ }
529
+ export function extractPiProviderError(value) {
530
+ const root = unwrapProviderPayload(value);
531
+ if (Array.isArray(root)) {
532
+ for (let index = root.length - 1; index >= 0; index -= 1) {
533
+ const error = extractPiProviderError(root[index]);
534
+ if (error)
535
+ return error;
536
+ }
537
+ return "";
538
+ }
539
+ const messages = readArray(root, "messages");
540
+ if (messages)
541
+ return extractPiProviderError(messages);
542
+ const message = asRecord(root)?.message ?? root;
543
+ const record = asRecord(message);
544
+ if (!record)
545
+ return "";
546
+ const error = record.errorMessage ?? record.error;
547
+ return typeof error === "string" ? error.trim() : "";
548
+ }
549
+ function extractLastOpenCodeAssistantMessageText(messages) {
550
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
551
+ const message = asRecord(messages[index]);
552
+ if (!message)
553
+ continue;
554
+ const info = asRecord(message.info);
555
+ const role = typeof info?.role === "string" ? info.role : message.role;
556
+ const type = typeof message.type === "string" ? message.type : undefined;
557
+ if (role !== "assistant" && type !== "assistant")
558
+ continue;
559
+ const text = extractOpenCodeAssistantMessageText(message);
560
+ if (text)
561
+ return text;
562
+ }
563
+ return "";
564
+ }
565
+ function extractOpenCodeAssistantMessageText(value) {
566
+ const message = asRecord(value);
567
+ if (!message)
568
+ return "";
569
+ const content = readArray(message, "content");
570
+ if (content) {
571
+ const text = content
572
+ .map((part) => {
573
+ const partRecord = asRecord(part);
574
+ if (!partRecord || partRecord.type !== "text")
575
+ return "";
576
+ return typeof partRecord.text === "string" ? partRecord.text : "";
577
+ })
578
+ .filter(Boolean)
579
+ .join("");
580
+ if (text.trim())
581
+ return text.trim();
582
+ }
583
+ const parts = readArray(message, "parts");
584
+ if (parts) {
585
+ const text = parts
586
+ .map((part) => {
587
+ const partRecord = asRecord(part);
588
+ if (!partRecord || partRecord.type !== "text")
589
+ return "";
590
+ return typeof partRecord.text === "string" ? partRecord.text : "";
591
+ })
592
+ .filter(Boolean)
593
+ .join("");
594
+ if (text.trim())
595
+ return text.trim();
596
+ }
597
+ const info = asRecord(message.info) ?? message;
598
+ return stringifyStructuredAssistantMessage(info.structured);
599
+ }
600
+ function extractPiAssistantMessageText(message) {
601
+ const content = message.content;
602
+ if (!Array.isArray(content))
603
+ return "";
604
+ return content
605
+ .map((part) => {
606
+ const partRecord = asRecord(part);
607
+ if (!partRecord || partRecord.type !== "text")
608
+ return "";
609
+ return typeof partRecord.text === "string" ? partRecord.text : "";
610
+ })
611
+ .filter(Boolean)
612
+ .join("\n\n")
613
+ .trim();
614
+ }
615
+ function stringifyStructuredAssistantMessage(value) {
616
+ if (value === undefined || value === null)
617
+ return "";
618
+ if (typeof value === "string")
619
+ return value.trim();
620
+ return JSON.stringify(value);
621
+ }
622
+ function unwrapProviderPayload(value) {
623
+ const record = asRecord(value);
624
+ if (!record)
625
+ return value;
626
+ return record.data ?? record.result ?? value;
627
+ }
628
+ function readArray(record, key) {
629
+ const value = asRecord(record)?.[key];
630
+ return Array.isArray(value) ? value : undefined;
631
+ }
632
+ function asRecord(value) {
633
+ if (!value || typeof value !== "object" || Array.isArray(value))
634
+ return undefined;
635
+ return value;
636
+ }
637
+ function readNestedString(value, path) {
638
+ let current = value;
639
+ for (const key of path) {
640
+ current = asRecord(current)?.[key];
641
+ }
642
+ return typeof current === "string" ? current : undefined;
643
+ }
644
+ function errorMessage(error) {
645
+ return error instanceof Error ? error.message : String(error);
646
+ }
647
+ function requireFinalResponse(provider, response) {
648
+ const trimmed = response.trim();
649
+ if (!trimmed) {
650
+ throw new Error(`${provider} did not return a final assistant response.`);
651
+ }
652
+ return trimmed;
653
+ }