@springbrand/agent-runtime 0.1.0 → 0.1.1

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.1.0",
3
+ "version": "0.1.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -84,7 +84,6 @@ export interface RuntimeProfile {
84
84
  denyPolicy?: RuntimeDenyPolicy;
85
85
  enabledSubagents: readonly string[];
86
86
  mcpServers: readonly RuntimeMcpServer[];
87
- enabledExtensions: readonly string[];
88
87
  executionLevel: ExecutionLevel;
89
88
  memory: RuntimeMemoryProfile;
90
89
  }
@@ -109,7 +108,6 @@ export function freezeRuntimeProfile(profile: RuntimeProfile): RuntimeProfile {
109
108
  : undefined,
110
109
  enabledSubagents: Object.freeze([...profile.enabledSubagents]),
111
110
  mcpServers: Object.freeze(profile.mcpServers.map((server) => Object.freeze({ ...server }))),
112
- enabledExtensions: Object.freeze([...profile.enabledExtensions]),
113
111
  memory: Object.freeze({ ...profile.memory }),
114
112
  };
115
113
  return Object.freeze(frozen);
@@ -45,7 +45,7 @@ interface PiAssemblySnapshot {
45
45
  >;
46
46
  readonly profile: Pick<
47
47
  RuntimeProfile,
48
- "denyPolicy" | "enabledExtensions" | "mcpServers"
48
+ "denyPolicy" | "mcpServers"
49
49
  >;
50
50
  readonly bindings: {
51
51
  readonly platform: Pick<RuntimePlatformPort, "loader">;
@@ -453,7 +453,7 @@ async function preparePiAssembly(
453
453
  const extensions = await assemblePiExtensions({
454
454
  extensions: snapshot.pi.extensions,
455
455
  published: extensionNames,
456
- enabled: new Set(snapshot.profile.enabledExtensions),
456
+ enabled: extensionNames,
457
457
  authorized: extensionNames,
458
458
  load: async (extension) => {
459
459
  const workspacePermission =
@@ -2,7 +2,9 @@ import type { StreamFn } from "@earendil-works/pi-agent-core";
2
2
  import {
3
3
  createModels,
4
4
  createProvider,
5
+ lazyStream,
5
6
  type Api,
7
+ type AssistantMessageEvent,
6
8
  type Model,
7
9
  type MutableModels,
8
10
  } from "@earendil-works/pi-ai";
@@ -44,19 +46,57 @@ const CATALOGS = {
44
46
  } satisfies Record<RuntimeModelProtocol, readonly Model<Api>[]>;
45
47
 
46
48
  const PROVIDER_MAX_RETRIES = 2;
49
+ const MODEL_STREAM_STALL_TIMEOUT_MS = 120_000;
50
+
51
+ async function* stopStalledModelStream(
52
+ source: AsyncIterable<AssistantMessageEvent>,
53
+ watchdog: AbortController,
54
+ ): AsyncGenerator<AssistantMessageEvent> {
55
+ const iterator = source[Symbol.asyncIterator]();
56
+ while (true) {
57
+ let timer: ReturnType<typeof setTimeout> | undefined;
58
+ const next = await Promise.race([
59
+ iterator.next(),
60
+ new Promise<never>((_, reject) => {
61
+ timer = setTimeout(() => {
62
+ const error = new Error(
63
+ `Model stream stalled for ${MODEL_STREAM_STALL_TIMEOUT_MS} ms`,
64
+ );
65
+ watchdog.abort(error);
66
+ reject(error);
67
+ }, MODEL_STREAM_STALL_TIMEOUT_MS);
68
+ }),
69
+ ]).finally(() => {
70
+ if (timer !== undefined) clearTimeout(timer);
71
+ });
72
+ if (next.done) {
73
+ throw new Error("Model stream ended without a terminal event");
74
+ }
75
+ yield next.value;
76
+ if (next.value.type === "done" || next.value.type === "error") return;
77
+ }
78
+ }
47
79
 
48
80
  /**
49
- * 给模型请求补上可中断的 provider 瞬时错误重试。
81
+ * 给模型请求补上可中断的 provider 瞬时错误重试和空闲终止边界。
50
82
  *
51
83
  * Runtime Turn 和 SubAgent 在把 `Models.streamSimple` 交给 Pi 前调用;显式传入的重试次数优先。
52
84
  *
53
- * Pi 0.83 默认 `maxRetries` 为 0,这里只补 2 次默认值,避免瞬时网络错误直接终止,同时不覆盖调用方策略。
85
+ * Pi 0.83 默认 `maxRetries` 为 0,且流无事件时会无限等待。这里补 2 次默认重试,
86
+ * 并在连续 120 秒没有模型事件时中止 provider、返回明确失败终态。
54
87
  */
55
88
  export function withProviderRetry(streamFn: StreamFn): StreamFn {
56
89
  return (model, context, options) =>
57
- streamFn(model, context, {
58
- ...options,
59
- maxRetries: options?.maxRetries ?? PROVIDER_MAX_RETRIES,
90
+ lazyStream(model, async () => {
91
+ const watchdog = new AbortController();
92
+ const source = await streamFn(model, context, {
93
+ ...options,
94
+ signal: options?.signal
95
+ ? AbortSignal.any([options.signal, watchdog.signal])
96
+ : watchdog.signal,
97
+ maxRetries: options?.maxRetries ?? PROVIDER_MAX_RETRIES,
98
+ });
99
+ return stopStalledModelStream(source, watchdog);
60
100
  });
61
101
  }
62
102
 
package/src/plugins.ts CHANGED
@@ -837,7 +837,6 @@ class RuntimeBuilder implements RuntimeContributionContext {
837
837
  denyPolicy: this.denyPolicy,
838
838
  enabledSubagents: [...this.enabledSubagents],
839
839
  mcpServers: [...this.connectors.values()],
840
- enabledExtensions: [...this.extensions.keys()],
841
840
  executionLevel: this.profile.executionLevel,
842
841
  memory: this.memoryProfile,
843
842
  });