@pasko70/pibo 3.4.3 → 3.5.0
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 +39 -12
- 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/adapter.js +1 -0
- package/dist/agent-runtimes/pi/routed-session.js +4 -2
- package/dist/apps/chat/bounded-event-stream.js +98 -0
- package/dist/apps/chat/chat-settings-routes.js +3 -3
- package/dist/apps/chat/data/chat-data-mappers.js +32 -13
- package/dist/apps/chat/data/event-command-service.js +30 -21
- package/dist/apps/chat/data/history-query-service.js +34 -25
- package/dist/apps/chat/data/read-state-service.js +13 -0
- package/dist/apps/chat/data/session-query-service.js +19 -11
- package/dist/apps/chat/data/timeline-query-service.js +19 -7
- package/dist/apps/chat/message-command-dispatcher.js +134 -0
- package/dist/apps/chat/output-compactor.js +9 -0
- package/dist/apps/chat/output-event-policy.js +9 -1
- package/dist/apps/chat/stream.js +21 -3
- package/dist/apps/chat/telemetry-retention-service.js +113 -8
- package/dist/apps/chat/trace-response-cache.js +46 -0
- package/dist/apps/chat/trace-v2.js +12 -6
- package/dist/apps/chat/trace.js +1 -1
- package/dist/apps/chat/web-app.js +608 -281
- package/dist/apps/chat-ui/assets/{dist-DeMKnZR8.js → dist-BG0n7zLd.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-C8GzUMPk.js → dist-D6TjFhAm.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BkUu8WPA.js → dist-D79vyxSX.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-B4YhOxh0.js → dist-DFZ8cwh0.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-Cyb4rVa6.js → dist-cOjokPrK.js} +1 -1
- package/dist/apps/chat-ui/assets/index-RMHUTJ62.js +229 -0
- package/dist/apps/chat-ui/assets/{index-BOceJ0jM.css → index-hEkrlRk-.css} +1 -1
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-xacbCyTx.js +44 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/compute/pool/seeds.js +25 -3
- package/dist/core/events.js +4 -0
- package/dist/core/output-render-sequence.js +2 -0
- package/dist/core/provider-capacity.js +33 -0
- package/dist/core/provider-telemetry.js +21 -6
- package/dist/core/runtime-capacity.js +174 -0
- package/dist/core/runtime-telemetry.js +40 -12
- package/dist/core/session-router.js +79 -2
- package/dist/data/async-chat-reads.js +38 -0
- package/dist/data/async-chat-storage.js +91 -0
- package/dist/data/async-telemetry-maintenance.js +9 -0
- package/dist/data/bounded-worker-client.js +256 -0
- package/dist/data/chat-read-projections.js +159 -0
- package/dist/data/chat-read-worker.js +73 -0
- package/dist/data/chat-storage-worker.js +146 -0
- package/dist/data/ingest-service.js +79 -14
- package/dist/data/message-command-store.js +148 -0
- package/dist/data/payload-store.js +92 -11
- package/dist/data/pibo-store.js +10 -8
- package/dist/data/schema.js +16 -2
- package/dist/data/session-store.js +3 -1
- package/dist/data/storage-backup.js +278 -0
- package/dist/data/telemetry-capture.js +188 -0
- package/dist/data/telemetry-command.js +3 -0
- package/dist/data/telemetry-maintenance-worker.js +40 -0
- package/dist/data/telemetry-maintenance.js +110 -0
- package/dist/data/telemetry-retention.js +16 -7
- package/dist/data/telemetry-worker.js +111 -0
- package/dist/data/telemetry-writer.js +150 -83
- package/dist/data/telemetry.js +5 -0
- package/dist/debug/index.js +52 -0
- package/dist/debug/storage-backup.js +33 -0
- package/dist/debug/telemetry-capture.js +66 -0
- package/dist/gateway/cli.js +104 -16
- package/dist/gateway/server.js +2 -0
- package/dist/providers/openai-gpt56.js +11 -6
- package/dist/session-ui/terminalRows.js +55 -13
- package/dist/sessions/pibo-data-store.js +30 -10
- package/dist/shared/debug-features.js +4 -0
- package/dist/shared/model-inference-metrics.js +23 -0
- package/dist/shared/trace-event-projection.js +59 -2
- package/dist/shared/trace-history.js +9 -0
- package/dist/shared/trace-live-reducer.js +1 -0
- package/dist/shared/trace-patch-nodes.js +19 -0
- package/dist/web/channel.js +8 -2
- package/dist/web/http.js +36 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-Dk4mbXAB.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-0oTGFHni.js +0 -43
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { previouslyClearedMessages } from "../core/events.js";
|
|
1
2
|
import { PiboSteeringUnavailableError, } from "../core/events.js";
|
|
2
3
|
import { PIBO_PROVIDER_RECOVERY_PROMPT, isPiboProviderFallbackError } from "../core/provider-recovery.js";
|
|
3
4
|
import { normalizeSessionErrorDetails, runtimeSessionErrorDetails } from "../core/session-errors.js";
|
|
@@ -166,8 +167,14 @@ export class RuntimeRoutedSession {
|
|
|
166
167
|
if (this.sessionIdentityOperationInFlight && !this.forkCandidatesRequest) {
|
|
167
168
|
throw new Error("Pibo session cannot accept messages while a session identity operation is in progress.");
|
|
168
169
|
}
|
|
170
|
+
const messages = this.queue.filter(item => item.kind === "message");
|
|
171
|
+
const bytes = Buffer.byteLength(event.text);
|
|
172
|
+
if (bytes > 1024 * 1024 || messages.length >= 64 || messages.reduce((total, item) => total + Buffer.byteLength(item.event.text), bytes) > 4 * 1024 * 1024
|
|
173
|
+
|| messages.some(item => Date.now() - item.acceptedAt >= 10 * 60 * 1000)) {
|
|
174
|
+
throw Object.assign(new Error("Session runtime queue count, byte or wait-age capacity reached."), { code: "runtime_capacity_unavailable" });
|
|
175
|
+
}
|
|
169
176
|
onAccepted();
|
|
170
|
-
this.queue.push({ kind: "message", event });
|
|
177
|
+
this.queue.push({ kind: "message", event, acceptedAt: Date.now() });
|
|
171
178
|
const output = {
|
|
172
179
|
type: "message_queued",
|
|
173
180
|
piboSessionId: this.piboSessionId,
|
|
@@ -297,6 +304,8 @@ export class RuntimeRoutedSession {
|
|
|
297
304
|
revision: binding.revision,
|
|
298
305
|
},
|
|
299
306
|
queuedMessages: this.queue.length,
|
|
307
|
+
activeEventId: this.activeMessage?.id ?? this.activeExecutionEvent?.id,
|
|
308
|
+
queuedEventIds: this.queue.map((item) => item.event.id ?? ""),
|
|
300
309
|
processing: this.disposed ? false : this.processing,
|
|
301
310
|
streaming: this.disposed ? false : status.streaming,
|
|
302
311
|
activeTools: [...status.enabledTools],
|
|
@@ -591,6 +600,7 @@ export class RuntimeRoutedSession {
|
|
|
591
600
|
if (inFlight?.event.id === eventId) {
|
|
592
601
|
if (!inFlight.cancellation) {
|
|
593
602
|
inFlight.cancelled = true;
|
|
603
|
+
inFlight.capacityAbort.abort(new Error("Message cancelled while waiting for capacity."));
|
|
594
604
|
this.notifyMessagesInterrupted([inFlight.event], "message cancelled");
|
|
595
605
|
const active = this.activeMessage?.id === eventId;
|
|
596
606
|
inFlight.cancellation = (async () => {
|
|
@@ -708,6 +718,7 @@ export class RuntimeRoutedSession {
|
|
|
708
718
|
result: event.result,
|
|
709
719
|
aborted: event.aborted,
|
|
710
720
|
errorMessage: event.errorMessage,
|
|
721
|
+
compactionStats: event.compactionStats,
|
|
711
722
|
}));
|
|
712
723
|
return;
|
|
713
724
|
case "approval_requested":
|
|
@@ -813,11 +824,29 @@ export class RuntimeRoutedSession {
|
|
|
813
824
|
this.notifyState();
|
|
814
825
|
}
|
|
815
826
|
}
|
|
827
|
+
async promptWithinCapacity(event, inFlight, input) {
|
|
828
|
+
const provider = this.runtimeSession.getStatus().activeModel?.provider ?? this.runtimeSession.runtimeInstanceId;
|
|
829
|
+
const lease = await this.options.acquireProviderCapacity?.(provider, inFlight.capacityAbort.signal);
|
|
830
|
+
try {
|
|
831
|
+
if (this.disposed || inFlight.cancelled)
|
|
832
|
+
return;
|
|
833
|
+
if (!inFlight.started) {
|
|
834
|
+
inFlight.started = true;
|
|
835
|
+
this.emit({ type: "message_started", piboSessionId: this.piboSessionId, eventId: event.id, text: event.text, source: event.source, provenance: event.provenance });
|
|
836
|
+
}
|
|
837
|
+
if (this.disposed || inFlight.cancelled)
|
|
838
|
+
return;
|
|
839
|
+
await this.runtimeSession.prompt(input);
|
|
840
|
+
}
|
|
841
|
+
finally {
|
|
842
|
+
lease?.release();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
816
845
|
async promptWithModelFallbacks(event, inFlight) {
|
|
817
846
|
const canSetModel = Boolean(this.runtimeSession.controls?.setModel);
|
|
818
847
|
const fallbackModels = this.modelFallbacks.filter((model) => !sameModel(model, this.primaryModel));
|
|
819
848
|
if (!canSetModel || fallbackModels.length === 0) {
|
|
820
|
-
await this.
|
|
849
|
+
await this.promptWithinCapacity(event, inFlight, {
|
|
821
850
|
text: event.text,
|
|
822
851
|
source: promptSource(event.source),
|
|
823
852
|
capabilityScope: event.capabilityScope,
|
|
@@ -843,7 +872,7 @@ export class RuntimeRoutedSession {
|
|
|
843
872
|
this.pendingProviderFailure = undefined;
|
|
844
873
|
let promptError;
|
|
845
874
|
try {
|
|
846
|
-
await this.
|
|
875
|
+
await this.promptWithinCapacity(event, inFlight, {
|
|
847
876
|
text: attempt === 0 ? event.text : PIBO_PROVIDER_RECOVERY_PROMPT,
|
|
848
877
|
source: attempt === 0 ? promptSource(event.source) : "rpc",
|
|
849
878
|
capabilityScope: event.capabilityScope,
|
|
@@ -905,14 +934,6 @@ export class RuntimeRoutedSession {
|
|
|
905
934
|
this.activeMessageFailed = false;
|
|
906
935
|
this.beginRunReminderTurnGuard(event);
|
|
907
936
|
this.resetContentIndices();
|
|
908
|
-
this.emit({
|
|
909
|
-
type: "message_started",
|
|
910
|
-
piboSessionId: this.piboSessionId,
|
|
911
|
-
eventId: event.id,
|
|
912
|
-
text: event.text,
|
|
913
|
-
source: event.source,
|
|
914
|
-
provenance: event.provenance,
|
|
915
|
-
});
|
|
916
937
|
if (inFlight.cancelled)
|
|
917
938
|
return;
|
|
918
939
|
await this.promptWithModelFallbacks(event, inFlight);
|
|
@@ -961,6 +982,7 @@ export class RuntimeRoutedSession {
|
|
|
961
982
|
const inFlight = {
|
|
962
983
|
event,
|
|
963
984
|
cancelled: false,
|
|
985
|
+
capacityAbort: new AbortController(),
|
|
964
986
|
settled: new Promise((resolve) => { resolveSettled = resolve; }),
|
|
965
987
|
resolveSettled: () => { resolveSettled?.(); },
|
|
966
988
|
};
|
|
@@ -1127,8 +1149,12 @@ export class RuntimeRoutedSession {
|
|
|
1127
1149
|
? await this.options.logoutRuntimeAuth(input)
|
|
1128
1150
|
: await this.pluginRegistry.logoutAgentRuntimeAuth(this.runtimeSession.runtimeInstanceId, input),
|
|
1129
1151
|
getProviderUsage: () => this.getActionProviderUsage(),
|
|
1130
|
-
clearQueue: () => this.clearQueue(),
|
|
1152
|
+
clearQueue: () => this.clearQueue() + previouslyClearedMessages(event),
|
|
1131
1153
|
abort: async () => {
|
|
1154
|
+
if (this.inFlightMessage && !this.inFlightMessage.started) {
|
|
1155
|
+
await this.cancelMessage(this.inFlightMessage.event.id);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1132
1158
|
if (this.activeMessage)
|
|
1133
1159
|
this.notifyMessagesInterrupted([this.activeMessage], "abort requested");
|
|
1134
1160
|
await this.runtimeSession.abort();
|
|
@@ -1185,6 +1211,7 @@ export class RuntimeRoutedSession {
|
|
|
1185
1211
|
if (this.disposed)
|
|
1186
1212
|
return false;
|
|
1187
1213
|
const activeMessage = this.activeMessage;
|
|
1214
|
+
this.inFlightMessage?.capacityAbort.abort(new Error(reason));
|
|
1188
1215
|
this.notifyMessagesInterrupted(this.activeAndQueuedMessages(), reason);
|
|
1189
1216
|
if (activeMessage) {
|
|
1190
1217
|
const error = `Session disposed while a message was active: ${reason}`;
|
|
@@ -16,6 +16,7 @@ import { CodexNativeResourceDelivery } from "./resource-delivery.js";
|
|
|
16
16
|
import { CODEX_NATIVE_MODEL_OPTIONS_SCHEMA, CODEX_NATIVE_MODEL_PROVIDER_ID, CODEX_NATIVE_REASONING_VALUES, CodexNativeSessionSettingsController, parseCodexNativeProfileOptions, readCodexNativeModelCatalog, readCodexNativePersistedSettings, toAgentRuntimeModelCatalog, } from "./models.js";
|
|
17
17
|
import { CodexNativeAuthController } from "./auth.js";
|
|
18
18
|
import { injectPortableHistoryIntoCodex } from "./portable-history.js";
|
|
19
|
+
import { readCodexNativeProviderUsage } from "./provider-usage.js";
|
|
19
20
|
import { CODEX_FIRST_USE_METADATA_KEY, CODEX_FIRST_USE_METADATA_VERSION, assertCodexNativeFirstUseMessageId, assertCodexNativeFirstUseDeliveryReceiptTurn, assertCodexNativePendingFirstUseRequest, assertCodexNativePendingFirstUseTurn, beginCodexNativeFirstUseAttempt, codexNativeFirstUseDeliveryReceipt, codexNativePendingFirstUseOwnerLiveness, endCodexNativeFirstUseAttempt, hashCanonicalCodexNativeFirstUsePrompt, isExactCodexNativeFirstUseDeliveryReplay, readCodexNativeFirstUseDeliveryReceipt, readCodexNativePendingFirstUse, } from "./first-use.js";
|
|
20
21
|
export { CODEX_NATIVE_ADAPTER_ID } from "./thread.js";
|
|
21
22
|
export const CODEX_NATIVE_ADAPTER_VERSION = "1.0.0";
|
|
@@ -490,6 +491,16 @@ export class CodexNativeThreadSession {
|
|
|
490
491
|
],
|
|
491
492
|
};
|
|
492
493
|
}
|
|
494
|
+
async getStatusSnapshot() {
|
|
495
|
+
const status = this.getStatus();
|
|
496
|
+
try {
|
|
497
|
+
const providerUsage = await readCodexNativeProviderUsage(this.process.client);
|
|
498
|
+
return providerUsage ? { ...status, providerUsage } : status;
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
return status;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
493
504
|
getNativeCompatibilityHandle() {
|
|
494
505
|
return this.process.client;
|
|
495
506
|
}
|
|
@@ -615,8 +626,10 @@ export class CodexNativeThreadSession {
|
|
|
615
626
|
let reboundSettings = false;
|
|
616
627
|
let phase = "stopping the previous process";
|
|
617
628
|
try {
|
|
618
|
-
await previousProcess.
|
|
629
|
+
await previousProcess.stop();
|
|
619
630
|
this.resourceProcessUnavailable = true;
|
|
631
|
+
// Generation cleanup may race with exiting MCP children and must not block App Server replacement.
|
|
632
|
+
void previousProcess.close().catch(() => { });
|
|
620
633
|
phase = "starting the replacement process";
|
|
621
634
|
next = await this.reloadProcess();
|
|
622
635
|
phase = "resuming the native thread";
|
|
@@ -393,11 +393,11 @@ function setBoundedPending(map, threadId, value) {
|
|
|
393
393
|
map.set(threadId, value);
|
|
394
394
|
}
|
|
395
395
|
function parseContextUsage(value) {
|
|
396
|
-
if (!isRecord(value.tokenUsage) || !isRecord(value.tokenUsage.
|
|
397
|
-
throw new CodexNativeModelProtocolError("Codex token-usage notification is missing
|
|
396
|
+
if (!isRecord(value.tokenUsage) || !isRecord(value.tokenUsage.last)) {
|
|
397
|
+
throw new CodexNativeModelProtocolError("Codex token-usage notification is missing last response usage.");
|
|
398
398
|
}
|
|
399
|
-
const
|
|
400
|
-
const tokens = requiredNonNegativeNumber(
|
|
399
|
+
const last = value.tokenUsage.last;
|
|
400
|
+
const tokens = requiredNonNegativeNumber(last.totalTokens, "last response token usage");
|
|
401
401
|
const contextWindow = value.tokenUsage.modelContextWindow === undefined || value.tokenUsage.modelContextWindow === null
|
|
402
402
|
? undefined
|
|
403
403
|
: requiredNonNegativeNumber(value.tokenUsage.modelContextWindow, "model context window");
|
|
@@ -6,6 +6,8 @@ import { protectPrivatePathsSync } from "../../core/private-path.js";
|
|
|
6
6
|
import { CodexAppServerClient } from "./client.js";
|
|
7
7
|
import { CODEX_APP_SERVER_PROTOCOL_NAME, CODEX_APP_SERVER_SUPPORTED_RANGE, CODEX_APP_SERVER_VERSION, } from "./protocol-version.js";
|
|
8
8
|
const MAX_VERSION_OUTPUT_BYTES = 64 * 1024;
|
|
9
|
+
const SESSION_PATH_CLEANUP_MAX_RETRIES = 10;
|
|
10
|
+
const SESSION_PATH_CLEANUP_RETRY_DELAY_MS = 50;
|
|
9
11
|
const PRIVATE_DIRECTORY_MODE = 0o700;
|
|
10
12
|
const PRIVATE_FILE_MODE = 0o600;
|
|
11
13
|
const INSTANCE_CONFIG = [
|
|
@@ -161,7 +163,12 @@ export async function prepareCodexNativeSessionPaths(input) {
|
|
|
161
163
|
}
|
|
162
164
|
}
|
|
163
165
|
export async function disposeCodexNativeSessionPaths(paths) {
|
|
164
|
-
await rm(paths.generationRoot, {
|
|
166
|
+
await rm(paths.generationRoot, {
|
|
167
|
+
recursive: true,
|
|
168
|
+
force: true,
|
|
169
|
+
maxRetries: SESSION_PATH_CLEANUP_MAX_RETRIES,
|
|
170
|
+
retryDelay: SESSION_PATH_CLEANUP_RETRY_DELAY_MS,
|
|
171
|
+
});
|
|
165
172
|
await rmdir(paths.sessionRoot).catch((error) => {
|
|
166
173
|
if (error.code !== "ENOTEMPTY" && error.code !== "ENOENT")
|
|
167
174
|
throw error;
|
|
@@ -393,23 +400,35 @@ export async function diagnoseCodexNativeRuntime(config, runtimeInstanceId, opti
|
|
|
393
400
|
export class CodexNativeAppServerProcess {
|
|
394
401
|
client;
|
|
395
402
|
paths;
|
|
396
|
-
|
|
397
|
-
|
|
403
|
+
disposePaths;
|
|
404
|
+
stopPromise;
|
|
405
|
+
cleanupPromise;
|
|
406
|
+
pathsDisposed = false;
|
|
407
|
+
constructor(client, paths, disposePaths = disposeCodexNativeSessionPaths) {
|
|
398
408
|
this.client = client;
|
|
399
409
|
this.paths = paths;
|
|
410
|
+
this.disposePaths = disposePaths;
|
|
411
|
+
}
|
|
412
|
+
async stop() {
|
|
413
|
+
if (!this.stopPromise)
|
|
414
|
+
this.stopPromise = this.client.close();
|
|
415
|
+
await this.stopPromise;
|
|
400
416
|
}
|
|
401
417
|
async close() {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
418
|
+
await this.stop();
|
|
419
|
+
if (this.pathsDisposed)
|
|
420
|
+
return;
|
|
421
|
+
if (!this.cleanupPromise)
|
|
422
|
+
this.cleanupPromise = this.disposePaths(this.paths);
|
|
423
|
+
const cleanup = this.cleanupPromise;
|
|
424
|
+
try {
|
|
425
|
+
await cleanup;
|
|
426
|
+
this.pathsDisposed = true;
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
if (this.cleanupPromise === cleanup)
|
|
430
|
+
this.cleanupPromise = undefined;
|
|
411
431
|
}
|
|
412
|
-
await this.closePromise;
|
|
413
432
|
}
|
|
414
433
|
}
|
|
415
434
|
export async function startCodexNativeAppServer(input) {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const PROVIDER_ID = "openai-codex";
|
|
2
|
+
const MAX_RATE_LIMIT_BUCKETS = 32;
|
|
3
|
+
const RATE_LIMIT_REQUEST_TIMEOUT_MS = 5_000;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function optionalString(value, label) {
|
|
8
|
+
if (value === undefined || value === null)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (typeof value !== "string")
|
|
11
|
+
throw new Error(`Codex ${label} is invalid.`);
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
function optionalNumber(value, label) {
|
|
15
|
+
if (value === undefined || value === null)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
18
|
+
throw new Error(`Codex ${label} is invalid.`);
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function formatWindowDuration(minutes, fallback) {
|
|
22
|
+
if (minutes === undefined || minutes <= 0)
|
|
23
|
+
return fallback;
|
|
24
|
+
if (minutes % (7 * 24 * 60) === 0)
|
|
25
|
+
return `${minutes / (7 * 24 * 60)}w`;
|
|
26
|
+
if (minutes % (24 * 60) === 0)
|
|
27
|
+
return `${minutes / (24 * 60)}d`;
|
|
28
|
+
if (minutes % 60 === 0)
|
|
29
|
+
return `${minutes / 60}h`;
|
|
30
|
+
return `${minutes}m`;
|
|
31
|
+
}
|
|
32
|
+
function normalizeWindow(value, fallbackLabel, prefix) {
|
|
33
|
+
if (value === undefined || value === null)
|
|
34
|
+
return undefined;
|
|
35
|
+
if (!isRecord(value))
|
|
36
|
+
throw new Error("Codex rate-limit window is invalid.");
|
|
37
|
+
const used = optionalNumber(value.usedPercent, "rate-limit percentage");
|
|
38
|
+
if (used === undefined)
|
|
39
|
+
throw new Error("Codex rate-limit percentage is missing.");
|
|
40
|
+
const usedPercent = Math.max(0, Math.min(100, used));
|
|
41
|
+
const duration = optionalNumber(value.windowDurationMins, "rate-limit duration");
|
|
42
|
+
const resetsAtSeconds = optionalNumber(value.resetsAt, "rate-limit reset time");
|
|
43
|
+
const windowLabel = `${formatWindowDuration(duration, fallbackLabel)} limit`;
|
|
44
|
+
return {
|
|
45
|
+
label: prefix ? `${prefix} ${windowLabel}` : windowLabel,
|
|
46
|
+
usedPercent,
|
|
47
|
+
remainingPercent: 100 - usedPercent,
|
|
48
|
+
...(resetsAtSeconds !== undefined ? { resetsAt: new Date(resetsAtSeconds * 1_000).toISOString() } : {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function snapshotLabel(snapshot, fallback) {
|
|
52
|
+
return optionalString(snapshot.limitName, "rate-limit name") ?? fallback;
|
|
53
|
+
}
|
|
54
|
+
function snapshotLimits(snapshot, prefix) {
|
|
55
|
+
return [
|
|
56
|
+
normalizeWindow(snapshot.primary, "primary", prefix),
|
|
57
|
+
normalizeWindow(snapshot.secondary, "weekly", prefix),
|
|
58
|
+
].filter((entry) => Boolean(entry));
|
|
59
|
+
}
|
|
60
|
+
function snapshotCredits(snapshot) {
|
|
61
|
+
if (snapshot.credits === undefined || snapshot.credits === null)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (!isRecord(snapshot.credits) || typeof snapshot.credits.hasCredits !== "boolean" || typeof snapshot.credits.unlimited !== "boolean") {
|
|
64
|
+
throw new Error("Codex credit snapshot is invalid.");
|
|
65
|
+
}
|
|
66
|
+
if (!snapshot.credits.hasCredits)
|
|
67
|
+
return undefined;
|
|
68
|
+
return {
|
|
69
|
+
unlimited: snapshot.credits.unlimited,
|
|
70
|
+
balance: optionalString(snapshot.credits.balance, "credit balance"),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function normalizeCodexNativeProviderUsage(value) {
|
|
74
|
+
if (!isRecord(value) || !isRecord(value.rateLimits))
|
|
75
|
+
throw new Error("Codex rate-limit response is invalid.");
|
|
76
|
+
const response = value;
|
|
77
|
+
const primary = response.rateLimits;
|
|
78
|
+
const limits = [...snapshotLimits(primary)];
|
|
79
|
+
let planType = optionalString(primary.planType, "plan type");
|
|
80
|
+
let credits = snapshotCredits(primary);
|
|
81
|
+
const primaryLimitId = optionalString(primary.limitId, "rate-limit id");
|
|
82
|
+
const additional = response.rateLimitsByLimitId;
|
|
83
|
+
if (additional !== undefined && additional !== null) {
|
|
84
|
+
if (!isRecord(additional))
|
|
85
|
+
throw new Error("Codex rate-limit bucket map is invalid.");
|
|
86
|
+
const entries = Object.entries(additional);
|
|
87
|
+
if (entries.length > MAX_RATE_LIMIT_BUCKETS)
|
|
88
|
+
throw new Error("Codex returned too many rate-limit buckets.");
|
|
89
|
+
for (const [limitId, rawSnapshot] of entries) {
|
|
90
|
+
if (limitId === primaryLimitId)
|
|
91
|
+
continue;
|
|
92
|
+
if (!isRecord(rawSnapshot))
|
|
93
|
+
throw new Error("Codex rate-limit bucket is invalid.");
|
|
94
|
+
const snapshot = rawSnapshot;
|
|
95
|
+
const prefix = snapshotLabel(snapshot, limitId);
|
|
96
|
+
limits.push(...snapshotLimits(snapshot, prefix));
|
|
97
|
+
planType ??= optionalString(snapshot.planType, "plan type");
|
|
98
|
+
credits ??= snapshotCredits(snapshot);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (limits.length === 0 && !credits)
|
|
102
|
+
return undefined;
|
|
103
|
+
return {
|
|
104
|
+
provider: PROVIDER_ID,
|
|
105
|
+
...(planType ? { planType } : {}),
|
|
106
|
+
limits,
|
|
107
|
+
...(credits ? { credits } : {}),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export async function readCodexNativeProviderUsage(client) {
|
|
111
|
+
return normalizeCodexNativeProviderUsage(await client.request("account/rateLimits/read", null, {
|
|
112
|
+
timeoutMs: RATE_LIMIT_REQUEST_TIMEOUT_MS,
|
|
113
|
+
retryOverloaded: false,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
@@ -253,6 +253,7 @@ export function semanticEventFromPibo(event) {
|
|
|
253
253
|
result: event.result,
|
|
254
254
|
aborted: event.aborted,
|
|
255
255
|
errorMessage: event.errorMessage,
|
|
256
|
+
compactionStats: event.compactionStats,
|
|
256
257
|
};
|
|
257
258
|
case "session_error":
|
|
258
259
|
return { type: "error", message: event.error, details: event.errorDetails };
|
|
@@ -863,8 +863,6 @@ export class RoutedSession {
|
|
|
863
863
|
if (assistantMessageEnded && !assistantMessageFailed)
|
|
864
864
|
this.flushPendingProviderWebSearchFinishes();
|
|
865
865
|
const usageEvent = assistantMessageEnded ? normalizeAssistantUsageEvent(this.piboSessionId, candidate?.message) : undefined;
|
|
866
|
-
if (usageEvent)
|
|
867
|
-
this.emit(this.withActiveMessage(usageEvent));
|
|
868
866
|
// Pi gets the first chance to recover through its short retry/compaction loop.
|
|
869
867
|
// Keep the final error pending so the routed turn can continue durable recovery.
|
|
870
868
|
if (assistantMessageEnded && normalized?.type === "session_error") {
|
|
@@ -889,6 +887,8 @@ export class RoutedSession {
|
|
|
889
887
|
this.emit(this.withActiveMessage(normalized));
|
|
890
888
|
}
|
|
891
889
|
}
|
|
890
|
+
if (usageEvent)
|
|
891
|
+
this.emit(this.withActiveMessage(usageEvent));
|
|
892
892
|
if (this.forwardPiEvents) {
|
|
893
893
|
this.emit({ type: "pi_event", piboSessionId: this.piboSessionId, event });
|
|
894
894
|
}
|
|
@@ -1149,6 +1149,8 @@ export class RoutedSession {
|
|
|
1149
1149
|
return {
|
|
1150
1150
|
piboSessionId: this.piboSessionId,
|
|
1151
1151
|
queuedMessages: this.queue.length,
|
|
1152
|
+
activeEventId: this.activeMessage?.id ?? this.activeExecutionEvent?.id,
|
|
1153
|
+
queuedEventIds: this.queue.map((item) => item.event.id ?? ""),
|
|
1152
1154
|
processing: this.disposed ? false : this.processing,
|
|
1153
1155
|
streaming: this.disposed ? false : this.runtime.session.isStreaming,
|
|
1154
1156
|
activeTools: enabledTools,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/** Owns the only application queue; HWM zero prevents a second hidden stream queue. */
|
|
2
|
+
export class BoundedEventStream {
|
|
3
|
+
onClose;
|
|
4
|
+
maximumBytes;
|
|
5
|
+
maximumAgeMs;
|
|
6
|
+
maximumFrames;
|
|
7
|
+
stream;
|
|
8
|
+
writer;
|
|
9
|
+
controller;
|
|
10
|
+
queue = [];
|
|
11
|
+
bytes = 0;
|
|
12
|
+
stopped = false;
|
|
13
|
+
finishing = false;
|
|
14
|
+
pendingPull;
|
|
15
|
+
capacityWaiter;
|
|
16
|
+
timer;
|
|
17
|
+
constructor(onClose, maximumBytes = 2 * 1024 * 1024, maximumAgeMs = 5000, maximumFrames = 4096) {
|
|
18
|
+
this.onClose = onClose;
|
|
19
|
+
this.maximumBytes = maximumBytes;
|
|
20
|
+
this.maximumAgeMs = maximumAgeMs;
|
|
21
|
+
this.maximumFrames = maximumFrames;
|
|
22
|
+
this.stream = new ReadableStream({
|
|
23
|
+
start: controller => { this.controller = controller; },
|
|
24
|
+
pull: () => {
|
|
25
|
+
const frame = this.queue.shift();
|
|
26
|
+
if (frame) {
|
|
27
|
+
this.bytes -= frame.value.byteLength;
|
|
28
|
+
this.controller.enqueue(frame.value);
|
|
29
|
+
this.releaseCapacity();
|
|
30
|
+
if (this.finishing && !this.queue.length)
|
|
31
|
+
this.stop("complete");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (this.finishing || this.stopped) {
|
|
35
|
+
this.stop("complete");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
return new Promise(resolve => { this.pendingPull = resolve; });
|
|
39
|
+
},
|
|
40
|
+
cancel: () => this.stop("cancel"),
|
|
41
|
+
}, { highWaterMark: 0 });
|
|
42
|
+
const self = this;
|
|
43
|
+
this.writer = { enqueue: value => { this.enqueue(value); }, close: () => this.finish(), error: () => this.stop("error"), get desiredSize() { return self.stopped ? null : self.maximumBytes - self.bytes; } };
|
|
44
|
+
this.timer = setInterval(() => { if (this.queue.length && performance.now() - this.queue[0].at > this.maximumAgeMs)
|
|
45
|
+
this.stop("slow_age"); }, Math.min(1000, Math.max(5, maximumAgeMs / 2)));
|
|
46
|
+
this.timer.unref();
|
|
47
|
+
}
|
|
48
|
+
get closed() { return this.stopped || this.finishing; }
|
|
49
|
+
enqueue(value) {
|
|
50
|
+
if (this.closed)
|
|
51
|
+
return;
|
|
52
|
+
if (value.byteLength > this.maximumBytes || this.bytes + value.byteLength > this.maximumBytes || this.queue.length >= this.maximumFrames) {
|
|
53
|
+
this.stop("slow_bytes");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (this.pendingPull) {
|
|
57
|
+
const resolve = this.pendingPull;
|
|
58
|
+
this.pendingPull = undefined;
|
|
59
|
+
this.controller.enqueue(value);
|
|
60
|
+
resolve();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
this.queue.push({ value, at: performance.now() });
|
|
64
|
+
this.bytes += value.byteLength;
|
|
65
|
+
}
|
|
66
|
+
async waitForCapacity() { if (this.closed)
|
|
67
|
+
return false; if (this.bytes < this.maximumBytes / 2)
|
|
68
|
+
return true; return new Promise(resolve => { this.capacityWaiter = resolve; }); }
|
|
69
|
+
releaseCapacity() { if (this.bytes < this.maximumBytes / 2) {
|
|
70
|
+
this.capacityWaiter?.(!this.closed);
|
|
71
|
+
this.capacityWaiter = undefined;
|
|
72
|
+
} }
|
|
73
|
+
finish() { if (this.stopped)
|
|
74
|
+
return; this.finishing = true; if (!this.queue.length)
|
|
75
|
+
this.stop("complete"); }
|
|
76
|
+
fail() { this.stop("error"); }
|
|
77
|
+
stop(reason) {
|
|
78
|
+
if (this.stopped)
|
|
79
|
+
return;
|
|
80
|
+
this.stopped = true;
|
|
81
|
+
clearInterval(this.timer);
|
|
82
|
+
this.queue.length = 0;
|
|
83
|
+
this.bytes = 0;
|
|
84
|
+
try {
|
|
85
|
+
if (reason === "complete")
|
|
86
|
+
this.controller.close();
|
|
87
|
+
else if (reason !== "cancel")
|
|
88
|
+
this.controller.error(Error("Event stream budget exhausted; reconnect using the last received cursor."));
|
|
89
|
+
}
|
|
90
|
+
catch { }
|
|
91
|
+
this.pendingPull?.();
|
|
92
|
+
this.pendingPull = undefined;
|
|
93
|
+
this.capacityWaiter?.(false);
|
|
94
|
+
this.capacityWaiter = undefined;
|
|
95
|
+
this.onClose(reason);
|
|
96
|
+
}
|
|
97
|
+
status() { return { frames: this.queue.length, bytes: this.bytes, oldestAgeMs: this.queue.length ? performance.now() - this.queue[0].at : 0, closed: this.closed }; }
|
|
98
|
+
}
|
|
@@ -8,7 +8,7 @@ import { parseToolMetricTokenCalculation } from "../../shared/tool-call-token-se
|
|
|
8
8
|
import { PiboWebHttpError, readJsonBody, responseJson } from "../../web/http.js";
|
|
9
9
|
import { CHAT_WEB_API_PREFIX } from "./chat-api-routes.js";
|
|
10
10
|
import { normalizeBasePromptMarkdown, normalizeBasePromptMode, normalizeCompactionPromptMarkdown, normalizeCompactionPromptMode, updateChatModelDefaults, } from "./chat-request-normalizers.js";
|
|
11
|
-
import {
|
|
11
|
+
import { pruneTelemetryOlderThanAsync } from "./telemetry-retention-service.js";
|
|
12
12
|
export function chatSettingsRoute(pathname, method) {
|
|
13
13
|
if (pathname === `${CHAT_WEB_API_PREFIX}/model-defaults` && method === "PATCH")
|
|
14
14
|
return { kind: "model-defaults" };
|
|
@@ -70,8 +70,8 @@ export async function handleChatSettingsRoute(input) {
|
|
|
70
70
|
const days = sanitizeTelemetryRetentionDays(body.days);
|
|
71
71
|
if (!days)
|
|
72
72
|
throw new PiboWebHttpError("Invalid telemetry retention days", 400);
|
|
73
|
-
const result =
|
|
74
|
-
if (result.applied)
|
|
73
|
+
const result = await pruneTelemetryOlderThanAsync({ dataStore: input.dataStore, days, apply: body.dryRun !== true, signal: request.signal });
|
|
74
|
+
if (result.applied && result.completed !== false)
|
|
75
75
|
updateTelemetryRetentionLastPrunedAt(new Date().toISOString());
|
|
76
76
|
return responseJson({ telemetryRetention: result });
|
|
77
77
|
}
|
|
@@ -3,13 +3,31 @@ import { roomWorkspaceFromMetadata } from "../types/rooms.js";
|
|
|
3
3
|
import { tracePayloadRefForStoredPayload } from "../trace-v2.js";
|
|
4
4
|
import { qualifiedToolNodeId } from "../../../shared/trace-tool-identity.js";
|
|
5
5
|
const MAX_TRACE_EVENT_HYDRATION_BYTES = 1024 * 1024;
|
|
6
|
-
export function storedPiboEventFromV2Row(row, payloadStore) {
|
|
6
|
+
export function storedPiboEventFromV2Row(row, payloadStore, hydrationBytes = MAX_TRACE_EVENT_HYDRATION_BYTES) {
|
|
7
|
+
if (typeof hydrationBytes !== "number" || !Number.isFinite(hydrationBytes))
|
|
8
|
+
hydrationBytes = MAX_TRACE_EVENT_HYDRATION_BYTES;
|
|
7
9
|
const attributes = parseJsonObject(row.attributes_json);
|
|
8
|
-
|
|
10
|
+
if (hydrationBytes < MAX_TRACE_EVENT_HYDRATION_BYTES && typeof attributes.inlineText === "string" && Buffer.byteLength(attributes.inlineText) > hydrationBytes)
|
|
11
|
+
attributes.inlineText = row.preview_text ?? "";
|
|
12
|
+
const payload = outputPayloadFromV2Row(row, attributes, readPersistedPayload(row, payloadStore, hydrationBytes));
|
|
9
13
|
if (!payload)
|
|
10
14
|
return undefined;
|
|
15
|
+
const storedPayloadRef = storedPayloadReference(row, attributes, payloadStore);
|
|
16
|
+
const renderSequence = numberAttribute(attributes, "renderSequence") ?? payload.renderSequence;
|
|
17
|
+
return { id: String(row.stream_id), piboSessionId: row.session_id ?? undefined, eventSequence: row.session_sequence ?? undefined, renderSequence, eventId: row.event_id ?? undefined, streamId: row.stream_id, storedPayloadRef, type: row.type, createdAt: row.created_at, payload };
|
|
18
|
+
}
|
|
19
|
+
export function storedChatEventFromV2Row(row, payloadStore, hydrationBytes = MAX_TRACE_EVENT_HYDRATION_BYTES) {
|
|
20
|
+
if (typeof hydrationBytes !== "number" || !Number.isFinite(hydrationBytes))
|
|
21
|
+
hydrationBytes = MAX_TRACE_EVENT_HYDRATION_BYTES;
|
|
22
|
+
const attributes = parseJsonObject(row.attributes_json);
|
|
23
|
+
if (hydrationBytes < MAX_TRACE_EVENT_HYDRATION_BYTES && typeof attributes.inlineText === "string" && Buffer.byteLength(attributes.inlineText) > hydrationBytes)
|
|
24
|
+
attributes.inlineText = row.preview_text ?? "";
|
|
25
|
+
const storedPayloadRef = storedPayloadReference(row, attributes, payloadStore);
|
|
26
|
+
return { storedPayloadRef, streamId: row.stream_id, roomId: row.room_id ?? undefined, piboSessionId: row.session_id ?? undefined, eventId: row.event_id ?? `evt_${row.stream_id}`, eventType: row.type, actorType: actorTypeValue(row.actor_type), actorId: row.actor_id ?? undefined, clientTxnId: typeof attributes.clientTxnId === "string" ? attributes.clientTxnId : undefined, createdAt: row.created_at, retentionClass: retentionClassValue(row.retention_class), payload: (outputPayloadFromV2Row(row, attributes, readPersistedPayload(row, payloadStore, hydrationBytes)) ?? null) };
|
|
27
|
+
}
|
|
28
|
+
function storedPayloadReference(row, attributes, payloadStore) {
|
|
11
29
|
const payloadIdentity = tracePayloadIdentityForEvent(row, attributes);
|
|
12
|
-
|
|
30
|
+
return row.payload_ref && row.session_id && payloadStore && payloadIdentity
|
|
13
31
|
? tracePayloadRefForStoredPayload({
|
|
14
32
|
payloadStore,
|
|
15
33
|
piboSessionId: row.session_id,
|
|
@@ -17,12 +35,6 @@ export function storedPiboEventFromV2Row(row, payloadStore) {
|
|
|
17
35
|
...payloadIdentity,
|
|
18
36
|
})
|
|
19
37
|
: undefined;
|
|
20
|
-
const renderSequence = numberAttribute(attributes, "renderSequence") ?? payload.renderSequence;
|
|
21
|
-
return { id: String(row.stream_id), piboSessionId: row.session_id ?? undefined, eventSequence: row.session_sequence ?? undefined, renderSequence, eventId: row.event_id ?? undefined, streamId: row.stream_id, storedPayloadRef, type: row.type, createdAt: row.created_at, payload };
|
|
22
|
-
}
|
|
23
|
-
export function storedChatEventFromV2Row(row, payloadStore) {
|
|
24
|
-
const attributes = parseJsonObject(row.attributes_json);
|
|
25
|
-
return { streamId: row.stream_id, roomId: row.room_id ?? undefined, piboSessionId: row.session_id ?? undefined, eventId: row.event_id ?? `evt_${row.stream_id}`, eventType: row.type, actorType: actorTypeValue(row.actor_type), actorId: row.actor_id ?? undefined, clientTxnId: typeof attributes.clientTxnId === "string" ? attributes.clientTxnId : undefined, createdAt: row.created_at, retentionClass: retentionClassValue(row.retention_class), payload: (outputPayloadFromV2Row(row, attributes, readPersistedPayload(row, payloadStore)) ?? null) };
|
|
26
38
|
}
|
|
27
39
|
function outputPayloadFromV2Row(row, attributes, persistedPayload) {
|
|
28
40
|
const inlinePayload = attributes.inlinePayload ?? persistedPayload;
|
|
@@ -112,7 +124,7 @@ function outputPayloadFromV2Row(row, attributes, persistedPayload) {
|
|
|
112
124
|
if (row.type === "compaction_start")
|
|
113
125
|
return { ...base, type: "compaction_start", compactionIndex: numberAttribute(attributes, "compactionIndex"), reason: stringAttribute(attributes, "reason") ?? row.preview_text ?? "unknown" };
|
|
114
126
|
if (row.type === "compaction_end")
|
|
115
|
-
return { ...base, type: "compaction_end", compactionIndex: numberAttribute(attributes, "compactionIndex"), reason: stringAttribute(attributes, "reason") ?? row.preview_text ?? "unknown", result: inlinePayload, aborted: booleanAttribute(attributes, "aborted") ?? false, errorMessage: stringAttribute(attributes, "errorMessage") };
|
|
127
|
+
return { ...base, type: "compaction_end", compactionIndex: numberAttribute(attributes, "compactionIndex"), reason: stringAttribute(attributes, "reason") ?? row.preview_text ?? "unknown", result: inlinePayload, aborted: booleanAttribute(attributes, "aborted") ?? false, errorMessage: stringAttribute(attributes, "errorMessage"), compactionStats: isRecord(attributes.compactionStats) ? attributes.compactionStats : undefined };
|
|
116
128
|
if (row.type === "session_error") {
|
|
117
129
|
const error = stringAttribute(attributes, "error") ?? row.preview_text ?? "Error";
|
|
118
130
|
return { ...base, type: "session_error", error, errorDetails: normalizeSessionErrorDetails(error, isRecord(attributes.errorDetails) ? attributes.errorDetails : undefined) };
|
|
@@ -121,14 +133,14 @@ function outputPayloadFromV2Row(row, attributes, persistedPayload) {
|
|
|
121
133
|
return { type: "user.message.accepted", piboSessionId, roomId: row.room_id ?? undefined, text: stringAttribute(attributes, "inlineText") ?? row.preview_text ?? "", clientTxnId: stringAttribute(attributes, "clientTxnId") };
|
|
122
134
|
return { ...base, type: row.type };
|
|
123
135
|
}
|
|
124
|
-
function readPersistedPayload(row, payloadStore) {
|
|
136
|
+
function readPersistedPayload(row, payloadStore, hydrationBytes = MAX_TRACE_EVENT_HYDRATION_BYTES) {
|
|
125
137
|
if (!row.payload_ref || !payloadStore)
|
|
126
138
|
return undefined;
|
|
127
139
|
try {
|
|
128
140
|
const metadata = payloadStore.getPayload(row.payload_ref);
|
|
129
|
-
if (!metadata || metadata.byteSize >
|
|
141
|
+
if (!metadata || metadata.byteSize > hydrationBytes)
|
|
130
142
|
return undefined;
|
|
131
|
-
const text = Buffer.from(payloadStore.readPayloadBytesBounded(row.payload_ref,
|
|
143
|
+
const text = Buffer.from(payloadStore.readPayloadBytesBounded(row.payload_ref, hydrationBytes)).toString("utf8");
|
|
132
144
|
return metadata.contentType.includes("json") ? JSON.parse(text) : text;
|
|
133
145
|
}
|
|
134
146
|
catch {
|
|
@@ -136,6 +148,13 @@ function readPersistedPayload(row, payloadStore) {
|
|
|
136
148
|
}
|
|
137
149
|
}
|
|
138
150
|
function tracePayloadIdentityForEvent(row, attributes) {
|
|
151
|
+
const semanticId = stringAttribute(attributes, "semanticEventId") ?? row.event_id;
|
|
152
|
+
if (semanticId && (row.type === "assistant_message" || row.type === "thinking_finished")) {
|
|
153
|
+
const thinking = row.type === "thinking_finished";
|
|
154
|
+
const index = numberAttribute(attributes, thinking ? "thinkingIndex" : "assistantIndex") ?? numberAttribute(attributes, "contentIndex");
|
|
155
|
+
const identity = index === undefined ? semanticId : `${semanticId}:${thinking ? "thinking" : "assistant"}:${index}`;
|
|
156
|
+
return { nodeId: `${thinking ? "reasoning" : "assistant"}:${identity}`, payloadKind: thinking ? "reasoning" : "output" };
|
|
157
|
+
}
|
|
139
158
|
const toolCallId = stringAttribute(attributes, "toolCallId");
|
|
140
159
|
if (!toolCallId)
|
|
141
160
|
return undefined;
|