@oh-my-pi/pi-agent-core 17.1.0 → 17.1.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/CHANGELOG.md +16 -0
- package/dist/types/agent.d.ts +6 -0
- package/dist/types/types.d.ts +14 -1
- package/package.json +7 -7
- package/src/agent-loop.ts +187 -4
- package/src/agent.ts +13 -0
- package/src/compaction/compaction.ts +8 -0
- package/src/compaction/openai.ts +153 -15
- package/src/types.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.2] - 2026-07-24
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `resolveFallbackTool` option to allow routing unadvertised tool calls to host-side transports (e.g., device mounts)
|
|
10
|
+
|
|
11
|
+
## [17.1.1] - 2026-07-24
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added the provider-neutral native computer-call lifecycle, preserving observation outputs and input actions across pending and acknowledged tool results.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Queued steering no longer hard-aborts non-interruptible tools (e.g. `bash`): it aborts interruptible waits only and raises a cooperative steering signal (`ToolCallContext.steeringSignal`) that long-running tools may observe to finish early or background themselves. The mid-batch steering/IRC watch now runs for every tool batch instead of only batches containing an interruptible tool.
|
|
20
|
+
|
|
5
21
|
## [17.1.0] - 2026-07-24
|
|
6
22
|
|
|
7
23
|
### Added
|
package/dist/types/agent.d.ts
CHANGED
|
@@ -134,6 +134,12 @@ export interface AgentOptions {
|
|
|
134
134
|
* Use for deobfuscating secrets or rewriting arguments.
|
|
135
135
|
*/
|
|
136
136
|
transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
137
|
+
/**
|
|
138
|
+
* Resolve a tool call whose name matched no advertised tool. Lets hosts
|
|
139
|
+
* route calls to tools exposed through side transports (e.g. `xd://`
|
|
140
|
+
* device mounts) instead of failing with "Tool not found".
|
|
141
|
+
*/
|
|
142
|
+
resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
|
|
137
143
|
/** Enable intent tracing schema injection/stripping in the harness. */
|
|
138
144
|
intentTracing?: boolean;
|
|
139
145
|
/**
|
package/dist/types/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApiKey, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Context, Effort, ImageContent, Message, Model, ServiceTier, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolChoice, ToolResultMessage, TSchema } from "@oh-my-pi/pi-ai";
|
|
1
|
+
import type { ApiKey, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Context, Effort, ImageContent, Message, Model, ServiceTier, SimpleStreamOptions, Static, streamSimple, TextContent, Tool, ToolCallProviderMetadata, ToolChoice, ToolResultMessage, ToolResultProviderMetadata, TSchema } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
3
3
|
import type { HarmonyAuditEvent } from "@oh-my-pi/pi-ai/utils/harmony-leak";
|
|
4
4
|
import type { AppendOnlyContextManager } from "./append-only-context.js";
|
|
@@ -230,6 +230,13 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
230
230
|
* Use for deobfuscating secrets or rewriting arguments.
|
|
231
231
|
*/
|
|
232
232
|
transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
233
|
+
/**
|
|
234
|
+
* Resolve a tool call whose name matched no advertised tool (including
|
|
235
|
+
* `customWireName` aliases). Lets hosts route calls to tools they expose
|
|
236
|
+
* through side transports (e.g. `xd://` device mounts) instead of failing
|
|
237
|
+
* with "Tool not found". Returning `undefined` keeps the failure.
|
|
238
|
+
*/
|
|
239
|
+
resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
|
|
233
240
|
/**
|
|
234
241
|
* Enable intent tracing for tool calls.
|
|
235
242
|
* When enabled, the harness injects a `string` field into tool schemas sent to the model,
|
|
@@ -399,6 +406,8 @@ export interface ToolCallContext {
|
|
|
399
406
|
id: string;
|
|
400
407
|
name: string;
|
|
401
408
|
}>;
|
|
409
|
+
/** Provider-native metadata for the current call, when present. */
|
|
410
|
+
providerMetadata?: ToolCallProviderMetadata;
|
|
402
411
|
/**
|
|
403
412
|
* Cooperative steering signal: aborted when a queued user/steering message
|
|
404
413
|
* (or an interrupting peer IRC) is detected while this tool batch runs.
|
|
@@ -437,6 +446,8 @@ export interface AfterToolCallResult {
|
|
|
437
446
|
content?: (TextContent | ImageContent)[];
|
|
438
447
|
/** If provided, replaces the tool result details payload in full. */
|
|
439
448
|
details?: unknown;
|
|
449
|
+
/** If provided, replaces the provider-native result metadata in full. */
|
|
450
|
+
providerMetadata?: ToolResultProviderMetadata;
|
|
440
451
|
/** If provided, replaces the error flag carried with the tool result. */
|
|
441
452
|
isError?: boolean;
|
|
442
453
|
/** If provided, replaces the contextually-useless flag carried with the tool result. */
|
|
@@ -512,6 +523,8 @@ export interface AgentToolResult<T = any, _TInput = unknown> {
|
|
|
512
523
|
content: (TextContent | ImageContent)[];
|
|
513
524
|
details?: T;
|
|
514
525
|
isError?: boolean;
|
|
526
|
+
/** Provider-native metadata that must survive into history replay unchanged. */
|
|
527
|
+
providerMetadata?: ToolResultProviderMetadata;
|
|
515
528
|
/** Marks the result as contextually useless: safe for compaction to elide once consumed (e.g. zero matches, wait timeout). Ignored when isError is set. */
|
|
516
529
|
useless?: boolean;
|
|
517
530
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-agent-core",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.2",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"fmt": "biome format --write ."
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@oh-my-pi/pi-ai": "17.1.
|
|
39
|
-
"@oh-my-pi/pi-catalog": "17.1.
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
42
|
-
"@oh-my-pi/pi-wire": "17.1.
|
|
43
|
-
"@oh-my-pi/snapcompact": "17.1.
|
|
38
|
+
"@oh-my-pi/pi-ai": "17.1.2",
|
|
39
|
+
"@oh-my-pi/pi-catalog": "17.1.2",
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.1.2",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.1.2",
|
|
42
|
+
"@oh-my-pi/pi-wire": "17.1.2",
|
|
43
|
+
"@oh-my-pi/snapcompact": "17.1.2",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
package/src/agent-loop.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import {
|
|
6
6
|
type AssistantMessage,
|
|
7
7
|
type AssistantMessageEvent,
|
|
8
|
+
type ComputerAction,
|
|
9
|
+
type ComputerSafetyCheck,
|
|
8
10
|
type Context,
|
|
9
11
|
EventStream,
|
|
10
12
|
isApiKeyResolver,
|
|
@@ -12,8 +14,10 @@ import {
|
|
|
12
14
|
seedApiKeyResolver,
|
|
13
15
|
streamSimple,
|
|
14
16
|
stripSchemaDescriptions,
|
|
17
|
+
type ToolCallProviderMetadata,
|
|
15
18
|
type ToolChoice,
|
|
16
19
|
type ToolResultMessage,
|
|
20
|
+
type ToolResultProviderMetadata,
|
|
17
21
|
type TSchema,
|
|
18
22
|
toolWireSchema,
|
|
19
23
|
validateToolArguments,
|
|
@@ -106,6 +110,7 @@ const MAX_SOFT_TOOL_ESCALATIONS = 3;
|
|
|
106
110
|
function hardToolChoiceBlocks(choice: ToolChoice | undefined, requiredTool: string): boolean {
|
|
107
111
|
if (choice === undefined) return false;
|
|
108
112
|
if (typeof choice === "string") return choice === "none";
|
|
113
|
+
if (choice.type === "computer") return requiredTool !== "computer";
|
|
109
114
|
const name = choice.type === "tool" ? choice.name : "function" in choice ? choice.function.name : choice.name;
|
|
110
115
|
return name !== requiredTool;
|
|
111
116
|
}
|
|
@@ -180,6 +185,160 @@ export function resolveOwnedDialectFromEnv(value: string | undefined): Dialect |
|
|
|
180
185
|
type AssistantContentBlock = AssistantMessage["content"][number];
|
|
181
186
|
type AssistantToolCallBlock = Extract<AssistantContentBlock, { type: "toolCall" }>;
|
|
182
187
|
|
|
188
|
+
function snapshotComputerSafetyChecks(value: unknown): ComputerSafetyCheck[] | undefined {
|
|
189
|
+
if (!Array.isArray(value)) return undefined;
|
|
190
|
+
const checks: ComputerSafetyCheck[] = [];
|
|
191
|
+
for (const raw of value) {
|
|
192
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
|
|
193
|
+
const check = raw as Record<string, unknown>;
|
|
194
|
+
if (typeof check.id !== "string" || check.id.length === 0) return undefined;
|
|
195
|
+
if (check.code !== undefined && check.code !== null && typeof check.code !== "string") return undefined;
|
|
196
|
+
if (check.message !== undefined && check.message !== null && typeof check.message !== "string") return undefined;
|
|
197
|
+
checks.push({
|
|
198
|
+
id: check.id,
|
|
199
|
+
...(check.code !== undefined ? { code: check.code as string | null } : {}),
|
|
200
|
+
...(check.message !== undefined ? { message: check.message as string | null } : {}),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return checks;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isFiniteCoordinate(value: unknown): value is number {
|
|
207
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function hasValidComputerKeys(value: unknown, optional: boolean): boolean {
|
|
211
|
+
return (
|
|
212
|
+
(optional && value === undefined) ||
|
|
213
|
+
value === null ||
|
|
214
|
+
(Array.isArray(value) && value.every(key => typeof key === "string"))
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function snapshotComputerAction(value: unknown): ComputerAction | undefined {
|
|
219
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
220
|
+
const action = value as Record<string, unknown>;
|
|
221
|
+
switch (action.type) {
|
|
222
|
+
case "click":
|
|
223
|
+
if (
|
|
224
|
+
!(["left", "right", "wheel", "back", "forward"] as unknown[]).includes(action.button) ||
|
|
225
|
+
!isFiniteCoordinate(action.x) ||
|
|
226
|
+
!isFiniteCoordinate(action.y) ||
|
|
227
|
+
!hasValidComputerKeys(action.keys, true)
|
|
228
|
+
)
|
|
229
|
+
return undefined;
|
|
230
|
+
break;
|
|
231
|
+
case "double_click":
|
|
232
|
+
if (
|
|
233
|
+
!isFiniteCoordinate(action.x) ||
|
|
234
|
+
!isFiniteCoordinate(action.y) ||
|
|
235
|
+
!hasValidComputerKeys(action.keys, false)
|
|
236
|
+
)
|
|
237
|
+
return undefined;
|
|
238
|
+
break;
|
|
239
|
+
case "drag":
|
|
240
|
+
if (
|
|
241
|
+
!Array.isArray(action.path) ||
|
|
242
|
+
!action.path.every(
|
|
243
|
+
point =>
|
|
244
|
+
point &&
|
|
245
|
+
typeof point === "object" &&
|
|
246
|
+
isFiniteCoordinate((point as Record<string, unknown>).x) &&
|
|
247
|
+
isFiniteCoordinate((point as Record<string, unknown>).y),
|
|
248
|
+
) ||
|
|
249
|
+
!hasValidComputerKeys(action.keys, true)
|
|
250
|
+
)
|
|
251
|
+
return undefined;
|
|
252
|
+
break;
|
|
253
|
+
case "keypress":
|
|
254
|
+
if (!Array.isArray(action.keys) || !action.keys.every(key => typeof key === "string")) return undefined;
|
|
255
|
+
break;
|
|
256
|
+
case "move":
|
|
257
|
+
if (!isFiniteCoordinate(action.x) || !isFiniteCoordinate(action.y) || !hasValidComputerKeys(action.keys, true))
|
|
258
|
+
return undefined;
|
|
259
|
+
break;
|
|
260
|
+
case "screenshot":
|
|
261
|
+
case "wait":
|
|
262
|
+
break;
|
|
263
|
+
case "scroll":
|
|
264
|
+
if (
|
|
265
|
+
!isFiniteCoordinate(action.x) ||
|
|
266
|
+
!isFiniteCoordinate(action.y) ||
|
|
267
|
+
!isFiniteCoordinate(action.scroll_x) ||
|
|
268
|
+
!isFiniteCoordinate(action.scroll_y) ||
|
|
269
|
+
!hasValidComputerKeys(action.keys, true)
|
|
270
|
+
)
|
|
271
|
+
return undefined;
|
|
272
|
+
break;
|
|
273
|
+
case "type":
|
|
274
|
+
if (typeof action.text !== "string") return undefined;
|
|
275
|
+
break;
|
|
276
|
+
default:
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
return structuredCloneJSON(action) as ComputerAction;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function snapshotToolCallProviderMetadata(value: unknown): ToolCallProviderMetadata | undefined {
|
|
283
|
+
if (value === undefined) return undefined;
|
|
284
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
285
|
+
const metadata = value as Record<string, unknown>;
|
|
286
|
+
if (
|
|
287
|
+
metadata.type !== "computer" ||
|
|
288
|
+
typeof metadata.providerItemId !== "string" ||
|
|
289
|
+
metadata.providerItemId.length === 0
|
|
290
|
+
)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (!Array.isArray(metadata.actions) || metadata.actions.length === 0) return undefined;
|
|
293
|
+
const actions = metadata.actions.map(snapshotComputerAction);
|
|
294
|
+
if (actions.some(action => action === undefined)) return undefined;
|
|
295
|
+
const pendingSafetyChecks = snapshotComputerSafetyChecks(metadata.pendingSafetyChecks);
|
|
296
|
+
if (!pendingSafetyChecks) return undefined;
|
|
297
|
+
return {
|
|
298
|
+
type: "computer",
|
|
299
|
+
providerItemId: metadata.providerItemId,
|
|
300
|
+
actions: actions as ComputerAction[],
|
|
301
|
+
pendingSafetyChecks,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function snapshotToolResultProviderMetadata(value: unknown): {
|
|
306
|
+
metadata?: ToolResultProviderMetadata;
|
|
307
|
+
malformed: boolean;
|
|
308
|
+
} {
|
|
309
|
+
if (value === undefined) return { malformed: false };
|
|
310
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { malformed: true };
|
|
311
|
+
const metadata = value as Record<string, unknown>;
|
|
312
|
+
if (
|
|
313
|
+
metadata.type !== "computer" ||
|
|
314
|
+
!metadata.screenshot ||
|
|
315
|
+
typeof metadata.screenshot !== "object" ||
|
|
316
|
+
Array.isArray(metadata.screenshot)
|
|
317
|
+
) {
|
|
318
|
+
return { malformed: true };
|
|
319
|
+
}
|
|
320
|
+
const screenshot = metadata.screenshot as Record<string, unknown>;
|
|
321
|
+
const hasImageUrl = Object.hasOwn(screenshot, "image_url");
|
|
322
|
+
const hasFileId = Object.hasOwn(screenshot, "file_id");
|
|
323
|
+
if (screenshot.type !== "computer_screenshot" || hasImageUrl === hasFileId) return { malformed: true };
|
|
324
|
+
if (hasImageUrl && (typeof screenshot.image_url !== "string" || screenshot.image_url.length === 0))
|
|
325
|
+
return { malformed: true };
|
|
326
|
+
if (hasFileId && (typeof screenshot.file_id !== "string" || screenshot.file_id.length === 0))
|
|
327
|
+
return { malformed: true };
|
|
328
|
+
const acknowledgedSafetyChecks = snapshotComputerSafetyChecks(metadata.acknowledgedSafetyChecks);
|
|
329
|
+
if (!acknowledgedSafetyChecks) return { malformed: true };
|
|
330
|
+
return {
|
|
331
|
+
malformed: false,
|
|
332
|
+
metadata: {
|
|
333
|
+
type: "computer",
|
|
334
|
+
screenshot: hasImageUrl
|
|
335
|
+
? { type: "computer_screenshot", image_url: screenshot.image_url as string }
|
|
336
|
+
: { type: "computer_screenshot", file_id: screenshot.file_id as string },
|
|
337
|
+
acknowledgedSafetyChecks,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
183
342
|
function snapshotAssistantContentBlock(block: AssistantContentBlock): AssistantContentBlock {
|
|
184
343
|
switch (block.type) {
|
|
185
344
|
case "text":
|
|
@@ -189,10 +348,16 @@ function snapshotAssistantContentBlock(block: AssistantContentBlock): AssistantC
|
|
|
189
348
|
return { ...block };
|
|
190
349
|
case "redactedThinking":
|
|
191
350
|
return { ...block };
|
|
351
|
+
case "anthropicServerTool":
|
|
352
|
+
return { ...block, block: structuredCloneJSON(block.block) };
|
|
192
353
|
case "fallback":
|
|
193
354
|
return { ...block, from: { ...block.from }, to: { ...block.to } };
|
|
194
355
|
case "toolCall":
|
|
195
|
-
return {
|
|
356
|
+
return {
|
|
357
|
+
...block,
|
|
358
|
+
arguments: structuredCloneJSON(block.arguments),
|
|
359
|
+
providerMetadata: snapshotToolCallProviderMetadata(block.providerMetadata),
|
|
360
|
+
};
|
|
196
361
|
}
|
|
197
362
|
}
|
|
198
363
|
|
|
@@ -268,6 +433,10 @@ function coerceToolResult(raw: unknown): { result: AgentToolResult<unknown>; mal
|
|
|
268
433
|
const rawObj = raw && typeof raw === "object" ? (raw as Record<string, unknown>) : null;
|
|
269
434
|
const rawContent = rawObj?.content;
|
|
270
435
|
const details = rawObj && "details" in rawObj ? rawObj.details : {};
|
|
436
|
+
const providerMetadataResult = snapshotToolResultProviderMetadata(
|
|
437
|
+
rawObj && "providerMetadata" in rawObj ? rawObj.providerMetadata : undefined,
|
|
438
|
+
);
|
|
439
|
+
const providerMetadata = providerMetadataResult.metadata;
|
|
271
440
|
// Tools may flag a non-throwing failure on the result itself (e.g. an
|
|
272
441
|
// aggregator that catches per-entry errors and synthesizes a combined
|
|
273
442
|
// result). Preserve the flag so agent-loop can surface it on the wire.
|
|
@@ -312,7 +481,13 @@ function coerceToolResult(raw: unknown): { result: AgentToolResult<unknown>; mal
|
|
|
312
481
|
text: `Tool returned an invalid result: ${invalidBlocks} content block${invalidBlocks === 1 ? "" : "s"} had an unsupported shape.`,
|
|
313
482
|
});
|
|
314
483
|
}
|
|
315
|
-
|
|
484
|
+
if (providerMetadataResult.malformed) {
|
|
485
|
+
content.push({
|
|
486
|
+
type: "text",
|
|
487
|
+
text: "Tool returned an invalid result: computer providerMetadata had an unsupported shape.",
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
const isError = explicitError || invalidBlocks > 0 || providerMetadataResult.malformed;
|
|
316
491
|
// Anthropic rejects tool_result blocks with is_error: true and empty content.
|
|
317
492
|
if (isError && !hasSubstantiveToolResultContent(content)) {
|
|
318
493
|
content.length = 0;
|
|
@@ -322,10 +497,11 @@ function coerceToolResult(raw: unknown): { result: AgentToolResult<unknown>; mal
|
|
|
322
497
|
result: {
|
|
323
498
|
content,
|
|
324
499
|
details,
|
|
500
|
+
providerMetadata,
|
|
325
501
|
...(isError ? { isError: true } : {}),
|
|
326
502
|
...(useless && !isError ? { useless: true } : {}),
|
|
327
503
|
},
|
|
328
|
-
malformed: invalidBlocks > 0,
|
|
504
|
+
malformed: invalidBlocks > 0 || providerMetadataResult.malformed,
|
|
329
505
|
};
|
|
330
506
|
}
|
|
331
507
|
|
|
@@ -1799,6 +1975,7 @@ async function executeToolCalls(
|
|
|
1799
1975
|
interruptMode = "immediate",
|
|
1800
1976
|
getToolContext,
|
|
1801
1977
|
transformToolCallArguments,
|
|
1978
|
+
resolveFallbackTool,
|
|
1802
1979
|
intentTracing,
|
|
1803
1980
|
beforeToolCall,
|
|
1804
1981
|
afterToolCall,
|
|
@@ -1841,7 +2018,10 @@ async function executeToolCalls(
|
|
|
1841
2018
|
// determinism if both somehow collide.
|
|
1842
2019
|
const tool =
|
|
1843
2020
|
tools?.find(t => t.name === toolCall.name) ??
|
|
1844
|
-
tools?.find(t => t.customWireName !== undefined && t.customWireName === toolCall.name)
|
|
2021
|
+
tools?.find(t => t.customWireName !== undefined && t.customWireName === toolCall.name) ??
|
|
2022
|
+
// Not in the advertised set: let the host route side-transport tools
|
|
2023
|
+
// (e.g. xd:// device mounts) called by their top-level name.
|
|
2024
|
+
resolveFallbackTool?.(toolCall.name);
|
|
1845
2025
|
const args = toolCall.arguments as Record<string, unknown>;
|
|
1846
2026
|
const interruptibleMode = tool?.interruptible;
|
|
1847
2027
|
let interruptible = false;
|
|
@@ -1947,6 +2127,7 @@ async function executeToolCalls(
|
|
|
1947
2127
|
toolName: toolCall.name,
|
|
1948
2128
|
content: result.content,
|
|
1949
2129
|
details: result.details,
|
|
2130
|
+
providerMetadata: result.providerMetadata,
|
|
1950
2131
|
isError,
|
|
1951
2132
|
...(result.useless && !isError ? { useless: true } : {}),
|
|
1952
2133
|
timestamp: Date.now(),
|
|
@@ -2111,6 +2292,7 @@ async function executeToolCalls(
|
|
|
2111
2292
|
total: toolCalls.length,
|
|
2112
2293
|
toolCalls: toolCallInfos,
|
|
2113
2294
|
steeringSignal: steeringSoftController.signal,
|
|
2295
|
+
providerMetadata: toolCall.providerMetadata,
|
|
2114
2296
|
})
|
|
2115
2297
|
: undefined;
|
|
2116
2298
|
const rawResult = await tool.execute(
|
|
@@ -2163,6 +2345,7 @@ async function executeToolCalls(
|
|
|
2163
2345
|
content: after.content ?? result.content,
|
|
2164
2346
|
details: after.details ?? result.details,
|
|
2165
2347
|
isError: after.isError ?? result.isError,
|
|
2348
|
+
providerMetadata: after.providerMetadata ?? result.providerMetadata,
|
|
2166
2349
|
useless: after.useless ?? result.useless,
|
|
2167
2350
|
});
|
|
2168
2351
|
result = coerced.result;
|
package/src/agent.ts
CHANGED
|
@@ -72,6 +72,9 @@ function refreshToolChoiceForActiveTools(
|
|
|
72
72
|
if (!toolChoice || typeof toolChoice === "string") {
|
|
73
73
|
return toolChoice;
|
|
74
74
|
}
|
|
75
|
+
if (toolChoice.type === "computer") {
|
|
76
|
+
return tools.some(tool => tool.native?.type === "computer") ? toolChoice : undefined;
|
|
77
|
+
}
|
|
75
78
|
|
|
76
79
|
const toolName =
|
|
77
80
|
toolChoice.type === "tool"
|
|
@@ -238,6 +241,13 @@ export interface AgentOptions {
|
|
|
238
241
|
*/
|
|
239
242
|
transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
240
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Resolve a tool call whose name matched no advertised tool. Lets hosts
|
|
246
|
+
* route calls to tools exposed through side transports (e.g. `xd://`
|
|
247
|
+
* device mounts) instead of failing with "Tool not found".
|
|
248
|
+
*/
|
|
249
|
+
resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
|
|
250
|
+
|
|
241
251
|
/** Enable intent tracing schema injection/stripping in the harness. */
|
|
242
252
|
intentTracing?: boolean;
|
|
243
253
|
/**
|
|
@@ -375,6 +385,7 @@ export class Agent {
|
|
|
375
385
|
#kimiApiFormat?: "openai" | "anthropic";
|
|
376
386
|
#preferWebsockets?: boolean;
|
|
377
387
|
#transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
388
|
+
#resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
|
|
378
389
|
#intentTracing: boolean;
|
|
379
390
|
#pruneToolDescriptions: boolean;
|
|
380
391
|
#dialect?: Dialect;
|
|
@@ -455,6 +466,7 @@ export class Agent {
|
|
|
455
466
|
this.#kimiApiFormat = opts.kimiApiFormat;
|
|
456
467
|
this.#preferWebsockets = opts.preferWebsockets;
|
|
457
468
|
this.#transformToolCallArguments = opts.transformToolCallArguments;
|
|
469
|
+
this.#resolveFallbackTool = opts.resolveFallbackTool;
|
|
458
470
|
this.#intentTracing = opts.intentTracing === true;
|
|
459
471
|
this.#pruneToolDescriptions = opts.pruneToolDescriptions === true;
|
|
460
472
|
this.#dialect = opts.dialect;
|
|
@@ -1190,6 +1202,7 @@ export class Agent {
|
|
|
1190
1202
|
cwd: this.#cwd,
|
|
1191
1203
|
getCwd: this.#cwdResolver,
|
|
1192
1204
|
transformToolCallArguments: this.#transformToolCallArguments,
|
|
1205
|
+
resolveFallbackTool: this.#resolveFallbackTool,
|
|
1193
1206
|
intentTracing: this.#intentTracing,
|
|
1194
1207
|
pruneToolDescriptions: this.#pruneToolDescriptions,
|
|
1195
1208
|
dialect: this.#dialect,
|
|
@@ -427,6 +427,14 @@ function computeMessageTokens(message: AgentMessage, options?: { excludeEncrypte
|
|
|
427
427
|
// Encrypted reasoning blob the provider still bills for on replay;
|
|
428
428
|
// excluded from the compaction floor for the same reason as above.
|
|
429
429
|
if (!options?.excludeEncryptedReasoning) fragments.push(block.data);
|
|
430
|
+
} else if (block.type === "anthropicServerTool") {
|
|
431
|
+
// Native Anthropic server-tool call/result replayed verbatim on the
|
|
432
|
+
// wire (server_tool_use input, web_search_tool_result
|
|
433
|
+
// encrypted_content). Opaque provider-replay state the provider still
|
|
434
|
+
// bills for on same-provider replay; excluded from the compaction
|
|
435
|
+
// floor like other encrypted reasoning because its local byte size
|
|
436
|
+
// diverges from provider billing.
|
|
437
|
+
if (!options?.excludeEncryptedReasoning) fragments.push(stringifyJson(block.block) ?? "null");
|
|
430
438
|
}
|
|
431
439
|
}
|
|
432
440
|
break;
|
package/src/compaction/openai.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { applyCodexResponsesLiteShape } from "@oh-my-pi/pi-ai/providers/openai-c
|
|
|
20
20
|
import {
|
|
21
21
|
createOpenAICodexCompactionRequestContext,
|
|
22
22
|
createOpenAICodexCompatibilityMetadata,
|
|
23
|
+
getCodexAttestationHeader,
|
|
23
24
|
} from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
24
25
|
import { parseAzureDeploymentNameMap, parseTextSignature } from "@oh-my-pi/pi-ai/providers/openai-shared";
|
|
25
26
|
import { transformMessages } from "@oh-my-pi/pi-ai/providers/transform-messages";
|
|
@@ -43,7 +44,7 @@ import {
|
|
|
43
44
|
OPENAI_HEADER_VALUES,
|
|
44
45
|
OPENAI_HEADERS,
|
|
45
46
|
} from "@oh-my-pi/pi-catalog/wire/codex";
|
|
46
|
-
import { $env, logger, stringifyJson } from "@oh-my-pi/pi-utils";
|
|
47
|
+
import { $env, logger, stringifyJson, structuredCloneJSON } from "@oh-my-pi/pi-utils";
|
|
47
48
|
|
|
48
49
|
export * from "./compaction-v2-streaming";
|
|
49
50
|
|
|
@@ -254,6 +255,7 @@ function addOpenAiCallIds(
|
|
|
254
255
|
items: Array<Record<string, unknown>>,
|
|
255
256
|
knownCallIds: Set<string>,
|
|
256
257
|
customCallIds: Set<string>,
|
|
258
|
+
computerCallIds: Set<string>,
|
|
257
259
|
): void {
|
|
258
260
|
for (const item of items) {
|
|
259
261
|
if (typeof item.call_id !== "string") continue;
|
|
@@ -262,10 +264,57 @@ function addOpenAiCallIds(
|
|
|
262
264
|
} else if (item.type === "custom_tool_call") {
|
|
263
265
|
knownCallIds.add(item.call_id);
|
|
264
266
|
customCallIds.add(item.call_id);
|
|
267
|
+
} else if (item.type === "computer_call") {
|
|
268
|
+
knownCallIds.add(item.call_id);
|
|
269
|
+
computerCallIds.add(item.call_id);
|
|
265
270
|
}
|
|
266
271
|
}
|
|
267
272
|
}
|
|
268
273
|
|
|
274
|
+
function computerHistoryNote(item: Record<string, unknown>): Record<string, unknown> {
|
|
275
|
+
const serialized = stringifyJson(item) ?? "";
|
|
276
|
+
return {
|
|
277
|
+
type: "message",
|
|
278
|
+
id: `msg_${Bun.hash(`computer-history:${serialized}`).toString(36)}`,
|
|
279
|
+
role: "assistant",
|
|
280
|
+
content: [
|
|
281
|
+
{
|
|
282
|
+
type: "output_text",
|
|
283
|
+
text: `[Previous computer history unavailable to this model]: ${serialized}`,
|
|
284
|
+
annotations: [],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
status: "completed",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function adaptComputerHistoryForCompaction(
|
|
292
|
+
items: Array<Record<string, unknown>>,
|
|
293
|
+
supportsComputerUse: boolean,
|
|
294
|
+
): Array<Record<string, unknown>> {
|
|
295
|
+
if (supportsComputerUse) return items;
|
|
296
|
+
return items.map(item =>
|
|
297
|
+
item.type === "computer_call" || item.type === "computer_call_output" ? computerHistoryNote(item) : item,
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function computerFailureNote(call: Record<string, unknown>, output: string): Record<string, unknown> {
|
|
302
|
+
const serialized = stringifyJson(call) ?? "";
|
|
303
|
+
return {
|
|
304
|
+
type: "message",
|
|
305
|
+
id: `msg_${Bun.hash(`computer-failure:${serialized}:${output}`).toString(36)}`,
|
|
306
|
+
role: "assistant",
|
|
307
|
+
content: [
|
|
308
|
+
{
|
|
309
|
+
type: "output_text",
|
|
310
|
+
text: `[Computer call failed before a screenshot was recorded]: ${serialized}${output ? `\n${output}` : ""}`,
|
|
311
|
+
annotations: [],
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
status: "completed",
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
269
318
|
// ============================================================================
|
|
270
319
|
// Native history construction (responses-API shape)
|
|
271
320
|
// ============================================================================
|
|
@@ -287,20 +336,32 @@ export function buildOpenAiNativeHistory(
|
|
|
287
336
|
model: Model,
|
|
288
337
|
previousReplacementHistory?: Array<Record<string, unknown>>,
|
|
289
338
|
): Array<Record<string, unknown>> {
|
|
290
|
-
const input: Array<Record<string, unknown>> = previousReplacementHistory
|
|
339
|
+
const input: Array<Record<string, unknown>> = previousReplacementHistory
|
|
340
|
+
? adaptComputerHistoryForCompaction([...previousReplacementHistory], model.supportsComputerUse === true)
|
|
341
|
+
: [];
|
|
291
342
|
const transformedMessages = transformMessages(messages, model, id => normalizeOpenAiCompactionToolCallId(id));
|
|
292
343
|
|
|
293
344
|
let msgIndex = 0;
|
|
294
345
|
const knownCallIds = new Set<string>();
|
|
295
346
|
const customCallIds = new Set<string>();
|
|
296
|
-
|
|
347
|
+
const computerCallIds = new Set<string>();
|
|
348
|
+
const demotedComputerCallIds = new Set<string>();
|
|
349
|
+
addOpenAiCallIds(input, knownCallIds, customCallIds, computerCallIds);
|
|
297
350
|
for (const message of transformedMessages) {
|
|
298
351
|
if (message.role === "user" || message.role === "developer") {
|
|
299
352
|
const providerPayload = (message as { providerPayload?: AssistantMessage["providerPayload"] }).providerPayload;
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
353
|
+
const rawHistoryItems = getOpenAIResponsesHistoryItems(providerPayload, model.provider);
|
|
354
|
+
if (rawHistoryItems) {
|
|
355
|
+
if (model.supportsComputerUse !== true) {
|
|
356
|
+
for (const item of rawHistoryItems) {
|
|
357
|
+
if (item.type === "computer_call" && typeof item.call_id === "string") {
|
|
358
|
+
demotedComputerCallIds.add(item.call_id);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
const historyItems = adaptComputerHistoryForCompaction(rawHistoryItems, model.supportsComputerUse === true);
|
|
302
363
|
input.push(...historyItems);
|
|
303
|
-
addOpenAiCallIds(historyItems, knownCallIds, customCallIds);
|
|
364
|
+
addOpenAiCallIds(historyItems, knownCallIds, customCallIds, computerCallIds);
|
|
304
365
|
msgIndex++;
|
|
305
366
|
continue;
|
|
306
367
|
}
|
|
@@ -341,14 +402,27 @@ export function buildOpenAiNativeHistory(
|
|
|
341
402
|
assistant.provider,
|
|
342
403
|
);
|
|
343
404
|
if (providerPayload) {
|
|
405
|
+
if (!providerPayload.dt) demotedComputerCallIds.clear();
|
|
406
|
+
if (model.supportsComputerUse !== true) {
|
|
407
|
+
for (const item of providerPayload.items) {
|
|
408
|
+
if (item.type === "computer_call" && typeof item.call_id === "string") {
|
|
409
|
+
demotedComputerCallIds.add(item.call_id);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
const historyItems = adaptComputerHistoryForCompaction(
|
|
414
|
+
providerPayload.items,
|
|
415
|
+
model.supportsComputerUse === true,
|
|
416
|
+
);
|
|
344
417
|
if (providerPayload.dt) {
|
|
345
|
-
input.push(...
|
|
346
|
-
addOpenAiCallIds(
|
|
418
|
+
input.push(...historyItems);
|
|
419
|
+
addOpenAiCallIds(historyItems, knownCallIds, customCallIds, computerCallIds);
|
|
347
420
|
} else {
|
|
348
|
-
input.splice(0, input.length, ...
|
|
421
|
+
input.splice(0, input.length, ...historyItems);
|
|
349
422
|
knownCallIds.clear();
|
|
350
423
|
customCallIds.clear();
|
|
351
|
-
|
|
424
|
+
computerCallIds.clear();
|
|
425
|
+
addOpenAiCallIds(input, knownCallIds, customCallIds, computerCallIds);
|
|
352
426
|
}
|
|
353
427
|
msgIndex++;
|
|
354
428
|
continue;
|
|
@@ -394,6 +468,25 @@ export function buildOpenAiNativeHistory(
|
|
|
394
468
|
|
|
395
469
|
if (block.type === "toolCall") {
|
|
396
470
|
const normalized = normalizeResponsesToolCallId(block.id, block.customWireName ? "ctc" : "fc");
|
|
471
|
+
if (block.providerMetadata?.type === "computer") {
|
|
472
|
+
const computerCall = {
|
|
473
|
+
type: "computer_call",
|
|
474
|
+
id: block.providerMetadata.providerItemId,
|
|
475
|
+
call_id: normalized.callId,
|
|
476
|
+
actions: structuredCloneJSON(block.providerMetadata.actions),
|
|
477
|
+
pending_safety_checks: structuredCloneJSON(block.providerMetadata.pendingSafetyChecks),
|
|
478
|
+
status: "completed",
|
|
479
|
+
};
|
|
480
|
+
if (model.supportsComputerUse !== true) {
|
|
481
|
+
input.push(computerHistoryNote(computerCall));
|
|
482
|
+
demotedComputerCallIds.add(normalized.callId);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
knownCallIds.add(normalized.callId);
|
|
486
|
+
computerCallIds.add(normalized.callId);
|
|
487
|
+
input.push(computerCall);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
397
490
|
let itemId: string | undefined = normalized.itemId;
|
|
398
491
|
if (
|
|
399
492
|
isDifferentModel &&
|
|
@@ -430,17 +523,58 @@ export function buildOpenAiNativeHistory(
|
|
|
430
523
|
|
|
431
524
|
if (message.role === "toolResult") {
|
|
432
525
|
const normalized = normalizeResponsesToolCallId(message.toolCallId);
|
|
433
|
-
if (!knownCallIds.has(normalized.callId)) {
|
|
434
|
-
msgIndex++;
|
|
435
|
-
continue;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
526
|
const textOutput = message.content
|
|
439
527
|
.filter(block => block.type === "text")
|
|
440
528
|
.map(block => block.text)
|
|
441
529
|
.join("\n");
|
|
442
530
|
const hasImages = message.content.some(block => block.type === "image");
|
|
443
531
|
const outputText = textOutput.length > 0 ? textOutput : hasImages ? "(see attached image)" : "";
|
|
532
|
+
if (demotedComputerCallIds.has(normalized.callId)) {
|
|
533
|
+
const resultItem =
|
|
534
|
+
message.providerMetadata?.type === "computer"
|
|
535
|
+
? {
|
|
536
|
+
type: "computer_call_output",
|
|
537
|
+
call_id: normalized.callId,
|
|
538
|
+
output: structuredCloneJSON(message.providerMetadata.screenshot),
|
|
539
|
+
acknowledged_safety_checks: structuredCloneJSON(
|
|
540
|
+
message.providerMetadata.acknowledgedSafetyChecks,
|
|
541
|
+
),
|
|
542
|
+
}
|
|
543
|
+
: { type: "computer_call_output", call_id: normalized.callId, error: outputText };
|
|
544
|
+
input.push(computerHistoryNote(resultItem));
|
|
545
|
+
demotedComputerCallIds.delete(normalized.callId);
|
|
546
|
+
msgIndex++;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!knownCallIds.has(normalized.callId)) {
|
|
550
|
+
msgIndex++;
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (computerCallIds.has(normalized.callId)) {
|
|
554
|
+
if (message.providerMetadata?.type === "computer") {
|
|
555
|
+
input.push({
|
|
556
|
+
type: "computer_call_output",
|
|
557
|
+
call_id: normalized.callId,
|
|
558
|
+
output: structuredCloneJSON(message.providerMetadata.screenshot),
|
|
559
|
+
acknowledged_safety_checks: structuredCloneJSON(message.providerMetadata.acknowledgedSafetyChecks),
|
|
560
|
+
});
|
|
561
|
+
msgIndex++;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const callIndex = input.findLastIndex(
|
|
566
|
+
item => item.type === "computer_call" && item.call_id === normalized.callId,
|
|
567
|
+
);
|
|
568
|
+
if (callIndex >= 0) {
|
|
569
|
+
const [call] = input.splice(callIndex, 1);
|
|
570
|
+
if (call) input.splice(callIndex, 0, computerFailureNote(call, outputText));
|
|
571
|
+
}
|
|
572
|
+
knownCallIds.delete(normalized.callId);
|
|
573
|
+
computerCallIds.delete(normalized.callId);
|
|
574
|
+
msgIndex++;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
|
|
444
578
|
input.push({
|
|
445
579
|
type: customCallIds.has(normalized.callId) ? "custom_tool_call_output" : "function_call_output",
|
|
446
580
|
call_id: normalized.callId,
|
|
@@ -517,6 +651,10 @@ export async function requestOpenAiRemoteCompaction(
|
|
|
517
651
|
if (accountId) {
|
|
518
652
|
headers[OPENAI_HEADERS.ACCOUNT_ID] = accountId;
|
|
519
653
|
}
|
|
654
|
+
const attestation = await getCodexAttestationHeader(accountId);
|
|
655
|
+
if (attestation) {
|
|
656
|
+
headers[OPENAI_HEADERS.ATTESTATION] = attestation;
|
|
657
|
+
}
|
|
520
658
|
headers[OPENAI_HEADERS.BETA] = OPENAI_HEADER_VALUES.BETA_RESPONSES;
|
|
521
659
|
headers[OPENAI_HEADERS.ORIGINATOR] = OPENAI_HEADER_VALUES.ORIGINATOR_CODEX;
|
|
522
660
|
Object.assign(
|
package/src/types.ts
CHANGED
|
@@ -14,8 +14,10 @@ import type {
|
|
|
14
14
|
streamSimple,
|
|
15
15
|
TextContent,
|
|
16
16
|
Tool,
|
|
17
|
+
ToolCallProviderMetadata,
|
|
17
18
|
ToolChoice,
|
|
18
19
|
ToolResultMessage,
|
|
20
|
+
ToolResultProviderMetadata,
|
|
19
21
|
TSchema,
|
|
20
22
|
} from "@oh-my-pi/pi-ai";
|
|
21
23
|
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
@@ -274,6 +276,13 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
274
276
|
* Use for deobfuscating secrets or rewriting arguments.
|
|
275
277
|
*/
|
|
276
278
|
transformToolCallArguments?: (args: Record<string, unknown>, toolName: string) => Record<string, unknown>;
|
|
279
|
+
/**
|
|
280
|
+
* Resolve a tool call whose name matched no advertised tool (including
|
|
281
|
+
* `customWireName` aliases). Lets hosts route calls to tools they expose
|
|
282
|
+
* through side transports (e.g. `xd://` device mounts) instead of failing
|
|
283
|
+
* with "Tool not found". Returning `undefined` keeps the failure.
|
|
284
|
+
*/
|
|
285
|
+
resolveFallbackTool?: (name: string) => AgentTool<any> | undefined;
|
|
277
286
|
|
|
278
287
|
/**
|
|
279
288
|
* Enable intent tracing for tool calls.
|
|
@@ -458,6 +467,8 @@ export interface ToolCallContext {
|
|
|
458
467
|
index: number;
|
|
459
468
|
total: number;
|
|
460
469
|
toolCalls: Array<{ id: string; name: string }>;
|
|
470
|
+
/** Provider-native metadata for the current call, when present. */
|
|
471
|
+
providerMetadata?: ToolCallProviderMetadata;
|
|
461
472
|
/**
|
|
462
473
|
* Cooperative steering signal: aborted when a queued user/steering message
|
|
463
474
|
* (or an interrupting peer IRC) is detected while this tool batch runs.
|
|
@@ -497,6 +508,8 @@ export interface AfterToolCallResult {
|
|
|
497
508
|
content?: (TextContent | ImageContent)[];
|
|
498
509
|
/** If provided, replaces the tool result details payload in full. */
|
|
499
510
|
details?: unknown;
|
|
511
|
+
/** If provided, replaces the provider-native result metadata in full. */
|
|
512
|
+
providerMetadata?: ToolResultProviderMetadata;
|
|
500
513
|
/** If provided, replaces the error flag carried with the tool result. */
|
|
501
514
|
isError?: boolean;
|
|
502
515
|
/** If provided, replaces the contextually-useless flag carried with the tool result. */
|
|
@@ -583,6 +596,8 @@ export interface AgentToolResult<T = any, _TInput = unknown> {
|
|
|
583
596
|
// Marks a non-throwing failure (e.g. an aggregator catching per-entry errors).
|
|
584
597
|
// agent-loop honors this and surfaces it as a tool error on the wire.
|
|
585
598
|
isError?: boolean;
|
|
599
|
+
/** Provider-native metadata that must survive into history replay unchanged. */
|
|
600
|
+
providerMetadata?: ToolResultProviderMetadata;
|
|
586
601
|
/** Marks the result as contextually useless: safe for compaction to elide once consumed (e.g. zero matches, wait timeout). Ignored when isError is set. */
|
|
587
602
|
useless?: boolean;
|
|
588
603
|
}
|