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