@doist/doistbot-cli 1.0.4 → 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 (82) hide show
  1. package/dist/actions/auth.js +62 -28
  2. package/dist/actions/doctor.js +16 -11
  3. package/dist/actions/review.js +77 -11
  4. package/dist/auth.js +53 -8
  5. package/dist/config.js +40 -6
  6. package/dist/env.js +1 -1
  7. package/dist/file-store.js +1 -1
  8. package/dist/runtime.js +1 -1
  9. package/dist/terminal.js +2 -7
  10. package/package.json +7 -7
  11. package/sandbox/_node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
  12. package/sandbox/_node_modules/doistbot-repo-config/dist/index.js +9 -85
  13. package/sandbox/dist/core/bot-logins.js +0 -13
  14. package/sandbox/dist/core/check-run.js +16 -0
  15. package/sandbox/dist/core/datadog-metrics.js +6 -54
  16. package/sandbox/dist/core/logger-span-processor.js +78 -0
  17. package/sandbox/dist/core/pi.js +270 -186
  18. package/sandbox/dist/core/tracing-setup.js +15 -0
  19. package/sandbox/dist/core/tracing.js +71 -0
  20. package/sandbox/dist/main.js +4 -1
  21. package/sandbox/dist/providers/config.js +1 -1
  22. package/sandbox/dist/providers/credentials.js +40 -0
  23. package/sandbox/dist/providers/gemini.js +8 -10
  24. package/sandbox/dist/providers/helpers.js +18 -38
  25. package/sandbox/dist/providers/index.js +4 -3
  26. package/sandbox/dist/providers/openai.js +1 -8
  27. package/sandbox/dist/providers/openrouter-pi.js +25 -0
  28. package/sandbox/dist/providers/pi-review.js +28 -55
  29. package/sandbox/dist/providers/schemas.js +0 -39
  30. package/sandbox/dist/tasks/chat/chat.js +162 -101
  31. package/sandbox/dist/tasks/issue-fix-retry/fix-retry.js +210 -185
  32. package/sandbox/dist/tasks/issue-summarize/context.js +1 -1
  33. package/sandbox/dist/tasks/issue-summarize/model.js +4 -2
  34. package/sandbox/dist/tasks/issue-summarize/summarize.js +1 -1
  35. package/sandbox/dist/tasks/issue-triage/clone.js +40 -0
  36. package/sandbox/dist/tasks/issue-triage/context.js +25 -2
  37. package/sandbox/dist/tasks/issue-triage/fix-attempt.js +31 -8
  38. package/sandbox/dist/tasks/issue-triage/fix-dispatch.js +154 -101
  39. package/sandbox/dist/tasks/issue-triage/fix-loop/runners.js +15 -6
  40. package/sandbox/dist/tasks/issue-triage/model.js +120 -36
  41. package/sandbox/dist/tasks/issue-triage/output.js +27 -0
  42. package/sandbox/dist/tasks/issue-triage/pr-creator.js +3 -2
  43. package/sandbox/dist/tasks/issue-triage/resolution.js +26 -0
  44. package/sandbox/dist/tasks/issue-triage/routing.js +9 -9
  45. package/sandbox/dist/tasks/issue-triage/side-effects.js +9 -0
  46. package/sandbox/dist/tasks/issue-triage/triage.js +288 -418
  47. package/sandbox/dist/tasks/persist-logs/dd-proxy.js +2 -2
  48. package/sandbox/dist/tasks/persist-logs/llm-query-planner.js +1 -1
  49. package/sandbox/dist/tasks/persist-logs/persist-logs.js +1 -1
  50. package/sandbox/dist/tasks/review/check-run.js +2 -1
  51. package/sandbox/dist/tasks/review/conversation-context.js +15 -8
  52. package/sandbox/dist/tasks/review/engines/dedupe.js +19 -9
  53. package/sandbox/dist/tasks/review/engines/multi-focus.js +62 -36
  54. package/sandbox/dist/tasks/review/engines/shared.js +15 -61
  55. package/sandbox/dist/tasks/review/github-comment-format.js +64 -10
  56. package/sandbox/dist/tasks/review/local.js +2 -29
  57. package/sandbox/dist/tasks/review/prompt.js +2 -49
  58. package/sandbox/dist/tasks/review/review-summary.js +120 -0
  59. package/sandbox/dist/tasks/review/review.js +49 -69
  60. package/sandbox/dist/tasks/review/runner.js +57 -52
  61. package/sandbox/dist/tasks/review/summary-model.js +172 -16
  62. package/sandbox/dist/tasks/review/thinking-level.js +4 -2
  63. package/sandbox/node_modules/doistbot-repo-config/dist/index.d.ts +5 -23
  64. package/sandbox/node_modules/doistbot-repo-config/dist/index.js +9 -85
  65. package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
  66. package/sandbox/_node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
  67. package/sandbox/dist/providers/anthropic.js +0 -30
  68. package/sandbox/dist/tasks/review/author-format.js +0 -14
  69. package/sandbox/dist/tasks/review/author-profile.js +0 -67
  70. package/sandbox/dist/tasks/review/council/normalizer.js +0 -104
  71. package/sandbox/dist/tasks/review/council/orchestrator.js +0 -194
  72. package/sandbox/dist/tasks/review/council/provider-health.js +0 -31
  73. package/sandbox/dist/tasks/review/council/summary.js +0 -192
  74. package/sandbox/dist/tasks/review/council/types.js +0 -4
  75. package/sandbox/dist/tasks/review/council/voter.js +0 -384
  76. package/sandbox/dist/tasks/review/engines/council.js +0 -126
  77. package/sandbox/dist/tasks/review/engines/index.js +0 -12
  78. package/sandbox/dist/tasks/review/review-engine.js +0 -5
  79. package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.d.ts +0 -11
  80. package/sandbox/node_modules/doistbot-repo-config/dist/review-engine.js +0 -41
  81. package/sandbox/review-prompt.md +0 -209
  82. package/sandbox/vote-prompt.md +0 -69
