@springbrand/agent-runtime 0.2.0-alpha.56 → 0.2.0-alpha.57

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/agent-runtime",
3
- "version": "0.2.0-alpha.56",
3
+ "version": "0.2.0-alpha.57",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -45,6 +45,7 @@ import {
45
45
  } from "./assembly";
46
46
  import {
47
47
  isRecoverableAssistantError,
48
+ isOpenRouterServerToolTimeout,
48
49
  isModelStreamStallMessage,
49
50
  withProviderRetry,
50
51
  type PiGenerationLifecycleObserver,
@@ -490,7 +491,7 @@ export interface PiTurnDurability {
490
491
  export interface CreatePreparedPiTurnOptions {
491
492
  readonly prepared: PreparedPiRuntime;
492
493
  readonly pinnedDescriptor: string;
493
- /** 同一 Session 的所有 Submission 共用的稳定 Provider 路由身份。 */
494
+ /** Session 当前的稳定 Provider 路由身份;终态 Provider 故障可在下一 Submission 前旋转它。 */
494
495
  readonly modelSessionId: string;
495
496
  readonly submission: {
496
497
  readonly id: string;
@@ -609,6 +610,7 @@ export type PiCanonicalMessageCommit =
609
610
  export interface PiTurnTerminalIntent {
610
611
  readonly outcome: "succeeded" | "failed" | "aborted";
611
612
  readonly message?: string;
613
+ readonly resetModelSession?: boolean;
612
614
  }
613
615
 
614
616
  export interface PiPreparedTurnRunOptions {
@@ -1134,6 +1136,9 @@ export class PreparedPiTurnAdapter {
1134
1136
  );
1135
1137
  this.terminalIntent = {
1136
1138
  outcome: terminal.outcome,
1139
+ ...(isOpenRouterServerToolTimeout(authoritativeMessage)
1140
+ ? { resetModelSession: true }
1141
+ : {}),
1137
1142
  ...(authoritativeMessage.errorMessage
1138
1143
  ? { message: authoritativeMessage.errorMessage }
1139
1144
  : terminal.message
@@ -182,6 +182,32 @@ export function readModelStreamStallDetails(
182
182
  }
183
183
  }
184
184
 
185
+ export function isOpenRouterServerToolTimeout(
186
+ message: AssistantMessage,
187
+ ): boolean {
188
+ if (
189
+ message.stopReason !== "error" ||
190
+ !message.provider.startsWith("model-api-openrouter-messages-") ||
191
+ !message.errorMessage
192
+ ) return false;
193
+ try {
194
+ const payload = JSON.parse(message.errorMessage) as {
195
+ type?: unknown;
196
+ error?: {
197
+ type?: unknown;
198
+ message?: unknown;
199
+ error_type?: unknown;
200
+ };
201
+ };
202
+ return payload.type === "error" &&
203
+ payload.error?.type === "timeout_error" &&
204
+ payload.error.message === "Server tool request failed" &&
205
+ payload.error.error_type === "timeout";
206
+ } catch {
207
+ return false;
208
+ }
209
+ }
210
+
185
211
  export function isRecoverableAssistantError(
186
212
  message: AssistantMessage,
187
213
  ): boolean {
@@ -189,6 +215,7 @@ export function isRecoverableAssistantError(
189
215
  message.stopReason === "error" &&
190
216
  /thought[_ ]signature/i.test(message.errorMessage ?? "")
191
217
  ) return false;
218
+ if (isOpenRouterServerToolTimeout(message)) return false;
192
219
  return isRetryableAssistantError(message) ||
193
220
  (message.stopReason === "error" &&
194
221
  /upstream http\/2 stream failed/i.test(message.errorMessage ?? ""));
package/src/runtime.ts CHANGED
@@ -134,6 +134,7 @@ import {
134
134
  const SCHEDULED_STABLE_TIMEOUT_MS = 30_000;
135
135
  const TURN_EVENT_RETRY_SECONDS = 10;
136
136
  const RUNTIME_EVENT_MAX_ATTEMPTS = 5;
137
+ const MODEL_SESSION_ID_STORAGE_KEY = "universal-agent:model-session-id";
137
138
 
138
139
  // 计划续跑丢失多久后由看门狗接手。必须显著长于一个正常执行片的时长,
139
140
  // 否则会在健康的长执行片上误判并派发出重复的一片。
@@ -442,6 +443,8 @@ export abstract class AgentRuntimeKernel<
442
443
  private readonly approvals: ApprovalLifecycle<StoredSubmission>;
443
444
  private readonly interactions: InteractionLifecycle<StoredSubmission>;
444
445
  private runtimeLoadTracker?: RuntimeLoadTracker;
446
+ private modelSessionId?: string;
447
+ private readonly modelSessionIdReady?: Promise<void>;
445
448
  private runtimeEventDrainTail?: Promise<void>;
446
449
  private readonly streamBySubmission = new Map<string, string>();
447
450
  private migratedSubmissionIds?: Set<string>;
@@ -521,6 +524,11 @@ export abstract class AgentRuntimeKernel<
521
524
  snapshotKey: "__cfPiFiberSnapshot",
522
525
  snapshotKind: "pi-turn",
523
526
  });
527
+ this.modelSessionIdReady = ctx.storage.get<string>(
528
+ MODEL_SESSION_ID_STORAGE_KEY,
529
+ ).then((stored) => {
530
+ this.modelSessionId = stored;
531
+ });
524
532
  this.piAdapter = pi;
525
533
  this.db = new RuntimeDatabase(
526
534
  this.sql.bind(this),
@@ -734,6 +742,7 @@ export abstract class AgentRuntimeKernel<
734
742
  }
735
743
 
736
744
  protected async initCandidate(candidate: RuntimeCandidate): Promise<void> {
745
+ await this.modelSessionIdReady;
737
746
  const previous = this.activeRuntime;
738
747
  const candidateSnapshot = candidate.snapshot;
739
748
  this.runtimeLoad.advance("assembly");
@@ -870,6 +879,7 @@ export abstract class AgentRuntimeKernel<
870
879
  terminal: {
871
880
  outcome: "succeeded" | "failed" | "aborted";
872
881
  message?: string;
882
+ resetModelSession?: boolean;
873
883
  },
874
884
  ) => void | Promise<void>;
875
885
  } = {},
@@ -909,7 +919,7 @@ export abstract class AgentRuntimeKernel<
909
919
  return this.pi.createTurn({
910
920
  prepared: this.preparedPi(),
911
921
  pinnedDescriptor: submission.assemblyDescriptor,
912
- modelSessionId: this.name,
922
+ modelSessionId: this.modelSessionId ?? this.name,
913
923
  submission: {
914
924
  id: submission.submissionId,
915
925
  requestId: submission.requestId,
@@ -3038,6 +3048,7 @@ export abstract class AgentRuntimeKernel<
3038
3048
  | {
3039
3049
  outcome: "succeeded" | "failed" | "aborted";
3040
3050
  message?: string;
3051
+ resetModelSession?: boolean;
3041
3052
  }
3042
3053
  | undefined;
3043
3054
  const startedAt = ready.createdAt;
@@ -3050,13 +3061,20 @@ export abstract class AgentRuntimeKernel<
3050
3061
  this.migratedSubmissions.has(submissionId)
3051
3062
  ? undefined
3052
3063
  : this.persistAndBroadcastRecord(turn, record),
3053
- onTerminal: (intent) => {
3064
+ onTerminal: async (intent) => {
3054
3065
  terminalIntent = intent;
3055
3066
  this.appendTerminalIntent(
3056
3067
  submission,
3057
3068
  intent.outcome,
3058
3069
  intent.message,
3059
3070
  );
3071
+ if (intent.resetModelSession) {
3072
+ this.modelSessionId = crypto.randomUUID();
3073
+ await this.ctx.storage.put(
3074
+ MODEL_SESSION_ID_STORAGE_KEY,
3075
+ this.modelSessionId,
3076
+ );
3077
+ }
3060
3078
  },
3061
3079
  });
3062
3080
  for (const pending of this.db.steers.listForSubmission(submissionId)) {