@springbrand/agent-runtime 0.2.0-alpha.61 → 0.2.0-alpha.62
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
CHANGED
package/src/kernel/state.ts
CHANGED
|
@@ -62,7 +62,10 @@ export interface RuntimeTurnState {
|
|
|
62
62
|
activeMessageId?: string;
|
|
63
63
|
recoveryAttempt?: number;
|
|
64
64
|
recoveryMax?: number;
|
|
65
|
-
recoveryReason?:
|
|
65
|
+
recoveryReason?:
|
|
66
|
+
| "no_meaningful_model_progress"
|
|
67
|
+
| "server_tool_timeout"
|
|
68
|
+
| "transient_model_error";
|
|
66
69
|
steerable: boolean;
|
|
67
70
|
hasPendingSteer: boolean;
|
|
68
71
|
queued: RuntimeQueuedSubmission[];
|
|
@@ -83,6 +83,8 @@ const MAX_WRAP_UP_TURNS = 2;
|
|
|
83
83
|
|
|
84
84
|
export class RetryableModelError extends Error {}
|
|
85
85
|
|
|
86
|
+
export class RetryableModelSessionError extends RetryableModelError {}
|
|
87
|
+
|
|
86
88
|
export class ContextOverflowError extends RetryableModelError {}
|
|
87
89
|
|
|
88
90
|
function normalizePlanToolCalls(message: AgentMessage): void {
|
|
@@ -610,7 +612,6 @@ export type PiCanonicalMessageCommit =
|
|
|
610
612
|
export interface PiTurnTerminalIntent {
|
|
611
613
|
readonly outcome: "succeeded" | "failed" | "aborted";
|
|
612
614
|
readonly message?: string;
|
|
613
|
-
readonly resetModelSession?: boolean;
|
|
614
615
|
}
|
|
615
616
|
|
|
616
617
|
export interface PiPreparedTurnRunOptions {
|
|
@@ -1058,7 +1059,8 @@ export class PreparedPiTurnAdapter {
|
|
|
1058
1059
|
) || isModelStreamStallMessage(
|
|
1059
1060
|
event.assistantMessageEvent.error.errorMessage,
|
|
1060
1061
|
) ||
|
|
1061
|
-
isRecoverableAssistantError(event.assistantMessageEvent.error)
|
|
1062
|
+
isRecoverableAssistantError(event.assistantMessageEvent.error) ||
|
|
1063
|
+
isOpenRouterServerToolTimeout(event.assistantMessageEvent.error))
|
|
1062
1064
|
) {
|
|
1063
1065
|
return;
|
|
1064
1066
|
}
|
|
@@ -1073,10 +1075,11 @@ export class PreparedPiTurnAdapter {
|
|
|
1073
1075
|
}
|
|
1074
1076
|
const stalled = message.stopReason === "error" &&
|
|
1075
1077
|
isModelStreamStallMessage(message.errorMessage);
|
|
1078
|
+
const resetModelSession = isOpenRouterServerToolTimeout(message);
|
|
1076
1079
|
const retryable = isRecoverableAssistantError(message);
|
|
1077
|
-
if (stalled || retryable) {
|
|
1080
|
+
if (stalled || retryable || resetModelSession) {
|
|
1078
1081
|
const content = visibleAssistantContent(message);
|
|
1079
|
-
if (content.length > 0) {
|
|
1082
|
+
if (!resetModelSession && content.length > 0) {
|
|
1080
1083
|
this.assistantOrdinal += 1;
|
|
1081
1084
|
await this.options.onCanonicalMessage({
|
|
1082
1085
|
kind: "commit-turn",
|
|
@@ -1090,6 +1093,9 @@ export class PreparedPiTurnAdapter {
|
|
|
1090
1093
|
if (stalled) {
|
|
1091
1094
|
throw new ChatStreamStalledError(message.errorMessage!);
|
|
1092
1095
|
}
|
|
1096
|
+
if (resetModelSession) {
|
|
1097
|
+
throw new RetryableModelSessionError(message.errorMessage);
|
|
1098
|
+
}
|
|
1093
1099
|
throw new RetryableModelError(message.errorMessage);
|
|
1094
1100
|
}
|
|
1095
1101
|
}
|
|
@@ -1136,9 +1142,6 @@ export class PreparedPiTurnAdapter {
|
|
|
1136
1142
|
);
|
|
1137
1143
|
this.terminalIntent = {
|
|
1138
1144
|
outcome: terminal.outcome,
|
|
1139
|
-
...(isOpenRouterServerToolTimeout(authoritativeMessage)
|
|
1140
|
-
? { resetModelSession: true }
|
|
1141
|
-
: {}),
|
|
1142
1145
|
...(authoritativeMessage.errorMessage
|
|
1143
1146
|
? { message: authoritativeMessage.errorMessage }
|
|
1144
1147
|
: terminal.message
|
|
@@ -2,7 +2,11 @@ import {
|
|
|
2
2
|
PreparedPiTurnAdapter,
|
|
3
3
|
type CreatePreparedPiTurnOptions,
|
|
4
4
|
} from "./execution";
|
|
5
|
-
export {
|
|
5
|
+
export {
|
|
6
|
+
ContextOverflowError,
|
|
7
|
+
RetryableModelError,
|
|
8
|
+
RetryableModelSessionError,
|
|
9
|
+
} from "./execution";
|
|
6
10
|
import {
|
|
7
11
|
uiUserMessageToPi,
|
|
8
12
|
} from "../message";
|
|
@@ -215,6 +215,7 @@ export function isRecoverableAssistantError(
|
|
|
215
215
|
message.stopReason === "error" &&
|
|
216
216
|
/thought[_ ]signature/i.test(message.errorMessage ?? "")
|
|
217
217
|
) return false;
|
|
218
|
+
// Runtime resets the model Session and gives this timeout its own one-retry budget.
|
|
218
219
|
if (isOpenRouterServerToolTimeout(message)) return false;
|
|
219
220
|
return isRetryableAssistantError(message) ||
|
|
220
221
|
(message.stopReason === "error" &&
|
package/src/runtime.ts
CHANGED
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
MODEL_STREAM_STALL_TIMEOUT_MS,
|
|
72
72
|
readModelStreamStallDetails,
|
|
73
73
|
RetryableModelError,
|
|
74
|
+
RetryableModelSessionError,
|
|
74
75
|
type PiCanonicalUserInput,
|
|
75
76
|
type PiCanonicalTranscriptSnapshot,
|
|
76
77
|
type PiChatRecoveryData,
|
|
@@ -145,6 +146,7 @@ const CONTINUATION_WATCHDOG_SECONDS = 300;
|
|
|
145
146
|
// Host 侧的一次抖动会把 Session 永久卡在「队列已满」。
|
|
146
147
|
const ADMISSION_ATTEMPT_WINDOW_MS = 60_000;
|
|
147
148
|
export const CHAT_RECOVERY_MAX_ATTEMPTS = 5;
|
|
149
|
+
const SERVER_TOOL_TIMEOUT_MAX_ATTEMPTS = 2;
|
|
148
150
|
// stall 单独收窄。理由不是「stall 更不值得救」,而是它的重试**期望值和别的错不一样**:
|
|
149
151
|
// 瞬时错(5xx / 断流)重跑一次往往就好了;stall 的重跑是拿同一份 transcript 让模型
|
|
150
152
|
// 重新想同样久,如果它本来就超预算,再跑几次也一样超。把看门狗放宽到 240s 之后,
|
|
@@ -177,10 +179,7 @@ interface StoredSubmission extends SubmissionReceipt {
|
|
|
177
179
|
userMessageId?: string | null;
|
|
178
180
|
regenerateMessageId?: string | null;
|
|
179
181
|
recoveryErrorCount: number;
|
|
180
|
-
recoveryReason?:
|
|
181
|
-
| "no_meaningful_model_progress"
|
|
182
|
-
| "transient_model_error"
|
|
183
|
-
| "context_overflow";
|
|
182
|
+
recoveryReason?: SubmissionRecoveryReason;
|
|
184
183
|
runId: string | null;
|
|
185
184
|
accountId: string | null;
|
|
186
185
|
rateVersion: number | null;
|
|
@@ -893,7 +892,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
893
892
|
terminal: {
|
|
894
893
|
outcome: "succeeded" | "failed" | "aborted";
|
|
895
894
|
message?: string;
|
|
896
|
-
resetModelSession?: boolean;
|
|
897
895
|
},
|
|
898
896
|
) => void | Promise<void>;
|
|
899
897
|
} = {},
|
|
@@ -3062,7 +3060,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
3062
3060
|
| {
|
|
3063
3061
|
outcome: "succeeded" | "failed" | "aborted";
|
|
3064
3062
|
message?: string;
|
|
3065
|
-
resetModelSession?: boolean;
|
|
3066
3063
|
}
|
|
3067
3064
|
| undefined;
|
|
3068
3065
|
const startedAt = ready.createdAt;
|
|
@@ -3082,13 +3079,6 @@ export abstract class AgentRuntimeKernel<
|
|
|
3082
3079
|
intent.outcome,
|
|
3083
3080
|
intent.message,
|
|
3084
3081
|
);
|
|
3085
|
-
if (intent.resetModelSession) {
|
|
3086
|
-
this.modelSessionId = crypto.randomUUID();
|
|
3087
|
-
await this.ctx.storage.put(
|
|
3088
|
-
MODEL_SESSION_ID_STORAGE_KEY,
|
|
3089
|
-
this.modelSessionId,
|
|
3090
|
-
);
|
|
3091
|
-
}
|
|
3092
3082
|
},
|
|
3093
3083
|
});
|
|
3094
3084
|
for (const pending of this.db.steers.listForSubmission(submissionId)) {
|
|
@@ -3212,13 +3202,23 @@ export abstract class AgentRuntimeKernel<
|
|
|
3212
3202
|
}
|
|
3213
3203
|
const stalled = error instanceof ChatStreamStalledError;
|
|
3214
3204
|
const contextOverflow = error instanceof ContextOverflowError;
|
|
3205
|
+
const resetModelSession = error instanceof RetryableModelSessionError;
|
|
3215
3206
|
const abortReason = this.readSubmission(submissionId)?.abortReason;
|
|
3216
3207
|
if (!abortReason && (stalled || error instanceof RetryableModelError)) {
|
|
3208
|
+
if (resetModelSession) {
|
|
3209
|
+
this.modelSessionId = crypto.randomUUID();
|
|
3210
|
+
await this.ctx.storage.put(
|
|
3211
|
+
MODEL_SESSION_ID_STORAGE_KEY,
|
|
3212
|
+
this.modelSessionId,
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3217
3215
|
const recoveryReason: SubmissionRecoveryReason = stalled
|
|
3218
3216
|
? "no_meaningful_model_progress"
|
|
3219
|
-
:
|
|
3220
|
-
? "
|
|
3221
|
-
:
|
|
3217
|
+
: resetModelSession
|
|
3218
|
+
? "server_tool_timeout"
|
|
3219
|
+
: contextOverflow
|
|
3220
|
+
? "context_overflow"
|
|
3221
|
+
: "transient_model_error";
|
|
3222
3222
|
let contextOverflowRecoveryClaimed = false;
|
|
3223
3223
|
const recoveryErrorCount = this.db.transaction(() => {
|
|
3224
3224
|
if (contextOverflow) {
|
|
@@ -3253,7 +3253,11 @@ export abstract class AgentRuntimeKernel<
|
|
|
3253
3253
|
? contextOverflowRecoveryClaimed &&
|
|
3254
3254
|
recoveryErrorCount < CHAT_RECOVERY_MAX_ATTEMPTS
|
|
3255
3255
|
: recoveryErrorCount <
|
|
3256
|
-
(stalled
|
|
3256
|
+
(stalled
|
|
3257
|
+
? CHAT_STALL_MAX_ATTEMPTS
|
|
3258
|
+
: resetModelSession
|
|
3259
|
+
? SERVER_TOOL_TIMEOUT_MAX_ATTEMPTS
|
|
3260
|
+
: CHAT_RECOVERY_MAX_ATTEMPTS);
|
|
3257
3261
|
if (
|
|
3258
3262
|
!stoppedDuringRecovery &&
|
|
3259
3263
|
retryAllowed
|
|
@@ -4710,7 +4714,9 @@ export abstract class AgentRuntimeKernel<
|
|
|
4710
4714
|
: current.recoveryReason ===
|
|
4711
4715
|
"no_meaningful_model_progress"
|
|
4712
4716
|
? CHAT_STALL_MAX_ATTEMPTS
|
|
4713
|
-
:
|
|
4717
|
+
: current.recoveryReason === "server_tool_timeout"
|
|
4718
|
+
? SERVER_TOOL_TIMEOUT_MAX_ATTEMPTS
|
|
4719
|
+
: CHAT_RECOVERY_MAX_ATTEMPTS,
|
|
4714
4720
|
...(current.recoveryReason &&
|
|
4715
4721
|
current.recoveryReason !== "context_overflow"
|
|
4716
4722
|
? { recoveryReason: current.recoveryReason }
|