@springbrand/agent-runtime 0.2.0-alpha.32 → 0.2.0-alpha.34
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/package.json +1 -1
- package/src/kernel/interaction-lifecycle.ts +5 -5
- package/src/kernel/tool-surface.ts +1 -0
- package/src/pi/runtime-adapter/recovery.ts +19 -22
- package/src/pi/runtime-adapter/transcript.ts +1 -2
- package/src/pi/tool/compiler.ts +1 -1
- package/src/pi/tool/core-host.ts +12 -4
- package/src/pi/turn/tool-recovery.ts +2 -2
- package/src/runtime.ts +1 -4
- package/src/tool-registry.ts +1 -2
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
PiToolInteraction,
|
|
13
13
|
PiToolInteractionCancelReason,
|
|
14
14
|
} from "../pi/runtime-adapter";
|
|
15
|
-
import type {
|
|
15
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
16
16
|
|
|
17
17
|
// #region 类型约定
|
|
18
18
|
|
|
@@ -43,10 +43,10 @@ export interface InteractionRecord extends PiToolInteraction {
|
|
|
43
43
|
* `content` / `details` 由 Tool 自己的 `settle` 映射产出 —— 本流程不解释响应体语义。
|
|
44
44
|
* 取消不带载荷,由纯函数层用固定文案兜底。
|
|
45
45
|
*/
|
|
46
|
-
export
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
export type InteractionSettlementPayload = Omit<
|
|
47
|
+
AgentToolResult<unknown>,
|
|
48
|
+
"terminate"
|
|
49
|
+
>;
|
|
50
50
|
|
|
51
51
|
// 宿主用这些依赖把 interaction 状态机接入数据库、Pi 恢复计算和 Turn 续跑。
|
|
52
52
|
interface InteractionLifecycleOptions<
|
|
@@ -31,6 +31,7 @@ export interface ToolSpec extends Partial<
|
|
|
31
31
|
readonly requiredExecutionLevel: ExecutionLevel;
|
|
32
32
|
/** Require a fresh human decision for every call, regardless of execution level. */
|
|
33
33
|
readonly alwaysRequiresApproval?: boolean;
|
|
34
|
+
/** Preserve adapted Pi result envelopes; only details-only adapters may unwrap them. */
|
|
34
35
|
readonly execute: (
|
|
35
36
|
input: unknown,
|
|
36
37
|
ctx: ToolContext,
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
type PiToolRecoveryState,
|
|
16
16
|
} from "../turn";
|
|
17
17
|
import type { ToolResultMessage } from "@earendil-works/pi-ai";
|
|
18
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
19
|
+
import { isEqual } from "lodash-es";
|
|
18
20
|
import {
|
|
19
21
|
aiSdkRecoveryCodec,
|
|
20
22
|
shouldCreditStreamProgress,
|
|
@@ -35,10 +37,7 @@ export interface PiRecoveredToolSettlement {
|
|
|
35
37
|
readonly toolCallId: string;
|
|
36
38
|
readonly toolName: string;
|
|
37
39
|
readonly args: unknown;
|
|
38
|
-
readonly result:
|
|
39
|
-
readonly content: ToolResultMessage["content"];
|
|
40
|
-
readonly details: unknown;
|
|
41
|
-
};
|
|
40
|
+
readonly result: Omit<AgentToolResult<unknown>, "terminate">;
|
|
42
41
|
readonly isError: boolean;
|
|
43
42
|
readonly createdAt: number;
|
|
44
43
|
}
|
|
@@ -75,10 +74,7 @@ export type PiRecoveryCommand =
|
|
|
75
74
|
| {
|
|
76
75
|
readonly kind: "respond";
|
|
77
76
|
readonly response: unknown;
|
|
78
|
-
readonly result:
|
|
79
|
-
readonly content: ToolResultMessage["content"];
|
|
80
|
-
readonly details: unknown;
|
|
81
|
-
};
|
|
77
|
+
readonly result: Omit<AgentToolResult<unknown>, "terminate">;
|
|
82
78
|
}
|
|
83
79
|
| {
|
|
84
80
|
readonly kind: "cancel";
|
|
@@ -89,10 +85,7 @@ export type PiRecoveryCommand =
|
|
|
89
85
|
readonly kind: "record-tool-result";
|
|
90
86
|
readonly toolCallId: string;
|
|
91
87
|
readonly toolName: string;
|
|
92
|
-
readonly result:
|
|
93
|
-
readonly content: ToolResultMessage["content"];
|
|
94
|
-
readonly details: unknown;
|
|
95
|
-
};
|
|
88
|
+
readonly result: AgentToolResult<unknown>;
|
|
96
89
|
readonly isError: boolean;
|
|
97
90
|
readonly timestamp: number;
|
|
98
91
|
readonly needsContinuation?: boolean;
|
|
@@ -226,6 +219,10 @@ function recoveredToolSettlements(
|
|
|
226
219
|
result: {
|
|
227
220
|
content: toolResult.content,
|
|
228
221
|
details: toolResult.details,
|
|
222
|
+
...(toolResult.usage ? { usage: toolResult.usage } : {}),
|
|
223
|
+
...(toolResult.addedToolNames?.length
|
|
224
|
+
? { addedToolNames: toolResult.addedToolNames }
|
|
225
|
+
: {}),
|
|
229
226
|
},
|
|
230
227
|
isError: toolResult.isError,
|
|
231
228
|
createdAt: toolResult.timestamp,
|
|
@@ -248,21 +245,17 @@ function approvalExecutionId(continuationKey: string): string | null {
|
|
|
248
245
|
// 记录必须已经存在 —— 拿不到 toolCallId/toolName 就无法把结果绑回原调用,这时失败关闭而不是编一个 id。
|
|
249
246
|
function interactionToolResult(
|
|
250
247
|
interaction: PiToolInteraction | undefined,
|
|
251
|
-
result:
|
|
252
|
-
readonly content: ToolResultMessage["content"];
|
|
253
|
-
readonly details: unknown;
|
|
254
|
-
},
|
|
248
|
+
result: Omit<AgentToolResult<unknown>, "terminate">,
|
|
255
249
|
timestamp: number,
|
|
256
250
|
): ToolResultMessage {
|
|
257
251
|
if (!interaction) {
|
|
258
252
|
throw new Error("SpringBrand Tool interaction is missing for its response");
|
|
259
253
|
}
|
|
260
254
|
return {
|
|
255
|
+
...result,
|
|
261
256
|
role: "toolResult",
|
|
262
257
|
toolCallId: interaction.toolCallId,
|
|
263
258
|
toolName: interaction.toolName,
|
|
264
|
-
content: result.content,
|
|
265
|
-
details: result.details,
|
|
266
259
|
isError: false,
|
|
267
260
|
timestamp,
|
|
268
261
|
};
|
|
@@ -448,9 +441,9 @@ export function decidePiRecovery(
|
|
|
448
441
|
const existing = state.toolCalls[record.toolCallId];
|
|
449
442
|
if (existing) {
|
|
450
443
|
if (
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
444
|
+
existing.toolName !== record.toolName ||
|
|
445
|
+
existing.retry !== record.retry ||
|
|
446
|
+
!isEqual(existing.input, record.input)
|
|
454
447
|
) {
|
|
455
448
|
throw new Error(
|
|
456
449
|
`Conflicting SpringBrand Tool input for ${record.toolCallId}`,
|
|
@@ -614,12 +607,16 @@ export function decidePiRecovery(
|
|
|
614
607
|
toolName: command.toolName,
|
|
615
608
|
content: command.result.content,
|
|
616
609
|
details: command.result.details,
|
|
610
|
+
...(command.result.usage ? { usage: command.result.usage } : {}),
|
|
611
|
+
...(command.result.addedToolNames?.length
|
|
612
|
+
? { addedToolNames: command.result.addedToolNames }
|
|
613
|
+
: {}),
|
|
617
614
|
isError: command.isError,
|
|
618
615
|
timestamp: command.timestamp,
|
|
619
616
|
};
|
|
620
617
|
const existing = state.toolCalls[command.toolCallId]?.result;
|
|
621
618
|
if (existing) {
|
|
622
|
-
if (
|
|
619
|
+
if (!isEqual(existing, toolResult)) {
|
|
623
620
|
throw new Error(
|
|
624
621
|
`Conflicting SpringBrand Tool result for ${command.toolCallId}`,
|
|
625
622
|
);
|
|
@@ -346,11 +346,10 @@ export class PiRuntimeTranscript {
|
|
|
346
346
|
return this.append(
|
|
347
347
|
`${submissionId}:tool:${settlement.toolCallId}`,
|
|
348
348
|
{
|
|
349
|
+
...settlement.result,
|
|
349
350
|
role: "toolResult",
|
|
350
351
|
toolCallId: settlement.toolCallId,
|
|
351
352
|
toolName: settlement.toolName,
|
|
352
|
-
content: settlement.result.content,
|
|
353
|
-
details: settlement.result.details,
|
|
354
353
|
isError: settlement.isError,
|
|
355
354
|
timestamp: settlement.createdAt,
|
|
356
355
|
},
|
package/src/pi/tool/compiler.ts
CHANGED
package/src/pi/tool/core-host.ts
CHANGED
|
@@ -46,10 +46,18 @@ function codeExecutionTools(tools: ToolRegistry): ToolSet {
|
|
|
46
46
|
aiTool({
|
|
47
47
|
description: spec.description,
|
|
48
48
|
inputSchema: jsonSchema(spec.parameters as never),
|
|
49
|
-
execute: (input, options) =>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
execute: async (input, options) => {
|
|
50
|
+
const result = await spec.execute(input, {
|
|
51
|
+
toolCallId: options?.toolCallId ?? name,
|
|
52
|
+
signal: options?.abortSignal ?? new AbortController().signal,
|
|
53
|
+
});
|
|
54
|
+
return result != null &&
|
|
55
|
+
typeof result === "object" &&
|
|
56
|
+
"content" in result &&
|
|
57
|
+
Array.isArray((result as { content?: unknown }).content)
|
|
58
|
+
? (result as { details?: unknown }).details ?? result
|
|
59
|
+
: result;
|
|
60
|
+
},
|
|
53
61
|
}),
|
|
54
62
|
]));
|
|
55
63
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ToolResultMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { isEqual } from "lodash-es";
|
|
2
3
|
import { EXECUTION_LEVELS } from "../../lib/execution-level";
|
|
3
4
|
import {
|
|
4
5
|
decidePiToolApproval,
|
|
@@ -444,11 +445,10 @@ function sameToolInput(
|
|
|
444
445
|
{ type: "tool-input" }
|
|
445
446
|
>,
|
|
446
447
|
): boolean {
|
|
447
|
-
// 待确认:JSON.stringify 对对象键顺序敏感;当前写入链会保留输入顺序,重建输入的调用方可能产生语义相同但顺序不同的对象。
|
|
448
448
|
return (
|
|
449
449
|
previous.toolName === milestone.toolName &&
|
|
450
450
|
previous.retry === milestone.retry &&
|
|
451
|
-
|
|
451
|
+
isEqual(previous.input, milestone.input)
|
|
452
452
|
);
|
|
453
453
|
}
|
|
454
454
|
|
package/src/runtime.ts
CHANGED
|
@@ -4086,10 +4086,7 @@ export abstract class AgentRuntimeKernel<
|
|
|
4086
4086
|
const settled = spec.settle
|
|
4087
4087
|
? spec.settle(input, response)
|
|
4088
4088
|
: defaultInteractionResult(response);
|
|
4089
|
-
return this.interactions.respond(pending.interactionId, response,
|
|
4090
|
-
content: settled.content,
|
|
4091
|
-
details: settled.details,
|
|
4092
|
-
});
|
|
4089
|
+
return this.interactions.respond(pending.interactionId, response, settled);
|
|
4093
4090
|
}
|
|
4094
4091
|
|
|
4095
4092
|
/**
|
package/src/tool-registry.ts
CHANGED
|
@@ -85,13 +85,12 @@ export function piCandidateToToolSpec(candidate: PiToolCandidate): ToolSpec {
|
|
|
85
85
|
name,
|
|
86
86
|
arguments: input as Record<string, unknown>,
|
|
87
87
|
});
|
|
88
|
-
|
|
88
|
+
return execute(
|
|
89
89
|
ctx.toolCallId,
|
|
90
90
|
validated,
|
|
91
91
|
ctx.signal,
|
|
92
92
|
undefined,
|
|
93
93
|
);
|
|
94
|
-
return result.details ?? result;
|
|
95
94
|
},
|
|
96
95
|
};
|
|
97
96
|
}
|