@pasko70/pibo 2.5.1 → 3.0.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/context/pibo-native-tooling.md +1 -0
- package/dist/agent-runtime/contract.js +2 -1
- package/dist/agent-runtime/history.js +53 -1
- package/dist/agent-runtime/routed-session.js +100 -26
- package/dist/agent-runtimes/codex-native/adapter.js +307 -31
- package/dist/agent-runtimes/codex-native/client.js +22 -3
- package/dist/agent-runtimes/codex-native/first-use.js +294 -0
- package/dist/agent-runtimes/codex-native/history.js +26 -7
- package/dist/agent-runtimes/codex-native/models.js +48 -1
- package/dist/agent-runtimes/codex-native/process.js +15 -1
- package/dist/agent-runtimes/codex-native/thread.js +75 -31
- package/dist/agent-runtimes/codex-native/turn.js +30 -0
- package/dist/agent-runtimes/history-proof.js +8 -0
- package/dist/agent-runtimes/omp/adapter.js +61 -14
- package/dist/agent-runtimes/omp/history.js +189 -34
- package/dist/agent-runtimes/omp/thread.js +82 -36
- package/dist/agent-runtimes/pi/adapter.js +30 -3
- package/dist/agent-runtimes/pi/history.js +66 -5
- package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
- package/dist/agent-runtimes/pi/routed-session.js +344 -37
- package/dist/apps/chat/agent-store.js +3 -0
- package/dist/apps/chat/chat-api-routes.js +1 -1
- package/dist/apps/chat/chat-files.js +130 -9
- package/dist/apps/chat/chat-request-normalizers.js +6 -0
- package/dist/apps/chat/chat-settings-routes.js +27 -2
- package/dist/apps/chat/chat-speech.js +100 -0
- package/dist/apps/chat/data/chat-data-mappers.js +25 -5
- package/dist/apps/chat/data/timeline-query-service.js +89 -1
- package/dist/apps/chat/trace-v2.js +210 -8
- package/dist/apps/chat/trace.js +3 -0
- package/dist/apps/chat/web-app.js +108 -3
- package/dist/apps/chat-ui/assets/{dist-GS8xUdfg.js → dist-BBDMeU5-.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BpVSf6ZV.js → dist-BDIATCQt.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CRv7fzi3.js → dist-JnW4v8UE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-CbBXbGVi.js → dist-lRiLP9rr.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-HC0xQ92C.js → dist-mqCviI-c.js} +1 -1
- package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
- package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
- package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
- package/dist/apps/chat-vscode-web/index.html +2 -2
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.0.vsix +0 -0
- package/dist/cli.js +49 -5
- package/dist/config/config.js +59 -8
- package/dist/core/preview-server-settings.js +20 -0
- package/dist/core/profiles.js +1 -0
- package/dist/core/session-router.js +476 -44
- package/dist/core/user-settings.js +10 -0
- package/dist/data/payload-store.js +57 -1
- package/dist/data/schema.js +18 -1
- package/dist/debug/trace.js +5 -0
- package/dist/gateway/client.js +368 -65
- package/dist/gateway/protocol.js +2 -0
- package/dist/gateway/server.js +8 -0
- package/dist/gateway/web.js +2 -0
- package/dist/index.js +1 -1
- package/dist/loops/store.js +6 -4
- package/dist/mcp/config.js +89 -17
- package/dist/mcp/daemon-client.js +473 -153
- package/dist/mcp/daemon.js +401 -37
- package/dist/plugins/builtin.js +2 -2
- package/dist/plugins/codex-native.js +2 -0
- package/dist/plugins/registry.js +260 -1
- package/dist/previews/cli.js +345 -0
- package/dist/previews/config.js +61 -0
- package/dist/previews/manager.js +644 -0
- package/dist/previews/network.js +198 -0
- package/dist/previews/plugin.js +11 -0
- package/dist/previews/process-supervisor.js +38 -0
- package/dist/previews/proxy.js +387 -0
- package/dist/previews/store.js +750 -0
- package/dist/previews/types.js +1 -0
- package/dist/previews/web-app.js +446 -0
- package/dist/runs/registry.js +22 -0
- package/dist/runs/tools.js +46 -7
- package/dist/session-ui/terminalRows.js +187 -13
- package/dist/sessions/pibo-data-store.js +37 -0
- package/dist/sessions/runtime-binding-persistence.js +34 -0
- package/dist/sessions/runtime-binding.js +6 -0
- package/dist/sessions/sqlite-store.js +37 -0
- package/dist/shared/trace-engine.js +12 -64
- package/dist/shared/trace-event-projection.js +16 -0
- package/dist/shared/trace-history.js +684 -184
- package/dist/shared/trace-limits.js +7 -0
- package/dist/shared/trace-live-patch.js +61 -0
- package/dist/shared/trace-tool-identity.js +43 -0
- package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
- package/dist/speech/openai-codex.js +407 -0
- package/dist/speech/types.js +8 -0
- package/dist/subagents/context.js +5 -3
- package/dist/subagents/observations.js +94 -6
- package/dist/subagents/tool.js +103 -20
- package/dist/tools/codex-image-artifacts.js +24 -0
- package/dist/tools/codex-image-generation.js +4 -22
- package/dist/web/channel.js +88 -4
- package/dist/web/http.js +2 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
- package/dist/apps/chat-ui/assets/index-DT5jCQBP.js +0 -228
- package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.1.vsix +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export function withOpenAiResponseEventObserver(options, observer) {
|
|
2
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
3
|
+
return {
|
|
4
|
+
...(options ?? {}),
|
|
5
|
+
// Pi does not expose raw events from its shared Codex WebSocket transport.
|
|
6
|
+
// SSE keeps observation request-scoped instead of replacing global constructors.
|
|
7
|
+
transport: "sse",
|
|
8
|
+
fetch: async (input, init) => observeOpenAiSseResponse(await fetchImpl(input, init), observer),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function observeOpenAiSseResponse(response, observer) {
|
|
12
|
+
if (!response.body || !response.headers.get("content-type")?.toLowerCase().includes("text/event-stream")) {
|
|
13
|
+
return response;
|
|
14
|
+
}
|
|
15
|
+
const reader = response.body.getReader();
|
|
16
|
+
const parser = new OpenAiSseParser(observer);
|
|
17
|
+
let cancelled = false;
|
|
18
|
+
const body = new ReadableStream({
|
|
19
|
+
async pull(controller) {
|
|
20
|
+
try {
|
|
21
|
+
const chunk = await reader.read();
|
|
22
|
+
if (cancelled)
|
|
23
|
+
return;
|
|
24
|
+
if (chunk.done) {
|
|
25
|
+
parser.finish();
|
|
26
|
+
reader.releaseLock();
|
|
27
|
+
controller.close();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
parser.push(chunk.value);
|
|
31
|
+
controller.enqueue(chunk.value);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (cancelled)
|
|
35
|
+
return;
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
controller.error(error);
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
async cancel(reason) {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
try {
|
|
43
|
+
await reader.cancel(reason);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
reader.releaseLock();
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}, { highWaterMark: 0 });
|
|
50
|
+
return new Response(body, {
|
|
51
|
+
status: response.status,
|
|
52
|
+
statusText: response.statusText,
|
|
53
|
+
headers: response.headers,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
class OpenAiSseParser {
|
|
57
|
+
observer;
|
|
58
|
+
decoder = new TextDecoder();
|
|
59
|
+
buffer = "";
|
|
60
|
+
constructor(observer) {
|
|
61
|
+
this.observer = observer;
|
|
62
|
+
}
|
|
63
|
+
push(chunk) {
|
|
64
|
+
this.buffer += this.decoder.decode(chunk, { stream: true });
|
|
65
|
+
this.drain();
|
|
66
|
+
}
|
|
67
|
+
finish() {
|
|
68
|
+
this.buffer += this.decoder.decode();
|
|
69
|
+
this.drain(true);
|
|
70
|
+
}
|
|
71
|
+
drain(final = false) {
|
|
72
|
+
this.buffer = this.buffer.replaceAll("\r\n", "\n");
|
|
73
|
+
let separator = this.buffer.indexOf("\n\n");
|
|
74
|
+
while (separator >= 0) {
|
|
75
|
+
this.observeBlock(this.buffer.slice(0, separator));
|
|
76
|
+
this.buffer = this.buffer.slice(separator + 2);
|
|
77
|
+
separator = this.buffer.indexOf("\n\n");
|
|
78
|
+
}
|
|
79
|
+
if (final && this.buffer.trim()) {
|
|
80
|
+
this.observeBlock(this.buffer);
|
|
81
|
+
this.buffer = "";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
observeBlock(block) {
|
|
85
|
+
const data = block
|
|
86
|
+
.split("\n")
|
|
87
|
+
.filter((line) => line.startsWith("data:"))
|
|
88
|
+
.map((line) => line.slice(5).trim())
|
|
89
|
+
.join("\n")
|
|
90
|
+
.trim();
|
|
91
|
+
if (data && data !== "[DONE]")
|
|
92
|
+
observeJson(data, this.observer);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function observeJson(value, observer) {
|
|
96
|
+
try {
|
|
97
|
+
observer(JSON.parse(value));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Provider observation must never interfere with the provider stream.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -8,6 +8,7 @@ import { PIBO_PROVIDER_RECOVERY_MESSAGE_TYPE, PIBO_PROVIDER_RECOVERY_PROMPT, Pib
|
|
|
8
8
|
import { PiAgentRuntimeAuthController } from "./auth.js";
|
|
9
9
|
import { piIntentTracingInstalled, piToolIntentField, splitPiToolIntentArguments } from "./intent-tracing.js";
|
|
10
10
|
import { loadModelCatalog as loadPiModelCatalog } from "./model-catalog.js";
|
|
11
|
+
import { withOpenAiResponseEventObserver } from "./openai-response-observer.js";
|
|
11
12
|
import { PIBO_TRANSCRIPT_INTEGRITY_RESUME_MESSAGE_TYPE, PIBO_TRANSCRIPT_INTEGRITY_RESUME_PROMPT, PiboTranscriptIntegrityError, claimPiboTranscriptIntegrityContinuation, settlePiboTranscriptIntegrityContinuation, } from "../../core/transcript-integrity.js";
|
|
12
13
|
const FAST_SERVICE_TIER = "priority";
|
|
13
14
|
const RUN_REMINDER_CAPABILITY_TOOLS = new Set([
|
|
@@ -24,6 +25,11 @@ function modelSupportsFastServiceTier(model) {
|
|
|
24
25
|
return true;
|
|
25
26
|
return model.api === "openai-responses" && (model.provider === "openai" || model.provider === "openai-codex");
|
|
26
27
|
}
|
|
28
|
+
function modelUsesOpenAiResponses(model) {
|
|
29
|
+
if (model?.api === "openai-codex-responses")
|
|
30
|
+
return true;
|
|
31
|
+
return model?.api === "openai-responses" && (model.provider === "openai" || model.provider === "openai-codex");
|
|
32
|
+
}
|
|
27
33
|
function withFastServiceTier(payload) {
|
|
28
34
|
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
29
35
|
return payload;
|
|
@@ -250,6 +256,9 @@ function normalizeToolExecutionEvent(piboSessionId, candidate, context) {
|
|
|
250
256
|
}
|
|
251
257
|
return undefined;
|
|
252
258
|
}
|
|
259
|
+
function providerWebSearchToolCallId(rawId) {
|
|
260
|
+
return rawId.startsWith("provider:web_search:") ? rawId : `provider:web_search:${rawId}`;
|
|
261
|
+
}
|
|
253
262
|
function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
|
|
254
263
|
const item = recordValue(candidate.item) ?? recordValue(candidate.assistantMessageEvent?.item);
|
|
255
264
|
const itemType = stringValue(item?.type);
|
|
@@ -269,14 +278,22 @@ function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
|
|
|
269
278
|
? `content-${candidate.assistantMessageEvent.contentIndex}`
|
|
270
279
|
: undefined) ??
|
|
271
280
|
"active";
|
|
272
|
-
const toolCallId = rawId
|
|
273
|
-
const query =
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const
|
|
279
|
-
const
|
|
281
|
+
const toolCallId = providerWebSearchToolCallId(rawId);
|
|
282
|
+
const query = firstTrimmedString(candidate.query, candidate.assistantMessageEvent?.query, action?.query, item?.query, item?.search_query);
|
|
283
|
+
const queries = firstStringArray(action?.queries, item?.queries);
|
|
284
|
+
const url = normalizeProviderWebUrl(firstTrimmedString(action?.url, item?.url));
|
|
285
|
+
const pattern = firstTrimmedString(action?.pattern, item?.pattern);
|
|
286
|
+
const actionType = normalizeProviderWebSearchActionType(action?.type, { query, queries, url, pattern });
|
|
287
|
+
const rawSources = firstArray(candidate.sources, candidate.assistantMessageEvent?.sources, action?.sources, item?.sources, item?.results, item?.citations);
|
|
288
|
+
const sources = normalizeProviderWebSearchSources(rawSources);
|
|
289
|
+
const details = {
|
|
290
|
+
...(actionType ? { actionType } : {}),
|
|
291
|
+
...(query ? { query } : {}),
|
|
292
|
+
...(queries ? { queries } : {}),
|
|
293
|
+
...(url ? { url } : {}),
|
|
294
|
+
...(pattern ? { pattern } : {}),
|
|
295
|
+
};
|
|
296
|
+
const args = { providerTool: "web_search", ...details };
|
|
280
297
|
if (status === "running") {
|
|
281
298
|
return {
|
|
282
299
|
type: "tool_execution_started",
|
|
@@ -295,12 +312,106 @@ function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
|
|
|
295
312
|
result: status === "error"
|
|
296
313
|
? (error ?? "Web search failed")
|
|
297
314
|
: {
|
|
298
|
-
...
|
|
299
|
-
...(
|
|
315
|
+
...details,
|
|
316
|
+
...(rawSources ? { sources, sourceCount: sources.length } : {}),
|
|
300
317
|
},
|
|
301
318
|
isError: status === "error",
|
|
302
319
|
};
|
|
303
320
|
}
|
|
321
|
+
function inferProviderWebSearchActionType(details) {
|
|
322
|
+
if (details.pattern)
|
|
323
|
+
return "find_in_page";
|
|
324
|
+
if (details.url)
|
|
325
|
+
return "open_page";
|
|
326
|
+
if (details.query || details.queries)
|
|
327
|
+
return "search";
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
function normalizeProviderWebSearchActionType(value, details) {
|
|
331
|
+
const explicit = firstTrimmedString(value)?.toLowerCase();
|
|
332
|
+
if (explicit === "search")
|
|
333
|
+
return "search";
|
|
334
|
+
if (explicit === "open" || explicit === "open_page")
|
|
335
|
+
return "open_page";
|
|
336
|
+
if (explicit === "find" || explicit === "find_in_page")
|
|
337
|
+
return "find_in_page";
|
|
338
|
+
if (explicit === "other")
|
|
339
|
+
return "other";
|
|
340
|
+
return inferProviderWebSearchActionType(details);
|
|
341
|
+
}
|
|
342
|
+
function normalizeProviderWebSearchSources(candidates) {
|
|
343
|
+
if (!candidates)
|
|
344
|
+
return [];
|
|
345
|
+
const sources = new Map();
|
|
346
|
+
for (const candidate of candidates) {
|
|
347
|
+
const record = recordValue(candidate);
|
|
348
|
+
const rawUrl = typeof candidate === "string"
|
|
349
|
+
? candidate
|
|
350
|
+
: firstTrimmedString(record?.url, record?.href, record?.link);
|
|
351
|
+
const url = normalizeProviderWebUrl(rawUrl, true);
|
|
352
|
+
if (!url)
|
|
353
|
+
continue;
|
|
354
|
+
const title = firstTrimmedString(record?.title, record?.name);
|
|
355
|
+
const existing = sources.get(url);
|
|
356
|
+
if (!existing || (!existing.title && title))
|
|
357
|
+
sources.set(url, { url, ...(title ? { title } : {}) });
|
|
358
|
+
}
|
|
359
|
+
return [...sources.values()];
|
|
360
|
+
}
|
|
361
|
+
function normalizeProviderWebUrl(value, stripHash = false) {
|
|
362
|
+
if (!value)
|
|
363
|
+
return undefined;
|
|
364
|
+
try {
|
|
365
|
+
const url = new URL(value);
|
|
366
|
+
if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username || url.password)
|
|
367
|
+
return undefined;
|
|
368
|
+
if (stripHash)
|
|
369
|
+
url.hash = "";
|
|
370
|
+
return url.href;
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function providerWebSearchCompletionIsProvisional(candidate) {
|
|
377
|
+
const rawEventType = (stringValue(candidate.type) ?? stringValue(candidate.assistantMessageEvent?.type))?.toLowerCase();
|
|
378
|
+
if (!rawEventType?.includes("web_search_call.completed"))
|
|
379
|
+
return false;
|
|
380
|
+
return !recordValue(candidate.item) && !recordValue(candidate.assistantMessageEvent?.item);
|
|
381
|
+
}
|
|
382
|
+
function providerResponseCompleted(candidate) {
|
|
383
|
+
const rawEventType = (stringValue(candidate.type) ?? stringValue(candidate.assistantMessageEvent?.type))?.toLowerCase();
|
|
384
|
+
return rawEventType === "response.completed";
|
|
385
|
+
}
|
|
386
|
+
function normalizeNativeProviderWebSearchEvent(event) {
|
|
387
|
+
if (!event ||
|
|
388
|
+
(event.type !== "tool_execution_started" &&
|
|
389
|
+
event.type !== "tool_execution_updated" &&
|
|
390
|
+
event.type !== "tool_execution_finished") ||
|
|
391
|
+
event.toolName !== "web_search") {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
return { ...event, toolCallId: providerWebSearchToolCallId(event.toolCallId) };
|
|
395
|
+
}
|
|
396
|
+
function providerWebSearchArgsFromResult(result) {
|
|
397
|
+
const details = recordValue(result);
|
|
398
|
+
return {
|
|
399
|
+
providerTool: "web_search",
|
|
400
|
+
...(details?.actionType ? { actionType: details.actionType } : {}),
|
|
401
|
+
...(details?.query ? { query: details.query } : {}),
|
|
402
|
+
...(details?.queries ? { queries: details.queries } : {}),
|
|
403
|
+
...(details?.url ? { url: details.url } : {}),
|
|
404
|
+
...(details?.pattern ? { pattern: details.pattern } : {}),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
function providerWebSearchArgsKey(args) {
|
|
408
|
+
try {
|
|
409
|
+
return JSON.stringify(args);
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return String(args);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
304
415
|
function providerWebSearchStatus(rawType, rawStatus) {
|
|
305
416
|
const type = rawType.toLowerCase();
|
|
306
417
|
const status = stringValue(rawStatus)?.toLowerCase();
|
|
@@ -322,6 +433,29 @@ function firstArray(...values) {
|
|
|
322
433
|
}
|
|
323
434
|
return undefined;
|
|
324
435
|
}
|
|
436
|
+
function firstStringArray(...values) {
|
|
437
|
+
for (const value of values) {
|
|
438
|
+
if (!Array.isArray(value))
|
|
439
|
+
continue;
|
|
440
|
+
const strings = [...new Set(value.flatMap((item) => {
|
|
441
|
+
const normalized = firstTrimmedString(item);
|
|
442
|
+
return normalized ? [normalized] : [];
|
|
443
|
+
}))];
|
|
444
|
+
if (strings.length > 0)
|
|
445
|
+
return strings;
|
|
446
|
+
}
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
function firstTrimmedString(...values) {
|
|
450
|
+
for (const value of values) {
|
|
451
|
+
if (typeof value !== "string")
|
|
452
|
+
continue;
|
|
453
|
+
const trimmed = value.trim();
|
|
454
|
+
if (trimmed)
|
|
455
|
+
return trimmed;
|
|
456
|
+
}
|
|
457
|
+
return undefined;
|
|
458
|
+
}
|
|
325
459
|
export function normalizePiEvent(piboSessionId, event, context) {
|
|
326
460
|
if (!event || typeof event !== "object")
|
|
327
461
|
return undefined;
|
|
@@ -462,13 +596,16 @@ export class RoutedSession {
|
|
|
462
596
|
drainPromise;
|
|
463
597
|
inFlightMessage;
|
|
464
598
|
fastMode = false;
|
|
465
|
-
|
|
599
|
+
providerRequestPatchedAgents = new WeakSet();
|
|
600
|
+
providerWebSearchObservationEnabled = false;
|
|
601
|
+
providerWebSearchLifecycle = new Map();
|
|
466
602
|
activeMessage;
|
|
467
603
|
activeExecutionEvent;
|
|
468
604
|
activeAssistantIndex;
|
|
469
605
|
nextAssistantIndex = 0;
|
|
470
606
|
activeThinkingIndex;
|
|
471
607
|
nextThinkingIndex = 0;
|
|
608
|
+
sessionIdentityOperationInFlight = false;
|
|
472
609
|
pendingAssistantError;
|
|
473
610
|
pendingAssistantErrorRetryable = false;
|
|
474
611
|
activeMessageFailed = false;
|
|
@@ -493,27 +630,43 @@ export class RoutedSession {
|
|
|
493
630
|
this.messagePreflight = messagePreflight;
|
|
494
631
|
this.fastMode = initialFastMode && this.fastModeSupported();
|
|
495
632
|
this.bindRuntimeSession();
|
|
496
|
-
this.
|
|
633
|
+
this.patchProviderRequest();
|
|
497
634
|
this.patchAgentContinue();
|
|
498
635
|
this.runtime.setRebindSession(async () => {
|
|
499
636
|
this.bindRuntimeSession();
|
|
500
|
-
this.
|
|
637
|
+
this.patchProviderRequest();
|
|
501
638
|
this.patchAgentContinue();
|
|
502
639
|
});
|
|
503
640
|
}
|
|
504
|
-
|
|
641
|
+
enableProviderWebSearchObservation() {
|
|
642
|
+
this.providerWebSearchObservationEnabled = true;
|
|
643
|
+
}
|
|
644
|
+
patchProviderRequest() {
|
|
505
645
|
const agent = this.runtime.session.agent;
|
|
506
646
|
if (!agent || typeof agent !== "object")
|
|
507
647
|
return;
|
|
508
|
-
if (this.
|
|
648
|
+
if (this.providerRequestPatchedAgents.has(agent))
|
|
509
649
|
return;
|
|
510
|
-
this.
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
agent.streamFn
|
|
514
|
-
|
|
650
|
+
this.providerRequestPatchedAgents.add(agent);
|
|
651
|
+
const streamFunctionKey = typeof agent.streamFunction === "function"
|
|
652
|
+
? "streamFunction"
|
|
653
|
+
: typeof agent.streamFn === "function"
|
|
654
|
+
? "streamFn"
|
|
655
|
+
: undefined;
|
|
656
|
+
const originalStreamFn = streamFunctionKey ? agent[streamFunctionKey]?.bind(agent) : undefined;
|
|
657
|
+
if (streamFunctionKey && originalStreamFn) {
|
|
658
|
+
const wrappedStreamFn = (model, context, options) => {
|
|
659
|
+
let nextOptions = this.shouldUseFastServiceTier(model) ? withFastServiceTierOption(options) : options;
|
|
660
|
+
if (this.providerWebSearchObservationEnabled && modelUsesOpenAiResponses(model)) {
|
|
661
|
+
const activeEventId = this.activeMessage?.id;
|
|
662
|
+
nextOptions = withOpenAiResponseEventObserver(nextOptions, (event) => this.observeProviderResponseEvent(event, activeEventId));
|
|
663
|
+
}
|
|
515
664
|
return originalStreamFn(model, context, nextOptions);
|
|
516
665
|
};
|
|
666
|
+
if (streamFunctionKey === "streamFunction")
|
|
667
|
+
agent.streamFunction = wrappedStreamFn;
|
|
668
|
+
else
|
|
669
|
+
agent.streamFn = wrappedStreamFn;
|
|
517
670
|
}
|
|
518
671
|
const originalOnPayload = agent.onPayload?.bind(agent);
|
|
519
672
|
agent.onPayload = async (payload, model) => {
|
|
@@ -523,6 +676,111 @@ export class RoutedSession {
|
|
|
523
676
|
return useFastTier ? withFastServiceTier(nextPayload) : nextPayload;
|
|
524
677
|
};
|
|
525
678
|
}
|
|
679
|
+
observeProviderResponseEvent(event, activeEventId) {
|
|
680
|
+
if (!event || typeof event !== "object")
|
|
681
|
+
return;
|
|
682
|
+
if (activeEventId && this.activeMessage?.id !== activeEventId)
|
|
683
|
+
return;
|
|
684
|
+
const candidate = event;
|
|
685
|
+
if (providerResponseCompleted(candidate)) {
|
|
686
|
+
this.flushPendingProviderWebSearchFinishes();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.emitProviderWebSearchEvent(candidate, undefined, "provider");
|
|
690
|
+
}
|
|
691
|
+
emitProviderWebSearchEvent(candidate, normalized = normalizeProviderWebSearchEvent(this.piboSessionId, candidate), source = "provider") {
|
|
692
|
+
if (!normalized || (normalized.type !== "tool_execution_started" &&
|
|
693
|
+
normalized.type !== "tool_execution_updated" &&
|
|
694
|
+
normalized.type !== "tool_execution_finished"))
|
|
695
|
+
return false;
|
|
696
|
+
const lifecycle = this.providerWebSearchLifecycle.get(normalized.toolCallId) ?? { started: false, finished: false };
|
|
697
|
+
if (source === "provider")
|
|
698
|
+
lifecycle.providerObserved = true;
|
|
699
|
+
else if (lifecycle.providerObserved) {
|
|
700
|
+
if (normalized.type === "tool_execution_finished" && !lifecycle.finished && !lifecycle.pendingFinish) {
|
|
701
|
+
lifecycle.pendingFinish = normalized;
|
|
702
|
+
this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
|
|
703
|
+
}
|
|
704
|
+
return true;
|
|
705
|
+
}
|
|
706
|
+
if (normalized.type === "tool_execution_started" || normalized.type === "tool_execution_updated") {
|
|
707
|
+
if (lifecycle.finished)
|
|
708
|
+
return true;
|
|
709
|
+
const argsKey = providerWebSearchArgsKey(normalized.args);
|
|
710
|
+
if (!lifecycle.started) {
|
|
711
|
+
lifecycle.started = true;
|
|
712
|
+
lifecycle.argsKey = argsKey;
|
|
713
|
+
this.emit(this.withActiveMessage(normalized.type === "tool_execution_started" ? normalized : {
|
|
714
|
+
type: "tool_execution_started",
|
|
715
|
+
piboSessionId: normalized.piboSessionId,
|
|
716
|
+
toolCallId: normalized.toolCallId,
|
|
717
|
+
toolName: normalized.toolName,
|
|
718
|
+
args: normalized.args,
|
|
719
|
+
}));
|
|
720
|
+
}
|
|
721
|
+
else if (argsKey !== lifecycle.argsKey) {
|
|
722
|
+
lifecycle.argsKey = argsKey;
|
|
723
|
+
this.emit(this.withActiveMessage({
|
|
724
|
+
type: "tool_execution_updated",
|
|
725
|
+
piboSessionId: this.piboSessionId,
|
|
726
|
+
toolCallId: normalized.toolCallId,
|
|
727
|
+
toolName: normalized.toolName,
|
|
728
|
+
args: normalized.args,
|
|
729
|
+
partialResult: normalized.args,
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
|
|
733
|
+
return true;
|
|
734
|
+
}
|
|
735
|
+
if (providerWebSearchCompletionIsProvisional(candidate) || (source === "pi" && this.providerWebSearchObservationEnabled)) {
|
|
736
|
+
if (!lifecycle.started) {
|
|
737
|
+
const args = providerWebSearchArgsFromResult(normalized.result);
|
|
738
|
+
lifecycle.started = true;
|
|
739
|
+
lifecycle.argsKey = providerWebSearchArgsKey(args);
|
|
740
|
+
this.emit(this.withActiveMessage({
|
|
741
|
+
type: "tool_execution_started",
|
|
742
|
+
piboSessionId: this.piboSessionId,
|
|
743
|
+
toolCallId: normalized.toolCallId,
|
|
744
|
+
toolName: normalized.toolName,
|
|
745
|
+
args,
|
|
746
|
+
}));
|
|
747
|
+
}
|
|
748
|
+
lifecycle.pendingFinish = normalized;
|
|
749
|
+
this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
if (lifecycle.finished)
|
|
753
|
+
return true;
|
|
754
|
+
lifecycle.pendingFinish = undefined;
|
|
755
|
+
this.finishProviderWebSearchLifecycle(normalized, lifecycle);
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
finishProviderWebSearchLifecycle(normalized, lifecycle) {
|
|
759
|
+
if (!lifecycle.started) {
|
|
760
|
+
const args = providerWebSearchArgsFromResult(normalized.result);
|
|
761
|
+
lifecycle.started = true;
|
|
762
|
+
lifecycle.argsKey = providerWebSearchArgsKey(args);
|
|
763
|
+
this.emit(this.withActiveMessage({
|
|
764
|
+
type: "tool_execution_started",
|
|
765
|
+
piboSessionId: this.piboSessionId,
|
|
766
|
+
toolCallId: normalized.toolCallId,
|
|
767
|
+
toolName: normalized.toolName,
|
|
768
|
+
args,
|
|
769
|
+
}));
|
|
770
|
+
}
|
|
771
|
+
lifecycle.finished = true;
|
|
772
|
+
this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
|
|
773
|
+
this.emit(this.withActiveMessage(normalized));
|
|
774
|
+
}
|
|
775
|
+
flushPendingProviderWebSearchFinishes() {
|
|
776
|
+
for (const lifecycle of this.providerWebSearchLifecycle.values()) {
|
|
777
|
+
const pending = lifecycle.pendingFinish;
|
|
778
|
+
if (!pending || lifecycle.finished)
|
|
779
|
+
continue;
|
|
780
|
+
lifecycle.pendingFinish = undefined;
|
|
781
|
+
this.finishProviderWebSearchLifecycle(pending, lifecycle);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
526
784
|
shouldUseFastServiceTier(model) {
|
|
527
785
|
if (this.getFastModeResult().mode !== "fast")
|
|
528
786
|
return false;
|
|
@@ -576,13 +834,17 @@ export class RoutedSession {
|
|
|
576
834
|
this.unsubscribe = session.subscribe((event) => {
|
|
577
835
|
this.onPiEventTelemetry?.(this.piboSessionId, event, { status: this.getStatus(), activeEventId: this.activeMessage?.id ?? this.activeExecutionEvent?.id });
|
|
578
836
|
const model = this.runtime.session.model;
|
|
579
|
-
const
|
|
837
|
+
const candidate = event && typeof event === "object" ? event : undefined;
|
|
838
|
+
const providerWebSearchEvent = candidate ? normalizeProviderWebSearchEvent(this.piboSessionId, candidate) : undefined;
|
|
839
|
+
const normalized = providerWebSearchEvent ?? normalizePiEvent(this.piboSessionId, event, {
|
|
580
840
|
contextWindow: numberValue(model?.contextWindow),
|
|
581
841
|
intentTracing: piIntentTracingInstalled(session),
|
|
582
842
|
intentFieldForTool: (toolName) => piToolIntentField(session, toolName),
|
|
583
843
|
});
|
|
584
|
-
const candidate = event && typeof event === "object" ? event : undefined;
|
|
585
844
|
const assistantMessageEnded = candidate?.type === "message_end" && isAssistantMessage(candidate.message);
|
|
845
|
+
const assistantMessageFailed = assistantMessageEnded && normalized?.type === "session_error";
|
|
846
|
+
if (assistantMessageEnded && !assistantMessageFailed)
|
|
847
|
+
this.flushPendingProviderWebSearchFinishes();
|
|
586
848
|
const usageEvent = assistantMessageEnded ? normalizeAssistantUsageEvent(this.piboSessionId, candidate?.message) : undefined;
|
|
587
849
|
if (usageEvent)
|
|
588
850
|
this.emit(this.withActiveMessage(usageEvent));
|
|
@@ -597,8 +859,18 @@ export class RoutedSession {
|
|
|
597
859
|
this.pendingAssistantError = undefined;
|
|
598
860
|
this.pendingAssistantErrorRetryable = false;
|
|
599
861
|
}
|
|
600
|
-
|
|
862
|
+
const nativeProviderWebSearchEvent = this.providerWebSearchObservationEnabled
|
|
863
|
+
? normalizeNativeProviderWebSearchEvent(normalized)
|
|
864
|
+
: undefined;
|
|
865
|
+
if (providerWebSearchEvent && candidate) {
|
|
866
|
+
this.emitProviderWebSearchEvent(candidate, providerWebSearchEvent, "provider");
|
|
867
|
+
}
|
|
868
|
+
else if (nativeProviderWebSearchEvent && candidate) {
|
|
869
|
+
this.emitProviderWebSearchEvent(candidate, nativeProviderWebSearchEvent, "pi");
|
|
870
|
+
}
|
|
871
|
+
else if (normalized) {
|
|
601
872
|
this.emit(this.withActiveMessage(normalized));
|
|
873
|
+
}
|
|
602
874
|
}
|
|
603
875
|
if (this.forwardPiEvents) {
|
|
604
876
|
this.emit({ type: "pi_event", piboSessionId: this.piboSessionId, event });
|
|
@@ -762,6 +1034,9 @@ export class RoutedSession {
|
|
|
762
1034
|
}
|
|
763
1035
|
enqueueMessage(event) {
|
|
764
1036
|
this.assertActive();
|
|
1037
|
+
if (this.sessionIdentityOperationInFlight) {
|
|
1038
|
+
throw new Error("Pibo session cannot accept messages while a session identity operation is in progress.");
|
|
1039
|
+
}
|
|
765
1040
|
this.queue.push({ kind: "message", event });
|
|
766
1041
|
const output = {
|
|
767
1042
|
type: "message_queued",
|
|
@@ -807,18 +1082,29 @@ export class RoutedSession {
|
|
|
807
1082
|
if (event.action === "compact") {
|
|
808
1083
|
return this.enqueueCompactAction(event);
|
|
809
1084
|
}
|
|
810
|
-
const
|
|
811
|
-
if (
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
1085
|
+
const changesSessionIdentity = event.action === "session.fork" || event.action === "session.clone";
|
|
1086
|
+
if (changesSessionIdentity) {
|
|
1087
|
+
this.assertSessionIdentityOperationIdle(event.action === "session.fork" ? "fork" : "clone");
|
|
1088
|
+
this.sessionIdentityOperationInFlight = true;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
const result = await this.runAction(event);
|
|
1092
|
+
if (isSessionOperationResult(result))
|
|
1093
|
+
await this.onSessionOperation?.(result, event);
|
|
1094
|
+
const output = {
|
|
1095
|
+
type: "execution_result",
|
|
1096
|
+
piboSessionId: this.piboSessionId,
|
|
1097
|
+
eventId: event.id,
|
|
1098
|
+
action: event.action,
|
|
1099
|
+
result,
|
|
1100
|
+
};
|
|
1101
|
+
this.emit(output);
|
|
1102
|
+
return output;
|
|
1103
|
+
}
|
|
1104
|
+
finally {
|
|
1105
|
+
if (changesSessionIdentity)
|
|
1106
|
+
this.sessionIdentityOperationInFlight = false;
|
|
1107
|
+
}
|
|
822
1108
|
}
|
|
823
1109
|
getActiveMessage() {
|
|
824
1110
|
if (!this.activeMessage)
|
|
@@ -919,10 +1205,12 @@ export class RoutedSession {
|
|
|
919
1205
|
}));
|
|
920
1206
|
}
|
|
921
1207
|
getForkCandidates() {
|
|
1208
|
+
this.assertSessionIdentityOperationIdle("inspect fork candidates");
|
|
922
1209
|
return this.runtime.session.getUserMessagesForForking();
|
|
923
1210
|
}
|
|
924
1211
|
async forkSession(entryId) {
|
|
925
1212
|
this.assertActive();
|
|
1213
|
+
this.assertSessionWorkIdle("fork");
|
|
926
1214
|
const previous = this.createSessionSnapshot();
|
|
927
1215
|
const result = await this.runtime.fork(entryId);
|
|
928
1216
|
return {
|
|
@@ -935,6 +1223,7 @@ export class RoutedSession {
|
|
|
935
1223
|
}
|
|
936
1224
|
async cloneSession() {
|
|
937
1225
|
this.assertActive();
|
|
1226
|
+
this.assertSessionWorkIdle("clone");
|
|
938
1227
|
const leafId = this.runtime.session.sessionManager.getLeafId();
|
|
939
1228
|
if (!leafId) {
|
|
940
1229
|
throw new Error("Cannot clone session: no current entry selected");
|
|
@@ -948,6 +1237,18 @@ export class RoutedSession {
|
|
|
948
1237
|
cancelled: result.cancelled,
|
|
949
1238
|
};
|
|
950
1239
|
}
|
|
1240
|
+
assertSessionIdentityOperationIdle(operation) {
|
|
1241
|
+
if (this.sessionIdentityOperationInFlight) {
|
|
1242
|
+
throw new Error(`Pibo session already has a session identity operation in progress; cannot ${operation}.`);
|
|
1243
|
+
}
|
|
1244
|
+
this.assertSessionWorkIdle(operation);
|
|
1245
|
+
}
|
|
1246
|
+
assertSessionWorkIdle(operation) {
|
|
1247
|
+
const status = this.getStatus();
|
|
1248
|
+
if (status.processing || status.streaming || status.queuedMessages > 0) {
|
|
1249
|
+
throw new Error(`Pibo session must be idle to ${operation}.`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
951
1252
|
getSessionTree() {
|
|
952
1253
|
this.assertActive();
|
|
953
1254
|
return {
|
|
@@ -1151,8 +1452,11 @@ export class RoutedSession {
|
|
|
1151
1452
|
const drain = this.drain();
|
|
1152
1453
|
this.drainPromise = drain;
|
|
1153
1454
|
void drain.finally(() => {
|
|
1154
|
-
if (this.drainPromise
|
|
1155
|
-
|
|
1455
|
+
if (this.drainPromise !== drain)
|
|
1456
|
+
return;
|
|
1457
|
+
this.drainPromise = undefined;
|
|
1458
|
+
if (this.queue.length > 0 && !this.disposed)
|
|
1459
|
+
this.startDrain();
|
|
1156
1460
|
});
|
|
1157
1461
|
}
|
|
1158
1462
|
async drain() {
|
|
@@ -1205,6 +1509,7 @@ export class RoutedSession {
|
|
|
1205
1509
|
if (this.disposed || inFlight.cancelled)
|
|
1206
1510
|
return;
|
|
1207
1511
|
this.activeMessage = event;
|
|
1512
|
+
this.providerWebSearchLifecycle.clear();
|
|
1208
1513
|
this.providerRecoveryCancelled = false;
|
|
1209
1514
|
this.pendingAssistantError = undefined;
|
|
1210
1515
|
this.pendingAssistantErrorRetryable = false;
|
|
@@ -1239,6 +1544,7 @@ export class RoutedSession {
|
|
|
1239
1544
|
return;
|
|
1240
1545
|
this.flushPendingAssistantError();
|
|
1241
1546
|
if (!this.activeMessageFailed) {
|
|
1547
|
+
this.flushPendingProviderWebSearchFinishes();
|
|
1242
1548
|
this.emit({
|
|
1243
1549
|
type: "message_finished",
|
|
1244
1550
|
piboSessionId: this.piboSessionId,
|
|
@@ -1272,6 +1578,7 @@ export class RoutedSession {
|
|
|
1272
1578
|
this.nextAssistantIndex = 0;
|
|
1273
1579
|
this.activeThinkingIndex = undefined;
|
|
1274
1580
|
this.nextThinkingIndex = 0;
|
|
1581
|
+
this.providerWebSearchLifecycle.clear();
|
|
1275
1582
|
if (this.inFlightMessage === inFlight)
|
|
1276
1583
|
this.inFlightMessage = undefined;
|
|
1277
1584
|
inFlight.resolveSettled();
|
|
@@ -793,6 +793,9 @@ function sanitizeSubagents(value) {
|
|
|
793
793
|
const thinkingLevel = sanitizeThinkingLevel(candidate.thinkingLevel);
|
|
794
794
|
if (thinkingLevel)
|
|
795
795
|
subagent.thinkingLevel = thinkingLevel;
|
|
796
|
+
if (candidate.runtimeOptions && typeof candidate.runtimeOptions === "object" && !Array.isArray(candidate.runtimeOptions)) {
|
|
797
|
+
subagent.runtimeOptions = structuredClone(candidate.runtimeOptions);
|
|
798
|
+
}
|
|
796
799
|
if (typeof candidate.timeoutMs === "number")
|
|
797
800
|
subagent.timeoutMs = candidate.timeoutMs;
|
|
798
801
|
if (typeof candidate.maxDepth === "number")
|
|
@@ -314,7 +314,7 @@ export function sessionActionResource(pathname) {
|
|
|
314
314
|
if (parts.length !== 2 || !parts[0])
|
|
315
315
|
return undefined;
|
|
316
316
|
const action = parts[1];
|
|
317
|
-
if (action !== "read" && action !== "kill" && action !== "kill-all" && action !== "runtime-binding")
|
|
317
|
+
if (action !== "read" && action !== "kill" && action !== "kill-all" && action !== "runtime-binding" && action !== "fork-candidates")
|
|
318
318
|
return undefined;
|
|
319
319
|
try {
|
|
320
320
|
return { piboSessionId: decodeURIComponent(parts[0]), action };
|