@doist/doistbot-cli 1.0.5 → 1.0.6

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 (53) hide show
  1. package/dist/actions/auth.js +55 -21
  2. package/dist/actions/doctor.js +13 -6
  3. package/dist/actions/review.js +75 -10
  4. package/dist/auth.js +51 -6
  5. package/dist/config.js +34 -5
  6. package/dist/env.js +1 -0
  7. package/dist/runtime.js +1 -1
  8. package/dist/terminal.js +2 -0
  9. package/package.json +4 -4
  10. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +4 -8
  11. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +8 -20
  12. package/sandbox/dist/core/check-run.js +16 -0
  13. package/sandbox/dist/core/datadog-metrics.js +6 -52
  14. package/sandbox/dist/core/logger-span-processor.js +78 -0
  15. package/sandbox/dist/core/pi.js +257 -176
  16. package/sandbox/dist/core/tracing-setup.js +15 -0
  17. package/sandbox/dist/core/tracing.js +71 -0
  18. package/sandbox/dist/main.js +4 -1
  19. package/sandbox/dist/providers/credentials.js +12 -2
  20. package/sandbox/dist/providers/gemini.js +5 -1
  21. package/sandbox/dist/providers/helpers.js +12 -17
  22. package/sandbox/dist/providers/index.js +4 -0
  23. package/sandbox/dist/providers/openrouter-pi.js +25 -0
  24. package/sandbox/dist/providers/pi-review.js +12 -1
  25. package/sandbox/dist/tasks/chat/chat.js +161 -101
  26. package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
  27. package/sandbox/dist/tasks/issue-summarize/model.js +1 -1
  28. package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
  29. package/sandbox/dist/tasks/issue-triage/context.js +25 -1
  30. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +31 -8
  31. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +154 -101
  32. package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +15 -6
  33. package/sandbox/dist/tasks/issue-triage/model.js +120 -36
  34. package/sandbox/dist/tasks/issue-triage/output.js +27 -0
  35. package/sandbox/dist/tasks/issue-triage/pr-creator.js +3 -2
  36. package/sandbox/dist/tasks/issue-triage/resolution.js +26 -0
  37. package/sandbox/dist/tasks/issue-triage/routing.js +9 -9
  38. package/sandbox/dist/tasks/issue-triage/side-effects.js +9 -0
  39. package/sandbox/dist/tasks/issue-triage/triage.js +288 -418
  40. package/sandbox/dist/tasks/review/check-run.js +2 -1
  41. package/sandbox/dist/tasks/review/conversation-context.js +2 -1
  42. package/sandbox/dist/tasks/review/engines/dedupe.js +5 -4
  43. package/sandbox/dist/tasks/review/engines/multi-focus.js +40 -9
  44. package/sandbox/dist/tasks/review/engines/shared.js +8 -6
  45. package/sandbox/dist/tasks/review/github-comment-format.js +64 -10
  46. package/sandbox/dist/tasks/review/local.js +2 -4
  47. package/sandbox/dist/tasks/review/review-summary.js +45 -7
  48. package/sandbox/dist/tasks/review/review.js +38 -24
  49. package/sandbox/dist/tasks/review/runner.js +23 -2
  50. package/sandbox/dist/tasks/review/summary-model.js +164 -17
  51. package/sandbox/dist/tasks/review/thinking-level.js +4 -0
  52. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +4 -8
  53. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +8 -20
@@ -3,41 +3,53 @@ import { mkdtempSync, rmSync, writeFileSync } from 'fs';
3
3
  import { tmpdir } from 'os';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
