@opengeni/react 0.42.1 → 0.44.2
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/{chunk-SJKT4TKW.js → chunk-23EJ676W.js} +3 -1
- package/dist/chunk-23EJ676W.js.map +1 -0
- package/dist/{chunk-Q2NCKWTK.js → chunk-HFO4ERGQ.js} +51 -4
- package/dist/chunk-HFO4ERGQ.js.map +1 -0
- package/dist/{chunk-4IJCL7YO.js → chunk-LWR4MXSS.js} +4 -1
- package/dist/chunk-LWR4MXSS.js.map +1 -0
- package/dist/{chunk-UWYTCQWW.js → chunk-QQRM3DO3.js} +93 -22
- package/dist/{chunk-UWYTCQWW.js.map → chunk-QQRM3DO3.js.map} +1 -1
- package/dist/{chunk-JALF5FI3.js → chunk-SRFUT2ZU.js} +2 -2
- package/dist/{chunk-WZT5G5OR.js → chunk-U6K24XQD.js} +5 -4
- package/dist/{chunk-WZT5G5OR.js.map → chunk-U6K24XQD.js.map} +1 -1
- package/dist/{chunk-KR2SK5GJ.js → chunk-YNYIAYXQ.js} +427 -94
- package/dist/chunk-YNYIAYXQ.js.map +1 -0
- package/dist/components/chat-composer.d.ts +5 -1
- package/dist/components/composer-transcription-control.d.ts +3 -1
- package/dist/components/composer.d.ts +6 -4
- package/dist/components/session-chrome.d.ts +1 -1
- package/dist/composer.js +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +70 -21
- package/dist/index.js.map +1 -1
- package/dist/model-policy.d.ts +2 -0
- package/dist/model-policy.js +1 -1
- package/dist/realtime/realtime-control.d.ts +29 -1
- package/dist/realtime.d.ts +1 -1
- package/dist/realtime.js +269 -151
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.js +2 -2
- package/dist/session.js +3 -3
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/parsers.d.ts +13 -8
- package/package.json +2 -2
- package/src/components/chat-composer.tsx +58 -19
- package/src/components/composer-transcription-control.tsx +194 -165
- package/src/components/composer.tsx +78 -14
- package/src/components/model-policy-picker.tsx +7 -2
- package/src/components/session-chrome.tsx +89 -78
- package/src/hooks/use-composer.ts +79 -9
- package/src/index.ts +2 -0
- package/src/model-policy.ts +4 -0
- package/src/realtime/realtime-control.tsx +307 -137
- package/src/realtime.ts +1 -0
- package/src/timeline/index.ts +2 -0
- package/src/timeline/parsers.ts +201 -19
- package/src/timeline/projection.ts +11 -0
- package/src/timeline/tool-renderers.tsx +213 -8
- package/src/timeline/turn-summary.tsx +7 -2
- package/styles/tokens.css +8 -5
- package/dist/chunk-4IJCL7YO.js.map +0 -1
- package/dist/chunk-KR2SK5GJ.js.map +0 -1
- package/dist/chunk-Q2NCKWTK.js.map +0 -1
- package/dist/chunk-SJKT4TKW.js.map +0 -1
- /package/dist/{chunk-JALF5FI3.js.map → chunk-SRFUT2ZU.js.map} +0 -0
package/src/timeline/parsers.ts
CHANGED
|
@@ -210,40 +210,222 @@ export function v4aToGitFileDiff(op: ApplyPatchOperation): GitFileDiff {
|
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
const BEGIN_PATCH = "*** Begin Patch";
|
|
214
|
+
const END_PATCH = "*** End Patch";
|
|
215
|
+
const ADD_FILE = "*** Add File: ";
|
|
216
|
+
const DELETE_FILE = "*** Delete File: ";
|
|
217
|
+
const UPDATE_FILE = "*** Update File: ";
|
|
218
|
+
const MOVE_TO = "*** Move to: ";
|
|
219
|
+
|
|
220
|
+
const APPLY_PATCH_OP_TYPES = new Set(["create_file", "update_file", "delete_file"]);
|
|
221
|
+
|
|
222
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
223
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Freeform / `{ patch }` / command payloads — tolerate leading whitespace. */
|
|
227
|
+
function freeformApplyPatchOps(rawPatch: string): ApplyPatchOperation[] {
|
|
228
|
+
return parseFreeformApplyPatch(rawPatch.trimStart());
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function asApplyPatchOperation(value: unknown): ApplyPatchOperation | null {
|
|
232
|
+
if (!isRecord(value)) return null;
|
|
233
|
+
if (typeof value.type !== "string" || !APPLY_PATCH_OP_TYPES.has(value.type)) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
if (typeof value.path !== "string" || !value.path) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const op: ApplyPatchOperation = {
|
|
240
|
+
type: value.type as ApplyPatchOperation["type"],
|
|
241
|
+
path: value.path,
|
|
242
|
+
};
|
|
243
|
+
if (typeof value.diff === "string") op.diff = value.diff;
|
|
244
|
+
if (typeof value.moveTo === "string" && value.moveTo.length > 0) op.moveTo = value.moveTo;
|
|
245
|
+
return op;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function parseStructuredOperations(payloads: unknown[]): ApplyPatchOperation[] {
|
|
249
|
+
if (payloads.length === 0) return [];
|
|
250
|
+
const operations: ApplyPatchOperation[] = [];
|
|
251
|
+
for (const payload of payloads) {
|
|
252
|
+
const op = asApplyPatchOperation(payload);
|
|
253
|
+
if (!op) return [];
|
|
254
|
+
operations.push(op);
|
|
255
|
+
}
|
|
256
|
+
return operations;
|
|
257
|
+
}
|
|
258
|
+
|
|
213
259
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
* renderer and the turn-summary facet counter never drift.
|
|
260
|
+
* Mirror of `@openai/agents-core` freeform `*** Begin Patch` → ops. Kept here so
|
|
261
|
+
* the timeline can render Codex function-tool apply_patch without importing the
|
|
262
|
+
* server SDK package.
|
|
218
263
|
*/
|
|
219
|
-
export function
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
264
|
+
export function parseFreeformApplyPatch(rawPatch: string): ApplyPatchOperation[] {
|
|
265
|
+
const lines = rawPatch.split(/\r?\n/);
|
|
266
|
+
if (lines.at(-1) === "") lines.pop();
|
|
267
|
+
if (lines[0] !== BEGIN_PATCH) return [];
|
|
268
|
+
if (lines.length < 2 || lines.at(-1) !== END_PATCH) return [];
|
|
269
|
+
|
|
270
|
+
const operations: ApplyPatchOperation[] = [];
|
|
271
|
+
let index = 1;
|
|
272
|
+
while (index < lines.length - 1) {
|
|
273
|
+
const line = lines[index]!;
|
|
274
|
+
let parsed: { operation: ApplyPatchOperation; nextIndex: number } | { error: true } | null =
|
|
275
|
+
null;
|
|
276
|
+
if (line.startsWith(ADD_FILE)) parsed = parseAddFilePatch(lines, index);
|
|
277
|
+
else if (line.startsWith(DELETE_FILE)) parsed = parseDeleteFilePatch(lines, index);
|
|
278
|
+
else if (line.startsWith(UPDATE_FILE)) parsed = parseUpdateFilePatch(lines, index);
|
|
279
|
+
else return [];
|
|
280
|
+
if (!parsed || "error" in parsed) return [];
|
|
281
|
+
operations.push(parsed.operation);
|
|
282
|
+
index = parsed.nextIndex;
|
|
283
|
+
}
|
|
284
|
+
// Match the SDK: Begin/End with no file ops is not a valid patch.
|
|
285
|
+
return operations.length > 0 ? operations : [];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function parsePatchHeader(line: string, prefix: string): string | null {
|
|
289
|
+
const path = line.slice(prefix.length).trim();
|
|
290
|
+
return path || null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function isFileOperationHeader(line: string): boolean {
|
|
294
|
+
return line.startsWith(ADD_FILE) || line.startsWith(DELETE_FILE) || line.startsWith(UPDATE_FILE);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function joinDiff(lines: string[]): string {
|
|
298
|
+
return `${lines.join("\n")}\n`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function parseAddFilePatch(
|
|
302
|
+
lines: string[],
|
|
303
|
+
index: number,
|
|
304
|
+
): { operation: ApplyPatchOperation; nextIndex: number } | { error: true } {
|
|
305
|
+
const path = parsePatchHeader(lines[index]!, ADD_FILE);
|
|
306
|
+
if (!path) return { error: true };
|
|
307
|
+
index += 1;
|
|
308
|
+
const diffLines: string[] = [];
|
|
309
|
+
while (index < lines.length - 1 && !isFileOperationHeader(lines[index]!)) {
|
|
310
|
+
const line = lines[index]!;
|
|
311
|
+
if (!line.startsWith("+")) return { error: true };
|
|
312
|
+
diffLines.push(line);
|
|
313
|
+
index += 1;
|
|
314
|
+
}
|
|
315
|
+
if (diffLines.length === 0) return { error: true };
|
|
316
|
+
return {
|
|
317
|
+
operation: { type: "create_file", path, diff: joinDiff(diffLines) },
|
|
318
|
+
nextIndex: index,
|
|
223
319
|
};
|
|
224
|
-
|
|
225
|
-
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function parseDeleteFilePatch(
|
|
323
|
+
lines: string[],
|
|
324
|
+
index: number,
|
|
325
|
+
): { operation: ApplyPatchOperation; nextIndex: number } | { error: true } {
|
|
326
|
+
const path = parsePatchHeader(lines[index]!, DELETE_FILE);
|
|
327
|
+
if (!path) return { error: true };
|
|
328
|
+
index += 1;
|
|
329
|
+
if (index < lines.length - 1 && !isFileOperationHeader(lines[index]!)) {
|
|
330
|
+
return { error: true };
|
|
331
|
+
}
|
|
332
|
+
return { operation: { type: "delete_file", path }, nextIndex: index };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function parseUpdateFilePatch(
|
|
336
|
+
lines: string[],
|
|
337
|
+
index: number,
|
|
338
|
+
): { operation: ApplyPatchOperation; nextIndex: number } | { error: true } {
|
|
339
|
+
const path = parsePatchHeader(lines[index]!, UPDATE_FILE);
|
|
340
|
+
if (!path) return { error: true };
|
|
341
|
+
index += 1;
|
|
342
|
+
let moveTo: string | undefined;
|
|
343
|
+
if (index < lines.length - 1 && lines[index]!.startsWith(MOVE_TO)) {
|
|
344
|
+
const parsedMoveTo = parsePatchHeader(lines[index]!, MOVE_TO);
|
|
345
|
+
if (!parsedMoveTo) return { error: true };
|
|
346
|
+
moveTo = parsedMoveTo;
|
|
347
|
+
index += 1;
|
|
348
|
+
}
|
|
349
|
+
const diffLines: string[] = [];
|
|
350
|
+
while (index < lines.length - 1 && !isFileOperationHeader(lines[index]!)) {
|
|
351
|
+
diffLines.push(lines[index]!);
|
|
352
|
+
index += 1;
|
|
353
|
+
}
|
|
354
|
+
if (diffLines.length === 0 && !moveTo) return { error: true };
|
|
355
|
+
return {
|
|
356
|
+
operation: {
|
|
357
|
+
type: "update_file",
|
|
358
|
+
path,
|
|
359
|
+
diff: diffLines.length > 0 ? joinDiff(diffLines) : "",
|
|
360
|
+
...(moveTo ? { moveTo } : {}),
|
|
361
|
+
},
|
|
362
|
+
nextIndex: index,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Normalize every apply_patch payload the Agents SDK accepts into structured
|
|
368
|
+
* ops — hosted `{ operation }` / `{ operations }`, function-tool `{ patch }`,
|
|
369
|
+
* `command` tuple, flat op, freeform string, or op array.
|
|
370
|
+
*/
|
|
371
|
+
export function applyPatchOps(raw: unknown): ApplyPatchOperation[] {
|
|
372
|
+
if (raw == null) return [];
|
|
373
|
+
if (typeof raw === "string") {
|
|
374
|
+
const trimmed = raw.trimStart();
|
|
375
|
+
if (trimmed.startsWith(BEGIN_PATCH)) return freeformApplyPatchOps(trimmed);
|
|
376
|
+
const parsed = tryParseJson(trimmed);
|
|
377
|
+
return parsed === undefined ? [] : applyPatchOps(parsed);
|
|
378
|
+
}
|
|
379
|
+
if (Array.isArray(raw)) return parseStructuredOperations(raw);
|
|
380
|
+
if (!isRecord(raw)) return [];
|
|
381
|
+
|
|
382
|
+
if (typeof raw.patch === "string") return freeformApplyPatchOps(raw.patch);
|
|
383
|
+
if (Array.isArray(raw.command)) {
|
|
384
|
+
const [commandName, patch] = raw.command;
|
|
385
|
+
if (commandName === "apply_patch" && typeof patch === "string") {
|
|
386
|
+
return freeformApplyPatchOps(patch);
|
|
387
|
+
}
|
|
226
388
|
}
|
|
227
|
-
|
|
389
|
+
// Empty `operations: []` is not authoritative — fall through to operation/flat.
|
|
390
|
+
if (Array.isArray(raw.operations) && raw.operations.length > 0) {
|
|
391
|
+
return parseStructuredOperations(raw.operations);
|
|
392
|
+
}
|
|
393
|
+
if (raw.operation !== undefined) {
|
|
394
|
+
const op = asApplyPatchOperation(raw.operation);
|
|
395
|
+
return op ? [op] : [];
|
|
396
|
+
}
|
|
397
|
+
// Flat single op: `{ type, path, diff?, moveTo? }`.
|
|
398
|
+
const flat = asApplyPatchOperation(raw);
|
|
399
|
+
return flat ? [flat] : [];
|
|
228
400
|
}
|
|
229
401
|
|
|
230
|
-
/** Ops from provider `raw
|
|
402
|
+
/** Ops from provider `raw` and/or function-tool arguments (Codex path). */
|
|
231
403
|
export function applyPatchOpsFromToolItem(item: {
|
|
232
404
|
raw: unknown;
|
|
233
405
|
arguments: unknown;
|
|
234
406
|
}): ApplyPatchOperation[] {
|
|
235
407
|
const fromRaw = applyPatchOps(item.raw);
|
|
236
|
-
if (fromRaw.length > 0)
|
|
237
|
-
|
|
408
|
+
if (fromRaw.length > 0) return fromRaw;
|
|
409
|
+
|
|
410
|
+
// function_call envelopes sometimes keep the payload only under raw.arguments.
|
|
411
|
+
if (isRecord(item.raw)) {
|
|
412
|
+
const nested = item.raw.arguments ?? item.raw.input;
|
|
413
|
+
if (nested !== undefined && nested !== item.arguments) {
|
|
414
|
+
const fromNested = applyPatchOps(nested);
|
|
415
|
+
if (fromNested.length > 0) return fromNested;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (item.arguments !== undefined && item.arguments !== null) {
|
|
420
|
+
return applyPatchOps(item.arguments);
|
|
238
421
|
}
|
|
239
|
-
|
|
240
|
-
return applyPatchOps(args);
|
|
422
|
+
return [];
|
|
241
423
|
}
|
|
242
424
|
|
|
243
425
|
/**
|
|
244
|
-
* True when a tool item is
|
|
245
|
-
* `
|
|
246
|
-
*
|
|
426
|
+
* True when a tool item is apply_patch — hosted `raw.type === "apply_patch_call"`,
|
|
427
|
+
* function-tool `name` `apply_patch` / `apply_patch_call`, or an MCP-prefixed
|
|
428
|
+
* `…__apply_patch` leaf. Centralizes the rawType-or-name check.
|
|
247
429
|
*/
|
|
248
430
|
export function isApplyPatch(item: { name: string; raw: unknown }): boolean {
|
|
249
431
|
const type =
|
|
@@ -54,6 +54,11 @@ const WORKER_MESSAGE_TOOL = "session_send_message";
|
|
|
54
54
|
* `"agent"`. That keeps mid-turn goal tools from splitting the step rail with
|
|
55
55
|
* a breakaway GoalRow pill. Non-agent goal events (API, create-session,
|
|
56
56
|
* system auto-pause, continuations) still render as landmarks.
|
|
57
|
+
*
|
|
58
|
+
* Solo `goal_continuation` machine-input batches are also suppressed: the
|
|
59
|
+
* paired `goal.continuation` GoalRow already marks the tick; rendering both
|
|
60
|
+
* restates the goal text. Mixed batches (continuation + other kinds) still
|
|
61
|
+
* render as machine-input rows.
|
|
57
62
|
*/
|
|
58
63
|
const LANDMARK_ONLY_TOOL_LEAVES = new Set(["memory_save", "memory_correct"]);
|
|
59
64
|
|
|
@@ -140,6 +145,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
140
145
|
case "system.update.delivered": {
|
|
141
146
|
const inputs = machineInputMembers(payload.members);
|
|
142
147
|
if (inputs.length === 0) break;
|
|
148
|
+
// Goal continuations already land as `goal.continuation` GoalRows.
|
|
149
|
+
// A solo continuation batch would duplicate that landmark + dump the
|
|
150
|
+
// model-facing prompt — skip chrome for that case only.
|
|
151
|
+
if (inputs.every((member) => member.kind === "goal_continuation")) {
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
143
154
|
closeStreamingTail();
|
|
144
155
|
items.push({
|
|
145
156
|
kind: "machine-input-batch",
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
MessagesSquareIcon,
|
|
18
18
|
MessageSquareIcon,
|
|
19
19
|
MousePointer2Icon,
|
|
20
|
+
PackageSearchIcon,
|
|
20
21
|
PanelsTopLeftIcon,
|
|
21
22
|
PlugIcon,
|
|
22
23
|
SearchIcon,
|
|
@@ -364,12 +365,13 @@ function ApplyPatchRenderer({ item }: ToolRendererProps) {
|
|
|
364
365
|
</RunningPreview>
|
|
365
366
|
}
|
|
366
367
|
>
|
|
367
|
-
{ops.map((op) => {
|
|
368
|
+
{ops.map((op, index) => {
|
|
368
369
|
const file = safeParseOp(op);
|
|
370
|
+
const key = `${op.type}:${op.path}:${index}`;
|
|
369
371
|
return file ? (
|
|
370
|
-
<ToolDiff key={
|
|
372
|
+
<ToolDiff key={key} files={[file]} />
|
|
371
373
|
) : (
|
|
372
|
-
<div key={
|
|
374
|
+
<div key={key}>
|
|
373
375
|
<p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{op.path}</p>
|
|
374
376
|
<RawPatch diff={op.diff ?? ""} />
|
|
375
377
|
</div>
|
|
@@ -421,10 +423,11 @@ function ApplyPatchRenderer({ item }: ToolRendererProps) {
|
|
|
421
423
|
>
|
|
422
424
|
{ops.map((op, index) => {
|
|
423
425
|
const file = parsed[index];
|
|
426
|
+
const key = `${op.type}:${op.path}:${index}`;
|
|
424
427
|
return file ? (
|
|
425
|
-
<ToolDiff key={
|
|
428
|
+
<ToolDiff key={key} files={[file]} />
|
|
426
429
|
) : (
|
|
427
|
-
<div key={
|
|
430
|
+
<div key={key}>
|
|
428
431
|
<p className="mb-1 font-og-mono text-og-xs text-og-fg-muted">{op.path}</p>
|
|
429
432
|
<RawPatch diff={op.diff ?? ""} />
|
|
430
433
|
</div>
|
|
@@ -1031,6 +1034,204 @@ function SecretSetRenderer({ item }: ToolRendererProps) {
|
|
|
1031
1034
|
);
|
|
1032
1035
|
}
|
|
1033
1036
|
|
|
1037
|
+
/* ---- tool_search (progressive MCP disclosure) ------------------------------ */
|
|
1038
|
+
|
|
1039
|
+
type DisclosedTool = {
|
|
1040
|
+
/** Full wire name (`server__leaf` or bare). */
|
|
1041
|
+
name: string;
|
|
1042
|
+
/** Server / namespace prefix before `__`, when present. */
|
|
1043
|
+
source: string | null;
|
|
1044
|
+
/** Leaf tool name after `__`. */
|
|
1045
|
+
leaf: string;
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
function splitToolWireName(name: string): DisclosedTool {
|
|
1049
|
+
const boundary = name.indexOf("__");
|
|
1050
|
+
if (boundary <= 0) {
|
|
1051
|
+
return { name, source: null, leaf: name };
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
name,
|
|
1055
|
+
source: name.slice(0, boundary),
|
|
1056
|
+
leaf: name.slice(boundary + 2),
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/** Capability query from live tool_search args (object or JSON string). */
|
|
1061
|
+
function toolSearchQuery(item: ToolRendererProps["item"]): string {
|
|
1062
|
+
const fromArgs = parseToolArgs(item.arguments);
|
|
1063
|
+
if (typeof fromArgs.query === "string" && fromArgs.query.trim()) {
|
|
1064
|
+
return fromArgs.query.trim();
|
|
1065
|
+
}
|
|
1066
|
+
const raw = item.raw;
|
|
1067
|
+
if (raw && typeof raw === "object") {
|
|
1068
|
+
const rawArgs = (raw as { arguments?: unknown }).arguments;
|
|
1069
|
+
if (typeof rawArgs === "string" && rawArgs.trim()) {
|
|
1070
|
+
const parsed = tryParseJson(rawArgs);
|
|
1071
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1072
|
+
const query = (parsed as { query?: unknown }).query;
|
|
1073
|
+
if (typeof query === "string" && query.trim()) {
|
|
1074
|
+
return query.trim();
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
} else if (rawArgs && typeof rawArgs === "object" && !Array.isArray(rawArgs)) {
|
|
1078
|
+
const query = (rawArgs as { query?: unknown }).query;
|
|
1079
|
+
if (typeof query === "string" && query.trim()) {
|
|
1080
|
+
return query.trim();
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return "";
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
/**
|
|
1088
|
+
* Parse disclosed tools from the runtime event shape.
|
|
1089
|
+
* `normalizeSdkEvent` collapses `tool_search_output.tools[]` into text:
|
|
1090
|
+
* "Disclosed tools: a, b" | "No matching tools found."
|
|
1091
|
+
* Also accept a structured `tools` array when a host/enricher preserves it.
|
|
1092
|
+
*/
|
|
1093
|
+
function parseDisclosedTools(output: unknown): DisclosedTool[] | null {
|
|
1094
|
+
if (output && typeof output === "object" && !Array.isArray(output)) {
|
|
1095
|
+
const tools = (output as { tools?: unknown }).tools;
|
|
1096
|
+
if (Array.isArray(tools)) {
|
|
1097
|
+
return tools
|
|
1098
|
+
.map((tool) => {
|
|
1099
|
+
if (typeof tool === "string" && tool.trim()) {
|
|
1100
|
+
return splitToolWireName(tool.trim());
|
|
1101
|
+
}
|
|
1102
|
+
if (
|
|
1103
|
+
tool &&
|
|
1104
|
+
typeof tool === "object" &&
|
|
1105
|
+
typeof (tool as { name?: unknown }).name === "string"
|
|
1106
|
+
) {
|
|
1107
|
+
const name = (tool as { name: string }).name.trim();
|
|
1108
|
+
return name ? splitToolWireName(name) : null;
|
|
1109
|
+
}
|
|
1110
|
+
return null;
|
|
1111
|
+
})
|
|
1112
|
+
.filter((tool): tool is DisclosedTool => tool != null);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const { text } = unwrapMcpOutput(output);
|
|
1117
|
+
const trimmed = text.trim();
|
|
1118
|
+
if (!trimmed) {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
if (/^no matching tools found\.?$/i.test(trimmed)) {
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
const disclosed = trimmed.match(/^disclosed tools:\s*(.+)$/i);
|
|
1125
|
+
if (disclosed?.[1]) {
|
|
1126
|
+
return disclosed[1]
|
|
1127
|
+
.split(",")
|
|
1128
|
+
.map((part) => part.trim())
|
|
1129
|
+
.filter(Boolean)
|
|
1130
|
+
.map(splitToolWireName);
|
|
1131
|
+
}
|
|
1132
|
+
const parsed = tryParseJson(trimmed);
|
|
1133
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1134
|
+
return parseDisclosedTools(parsed);
|
|
1135
|
+
}
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
function toolSearchPreview(tools: DisclosedTool[] | null, cancelled: boolean): string | undefined {
|
|
1140
|
+
if (cancelled) {
|
|
1141
|
+
return undefined;
|
|
1142
|
+
}
|
|
1143
|
+
if (!tools) {
|
|
1144
|
+
return "Done";
|
|
1145
|
+
}
|
|
1146
|
+
if (tools.length === 0) {
|
|
1147
|
+
return "No matches";
|
|
1148
|
+
}
|
|
1149
|
+
if (tools.length === 1) {
|
|
1150
|
+
return tools[0]!.leaf;
|
|
1151
|
+
}
|
|
1152
|
+
const head = tools[0]!.leaf;
|
|
1153
|
+
return `${tools.length} tools · ${truncatePreview(head, 28)}`;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function ToolSearchRenderer({ item }: ToolRendererProps) {
|
|
1157
|
+
const query = toolSearchQuery(item);
|
|
1158
|
+
const icon = <PackageSearchIcon className={ICON_SIZE} />;
|
|
1159
|
+
const running = item.status === "running";
|
|
1160
|
+
const queryPreview = query ? truncatePreview(query, 64) : "";
|
|
1161
|
+
|
|
1162
|
+
if (running) {
|
|
1163
|
+
return (
|
|
1164
|
+
<ActivityDisclosure
|
|
1165
|
+
icon={icon}
|
|
1166
|
+
iconTone="running"
|
|
1167
|
+
title="Looking up tools"
|
|
1168
|
+
running
|
|
1169
|
+
preview={
|
|
1170
|
+
queryPreview ? (
|
|
1171
|
+
<RunningPreview>{queryPreview}</RunningPreview>
|
|
1172
|
+
) : (
|
|
1173
|
+
<RunningPreview>Matching capabilities…</RunningPreview>
|
|
1174
|
+
)
|
|
1175
|
+
}
|
|
1176
|
+
>
|
|
1177
|
+
{query ? <BodyNote>capability query: {query}</BodyNote> : null}
|
|
1178
|
+
<PayloadBlock label="Arguments" value={redactSecrets(parseToolArgs(item.arguments))} />
|
|
1179
|
+
</ActivityDisclosure>
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
const { text: outText, isError } = unwrapMcpOutput(item.output);
|
|
1184
|
+
if ((isError || item.status === "failed") && item.status !== "cancelled") {
|
|
1185
|
+
return (
|
|
1186
|
+
<ActivityDisclosure
|
|
1187
|
+
icon={icon}
|
|
1188
|
+
iconTone="failed"
|
|
1189
|
+
title="Tool lookup failed"
|
|
1190
|
+
failed
|
|
1191
|
+
preview={truncatePreview(outText, 80) || queryPreview || "Lookup failed"}
|
|
1192
|
+
>
|
|
1193
|
+
{query ? <BodyNote>capability query: {query}</BodyNote> : null}
|
|
1194
|
+
<PayloadBlock label="Arguments" value={redactSecrets(parseToolArgs(item.arguments))} />
|
|
1195
|
+
<PayloadBlock label="Error" value={outText} failed />
|
|
1196
|
+
</ActivityDisclosure>
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const tools = parseDisclosedTools(item.output);
|
|
1201
|
+
const preview = toolSearchPreview(tools, item.status === "cancelled");
|
|
1202
|
+
|
|
1203
|
+
return (
|
|
1204
|
+
<ActivityDisclosure
|
|
1205
|
+
icon={icon}
|
|
1206
|
+
iconTone="muted"
|
|
1207
|
+
title="Looked up tools"
|
|
1208
|
+
cancelled={item.status === "cancelled"}
|
|
1209
|
+
preview={preview}
|
|
1210
|
+
>
|
|
1211
|
+
{query ? <BodyNote>capability query: {query}</BodyNote> : null}
|
|
1212
|
+
{tools && tools.length > 0 ? (
|
|
1213
|
+
<ul className="grid gap-1.5">
|
|
1214
|
+
{tools.slice(0, 12).map((tool) => (
|
|
1215
|
+
<li key={tool.name} className="flex min-w-0 items-baseline gap-2">
|
|
1216
|
+
{tool.source ? (
|
|
1217
|
+
<span className="shrink-0 text-og-xs text-og-fg-subtle">{tool.source}</span>
|
|
1218
|
+
) : null}
|
|
1219
|
+
<span className="truncate font-mono text-og-sm text-og-fg">{tool.leaf}</span>
|
|
1220
|
+
</li>
|
|
1221
|
+
))}
|
|
1222
|
+
{tools.length > 12 ? (
|
|
1223
|
+
<li className="text-og-xs text-og-fg-muted">+{tools.length - 12} more</li>
|
|
1224
|
+
) : null}
|
|
1225
|
+
</ul>
|
|
1226
|
+
) : tools && tools.length === 0 ? (
|
|
1227
|
+
<BodyNote>no deferred tools matched this capability query.</BodyNote>
|
|
1228
|
+
) : null}
|
|
1229
|
+
<PayloadBlock label="Arguments" value={redactSecrets(parseToolArgs(item.arguments))} />
|
|
1230
|
+
{tools == null && outText ? <PayloadBlock label="Result" value={outText} /> : null}
|
|
1231
|
+
</ActivityDisclosure>
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1034
1235
|
/* ---- docs / knowledge search ----------------------------------------------- */
|
|
1035
1236
|
|
|
1036
1237
|
function DocsSearchRenderer({ item }: ToolRendererProps) {
|
|
@@ -1575,9 +1776,11 @@ function GenericToolIcon({ name }: { name: string }) {
|
|
|
1575
1776
|
leaf.includes("knowledge") ||
|
|
1576
1777
|
leaf === "list_document_bases"
|
|
1577
1778
|
? FileSearchIcon
|
|
1578
|
-
: leaf === "tool_search"
|
|
1579
|
-
?
|
|
1580
|
-
:
|
|
1779
|
+
: leaf === "tool_search"
|
|
1780
|
+
? PackageSearchIcon
|
|
1781
|
+
: leaf === "load_skill"
|
|
1782
|
+
? PlugIcon
|
|
1783
|
+
: WrenchIcon;
|
|
1581
1784
|
return <Icon className={ICON_SIZE} />;
|
|
1582
1785
|
}
|
|
1583
1786
|
|
|
@@ -1589,6 +1792,7 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
|
|
|
1589
1792
|
{ match: "rawType", type: "apply_patch_call", render: ApplyPatchRenderer },
|
|
1590
1793
|
{ match: "rawType", type: "computer_call", render: ComputerCallRenderer },
|
|
1591
1794
|
{ match: "rawType", type: "hosted_tool_call", render: WebSearchRenderer },
|
|
1795
|
+
{ match: "rawType", type: "tool_search_call", render: ToolSearchRenderer },
|
|
1592
1796
|
// First-party sandbox + MCP tools resolve by name (exact or MCP leaf).
|
|
1593
1797
|
{ match: "name", name: "exec_command", render: ExecRenderer },
|
|
1594
1798
|
{ match: "name", name: "request_human_input", render: AskRenderer },
|
|
@@ -1607,6 +1811,7 @@ const BASE_ENTRIES: ToolRegistryEntry[] = [
|
|
|
1607
1811
|
{ match: "name", name: "computer_keypress", render: ComputerCallRenderer },
|
|
1608
1812
|
{ match: "name", name: "computer_drag", render: ComputerCallRenderer },
|
|
1609
1813
|
{ match: "name", name: "web_search_call", render: WebSearchRenderer },
|
|
1814
|
+
{ match: "name", name: "tool_search", render: ToolSearchRenderer },
|
|
1610
1815
|
{ match: "name", name: "view_image", render: ViewImageRenderer },
|
|
1611
1816
|
{ match: "name", name: "environment_set_variable", render: SecretSetRenderer },
|
|
1612
1817
|
{ match: "name", name: "variable_set_set_variable", render: SecretSetRenderer },
|
|
@@ -16,7 +16,12 @@ import { MOTION_INSPECT_SCALE } from "../lib/motion-inspect";
|
|
|
16
16
|
import { useForcedDefaultOpen } from "./disclosure-context";
|
|
17
17
|
import { useEntranceAnimation } from "./entrance";
|
|
18
18
|
import { useFoldMemory, type FoldRestingState } from "./fold-memory";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
applyPatchOpsFromToolItem,
|
|
21
|
+
isApplyPatch,
|
|
22
|
+
mediaPreviewFact,
|
|
23
|
+
screenshotDataUrl,
|
|
24
|
+
} from "./parsers";
|
|
20
25
|
import { rawTypeOf } from "./registry";
|
|
21
26
|
import type { ActivityItem, ToolCallItem, TurnOutcome } from "./types";
|
|
22
27
|
export type { TurnOutcome } from "./types";
|
|
@@ -571,7 +576,7 @@ const BUILT_IN_TURN_SUMMARY_FACETS: readonly TurnSummaryFacet[] = Object.freeze(
|
|
|
571
576
|
let files = 0;
|
|
572
577
|
for (const item of toolCalls) {
|
|
573
578
|
if (isApplyPatch(item)) {
|
|
574
|
-
files +=
|
|
579
|
+
files += applyPatchOpsFromToolItem(item).length;
|
|
575
580
|
}
|
|
576
581
|
}
|
|
577
582
|
return files ? { content: `${files} ${files === 1 ? "file" : "files"} edited` } : null;
|
package/styles/tokens.css
CHANGED
|
@@ -97,16 +97,18 @@
|
|
|
97
97
|
--og-session-chrome-surface-open: color-mix(in oklch, var(--og-color-surface-2) 92%, transparent);
|
|
98
98
|
--og-session-chrome-border: color-mix(in oklch, var(--og-color-border) 85%, transparent);
|
|
99
99
|
--og-session-chrome-border-open: var(--og-color-border);
|
|
100
|
-
|
|
100
|
+
/* Selected chip: solid lift vs open dock surface (not a faint wash). */
|
|
101
|
+
--og-session-chrome-highlight: var(--og-color-surface-3);
|
|
102
|
+
--og-session-chrome-highlight-ring: color-mix(in oklch, var(--og-color-fg) 16%, transparent);
|
|
101
103
|
--og-session-chrome-shadow: 0 6px 22px -14px oklch(0 0 0 / 0.42);
|
|
102
104
|
--og-session-chrome-shadow-open: 0 10px 28px -16px oklch(0 0 0 / 0.5);
|
|
103
105
|
--og-session-chrome-radius: var(--og-radius-lg);
|
|
104
106
|
--og-session-chrome-chip-min-height: 1.75rem;
|
|
105
107
|
--og-session-chrome-chip-pad-x: 0.5rem;
|
|
106
108
|
--og-session-chrome-chip-gap: 0.125rem;
|
|
107
|
-
--og-session-chrome-rail-pad: 0.
|
|
108
|
-
--og-session-chrome-panel-pad-x: 0.
|
|
109
|
-
--og-session-chrome-panel-pad-y: 0.
|
|
109
|
+
--og-session-chrome-rail-pad: 0.15rem;
|
|
110
|
+
--og-session-chrome-panel-pad-x: 0.45rem;
|
|
111
|
+
--og-session-chrome-panel-pad-y: 0.3rem;
|
|
110
112
|
/* Expanded agents/queue/inbox/goal body — scroll inside, never grow unbound. */
|
|
111
113
|
--og-session-chrome-panel-max-height: min(18rem, 40dvh);
|
|
112
114
|
--og-session-chrome-duration: 220ms;
|
|
@@ -158,7 +160,8 @@
|
|
|
158
160
|
--og-session-chrome-surface-open: var(--og-color-surface-2);
|
|
159
161
|
--og-session-chrome-border: color-mix(in oklch, var(--og-color-border) 90%, transparent);
|
|
160
162
|
--og-session-chrome-border-open: var(--og-color-border);
|
|
161
|
-
--og-session-chrome-highlight:
|
|
163
|
+
--og-session-chrome-highlight: var(--og-color-surface-3);
|
|
164
|
+
--og-session-chrome-highlight-ring: color-mix(in oklch, var(--og-color-fg) 12%, transparent);
|
|
162
165
|
--og-session-chrome-shadow: 0 4px 16px -10px oklch(0.2 0.02 260 / 0.14);
|
|
163
166
|
--og-session-chrome-shadow-open: 0 8px 22px -12px oklch(0.2 0.02 260 / 0.18);
|
|
164
167
|
--og-session-chrome-row-hover: color-mix(in oklch, var(--og-color-surface-3) 70%, transparent);
|