@pasko70/pibo 3.4.2 → 3.4.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.
- package/dist/agent-runtime/routed-session.js +4 -1
- package/dist/agent-runtimes/codex-native/adapter.js +14 -1
- package/dist/agent-runtimes/codex-native/models.js +4 -4
- package/dist/agent-runtimes/codex-native/process.js +32 -13
- package/dist/agent-runtimes/codex-native/provider-usage.js +115 -0
- package/dist/agent-runtimes/pi/routed-session.js +2 -0
- package/dist/apps/chat/chat-settings-routes.js +10 -0
- package/dist/apps/chat-ui/assets/{dist-BDnWOTcs.js → dist-23lt_7qm.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CCR1HsaR.js → dist-Bf2KScPo.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B7Ju7u08.js → dist-CH3SvpYV.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CXWLhBio.js → dist-DsgL8w-W.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CAiO6h6z.js → dist-V06sfuZa.js} +1 -1
- package/dist/apps/chat-ui/assets/index-BOceJ0jM.css +1 -0
- package/dist/apps/chat-ui/assets/index-BOemYq-V.js +228 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-CMwTB8o8.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/core/session-router.js +1 -0
- package/dist/core/user-settings.js +11 -0
- package/dist/gateway/cli.js +104 -16
- package/dist/session-ui/terminalRows.js +7 -7
- package/dist/sessions/pibo-data-store.js +29 -10
- package/dist/shared/tool-call-metrics.js +68 -7
- package/dist/shared/tool-call-token-settings.js +45 -0
- package/dist/web/channel.js +5 -2
- package/npm-shrinkwrap.json +22 -13
- package/package.json +2 -1
- package/dist/apps/chat-ui/assets/index-0cCGS0o3.js +0 -228
- package/dist/apps/chat-ui/assets/index-BMJGoyM8.css +0 -1
- package/dist/apps/chat-vscode-web/assets/index-BD3Ogz9Z.js +0 -43
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-CMwTB8o8.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-b18ZkEo0.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
@@ -1552,6 +1552,7 @@ export class PiboSessionRouter {
|
|
|
1552
1552
|
return result;
|
|
1553
1553
|
},
|
|
1554
1554
|
statusResources,
|
|
1555
|
+
getToolMetricTokenCalculation: () => loadPiboUserSettings().toolMetrics.tokenCalculation,
|
|
1555
1556
|
});
|
|
1556
1557
|
this.sessions.set(piboSession.id, session);
|
|
1557
1558
|
return session;
|
|
@@ -4,6 +4,7 @@ import { piboHomePath } from "./pibo-home.js";
|
|
|
4
4
|
import { sanitizePreviewServerSettings } from "./preview-server-settings.js";
|
|
5
5
|
import { sanitizeTelemetryRetentionSettings } from "./telemetry-retention-settings.js";
|
|
6
6
|
import { sanitizeTelemetryStaleThresholdSettings } from "./telemetry-staleness.js";
|
|
7
|
+
import { DEFAULT_TOOL_METRIC_TOKEN_CALCULATION, parseToolMetricTokenCalculation, } from "../shared/tool-call-token-settings.js";
|
|
7
8
|
export const DEFAULT_USER_TIMEZONE = "UTC";
|
|
8
9
|
export const DEFAULT_WEB_ANNOTATIONS_TOGGLE_SHORTCUT = "Alt+Shift+A";
|
|
9
10
|
export const DEFAULT_TRANSCRIPTION_PROVIDER_ID = "openai-chatgpt";
|
|
@@ -64,6 +65,15 @@ export function sanitizeSpeechSettings(value) {
|
|
|
64
65
|
: {};
|
|
65
66
|
return { providerId: sanitizeTranscriptionProviderId(raw.providerId) ?? DEFAULT_SPEECH_PROVIDER_ID };
|
|
66
67
|
}
|
|
68
|
+
export function sanitizeToolMetricSettings(value) {
|
|
69
|
+
const raw = value && typeof value === "object" && !Array.isArray(value)
|
|
70
|
+
? value
|
|
71
|
+
: {};
|
|
72
|
+
return {
|
|
73
|
+
tokenCalculation: parseToolMetricTokenCalculation(raw.tokenCalculation)
|
|
74
|
+
?? DEFAULT_TOOL_METRIC_TOKEN_CALCULATION,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
67
77
|
export function sanitizeTimezone(value) {
|
|
68
78
|
if (typeof value !== "string")
|
|
69
79
|
return undefined;
|
|
@@ -87,6 +97,7 @@ function sanitizeUserSettings(value) {
|
|
|
87
97
|
shortcuts: sanitizeShortcutSettings(raw.shortcuts),
|
|
88
98
|
transcription: sanitizeTranscriptionSettings(raw.transcription),
|
|
89
99
|
speech: sanitizeSpeechSettings(raw.speech),
|
|
100
|
+
toolMetrics: sanitizeToolMetricSettings(raw.toolMetrics),
|
|
90
101
|
previewServers: sanitizePreviewServerSettings(raw.previewServers),
|
|
91
102
|
telemetryStaleThresholds: sanitizeTelemetryStaleThresholdSettings(raw.telemetryStaleThresholds),
|
|
92
103
|
telemetryRetention: sanitizeTelemetryRetentionSettings(raw.telemetryRetention),
|
package/dist/gateway/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, execFile } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync } from "node:fs";
|
|
3
4
|
import { homedir } from "node:os";
|
|
4
5
|
import { join } from "node:path";
|
|
5
6
|
import { promisify } from "node:util";
|
|
@@ -205,11 +206,14 @@ function runtimeTelemetryHint(value) {
|
|
|
205
206
|
}
|
|
206
207
|
function runtimeStatus(value) {
|
|
207
208
|
const obj = objectValue(value);
|
|
208
|
-
if (!obj)
|
|
209
|
+
if (!obj || !stringValue(obj.piboSessionId) || typeof obj.processing !== "boolean" || typeof obj.streaming !== "boolean"
|
|
210
|
+
|| !Number.isInteger(obj.queuedMessages) || Number(obj.queuedMessages) < 0)
|
|
209
211
|
return undefined;
|
|
210
212
|
return {
|
|
211
213
|
piboSessionId: stringValue(obj.piboSessionId),
|
|
212
214
|
queuedMessages: numberValue(obj.queuedMessages),
|
|
215
|
+
activeEventId: stringValue(obj.activeEventId),
|
|
216
|
+
queuedEventIds: Array.isArray(obj.queuedEventIds) && obj.queuedEventIds.every((id) => typeof id === "string") ? obj.queuedEventIds : undefined,
|
|
213
217
|
processing: booleanValue(obj.processing),
|
|
214
218
|
streaming: booleanValue(obj.streaming),
|
|
215
219
|
activeTelemetry: runtimeTelemetryHint(obj.activeTelemetry),
|
|
@@ -217,13 +221,14 @@ function runtimeStatus(value) {
|
|
|
217
221
|
}
|
|
218
222
|
function activeRun(value) {
|
|
219
223
|
const obj = objectValue(value);
|
|
220
|
-
if (!obj)
|
|
224
|
+
if (!obj || !stringValue(obj.runId) || !stringValue(obj.status)
|
|
225
|
+
|| !(stringValue(obj.controllerPiboSessionId) || stringValue(obj.piboSessionId)))
|
|
221
226
|
return undefined;
|
|
222
227
|
return {
|
|
223
228
|
runId: stringValue(obj.runId),
|
|
224
229
|
status: stringValue(obj.status),
|
|
225
230
|
toolName: stringValue(obj.toolName),
|
|
226
|
-
piboSessionId: stringValue(obj.piboSessionId),
|
|
231
|
+
piboSessionId: stringValue(obj.controllerPiboSessionId) ?? stringValue(obj.piboSessionId),
|
|
227
232
|
};
|
|
228
233
|
}
|
|
229
234
|
function parseGatewaySafetyPayload(payload, reachable) {
|
|
@@ -231,7 +236,9 @@ function parseGatewaySafetyPayload(payload, reachable) {
|
|
|
231
236
|
const mode = obj && (obj.mode === "dev" || obj.mode === "prod" || obj.mode === "fallback") ? obj.mode : "unknown";
|
|
232
237
|
const runtimeStatuses = Array.isArray(obj?.runtimeStatuses) ? obj.runtimeStatuses.map(runtimeStatus).filter((item) => Boolean(item)) : [];
|
|
233
238
|
const activeRuns = Array.isArray(obj?.activeRuns) ? obj.activeRuns.map(activeRun).filter((item) => Boolean(item)) : [];
|
|
234
|
-
|
|
239
|
+
const incomplete = !Array.isArray(obj?.runtimeStatuses) || !Array.isArray(obj?.activeRuns)
|
|
240
|
+
|| runtimeStatuses.length !== obj.runtimeStatuses.length || activeRuns.length !== obj.activeRuns.length;
|
|
241
|
+
return { reachable, mode, generation: stringValue(obj?.generation), health: obj?.health, runtimeStatuses, activeRuns, ambiguous: incomplete || booleanValue(obj?.ambiguous) };
|
|
235
242
|
}
|
|
236
243
|
export function checkActiveWork(status, target = "web") {
|
|
237
244
|
const reasons = [];
|
|
@@ -249,7 +256,7 @@ export function checkActiveWork(status, target = "web") {
|
|
|
249
256
|
reasons.push(`${id} is processing`);
|
|
250
257
|
if (session.streaming === true)
|
|
251
258
|
reasons.push(`${id} is streaming`);
|
|
252
|
-
if ((session.queuedMessages ?? 0) > 0)
|
|
259
|
+
if ((session.queuedMessages ?? 0) > 0 || (session.activeTelemetry?.queueDepth ?? 0) > 0)
|
|
253
260
|
reasons.push(`${id} has queued messages`);
|
|
254
261
|
if (session.activeTelemetry?.isStale === true) {
|
|
255
262
|
const phase = session.activeTelemetry.activePhase ? ` in ${session.activeTelemetry.activePhase}` : "";
|
|
@@ -260,6 +267,58 @@ export function checkActiveWork(status, target = "web") {
|
|
|
260
267
|
reasons.push(`${run.runId ?? "yielded run"} is ${run.status ?? "active"}`);
|
|
261
268
|
return { unsafe: reasons.length > 0, reasons };
|
|
262
269
|
}
|
|
270
|
+
function blockingSessions(status) {
|
|
271
|
+
return status.runtimeStatuses.filter((session) => session.processing || session.streaming
|
|
272
|
+
|| (session.queuedMessages ?? 0) > 0 || (session.activeTelemetry?.queueDepth ?? 0) > 0 || session.activeTelemetry?.isStale);
|
|
273
|
+
}
|
|
274
|
+
export function restartConfirmationToken(status) {
|
|
275
|
+
if (!status.reachable || status.error || status.ambiguous || status.mode !== "prod" || !status.generation)
|
|
276
|
+
return undefined;
|
|
277
|
+
const sessions = blockingSessions(status);
|
|
278
|
+
if (status.runtimeStatuses.some((session) => !session.piboSessionId || typeof session.processing !== "boolean"
|
|
279
|
+
|| typeof session.streaming !== "boolean" || !Number.isInteger(session.queuedMessages) || session.queuedMessages < 0))
|
|
280
|
+
return undefined;
|
|
281
|
+
if (sessions.some((session) => ((session.processing || session.streaming || session.activeTelemetry?.isStale)
|
|
282
|
+
&& !(session.activeEventId || session.activeTelemetry?.activeTurnId))
|
|
283
|
+
|| (session.queuedMessages > 0 && (session.queuedEventIds?.length !== session.queuedMessages || session.queuedEventIds?.some((id) => !id)))
|
|
284
|
+
|| (session.activeTelemetry?.queueDepth ?? 0) > (session.queuedMessages ?? 0)))
|
|
285
|
+
return undefined;
|
|
286
|
+
if (status.activeRuns.some((run) => !run.runId || !run.piboSessionId || !run.status))
|
|
287
|
+
return undefined;
|
|
288
|
+
// Exclude clock/progress counters: approval describes work identities, not polling time.
|
|
289
|
+
const snapshot = {
|
|
290
|
+
generation: status.generation,
|
|
291
|
+
target: "web", port: targetPort("web"), service: gatewayServiceName("web"), home: managedGatewayHome("web"),
|
|
292
|
+
sessions: sessions.map((session) => ({
|
|
293
|
+
id: session.piboSessionId, activeEventId: session.activeEventId,
|
|
294
|
+
processing: session.processing, streaming: session.streaming,
|
|
295
|
+
queuedEventIds: [...(session.queuedEventIds ?? [])], queuedMessages: session.queuedMessages,
|
|
296
|
+
telemetryTurnId: session.activeTelemetry?.activeTurnId,
|
|
297
|
+
stale: session.activeTelemetry?.isStale === true,
|
|
298
|
+
})).sort((a, b) => a.id.localeCompare(b.id)),
|
|
299
|
+
runs: [...status.activeRuns].sort((a, b) => a.runId.localeCompare(b.runId)),
|
|
300
|
+
};
|
|
301
|
+
return `${RESTART_CONFIRMATION_TOKEN}:${createHash("sha256").update(JSON.stringify(snapshot)).digest("hex")}`;
|
|
302
|
+
}
|
|
303
|
+
function printRestartApproval(status) {
|
|
304
|
+
const token = restartConfirmationToken(status);
|
|
305
|
+
if (token) {
|
|
306
|
+
console.log("Review the listed work and obtain user approval before running:");
|
|
307
|
+
console.log(` pibo gateway web restart --force --confirm ${token}`);
|
|
308
|
+
}
|
|
309
|
+
else
|
|
310
|
+
console.log("Snapshot approval unavailable: status must identify the gateway generation and all active work. Inspect: pibo gateway web doctor");
|
|
311
|
+
}
|
|
312
|
+
function auditRestart(status, decision, force, reason) {
|
|
313
|
+
const home = managedGatewayHome("web");
|
|
314
|
+
mkdirSync(home, { recursive: true });
|
|
315
|
+
appendFileSync(join(home, "gateway-restart-audit.jsonl"), `${JSON.stringify({
|
|
316
|
+
at: new Date().toISOString(), decision, force, reason, snapshot: restartConfirmationToken(status),
|
|
317
|
+
generation: status.generation,
|
|
318
|
+
sessionIds: [...new Set([...blockingSessions(status).map((session) => session.piboSessionId), ...status.activeRuns.map((run) => run.piboSessionId)].filter(Boolean))],
|
|
319
|
+
runIds: status.activeRuns.map((run) => run.runId), reasons: checkActiveWork(status).reasons,
|
|
320
|
+
})}\n`, { mode: 0o600 });
|
|
321
|
+
}
|
|
263
322
|
async function readGatewaySafetyStatus(target) {
|
|
264
323
|
const port = targetPort(target);
|
|
265
324
|
const reachable = await isPortReachable("127.0.0.1", port, 1500);
|
|
@@ -281,6 +340,10 @@ function printSafetyStatus(target, status) {
|
|
|
281
340
|
console.log(` runtime sessions: ${status.runtimeStatuses.length}`);
|
|
282
341
|
for (const session of status.runtimeStatuses) {
|
|
283
342
|
console.log(` ${session.piboSessionId ?? "unknown"}: processing=${session.processing === true} streaming=${session.streaming === true} queued=${session.queuedMessages ?? 0}`);
|
|
343
|
+
if (session.activeEventId)
|
|
344
|
+
console.log(` active event: ${session.activeEventId}`);
|
|
345
|
+
if (session.queuedEventIds?.length)
|
|
346
|
+
console.log(` queued events: ${session.queuedEventIds.join(", ")}`);
|
|
284
347
|
if (session.activeTelemetry) {
|
|
285
348
|
const parts = [
|
|
286
349
|
session.activeTelemetry.activePhase ? `phase=${session.activeTelemetry.activePhase}` : undefined,
|
|
@@ -295,7 +358,7 @@ function printSafetyStatus(target, status) {
|
|
|
295
358
|
}
|
|
296
359
|
console.log(` active yielded runs: ${status.activeRuns.length}`);
|
|
297
360
|
for (const run of status.activeRuns)
|
|
298
|
-
console.log(` ${run.runId ?? "unknown"}: ${run.status ?? "active"}${run.toolName ? ` (${run.toolName})` : ""}`);
|
|
361
|
+
console.log(` ${run.runId ?? "unknown"}: ${run.status ?? "active"}${run.toolName ? ` (${run.toolName})` : ""} session=${run.piboSessionId ?? "unknown"}`);
|
|
299
362
|
}
|
|
300
363
|
function managerRequiresShell(command) {
|
|
301
364
|
return process.platform === "win32" && /\.(?:cmd|bat)$/i.test(command);
|
|
@@ -332,6 +395,7 @@ async function runManagedGatewayCommand(target, command, args, argv = process.ar
|
|
|
332
395
|
}
|
|
333
396
|
else
|
|
334
397
|
console.log(" restart safety: idle");
|
|
398
|
+
printRestartApproval(status);
|
|
335
399
|
}
|
|
336
400
|
if (command === "doctor")
|
|
337
401
|
process.exitCode = status.reachable && !status.error && status.mode === expectedMode(target) ? 0 : 1;
|
|
@@ -380,19 +444,43 @@ async function runManagedGatewayCommand(target, command, args, argv = process.ar
|
|
|
380
444
|
const force = args.includes("--force");
|
|
381
445
|
const confirmIndex = args.indexOf("--confirm");
|
|
382
446
|
const confirmation = confirmIndex >= 0 ? args[confirmIndex + 1] : undefined;
|
|
383
|
-
if (force && confirmation !== RESTART_CONFIRMATION_TOKEN) {
|
|
447
|
+
if (target === "dev" && force && confirmation !== RESTART_CONFIRMATION_TOKEN) {
|
|
384
448
|
console.error(`Force restart requires: --confirm ${RESTART_CONFIRMATION_TOKEN}`);
|
|
385
449
|
process.exitCode = 1;
|
|
386
450
|
return true;
|
|
387
451
|
}
|
|
388
|
-
if (target === "web"
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
452
|
+
if (target === "web") {
|
|
453
|
+
// Both normal and delayed/forced commands inspect again at execution time.
|
|
454
|
+
let current = await readGatewaySafetyStatus(target);
|
|
455
|
+
for (let inspection = 0; inspection < 2; inspection += 1) {
|
|
456
|
+
const active = checkActiveWork(current, target);
|
|
457
|
+
const token = restartConfirmationToken(current);
|
|
458
|
+
printSafetyStatus(target, current);
|
|
394
459
|
for (const reason of active.reasons)
|
|
395
460
|
console.error(`- ${reason}`);
|
|
461
|
+
const blocked = force ? !token || confirmation !== token : active.unsafe;
|
|
462
|
+
if (blocked) {
|
|
463
|
+
const reason = force ? "Snapshot approval is missing, unavailable, or changed." : "Active work or unavailable gateway status.";
|
|
464
|
+
console.error(`Restart blocked: ${reason}`);
|
|
465
|
+
console.error("Ask the user before interrupting active sessions. Obtain fresh approval with: pibo gateway web status");
|
|
466
|
+
printRestartApproval(current);
|
|
467
|
+
try {
|
|
468
|
+
auditRestart(current, "blocked", force, reason);
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
console.error(`Restart audit failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
472
|
+
}
|
|
473
|
+
process.exitCode = 1;
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
if (inspection === 0)
|
|
477
|
+
current = await readGatewaySafetyStatus(target);
|
|
478
|
+
}
|
|
479
|
+
try {
|
|
480
|
+
auditRestart(current, "approved", force, force ? "Unchanged snapshot explicitly approved" : "Gateway idle");
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
console.error(`Restart blocked: cannot record restart audit: ${error instanceof Error ? error.message : String(error)}`);
|
|
396
484
|
process.exitCode = 1;
|
|
397
485
|
return true;
|
|
398
486
|
}
|
|
@@ -642,8 +730,8 @@ Commands:
|
|
|
642
730
|
dev doctor Check dev gateway health
|
|
643
731
|
|
|
644
732
|
Options:
|
|
645
|
-
--force --confirm
|
|
646
|
-
|
|
733
|
+
--force --confirm <snapshot-token>
|
|
734
|
+
Restart only the work explicitly approved from web status
|
|
647
735
|
|
|
648
736
|
Next:
|
|
649
737
|
pibo gateway web status
|
|
@@ -41,14 +41,7 @@ function applyToolDisplayMode(rows, mode) {
|
|
|
41
41
|
const slimRow = {
|
|
42
42
|
...row,
|
|
43
43
|
lines: row.lines.slice(0, 1),
|
|
44
|
-
input: undefined,
|
|
45
|
-
output: undefined,
|
|
46
|
-
error: undefined,
|
|
47
|
-
markdown: undefined,
|
|
48
|
-
expandable: false,
|
|
49
44
|
singleLine: true,
|
|
50
|
-
previewOmission: undefined,
|
|
51
|
-
detailItems: undefined,
|
|
52
45
|
};
|
|
53
46
|
if (mode !== "intent")
|
|
54
47
|
return [slimRow];
|
|
@@ -60,6 +53,13 @@ function applyToolDisplayMode(rows, mode) {
|
|
|
60
53
|
prefix: "bullet",
|
|
61
54
|
tokens: [token(intent, row.status === "error" ? "red" : row.status === "done" ? "green" : "cyan", "semibold")],
|
|
62
55
|
}],
|
|
56
|
+
input: undefined,
|
|
57
|
+
output: undefined,
|
|
58
|
+
error: undefined,
|
|
59
|
+
markdown: undefined,
|
|
60
|
+
expandable: false,
|
|
61
|
+
previewOmission: undefined,
|
|
62
|
+
detailItems: undefined,
|
|
63
63
|
}];
|
|
64
64
|
});
|
|
65
65
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChatDataIngestService } from "../data/ingest-service.js";
|
|
1
|
+
import { ChatDataIngestService, outputIdempotencyKey } from "../data/ingest-service.js";
|
|
2
2
|
import { PiboDataStore } from "../data/pibo-store.js";
|
|
3
3
|
import { PIBO_AGENT_OBSERVATION_AUTO_CURSOR_MAX_SCOPES, createPiboSession, matchesFindInput, } from "./store.js";
|
|
4
4
|
import { createLegacyPiRuntimeSessionBinding, nextRuntimeSessionBinding, RuntimeSessionBindingConflictError, } from "./runtime-binding.js";
|
|
@@ -469,9 +469,26 @@ export class PiboDataSessionStore {
|
|
|
469
469
|
const at = input.at ?? new Date().toISOString();
|
|
470
470
|
const runsBySession = groupRunsByController(input.recoveredRuns ?? []);
|
|
471
471
|
return this.dataStore.transaction(() => {
|
|
472
|
+
// Durable terminal output wins over stale telemetry. Do not send a different
|
|
473
|
+
// recovery payload through the same output identity during startup.
|
|
474
|
+
const terminalErrors = new Map();
|
|
472
475
|
const recoveredTurns = this.dataStore.telemetry.recoverInterruptedTurns({
|
|
473
476
|
at,
|
|
474
|
-
resolveOutcome: (turn) =>
|
|
477
|
+
resolveOutcome: (turn) => {
|
|
478
|
+
const identity = { type: "session_error", piboSessionId: turn.piboSessionId, eventId: recoveryEventId(turn), error: "" };
|
|
479
|
+
const existing = this.dataStore.eventLog.findByIdempotencyKey(outputIdempotencyKey(identity));
|
|
480
|
+
if (existing) {
|
|
481
|
+
const event = {
|
|
482
|
+
...identity,
|
|
483
|
+
error: typeof existing.attributes.error === "string" ? existing.attributes.error : existing.previewText ?? "Runtime error",
|
|
484
|
+
errorDetails: existing.attributes.errorDetails,
|
|
485
|
+
};
|
|
486
|
+
terminalErrors.set(turn.turnId, event);
|
|
487
|
+
const details = event.errorDetails;
|
|
488
|
+
return { status: details?.code === "timeout" ? "timeout" : details?.errorClass === "runtime_abort" ? "aborted" : "error", summary: event.error };
|
|
489
|
+
}
|
|
490
|
+
return recoveryOutcomeForTurn(turn, runsBySession.get(turn.piboSessionId) ?? []);
|
|
491
|
+
},
|
|
475
492
|
});
|
|
476
493
|
if (recoveredTurns.length === 0)
|
|
477
494
|
return [];
|
|
@@ -482,7 +499,8 @@ export class PiboDataSessionStore {
|
|
|
482
499
|
if (!session)
|
|
483
500
|
continue;
|
|
484
501
|
const row = this.db.prepare("SELECT room_id FROM sessions WHERE id = ?").get(session.id);
|
|
485
|
-
const
|
|
502
|
+
const existingError = terminalErrors.get(recovered.turn.turnId);
|
|
503
|
+
const event = existingError ?? {
|
|
486
504
|
type: "session_error",
|
|
487
505
|
piboSessionId: session.id,
|
|
488
506
|
eventId: recoveryEventId(recovered.turn),
|
|
@@ -512,13 +530,14 @@ export class PiboDataSessionStore {
|
|
|
512
530
|
updated_at = ?
|
|
513
531
|
WHERE session_id = ?
|
|
514
532
|
`).run(at, at, at, session.id);
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
533
|
+
if (!existingError)
|
|
534
|
+
ingest.ingestOutputEvent({
|
|
535
|
+
session,
|
|
536
|
+
roomId: row?.room_id ?? undefined,
|
|
537
|
+
actorId: session.id,
|
|
538
|
+
event,
|
|
539
|
+
createdAt: at,
|
|
540
|
+
});
|
|
522
541
|
results.push({
|
|
523
542
|
turnId: recovered.turn.turnId,
|
|
524
543
|
piboSessionId: session.id,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { DEFAULT_TOOL_METRIC_TOKEN_CALCULATION, sanitizeToolMetricTokenCalculation, toolMetricTokenBasis, } from "./tool-call-token-settings.js";
|
|
3
|
+
export const MAX_TIKTOKEN_PAYLOAD_CHARACTERS = 4_000_000;
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
let activeTokenizer;
|
|
6
|
+
/** Bounded structural walk; character mode never tokenizes, copies, or scans large strings. */
|
|
7
|
+
export function measureToolPayloadCharacters(payload) {
|
|
3
8
|
let budget = 10_000;
|
|
4
9
|
const seen = new WeakSet();
|
|
5
10
|
function size(value, depth) {
|
|
@@ -26,7 +31,6 @@ export function estimateToolPayloadTokens(payload) {
|
|
|
26
31
|
}
|
|
27
32
|
else {
|
|
28
33
|
const record = value;
|
|
29
|
-
// Binary/media payloads have no meaningful character-based token count.
|
|
30
34
|
if (["image", "audio", "document", "resource", "image_url"].includes(String(record.type)))
|
|
31
35
|
return NaN;
|
|
32
36
|
for (const key in record) {
|
|
@@ -44,7 +48,26 @@ export function estimateToolPayloadTokens(payload) {
|
|
|
44
48
|
}
|
|
45
49
|
try {
|
|
46
50
|
const chars = size(payload, 0);
|
|
47
|
-
return Number.isFinite(chars) ?
|
|
51
|
+
return Number.isFinite(chars) ? chars : undefined;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function estimateToolPayloadTokens(payload, calculation = DEFAULT_TOOL_METRIC_TOKEN_CALCULATION) {
|
|
58
|
+
try {
|
|
59
|
+
const normalized = sanitizeToolMetricTokenCalculation(calculation);
|
|
60
|
+
if (normalized.method === "characters") {
|
|
61
|
+
const chars = measureToolPayloadCharacters(payload);
|
|
62
|
+
if (chars === undefined)
|
|
63
|
+
return undefined;
|
|
64
|
+
const tokens = Math.ceil(chars / normalized.factor);
|
|
65
|
+
return Number.isSafeInteger(tokens) ? tokens : undefined;
|
|
66
|
+
}
|
|
67
|
+
const text = serializableToolPayloadText(payload);
|
|
68
|
+
if (text === undefined)
|
|
69
|
+
return undefined;
|
|
70
|
+
return tiktokenPayloadTokens(text, normalized.encoding);
|
|
48
71
|
}
|
|
49
72
|
catch {
|
|
50
73
|
// Diagnostics must not turn an otherwise successful tool into a failure.
|
|
@@ -52,26 +75,64 @@ export function estimateToolPayloadTokens(payload) {
|
|
|
52
75
|
}
|
|
53
76
|
}
|
|
54
77
|
export class ToolCallMetricsCollector {
|
|
78
|
+
getCalculation;
|
|
55
79
|
active = new Map();
|
|
80
|
+
constructor(getCalculation = () => DEFAULT_TOOL_METRIC_TOKEN_CALCULATION) {
|
|
81
|
+
this.getCalculation = getCalculation;
|
|
82
|
+
}
|
|
56
83
|
start(id, args, now = performance.now()) {
|
|
57
84
|
if (this.active.has(id))
|
|
58
85
|
return;
|
|
59
|
-
|
|
86
|
+
const calculation = this.currentCalculation();
|
|
87
|
+
this.active.set(id, {
|
|
88
|
+
startedAt: now,
|
|
89
|
+
inputTokens: estimateToolPayloadTokens(args, calculation),
|
|
90
|
+
calculation,
|
|
91
|
+
});
|
|
60
92
|
}
|
|
61
93
|
finish(id, result, now = performance.now()) {
|
|
62
94
|
const started = this.active.get(id);
|
|
63
95
|
this.active.delete(id);
|
|
96
|
+
const calculation = started?.calculation ?? this.currentCalculation();
|
|
64
97
|
// Harness result metadata is not model-visible tool output.
|
|
65
98
|
const output = result && typeof result === "object" && "content" in result
|
|
66
99
|
? result.content : result;
|
|
67
100
|
return {
|
|
68
|
-
tokenBasis:
|
|
101
|
+
tokenBasis: toolMetricTokenBasis(calculation),
|
|
69
102
|
durationMs: started ? Math.max(0, now - started.startedAt) : undefined,
|
|
70
103
|
inputTokens: started?.inputTokens,
|
|
71
|
-
outputTokens: estimateToolPayloadTokens(output),
|
|
104
|
+
outputTokens: estimateToolPayloadTokens(output, calculation),
|
|
72
105
|
};
|
|
73
106
|
}
|
|
74
107
|
clear() {
|
|
75
108
|
this.active.clear();
|
|
76
109
|
}
|
|
110
|
+
currentCalculation() {
|
|
111
|
+
try {
|
|
112
|
+
return sanitizeToolMetricTokenCalculation(this.getCalculation());
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return DEFAULT_TOOL_METRIC_TOKEN_CALCULATION;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function serializableToolPayloadText(payload) {
|
|
120
|
+
const measuredCharacters = measureToolPayloadCharacters(payload);
|
|
121
|
+
if (measuredCharacters === undefined || measuredCharacters > MAX_TIKTOKEN_PAYLOAD_CHARACTERS)
|
|
122
|
+
return undefined;
|
|
123
|
+
if (typeof payload === "string")
|
|
124
|
+
return payload;
|
|
125
|
+
const serialized = JSON.stringify(payload);
|
|
126
|
+
return typeof serialized === "string" && serialized.length <= MAX_TIKTOKEN_PAYLOAD_CHARACTERS
|
|
127
|
+
? serialized
|
|
128
|
+
: undefined;
|
|
129
|
+
}
|
|
130
|
+
function tiktokenPayloadTokens(text, encoding) {
|
|
131
|
+
if (activeTokenizer?.encoding !== encoding) {
|
|
132
|
+
activeTokenizer?.tokenizer.free();
|
|
133
|
+
const tiktoken = require("tiktoken");
|
|
134
|
+
activeTokenizer = { encoding, tokenizer: tiktoken.get_encoding(encoding) };
|
|
135
|
+
}
|
|
136
|
+
const tokens = activeTokenizer.tokenizer.encode_ordinary(text).length;
|
|
137
|
+
return Number.isSafeInteger(tokens) ? tokens : undefined;
|
|
77
138
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const TIKTOKEN_ENCODINGS = [
|
|
2
|
+
"o200k_base",
|
|
3
|
+
"cl100k_base",
|
|
4
|
+
"p50k_base",
|
|
5
|
+
"r50k_base",
|
|
6
|
+
"p50k_edit",
|
|
7
|
+
"gpt2",
|
|
8
|
+
];
|
|
9
|
+
export const DEFAULT_TOOL_METRIC_TOKEN_CALCULATION = {
|
|
10
|
+
method: "characters",
|
|
11
|
+
factor: 4,
|
|
12
|
+
};
|
|
13
|
+
export function parseToolMetricTokenCalculation(value) {
|
|
14
|
+
if (!isRecord(value))
|
|
15
|
+
return undefined;
|
|
16
|
+
if (value.method === "characters") {
|
|
17
|
+
return typeof value.factor === "number" && Number.isFinite(value.factor) && value.factor > 0
|
|
18
|
+
? { method: "characters", factor: value.factor }
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
if (value.method === "tiktoken" && typeof value.encoding === "string" && TIKTOKEN_ENCODINGS.includes(value.encoding)) {
|
|
22
|
+
return { method: "tiktoken", encoding: value.encoding };
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
export function sanitizeToolMetricTokenCalculation(value) {
|
|
27
|
+
return parseToolMetricTokenCalculation(value) ?? DEFAULT_TOOL_METRIC_TOKEN_CALCULATION;
|
|
28
|
+
}
|
|
29
|
+
export function toolMetricTokenBasis(calculation) {
|
|
30
|
+
return calculation.method === "characters"
|
|
31
|
+
? `characters/${calculation.factor}`
|
|
32
|
+
: `tiktoken/${calculation.encoding}`;
|
|
33
|
+
}
|
|
34
|
+
export function toolMetricTokenBasisLabel(basis) {
|
|
35
|
+
if (!basis)
|
|
36
|
+
return "—";
|
|
37
|
+
if (basis === "chars/4")
|
|
38
|
+
return "chars ÷ 4";
|
|
39
|
+
if (basis.startsWith("characters/"))
|
|
40
|
+
return `chars ÷ ${basis.slice("characters/".length)}`;
|
|
41
|
+
return `tiktoken · ${basis.slice("tiktoken/".length)}`;
|
|
42
|
+
}
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
package/dist/web/channel.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { createServer } from "node:http";
|
|
2
3
|
import { PiboAuthError } from "../auth/types.js";
|
|
3
4
|
import { handleSimpleAgentApiRequest } from "../api/simple-agent-api.js";
|
|
@@ -190,11 +191,12 @@ function createGatewayRuntimeStatuses(channelContext) {
|
|
|
190
191
|
}
|
|
191
192
|
});
|
|
192
193
|
}
|
|
193
|
-
function createGatewayStatusResponse(channelContext, options) {
|
|
194
|
+
function createGatewayStatusResponse(channelContext, options, generation) {
|
|
194
195
|
const mode = gatewayMode(options);
|
|
195
196
|
return responseJson({
|
|
196
197
|
status: "ok",
|
|
197
198
|
mode,
|
|
199
|
+
generation,
|
|
198
200
|
health: { status: "ok", mode },
|
|
199
201
|
runtimeStatuses: createGatewayRuntimeStatuses(channelContext),
|
|
200
202
|
activeRuns: collectActiveRuns(channelContext),
|
|
@@ -238,6 +240,7 @@ async function waitForServerClose(closePromise, timeoutMs) {
|
|
|
238
240
|
});
|
|
239
241
|
}
|
|
240
242
|
export function createWebHostChannel(options = {}) {
|
|
243
|
+
const generation = randomUUID();
|
|
241
244
|
const host = options.host ?? DEFAULT_WEB_CHANNEL_HOST;
|
|
242
245
|
const port = options.port ?? DEFAULT_WEB_CHANNEL_PORT;
|
|
243
246
|
const shutdownDrainTimeoutMs = options.shutdownDrainTimeoutMs ?? DEFAULT_SHUTDOWN_DRAIN_TIMEOUT_MS;
|
|
@@ -319,7 +322,7 @@ export function createWebHostChannel(options = {}) {
|
|
|
319
322
|
return;
|
|
320
323
|
}
|
|
321
324
|
if (url.pathname === "/gateway/status") {
|
|
322
|
-
await sendResponse(nodeResponse, createGatewayStatusResponse(requireContext(), options));
|
|
325
|
+
await sendResponse(nodeResponse, createGatewayStatusResponse(requireContext(), options, generation));
|
|
323
326
|
return;
|
|
324
327
|
}
|
|
325
328
|
if (url.pathname.startsWith("/api/auth/")) {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@pasko70/pibo",
|
|
9
|
-
"version": "3.4.
|
|
9
|
+
"version": "3.4.4",
|
|
10
10
|
"workspaces": [
|
|
11
11
|
"packages/workflows"
|
|
12
12
|
],
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"react-virtuoso": "^4.18.6",
|
|
40
40
|
"remark-gfm": "^4.0.1",
|
|
41
41
|
"tailwindcss": "^4.2.4",
|
|
42
|
+
"tiktoken": "^1.0.22",
|
|
42
43
|
"typebox": "1.1.38",
|
|
43
44
|
"xstate": "5.31.1"
|
|
44
45
|
},
|
|
@@ -11502,7 +11503,7 @@
|
|
|
11502
11503
|
"license": "MIT",
|
|
11503
11504
|
"peer": true,
|
|
11504
11505
|
"funding": {
|
|
11505
|
-
"type": "GitHub Sponsors
|
|
11506
|
+
"type": "GitHub Sponsors ❤",
|
|
11506
11507
|
"url": "https://github.com/sponsors/dmonad"
|
|
11507
11508
|
}
|
|
11508
11509
|
},
|
|
@@ -11781,7 +11782,7 @@
|
|
|
11781
11782
|
"node": ">=16"
|
|
11782
11783
|
},
|
|
11783
11784
|
"funding": {
|
|
11784
|
-
"type": "GitHub Sponsors
|
|
11785
|
+
"type": "GitHub Sponsors ❤",
|
|
11785
11786
|
"url": "https://github.com/sponsors/dmonad"
|
|
11786
11787
|
}
|
|
11787
11788
|
},
|
|
@@ -15811,6 +15812,12 @@
|
|
|
15811
15812
|
"url": "https://bevry.me/fund"
|
|
15812
15813
|
}
|
|
15813
15814
|
},
|
|
15815
|
+
"node_modules/tiktoken": {
|
|
15816
|
+
"version": "1.0.22",
|
|
15817
|
+
"resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.22.tgz",
|
|
15818
|
+
"integrity": "sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==",
|
|
15819
|
+
"license": "MIT"
|
|
15820
|
+
},
|
|
15814
15821
|
"node_modules/tinyglobby": {
|
|
15815
15822
|
"version": "0.2.17",
|
|
15816
15823
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
|
@@ -17073,6 +17080,16 @@
|
|
|
17073
17080
|
"node": ">=20.0"
|
|
17074
17081
|
}
|
|
17075
17082
|
},
|
|
17083
|
+
"node_modules/xstate": {
|
|
17084
|
+
"version": "5.31.1",
|
|
17085
|
+
"resolved": "https://registry.npmjs.org/xstate/-/xstate-5.31.1.tgz",
|
|
17086
|
+
"integrity": "sha512-3P7t7GQ61BvLu+8Cj6Zq7rcS34vecL9pvfN2ucUWmIFIUG+rAREviOs4Xy4OO3BuJHSz6RLU8eqDXxSbVotjDQ==",
|
|
17087
|
+
"license": "MIT",
|
|
17088
|
+
"funding": {
|
|
17089
|
+
"type": "opencollective",
|
|
17090
|
+
"url": "https://opencollective.com/xstate"
|
|
17091
|
+
}
|
|
17092
|
+
},
|
|
17076
17093
|
"node_modules/yallist": {
|
|
17077
17094
|
"version": "3.1.1",
|
|
17078
17095
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
|
@@ -17132,7 +17149,7 @@
|
|
|
17132
17149
|
"npm": ">=8.0.0"
|
|
17133
17150
|
},
|
|
17134
17151
|
"funding": {
|
|
17135
|
-
"type": "GitHub Sponsors
|
|
17152
|
+
"type": "GitHub Sponsors ❤",
|
|
17136
17153
|
"url": "https://github.com/sponsors/dmonad"
|
|
17137
17154
|
}
|
|
17138
17155
|
},
|
|
@@ -17204,14 +17221,6 @@
|
|
|
17204
17221
|
"dependencies": {
|
|
17205
17222
|
"xstate": "5.31.1"
|
|
17206
17223
|
}
|
|
17207
|
-
},
|
|
17208
|
-
"packages/workflows/node_modules/xstate": {
|
|
17209
|
-
"version": "5.31.1",
|
|
17210
|
-
"license": "MIT",
|
|
17211
|
-
"funding": {
|
|
17212
|
-
"type": "opencollective",
|
|
17213
|
-
"url": "https://opencollective.com/xstate"
|
|
17214
|
-
}
|
|
17215
17224
|
}
|
|
17216
17225
|
}
|
|
17217
17226
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pasko70/pibo",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
"react-virtuoso": "^4.18.6",
|
|
110
110
|
"remark-gfm": "^4.0.1",
|
|
111
111
|
"tailwindcss": "^4.2.4",
|
|
112
|
+
"tiktoken": "^1.0.22",
|
|
112
113
|
"typebox": "1.1.38",
|
|
113
114
|
"xstate": "5.31.1"
|
|
114
115
|
},
|