@springbrand/agent-runtime 0.2.0-alpha.27 → 0.2.0-alpha.28
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/pi/runtime-adapter/execution.ts +38 -1
- package/src/runtime.ts +30 -0
package/package.json
CHANGED
|
@@ -210,6 +210,41 @@ function projectToolResultsForModel(
|
|
|
210
210
|
return changed ? projected : [...messages];
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
function orderLateToolResultsBeforeTurnAbortMarker(
|
|
214
|
+
messages: readonly AgentMessage[],
|
|
215
|
+
): AgentMessage[] {
|
|
216
|
+
const ordered: AgentMessage[] = [];
|
|
217
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
218
|
+
const message = messages[index]!;
|
|
219
|
+
const previous = ordered.at(-1);
|
|
220
|
+
if (
|
|
221
|
+
message.role !== "custom" ||
|
|
222
|
+
message.customType !== "turn-aborted" ||
|
|
223
|
+
previous?.role !== "assistant"
|
|
224
|
+
) {
|
|
225
|
+
ordered.push(message);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const pending = new Set(
|
|
229
|
+
previous.content.flatMap((part) =>
|
|
230
|
+
part.type === "toolCall" ? [part.id] : []
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
let cursor = index + 1;
|
|
234
|
+
while (true) {
|
|
235
|
+
const result = messages[cursor];
|
|
236
|
+
if (result?.role !== "toolResult" || !pending.has(result.toolCallId)) {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
ordered.push(result);
|
|
240
|
+
cursor += 1;
|
|
241
|
+
}
|
|
242
|
+
ordered.push(message);
|
|
243
|
+
index = cursor - 1;
|
|
244
|
+
}
|
|
245
|
+
return ordered;
|
|
246
|
+
}
|
|
247
|
+
|
|
213
248
|
class PiTurnAdapter {
|
|
214
249
|
private piInstance?: PiCore;
|
|
215
250
|
private abortRequested = false;
|
|
@@ -304,7 +339,9 @@ class PiTurnAdapter {
|
|
|
304
339
|
transformContext: async (messages, signal) => {
|
|
305
340
|
const ctx = await this.opts.transformContext(messages, signal);
|
|
306
341
|
return transformMessages(
|
|
307
|
-
|
|
342
|
+
orderLateToolResultsBeforeTurnAbortMarker(
|
|
343
|
+
projectToolResultsForModel(ctx, selfBounded),
|
|
344
|
+
) as Message[],
|
|
308
345
|
this.opts.pi.model,
|
|
309
346
|
) as AgentMessage[];
|
|
310
347
|
},
|
package/src/runtime.ts
CHANGED
|
@@ -2560,6 +2560,36 @@ export abstract class AgentRuntimeKernel<
|
|
|
2560
2560
|
return { kind: "settled" };
|
|
2561
2561
|
case "resume-turn": {
|
|
2562
2562
|
const last = (await this.transcript.canonicalMessages()).at(-1);
|
|
2563
|
+
if (last?.role === "assistant" && last.stopReason === "toolUse") {
|
|
2564
|
+
// Tool input 会在真正执行前持久化。恢复状态仍为 idle 证明这些
|
|
2565
|
+
// canonical Tool Call 还没越过 attempt 边界,包括非幂等 Tool 也可以安全首次执行。
|
|
2566
|
+
const unstartedCalls = last.content.filter((part) =>
|
|
2567
|
+
part.type === "toolCall"
|
|
2568
|
+
);
|
|
2569
|
+
if (unstartedCalls.length > 0) {
|
|
2570
|
+
for (const call of unstartedCalls) {
|
|
2571
|
+
try {
|
|
2572
|
+
if (
|
|
2573
|
+
!await adapter.retryTool({
|
|
2574
|
+
toolName: call.name,
|
|
2575
|
+
toolCallId: call.id,
|
|
2576
|
+
input: call.arguments,
|
|
2577
|
+
})
|
|
2578
|
+
) {
|
|
2579
|
+
return {
|
|
2580
|
+
kind: "unresumable",
|
|
2581
|
+
reason:
|
|
2582
|
+
`SpringBrand could not resume this turn: Tool "${call.name}" is no longer available.`,
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
} catch {
|
|
2586
|
+
// The governed Tool persisted its bounded error ToolResult.
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
decision = await this.materializeRecoveredToolResults(submission);
|
|
2590
|
+
continue;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2563
2593
|
// Pi 的 agentLoopContinue 拒绝从 assistant 消息继续。悬空的尾部 assistant
|
|
2564
2594
|
// 意味着这条 transcript 续不下去了 —— 判断出来就必须收尾,不能默默退场。
|
|
2565
2595
|
return last?.role === "user" || last?.role === "toolResult"
|