@@ -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 = {
6
+ export const PI_PROVIDER_BY_DOISTBOT_PROVIDER = {
7
+ deepseek: 'openrouter',
7
8
  gemini: 'google',
9
+ zai: 'openrouter',
8
10
  openai: 'openai',
9
- anthropic: 'anthropic',
10
- };
11
- const PI_API_KEY_ENV_VAR_BY_PROVIDER = {
12
- gemini: 'GEMINI_API_KEY',
13
- openai: 'OPENAI_API_KEY',
14
- anthropic: 'ANTHROPIC_API_KEY',
15
11
  };
16
12
  export const DEFAULT_PI_MODELS = {
13
+ deepseek: 'deepseek-v4-pro',
17
14
  gemini: 'gemini-3.1-pro-preview',
18
- openai: 'gpt-5.4',
19
- anthropic: 'claude-opus-4-6',
15
+ zai: 'glm5.2',
16
+ openai: 'gpt-5.5',
17
+ };
18
+ export const DEFAULT_OPENROUTER_MODELS = {
19
+ deepseek: 'deepseek/deepseek-v4-pro',
20
+ gemini: 'google/gemini-3.1-pro-preview',
21
+ zai: 'z-ai/glm-5.2',
20
22
  };
21
23
  export const MAX_PI_THINKING_BY_PROVIDER = {
24
+ deepseek: 'high',
22
25
  gemini: 'high',
26
+ zai: 'high',
23
27
  openai: 'xhigh',
24
- anthropic: 'xhigh',
25
28
  };
26
- const PI_PROVIDER_IDS = new Set(['google', 'openai', 'openai-codex', 'anthropic']);
29
+ const PI_API_KEY_ENV_VAR_BY_PI_PROVIDER = {
30
+ google: 'GEMINI_API_KEY',
31
+ openai: 'OPENAI_API_KEY',
32
+ 'openai-codex': 'OPENAI_API_KEY',
33
+ openrouter: 'OPENROUTER_API_KEY',
34
+ };
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) {
@@ -62,10 +74,12 @@ function canExecuteNode(candidate) {
62
74
  }
63
75
  function getPiModelSpec({ provider, piProviderId, modelName, thinkingLevel, }) {
64
76
  const providerId = piProviderId ?? PI_PROVIDER_BY_DOISTBOT_PROVIDER[provider];
65
- const resolvedModelName = modelName ?? DEFAULT_PI_MODELS[provider];
77
+ const resolvedModelName = modelName ??
78
+ (providerId === 'openrouter' ? DEFAULT_OPENROUTER_MODELS[provider] : undefined) ??
79
+ DEFAULT_PI_MODELS[provider];
66
80
  return `${providerId}/${resolvedModelName}:${thinkingLevel}`;
67
81
  }
68
- function parsePiAuthProviderId(apiKey) {
82
+ export function parsePiAuthProviderId(apiKey) {
69
83
  if (!apiKey.startsWith(PI_AUTH_API_KEY_PREFIX)) {
70
84
  return undefined;
71
85
  }
@@ -110,6 +124,54 @@ function createImageArgs(images) {
110
124
  },
111
125
  };
112
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
+ }
113
175
  function extractTextSegments(content) {
114
176
  if (!Array.isArray(content)) {
115
177
  return [];
@@ -176,7 +238,8 @@ function maybeEmitProgress({ event, onProgress, lastThinkingAt, lastRespondingAt
176
238
  case 'tool_execution_start': {
177
239
  const toolName = event.toolName;
178
240
  if (typeof toolName === 'string' && toolName.length > 0) {
179
- onProgress({ type: 'tool_start', toolName });
241
+ const toolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : undefined;
242
+ onProgress({ type: 'tool_start', toolName, ...(toolCallId ? { toolCallId } : {}) });
180
243
  }
181
244
  return;
182
245
  }
@@ -185,23 +248,30 @@ function maybeEmitProgress({ event, onProgress, lastThinkingAt, lastRespondingAt
185
248
  if (typeof toolName !== 'string' || toolName.length === 0) {
186
249
  return;
187
250
  }
188
- const toolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : toolName;
251
+ const rawToolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : undefined;
252
+ const toolCallId = rawToolCallId ?? toolName;
189
253
  const now = Date.now();
190
254
  const lastUpdateAt = lastToolUpdateAt.get(toolCallId) ?? 0;
191
255
  if (now - lastUpdateAt >= PI_PROGRESS_HEARTBEAT_MS) {
192
256
  lastToolUpdateAt.set(toolCallId, now);
193
- onProgress({ type: 'tool_update', toolName });
257
+ onProgress({
258
+ type: 'tool_update',
259
+ toolName,
260
+ ...(rawToolCallId ? { toolCallId: rawToolCallId } : {}),
261
+ });
194
262
  }
195
263
  return;
196
264
  }
197
265
  case 'tool_execution_end': {
198
266
  const toolName = event.toolName;
199
267
  if (typeof toolName === 'string' && toolName.length > 0) {
200
- const toolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : toolName;
268
+ const rawToolCallId = typeof event.toolCallId === 'string' ? event.toolCallId : undefined;
269
+ const toolCallId = rawToolCallId ?? toolName;
201
270
  const startedAt = toolStartedAt.get(toolCallId);
202
271
  onProgress({
203
272
  type: 'tool_end',
204
273
  toolName,
274
+ ...(rawToolCallId ? { toolCallId: rawToolCallId } : {}),
205
275
  isError: event.isError === true,
206
276
  durationMs: startedAt == null ? undefined : Math.max(0, Date.now() - startedAt),
207
277
  });
@@ -217,18 +287,23 @@ function toSortedToolUsage(toolUsageByName) {
217
287
  return left.toolName.localeCompare(right.toolName);
218
288
  });
219
289
  }
220
- 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, }) {
221
291
  return {
222
292
  async invoke({ prompt, modelName, timeoutMs = 5 * 60 * 1000, images, }) {
223
293
  const startedAt = Date.now();
224
294
  const piAuthProviderId = parsePiAuthProviderId(apiKey);
295
+ const resolvedPiProviderId = piAuthProviderId ?? piProviderId ?? PI_PROVIDER_BY_DOISTBOT_PROVIDER[provider];
225
296
  const modelSpec = getPiModelSpec({
226
297
  provider,
227
- piProviderId: piAuthProviderId ?? piProviderId,
298
+ piProviderId: resolvedPiProviderId,
228
299
  modelName,
229
300
  thinkingLevel: thinkingLevel ?? MAX_PI_THINKING_BY_PROVIDER[provider],
230
301
  });
231
302
  const { imageArgs, cleanup } = createImageArgs(images);
303
+ const openRouterSessionExtension = createOpenRouterSessionExtension({
304
+ piProviderId: resolvedPiProviderId,
305
+ sessionId: openRouterSessionId,
306
+ });
232
307
  const tools = PI_TOOLS_BY_PRESET[capabilityPreset].join(',');
233
308
  const args = [
234
309
  PI_CLI_PATH,
@@ -238,12 +313,14 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
238
313
  '--no-extensions',
239
314
  '--no-skills',
240
315
  '--no-prompt-templates',
316
+ ...(noContextFiles ? ['--no-context-files'] : []),
241
317
  '--model',
242
318
  modelSpec,
243
319
  '--tools',
244
320
  tools,
245
321
  '--system-prompt',
246
322
  systemPrompt,
323
+ ...openRouterSessionExtension.args,
247
324
  ...imageArgs,
248
325
  ];
249
326
  const childEnv = {
@@ -252,8 +329,9 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
252
329
  PATH: process.env.PATH,
253
330
  GIT_TERMINAL_PROMPT: '0',
254
331
  ...extraEnv,
332
+ ...openRouterSessionExtension.extraEnv,
255
333
  };
256
- const apiKeyEnvVar = PI_API_KEY_ENV_VAR_BY_PROVIDER[provider];
334
+ const apiKeyEnvVar = PI_API_KEY_ENV_VAR_BY_PI_PROVIDER[resolvedPiProviderId];
257
335
  if (piAuthProviderId) {
258
336
  delete childEnv[apiKeyEnvVar];
259
337
  }
@@ -261,205 +339,209 @@ export function createPiInvoker({ provider, apiKey, piProviderId, cwd, extraEnv,
261
339
  childEnv[apiKeyEnvVar] = apiKey;
262
340
  }
263
341
  delete childEnv.GITHUB_TOKEN;
264
- const child = spawn(resolveNodeExecutable(), args, {
265
- cwd,
266
- env: childEnv,
267
- stdio: ['pipe', 'pipe', 'pipe'],
268
- });
269
- child.stdin?.on('error', () => { });
270
- child.stdin?.end(prompt);
271
- let timedOut = false;
272
- let stdoutBuffer = '';
273
- let stderr = '';
274
- let lastAssistantText = '';
275
- let lastAssistantError = '';
276
- let sessionHeaderSeen = false;
277
- const lastThinkingAt = { value: 0 };
278
- const lastRespondingAt = { value: 0 };
279
- const lastToolUpdateAt = new Map();
280
- const toolStartedAt = new Map();
281
- const toolUsageByName = new Map();
282
- function parseStdout(chunk) {
283
- stdoutBuffer += chunk;
284
- const lines = stdoutBuffer.split(/\r?\n/);
285
- stdoutBuffer = lines.pop() ?? '';
286
- for (const line of lines) {
287
- const trimmed = line.trim();
288
- if (!trimmed) {
289
- continue;
290
- }
291
- try {
292
- const parsed = JSON.parse(trimmed);
293
- if (parsed.type === 'session') {
294
- 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) {
295
368
  continue;
296
369
  }
297
- maybeEmitProgress({
298
- event: parsed,
299
- onProgress,
300
- lastThinkingAt,
301
- lastRespondingAt,
302
- lastToolUpdateAt,
303
- toolStartedAt,
304
- });
305
- if (parsed.type === 'tool_execution_start') {
306
- const toolName = typeof parsed.toolName === 'string' ? parsed.toolName : null;
307
- if (toolName) {
308
- const toolCallId = typeof parsed.toolCallId === 'string'
309
- ? parsed.toolCallId
310
- : toolName;
311
- toolStartedAt.set(toolCallId, Date.now());
312
- const current = toolUsageByName.get(toolName);
313
- if (current) {
314
- 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
+ }
315
403
  }
316
- else {
317
- 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) ?? {
318
409
  toolName,
319
- calls: 1,
410
+ calls: 0,
320
411
  totalDurationMs: 0,
321
412
  errorCount: 0,
322
- });
323
- }
324
- }
325
- }
326
- if (parsed.type === 'tool_execution_end') {
327
- const toolName = typeof parsed.toolName === 'string' ? parsed.toolName : null;
328
- if (toolName) {
329
- const current = toolUsageByName.get(toolName) ?? {
330
- toolName,
331
- calls: 0,
332
- totalDurationMs: 0,
333
- errorCount: 0,
334
- };
335
- const toolCallId = typeof parsed.toolCallId === 'string'
336
- ? parsed.toolCallId
337
- : toolName;
338
- const startedAt = toolStartedAt.get(toolCallId);
339
- toolStartedAt.delete(toolCallId);
340
- if (startedAt != null) {
341
- current.totalDurationMs += Math.max(0, Date.now() - startedAt);
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);
342
426
  }
343
- if (parsed.isError === true) {
344
- current.errorCount += 1;
345
- }
346
- toolUsageByName.set(toolName, current);
347
- }
348
- }
349
- if (parsed.type === 'message_end') {
350
- const result = extractAssistantMessageResult(parsed.message);
351
- if (result?.text) {
352
- lastAssistantText = result.text;
353
- }
354
- else if (result?.error) {
355
- lastAssistantError = result.error;
356
427
  }
357
- continue;
358
- }
359
- if (parsed.type === 'agent_end' && Array.isArray(parsed.messages)) {
360
- for (const message of parsed.messages) {
361
- const result = extractAssistantMessageResult(message);
428
+ if (parsed.type === 'message_end') {
429
+ const result = extractAssistantMessageResult(parsed.message);
362
430
  if (result?.text) {
363
431
  lastAssistantText = result.text;
364
432
  }
365
433
  else if (result?.error) {
366
434
  lastAssistantError = result.error;
367
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
+ }
368
448
  }
369
449
  }
370
- }
371
- catch {
372
- if (!sessionHeaderSeen && !lastAssistantText) {
373
- lastAssistantText = trimmed;
450
+ catch {
451
+ if (!sessionHeaderSeen && !lastAssistantText) {
452
+ lastAssistantText = trimmed;
453
+ }
374
454
  }
375
455
  }
376
456
  }
377
- }
378
- const result = await new Promise((resolve) => {
379
- const timeoutHandle = setTimeout(() => {
380
- timedOut = true;
381
- child.kill('SIGTERM');
382
- }, timeoutMs);
383
- child.stdout.setEncoding('utf8');
384
- child.stdout.on('data', (chunk) => {
385
- parseStdout(chunk);
386
- });
387
- child.stderr.setEncoding('utf8');
388
- child.stderr.on('data', (chunk) => {
389
- stderr += chunk;
390
- });
391
- child.on('error', (error) => {
392
- clearTimeout(timeoutHandle);
393
- resolve({
394
- ok: false,
395
- error: error.message,
396
- durationMs: Date.now() - startedAt,
397
- timedOut,
398
- 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);
399
465
  });
400
- });
401
- child.on('close', (code, signal) => {
402
- clearTimeout(timeoutHandle);
403
- if (stdoutBuffer.trim()) {
404
- parseStdout('\n');
405
- }
406
- if (timedOut) {
407
- resolve({
408
- ok: false,
409
- error: `Pi request timed out after ${timeoutMs}ms`,
410
- durationMs: Date.now() - startedAt,
411
- timedOut: true,
412
- toolUsage: toSortedToolUsage(toolUsageByName),
413
- });
414
- return;
415
- }
416
- if (signal) {
417
- resolve({
418
- ok: false,
419
- error: `Pi process terminated with signal ${signal}`,
420
- durationMs: Date.now() - startedAt,
421
- timedOut: false,
422
- toolUsage: toSortedToolUsage(toolUsageByName),
423
- });
424
- return;
425
- }
426
- 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);
427
472
  resolve({
428
473
  ok: false,
429
- error: stderr.trim() || `Pi exited with status ${String(code)}`,
474
+ error: error.message,
430
475
  durationMs: Date.now() - startedAt,
431
- timedOut: false,
476
+ timedOut,
432
477
  toolUsage: toSortedToolUsage(toolUsageByName),
433
478
  });
434
- return;
435
- }
436
- const output = lastAssistantText.trim();
437
- 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
+ }
438
528
  resolve({
439
- ok: false,
440
- error: lastAssistantError ||
441
- stderr.trim() ||
442
- 'Pi session did not produce an assistant response',
529
+ ok: true,
530
+ output,
443
531
  durationMs: Date.now() - startedAt,
444
- timedOut: false,
445
532
  toolUsage: toSortedToolUsage(toolUsageByName),
446
533
  });
447
- return;
448
- }
449
- resolve({
450
- ok: true,
451
- output,
452
- durationMs: Date.now() - startedAt,
453
- toolUsage: toSortedToolUsage(toolUsageByName),
454
534
  });
455
535
  });
456
- });
457
- cleanup();
458
- return result;
536
+ }
537
+ finally {
538
+ cleanup();
539
+ openRouterSessionExtension.cleanup();
540
+ }
459
541
  },
460
542
  };
461
543
  }
462
- 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, }) {
463
545
  return await createPiInvoker({
464
546
  provider,
465
547
  apiKey,
@@ -469,6 +551,8 @@ export async function invokePiPrompt({ provider, apiKey, piProviderId, prompt, c
469
551
  capabilityPreset,
470
552
  thinkingLevel,
471
553
  systemPrompt,
554
+ noContextFiles,
555
+ openRouterSessionId,
472
556
  onProgress,
473
557
  }).invoke({
474
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
+ }