- const PI_PROVIDER_BY_DOISTBOT_PROVIDER = {
7
- gemini: 'openrouter',
6
+ export const PI_PROVIDER_BY_DOISTBOT_PROVIDER = {
7
+ deepseek: 'openrouter',
8
+ gemini: 'google',
9
+ zai: 'openrouter',
8
10
  openai: 'openai',
9
11
  };
10
12
  export const DEFAULT_PI_MODELS = {
13
+ deepseek: 'deepseek-v4-pro',
11
14
  gemini: 'gemini-3.1-pro-preview',
12
- openai: 'gpt-5.4',
15
+ zai: 'glm5.2',
16
+ openai: 'gpt-5.5',
13
17
  };
14
18
  export const DEFAULT_OPENROUTER_MODELS = {
19
+ deepseek: 'deepseek/deepseek-v4-pro',
15
20
  gemini: 'google/gemini-3.1-pro-preview',
21
+ zai: 'z-ai/glm-5.2',
16
22
  };
17
23
  export const MAX_PI_THINKING_BY_PROVIDER = {
24
+ deepseek: 'high',
18
25
  gemini: 'high',
26
+ zai: 'high',
19
27
  openai: 'xhigh',
20
28
  };
21
29
  const PI_API_KEY_ENV_VAR_BY_PI_PROVIDER = {
30
+ google: 'GEMINI_API_KEY',
22
31
  openai: 'OPENAI_API_KEY',
23
32
  'openai-codex': 'OPENAI_API_KEY',
24
33
  openrouter: 'OPENROUTER_API_KEY',
25
34
  };
26
- const PI_PROVIDER_IDS = new Set(['openai', 'openai-codex', 'openrouter']);
35
+ const PI_PROVIDER_IDS = new Set(['google', 'openai', 'openai-codex', 'openrouter']);
27
36
  export const PI_AUTH_API_KEY_PREFIX = 'pi-auth://';
28
37
  const DOISTBOT_PI_SYSTEM_PROMPT = 'You are running inside doistbot in a non-interactive environment. Inspect the repository, configuration, documentation, and relevant history when they can improve accuracy. Use available tools proactively to gather evidence, avoid guessing when the workspace can answer the question, and follow the task prompt exactly.';
29
38
  const PI_PROGRESS_HEARTBEAT_MS = 30_000;
39
+ const OPENROUTER_SESSION_ID_ENV_VAR = 'DOISTBOT_OPENROUTER_SESSION_ID';
40
+ const OPENROUTER_SESSION_ID_MAX_LENGTH = 256;
30
41
  const PI_TOOLS_BY_PRESET = {
31
42
  'read-only': ['read', 'grep', 'find', 'ls'],
32
43
  'workspace-inspection': ['read', 'bash', 'grep', 'find', 'ls'],
33
44
  'shell-focused': ['read', 'bash', 'ls'],
34
45
  };
35
46
  function resolvePiCliPath() {
36
- const piPackageEntryPath = fileURLToPath(import.meta.resolve('@mariozechner/pi-coding-agent'));
47
+ const piPackageEntryPath = fileURLToPath(import.meta.resolve('@earendil-works/pi-coding-agent'));
37
48
  return path.join(path.dirname(piPackageEntryPath), 'cli.js');
38
49
  }
39
50
  const PI_CLI_PATH = resolvePiCliPath();
40
51
  let cachedNodeExecutable;
52
+ let cachedOpenRouterSessionExtensionPath;
41
53
  export function resolveNodeExecutable({ execPath = process.execPath, argv0 = process.argv0, canExecute = canExecuteNode, } = {}) {
42
54
  const canUseCachedExecutable = execPath === process.execPath && argv0 === process.argv0 && canExecute === canExecuteNode;
43
55
  if (canUseCachedExecutable && cachedNodeExecutable) {
@@ -67,7 +79,7 @@ function getPiModelSpec({ provider, piProviderId, modelName, thinkingLevel, }) {
67
79
  DEFAULT_PI_MODELS[provider];
68
80
  return `${providerId}/${resolvedModelName}:${thinkingLevel}`;
69
81
  }
70
- function parsePiAuthProviderId(apiKey) {
82
+ export function parsePiAuthProviderId(apiKey) {
71
83
  if (!apiKey.startsWith(PI_AUTH_API_KEY_PREFIX)) {
72
84
  return undefined;
73
85
  }
@@ -112,6 +124,54 @@ function createImageArgs(images) {
112
124
  },
113
125
  };
114
126
  }
127
+ function normalizeOpenRouterSessionId(sessionId) {
128
+ const trimmed = sessionId?.trim();
129
+ if (!trimmed) {
130
+ return undefined;
131
+ }
132
+ return trimmed.slice(0, OPENROUTER_SESSION_ID_MAX_LENGTH);
133
+ }
134
+ function getOpenRouterSessionExtensionPath() {
135
+ if (cachedOpenRouterSessionExtensionPath) {
136
+ return cachedOpenRouterSessionExtensionPath;
137
+ }
138
+ const tempDir = mkdtempSync(path.join(tmpdir(), 'doistbot-pi-openrouter-session-'));
139
+ const extensionPath = path.join(tempDir, 'openrouter-session-id.mjs');
140
+ writeFileSync(extensionPath, `export default function(pi) {
141
+ const sessionId = process.env[${JSON.stringify(OPENROUTER_SESSION_ID_ENV_VAR)}]
142
+ if (!sessionId) return
143
+
144
+ pi.on("before_provider_request", (event) => {
145
+ const payload = event.payload
146
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined
147
+
148
+ return { ...payload, session_id: sessionId }
149
+ })
150
+ }
151
+ `);
152
+ process.once('exit', () => {
153
+ rmSync(tempDir, { force: true, recursive: true });
154
+ });
155
+ cachedOpenRouterSessionExtensionPath = extensionPath;
156
+ return extensionPath;
157
+ }
158
+ function createOpenRouterSessionExtension({ piProviderId, sessionId, }) {
159
+ const normalizedSessionId = normalizeOpenRouterSessionId(sessionId);
160
+ if (piProviderId !== 'openrouter' || !normalizedSessionId) {
161
+ return {
162
+ args: [],
163
+ extraEnv: {},
164
+ cleanup() { },
165
+ };
166
+ }
167
+ return {
168
+ args: ['--extension', getOpenRouterSessionExtensionPath()],
169
+ extraEnv: {
170
+ [OPENROUTER_SESSION_ID_ENV_VAR]: normalizedSessionId,
171
+ },
172
+ cleanup() { },
173
+ };
174
+ }
115
175
  function extractTextSegments(content) {
116
176
  if (!Array.isArray(content)) {
117
177
  return [];
@@ -178,7 +238,8 @@ function maybeEmitProgress({ event, onProgress, lastThinkingAt, lastRespondingAt
178
238
  case 'tool_execution_start': {
179
239
  const toolName = event.toolName;
180
240
  if (typeof toolName === 'string' && toolName.length > 0) {
181
- onProgress({ type: 'tool_start', toolName });
241
+ const toolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : undefined;
242
+ onProgress({ type: 'tool_start', toolName, ...(toolCallId ? { toolCallId } : {}) });
182
243
  }
183
244
  return;
184
245
  }
@@ -187,23 +248,30 @@ function maybeEmitProgress({ event, onProgress, lastThinkingAt, lastRespondingAt
187
248
  if (typeof toolName !== 'string' || toolName.length === 0) {
188
249
  return;
189
250
  }
190
- const toolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : toolName;
251
+ const rawToolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : undefined;
252
+ const toolCallId = rawToolCallId ?? toolName;
191
253
  const now = Date.now();
192
254
  const lastUpdateAt = lastToolUpdateAt.get(toolCallId) ?? 0;
193
255
  if (now - lastUpdateAt >= PI_PROGRESS_HEARTBEAT_MS) {
194
256
  lastToolUpdateAt.set(toolCallId, now);
195
- onProgress({ type: 'tool_update', toolName });
257
+ onProgress({
258
+ type: 'tool_update',
259
+ toolName,
260
+ ...(rawToolCallId ? { toolCallId: rawToolCallId } : {}),
261
+ });
196
262
  }
197
263
  return;
198
264
  }
199
265
  case 'tool_execution_end': {
200
266
  const toolName = event.toolName;
201
267
  if (typeof toolName === 'string' && toolName.length > 0) {
202
- const toolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : toolName;
268
+ const rawToolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : undefined;
269
+ const toolCallId = rawToolCallId ?? toolName;
203
270
  const startedAt = toolStartedAt.get(toolCallId);
204
271
  onProgress({
205
272
  type: 'tool_end',
206
273
  toolName,
274
+ ...(rawToolCallId ? { toolCallId: rawToolCallId } : {}),
207
275
  isError: event.isError === true,
208
276
  durationMs: startedAt == null ? undefined : Math.max(0, Date.now() - startedAt),
209
277
  });
@@ -219,7 +287,7 @@ function toSortedToolUsage(toolUsageByName) {
219
287
  return left.toolName.localeCompare(right.toolName);
220
288
  });
221
289
  }
222
- export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv, capabilityPreset = 'workspace-inspection', thinkingLevel, systemPrompt = DOISTBOT_PI_SYSTEM_PROMPT, onProgress, }) {
290
+ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv, capabilityPreset = 'workspace-inspection', thinkingLevel, systemPrompt = DOISTBOT_PI_SYSTEM_PROMPT, noContextFiles = false, openRouterSessionId, onProgress, }) {
223
291
  return {
224
292
  async invoke({ prompt, modelName, timeoutMs = 5 * 60 * 1000, images, }) {
225
293
  const startedAt = Date.now();
@@ -232,6 +300,10 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
232
300
  thinkingLevel: thinkingLevel ?? MAX_PI_THINKING_BY_PROVIDER[provider],
233
301
  });
234
302
  const { imageArgs, cleanup } = createImageArgs(images);
303
+ const openRouterSessionExtension = createOpenRouterSessionExtension({
304
+ piProviderId: resolvedPiProviderId,
305
+ sessionId: openRouterSessionId,
306
+ });
235
307
  const tools = PI_TOOLS_BY_PRESET[capabilityPreset].join(',');
236
308
  const args = [
237
309
  PI_CLI_PATH,
@@ -241,12 +313,14 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
241
313
  '--no-extensions',
242
314
  '--no-skills',
243
315
  '--no-prompt-templates',
316
+ ...(noContextFiles ? ['--no-context-files'] : []),
244
317
  '--model',
245
318
  modelSpec,
246
319
  '--tools',
247
320
  tools,
248
321
  '--system-prompt',
249
322
  systemPrompt,
323
+ ...openRouterSessionExtension.args,
250
324
  ...imageArgs,
251
325
  ];
252
326
  const childEnv = {
@@ -255,6 +329,7 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
255
329
  PATH: process.env.PATH,
256
330
  GIT_TERMINAL_PROMPT: '0',
257
331
  ...extraEnv,
332
+ ...openRouterSessionExtension.extraEnv,
258
333
  };
259
334
  const apiKeyEnvVar = PI_API_KEY_ENV_VAR_BY_PI_PROVIDER[resolvedPiProviderId];
260
335
  if (piAuthProviderId) {
@@ -264,205 +339,209 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
264
339
  childEnv[apiKeyEnvVar] = apiKey;
265
340
  }
266
341
  delete childEnv.GITHUB_TOKEN;
267
- const child = spawn(resolveNodeExecutable(), args, {
268
- cwd,
269
- env: childEnv,
270
- stdio: ['pipe', 'pipe', 'pipe'],
271
- });
272
- child.stdin?.on('error', () => { });
273
- child.stdin?.end(prompt);
274
- let timedOut = false;
275
- let stdoutBuffer = '';
276
- let stderr = '';
277
- let lastAssistantText = '';
278
- let lastAssistantError = '';
279
- let sessionHeaderSeen = false;
280
- const lastThinkingAt = { value: 0 };
281
- const lastRespondingAt = { value: 0 };
282
- const lastToolUpdateAt = new Map();
283
- const toolStartedAt = new Map();
284
- const toolUsageByName = new Map();
285
- function parseStdout(chunk) {
286
- stdoutBuffer += chunk;
287
- const lines = stdoutBuffer.split(/\r?\n/);
288
- stdoutBuffer = lines.pop() ?? '';
289
- for (const line of lines) {
290
- const trimmed = line.trim();
291
- if (!trimmed) {
292
- continue;
293
- }
294
- try {
295
- const parsed = JSON.parse(trimmed);
296
- if (parsed.type === 'session') {
297
- sessionHeaderSeen = true;
342
+ try {
343
+ const child = spawn(resolveNodeExecutable(), args, {
344
+ cwd,
345
+ env: childEnv,
346
+ stdio: ['pipe', 'pipe', 'pipe'],
347
+ });
348
+ child.stdin?.on('error', () => { });
349
+ child.stdin?.end(prompt);
350
+ let timedOut = false;
351
+ let stdoutBuffer = '';
352
+ let stderr = '';
353
+ let lastAssistantText = '';
354
+ let lastAssistantError = '';
355
+ let sessionHeaderSeen = false;
356
+ const lastThinkingAt = { value: 0 };
357
+ const lastRespondingAt = { value: 0 };
358
+ const lastToolUpdateAt = new Map();
359
+ const toolStartedAt = new Map();
360
+ const toolUsageByName = new Map();
361
+ function parseStdout(chunk) {
362
+ stdoutBuffer += chunk;
363
+ const lines = stdoutBuffer.split(/\r?\n/);
364
+ stdoutBuffer = lines.pop() ?? '';
365
+ for (const line of lines) {
366
+ const trimmed = line.trim();
367
+ if (!trimmed) {
298
368
  continue;
299
369
  }
300
- maybeEmitProgress({
301
- event: parsed,
302
- onProgress,
303
- lastThinkingAt,
304
- lastRespondingAt,
305
- lastToolUpdateAt,
306
- toolStartedAt,
307
- });
308
- if (parsed.type === 'tool_execution_start') {
309
- const toolName = typeof parsed.toolName === 'string' ? parsed.toolName : null;
310
- if (toolName) {
311
- const toolCallId = typeof parsed.toolCallId === 'string'
312
- ? parsed.toolCallId
313
- : toolName;
314
- toolStartedAt.set(toolCallId, Date.now());
315
- const current = toolUsageByName.get(toolName);
316
- if (current) {
317
- current.calls += 1;
370
+ try {
371
+ const parsed = JSON.parse(trimmed);
372
+ if (parsed.type === 'session') {
373
+ sessionHeaderSeen = true;
374
+ continue;
375
+ }
376
+ maybeEmitProgress({
377
+ event: parsed,
378
+ onProgress,
379
+ lastThinkingAt,
380
+ lastRespondingAt,
381
+ lastToolUpdateAt,
382
+ toolStartedAt,
383
+ });
384
+ if (parsed.type === 'tool_execution_start') {
385
+ const toolName = typeof parsed.toolName === 'string' ? parsed.toolName : null;
386
+ if (toolName) {
387
+ const toolCallId = typeof parsed.toolCallId === 'string'
388
+ ? parsed.toolCallId
389
+ : toolName;
390
+ toolStartedAt.set(toolCallId, Date.now());
391
+ const current = toolUsageByName.get(toolName);
392
+ if (current) {
393
+ current.calls += 1;
394
+ }
395
+ else {
396
+ toolUsageByName.set(toolName, {
397
+ toolName,
398
+ calls: 1,
399
+ totalDurationMs: 0,
400
+ errorCount: 0,
401
+ });
402
+ }
318
403
  }
319
- else {
320
- toolUsageByName.set(toolName, {
404
+ }
405
+ if (parsed.type === 'tool_execution_end') {
406
+ const toolName = typeof parsed.toolName === 'string' ? parsed.toolName : null;
407
+ if (toolName) {
408
+ const current = toolUsageByName.get(toolName) ?? {
321
409
  toolName,
322
- calls: 1,
410
+ calls: 0,
323
411
  totalDurationMs: 0,
324
412
  errorCount: 0,
325
- });
413
+ };
414
+ const toolCallId = typeof parsed.toolCallId === 'string'
415
+ ? parsed.toolCallId
416
+ : toolName;
417
+ const startedAt = toolStartedAt.get(toolCallId);
418
+ toolStartedAt.delete(toolCallId);
419
+ if (startedAt != null) {
420
+ current.totalDurationMs += Math.max(0, Date.now() - startedAt);
421
+ }
422
+ if (parsed.isError === true) {
423
+ current.errorCount += 1;
424
+ }
425
+ toolUsageByName.set(toolName, current);
326
426
  }
327
427
  }
328
- }
329
- if (parsed.type === 'tool_execution_end') {
330
- const toolName = typeof parsed.toolName === 'string' ? parsed.toolName : null;
331
- if (toolName) {
332
- const current = toolUsageByName.get(toolName) ?? {
333
- toolName,
334
- calls: 0,
335
- totalDurationMs: 0,
336
- errorCount: 0,
337
- };
338
- const toolCallId = typeof parsed.toolCallId === 'string'
339
- ? parsed.toolCallId
340
- : toolName;
341
- const startedAt = toolStartedAt.get(toolCallId);
342
- toolStartedAt.delete(toolCallId);
343
- if (startedAt != null) {
344
- current.totalDurationMs += Math.max(0, Date.now() - startedAt);
345
- }
346
- if (parsed.isError === true) {
347
- current.errorCount += 1;
348
- }
349
- toolUsageByName.set(toolName, current);
350
- }
351
- }
352
- if (parsed.type === 'message_end') {
353
- const result = extractAssistantMessageResult(parsed.message);
354
- if (result?.text) {
355
- lastAssistantText = result.text;
356
- }
357
- else if (result?.error) {
358
- lastAssistantError = result.error;
359
- }
360
- continue;
361
- }
362
- if (parsed.type === 'agent_end' && Array.isArray(parsed.messages)) {
363
- for (const message of parsed.messages) {
364
- const result = extractAssistantMessageResult(message);
428
+ if (parsed.type === 'message_end') {
429
+ const result = extractAssistantMessageResult(parsed.message);
365
430
  if (result?.text) {
366
431
  lastAssistantText = result.text;
367
432
  }
368
433
  else if (result?.error) {
369
434
  lastAssistantError = result.error;
370
435
  }
436
+ continue;
437
+ }
438
+ if (parsed.type === 'agent_end' && Array.isArray(parsed.messages)) {
439
+ for (const message of parsed.messages) {
440
+ const result = extractAssistantMessageResult(message);
441
+ if (result?.text) {
442
+ lastAssistantText = result.text;
443
+ }
444
+ else if (result?.error) {
445
+ lastAssistantError = result.error;
446
+ }
447
+ }
371
448
  }
372
449
  }
373
- }
374
- catch {
375
- if (!sessionHeaderSeen && !lastAssistantText) {
376
- lastAssistantText = trimmed;
450
+ catch {
451
+ if (!sessionHeaderSeen && !lastAssistantText) {
452
+ lastAssistantText = trimmed;
453
+ }
377
454
  }
378
455
  }
379
456
  }
380
- }
381
- const result = await new Promise((resolve) => {
382
- const timeoutHandle = setTimeout(() => {
383
- timedOut = true;
384
- child.kill('SIGTERM');
385
- }, timeoutMs);
386
- child.stdout.setEncoding('utf8');
387
- child.stdout.on('data', (chunk) => {
388
- parseStdout(chunk);
389
- });
390
- child.stderr.setEncoding('utf8');
391
- child.stderr.on('data', (chunk) => {
392
- stderr += chunk;
393
- });
394
- child.on('error', (error) => {
395
- clearTimeout(timeoutHandle);
396
- resolve({
397
- ok: false,
398
- error: error.message,
399
- durationMs: Date.now() - startedAt,
400
- timedOut,
401
- toolUsage: toSortedToolUsage(toolUsageByName),
457
+ return await new Promise((resolve) => {
458
+ const timeoutHandle = setTimeout(() => {
459
+ timedOut = true;
460
+ child.kill('SIGTERM');
461
+ }, timeoutMs);
462
+ child.stdout.setEncoding('utf8');
463
+ child.stdout.on('data', (chunk) => {
464
+ parseStdout(chunk);
402
465
  });
403
- });
404
- child.on('close', (code, signal) => {
405
- clearTimeout(timeoutHandle);
406
- if (stdoutBuffer.trim()) {
407
- parseStdout('\n');
408
- }
409
- if (timedOut) {
410
- resolve({
411
- ok: false,
412
- error: `Pi request timed out after ${timeoutMs}ms`,
413
- durationMs: Date.now() - startedAt,
414
- timedOut: true,
415
- toolUsage: toSortedToolUsage(toolUsageByName),
416
- });
417
- return;
418
- }
419
- if (signal) {
420
- resolve({
421
- ok: false,
422
- error: `Pi process terminated with signal ${signal}`,
423
- durationMs: Date.now() - startedAt,
424
- timedOut: false,
425
- toolUsage: toSortedToolUsage(toolUsageByName),
426
- });
427
- return;
428
- }
429
- if (code !== 0) {
466
+ child.stderr.setEncoding('utf8');
467
+ child.stderr.on('data', (chunk) => {
468
+ stderr += chunk;
469
+ });
470
+ child.on('error', (error) => {
471
+ clearTimeout(timeoutHandle);
430
472
  resolve({
431
473
  ok: false,
432
- error: stderr.trim() || `Pi exited with status ${String(code)}`,
474
+ error: error.message,
433
475
  durationMs: Date.now() - startedAt,
434
- timedOut: false,
476
+ timedOut,
435
477
  toolUsage: toSortedToolUsage(toolUsageByName),
436
478
  });
437
- return;
438
- }
439
- const output = lastAssistantText.trim();
440
- if (!output) {
479
+ });
480
+ child.on('close', (code, signal) => {
481
+ clearTimeout(timeoutHandle);
482
+ if (stdoutBuffer.trim()) {
483
+ parseStdout('\n');
484
+ }
485
+ if (timedOut) {
486
+ resolve({
487
+ ok: false,
488
+ error: `Pi request timed out after ${timeoutMs}ms`,
489
+ durationMs: Date.now() - startedAt,
490
+ timedOut: true,
491
+ toolUsage: toSortedToolUsage(toolUsageByName),
492
+ });
493
+ return;
494
+ }
495
+ if (signal) {
496
+ resolve({
497
+ ok: false,
498
+ error: `Pi process terminated with signal ${signal}`,
499
+ durationMs: Date.now() - startedAt,
500
+ timedOut: false,
501
+ toolUsage: toSortedToolUsage(toolUsageByName),
502
+ });
503
+ return;
504
+ }
505
+ if (code !== 0) {
506
+ resolve({
507
+ ok: false,
508
+ error: stderr.trim() || `Pi exited with status ${String(code)}`,
509
+ durationMs: Date.now() - startedAt,
510
+ timedOut: false,
511
+ toolUsage: toSortedToolUsage(toolUsageByName),
512
+ });
513
+ return;
514
+ }
515
+ const output = lastAssistantText.trim();
516
+ if (!output) {
517
+ resolve({
518
+ ok: false,
519
+ error: lastAssistantError ||
520
+ stderr.trim() ||
521
+ 'Pi session did not produce an assistant response',
522
+ durationMs: Date.now() - startedAt,
523
+ timedOut: false,
524
+ toolUsage: toSortedToolUsage(toolUsageByName),
525
+ });
526
+ return;
527
+ }
441
528
  resolve({
442
- ok: false,
443
- error: lastAssistantError ||
444
- stderr.trim() ||
445
- 'Pi session did not produce an assistant response',
529
+ ok: true,
530
+ output,
446
531
  durationMs: Date.now() - startedAt,
447
- timedOut: false,
448
532
  toolUsage: toSortedToolUsage(toolUsageByName),
449
533
  });
450
- return;
451
- }
452
- resolve({
453
- ok: true,
454
- output,
455
- durationMs: Date.now() - startedAt,
456
- toolUsage: toSortedToolUsage(toolUsageByName),
457
534
  });
458
535
  });
459
- });
460
- cleanup();
461
- return result;
536
+ }
537
+ finally {
538
+ cleanup();
539
+ openRouterSessionExtension.cleanup();
540
+ }
462
541
  },
463
542
  };
464
543
  }
465
- export async function invokePiPrompt({ provider, apiKey, piProviderId, prompt, cwd, modelName, timeoutMs = 5 * 60 * 1000, extraEnv, images, capabilityPreset, thinkingLevel, systemPrompt, onProgress, }) {
544
+ export async function invokePiPrompt({ provider, apiKey, piProviderId, prompt, cwd, modelName, timeoutMs = 5 * 60 * 1000, extraEnv, images, capabilityPreset, thinkingLevel, systemPrompt, noContextFiles, openRouterSessionId, onProgress, }) {
466
545
  return await createPiInvoker({
467
546
  provider,
468
547
  apiKey,
@@ -472,6 +551,8 @@ export async function invokePiPrompt({ provider, apiKey, piProviderId, prompt, c
472
551
  capabilityPreset,
473
552
  thinkingLevel,
474
553
  systemPrompt,
554
+ noContextFiles,
555
+ openRouterSessionId,
475
556
  onProgress,
476
557
  }).invoke({
477
558
  prompt,
@@ -0,0 +1,15 @@
1
+ import { context, trace } from '@opentelemetry/api';
2
+ import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';
3
+ import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
4
+ import { LoggerSpanProcessor } from './logger-span-processor.js';
5
+ let registered = false;
6
+ export function setupTracing() {
7
+ if (registered)
8
+ return;
9
+ registered = true;
10
+ context.setGlobalContextManager(new AsyncLocalStorageContextManager());
11
+ const provider = new BasicTracerProvider({
12
+ spanProcessors: [new LoggerSpanProcessor()],
13
+ });
14
+ trace.setGlobalTracerProvider(provider);
15
+ }