@springbrand/agent-runtime 0.1.1 → 0.1.3-alpha.0
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/bindings.ts
CHANGED
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
openaiProvider,
|
|
28
28
|
} from "@earendil-works/pi-ai/providers/openai";
|
|
29
|
+
import {
|
|
30
|
+
openrouterProvider,
|
|
31
|
+
} from "@earendil-works/pi-ai/providers/openrouter";
|
|
29
32
|
import {
|
|
30
33
|
googleProvider,
|
|
31
34
|
} from "@earendil-works/pi-ai/providers/google";
|
|
@@ -40,6 +43,7 @@ import type {
|
|
|
40
43
|
|
|
41
44
|
const CATALOGS = {
|
|
42
45
|
"openai-chat": openaiProvider().getModels(),
|
|
46
|
+
"openrouter-chat": openrouterProvider().getModels(),
|
|
43
47
|
"anthropic-messages": anthropicProvider().getModels(),
|
|
44
48
|
"google-generative-ai": googleProvider().getModels(),
|
|
45
49
|
"openai-codex-responses": openaiCodexProvider().getModels(),
|
|
@@ -126,6 +130,7 @@ function providerId(endpoint: RuntimeModelEndpoint, index: number): string {
|
|
|
126
130
|
function apiFor(protocol: RuntimeModelProtocol): Api {
|
|
127
131
|
switch (protocol) {
|
|
128
132
|
case "openai-chat":
|
|
133
|
+
case "openrouter-chat":
|
|
129
134
|
return "openai-completions";
|
|
130
135
|
case "anthropic-messages":
|
|
131
136
|
return "anthropic-messages";
|
|
@@ -139,6 +144,7 @@ function apiFor(protocol: RuntimeModelProtocol): Api {
|
|
|
139
144
|
function piApiFor(protocol: RuntimeModelProtocol) {
|
|
140
145
|
switch (protocol) {
|
|
141
146
|
case "openai-chat":
|
|
147
|
+
case "openrouter-chat":
|
|
142
148
|
return openAICompletionsApi();
|
|
143
149
|
case "anthropic-messages":
|
|
144
150
|
return anthropicMessagesApi();
|
|
@@ -165,12 +171,11 @@ function configuredModel(
|
|
|
165
171
|
`Model is not in Pi's built-in catalog for ${endpoint.protocol}: ${modelId}`,
|
|
166
172
|
);
|
|
167
173
|
}
|
|
168
|
-
// 待确认:当前统一丢弃 catalogModel.compat;现有提交历史没有说明这样做对 Anthropic 兼容参数的影响,修改前需先验证 Pi 的流式请求行为。
|
|
169
174
|
const {
|
|
170
175
|
api: _api,
|
|
171
176
|
provider: _provider,
|
|
172
177
|
baseUrl: _baseUrl,
|
|
173
|
-
compat
|
|
178
|
+
compat,
|
|
174
179
|
headers: catalogHeaders,
|
|
175
180
|
...metadata
|
|
176
181
|
} = catalogModel;
|
|
@@ -183,6 +188,9 @@ function configuredModel(
|
|
|
183
188
|
api: apiFor(endpoint.protocol),
|
|
184
189
|
provider: providerId(endpoint, index),
|
|
185
190
|
baseUrl: endpoint.baseURL,
|
|
191
|
+
...(endpoint.protocol === "openrouter-chat" && compat
|
|
192
|
+
? { compat }
|
|
193
|
+
: {}),
|
|
186
194
|
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
187
195
|
};
|
|
188
196
|
}
|
|
@@ -228,10 +236,10 @@ export function resolvePiApiKey(
|
|
|
228
236
|
* 用当前部署端点完整重建一个已有的 Pi 模型集合。
|
|
229
237
|
*
|
|
230
238
|
* PiRuntimeAdapter 激活新运行快照时调用它;需要复用 MutableModels 的调用方也可以调用,并继续使用返回的同一实例。
|
|
231
|
-
*
|
|
239
|
+
* 该函数在全部新 provider 校验通过后才清空并替换集合,所以失败时旧集合保持可用。
|
|
232
240
|
*
|
|
233
241
|
* Pi 当前用 provider.id 在 Map 中注册和路由请求,因此每个部署端点都必须同时注册唯一 provider、对应模型、认证解析器和协议实现。
|
|
234
|
-
*
|
|
242
|
+
* 替换时先构建新 provider,确保任一未知 modelId 都不会让旧端点失效;校验通过后再清空旧集合,避免配置更新后旧端点继续可用。
|
|
235
243
|
*
|
|
236
244
|
* @returns 传入并已重建完成的 `models` 实例。
|
|
237
245
|
* @throws 当任一部署模型不在 Pi 内置 OpenAI、Anthropic catalog 中时抛出错误。
|
|
@@ -240,9 +248,8 @@ export function configurePiModels(
|
|
|
240
248
|
models: MutableModels,
|
|
241
249
|
provider: RuntimeProviderPort,
|
|
242
250
|
): MutableModels {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
models.setProvider(createProvider({
|
|
251
|
+
const configuredProviders = provider.endpoints.map((endpoint, index) =>
|
|
252
|
+
createProvider({
|
|
246
253
|
id: providerId(endpoint, index),
|
|
247
254
|
name: endpoint.protocol,
|
|
248
255
|
baseUrl: endpoint.baseURL,
|
|
@@ -259,8 +266,12 @@ export function configurePiModels(
|
|
|
259
266
|
configuredModel(endpoint, index, modelId),
|
|
260
267
|
),
|
|
261
268
|
api: piApiFor(endpoint.protocol),
|
|
262
|
-
})
|
|
263
|
-
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
models.clearProviders();
|
|
272
|
+
configuredProviders.forEach((configuredProvider) =>
|
|
273
|
+
models.setProvider(configuredProvider)
|
|
274
|
+
);
|
|
264
275
|
return models;
|
|
265
276
|
}
|
|
266
277
|
|
package/src/pi/tool/base.ts
CHANGED
|
@@ -66,9 +66,9 @@ function candidate<T extends TSchema>(tool: AgentTool<T>): PiToolCandidate {
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
/** Runtime
|
|
69
|
+
/** Runtime base tools, including native web search when the model supports it. */
|
|
70
70
|
export function basePiToolCandidates(
|
|
71
|
-
webSearch
|
|
71
|
+
webSearch?: WebSearch,
|
|
72
72
|
): PiToolCandidate[] {
|
|
73
73
|
return [
|
|
74
74
|
candidate({
|
|
@@ -105,6 +105,6 @@ export function basePiToolCandidates(
|
|
|
105
105
|
});
|
|
106
106
|
},
|
|
107
107
|
}),
|
|
108
|
-
webSearchPiToolCandidate(webSearch),
|
|
108
|
+
...(webSearch ? [webSearchPiToolCandidate(webSearch)] : []),
|
|
109
109
|
];
|
|
110
110
|
}
|
package/src/pi/tool/core-host.ts
CHANGED
|
@@ -14,6 +14,8 @@ import type { PiToolCandidate } from "./compiler";
|
|
|
14
14
|
|
|
15
15
|
// 本文件沿用 `../../index.ts` 入口定义的 Workspace、Port 和 Tool Candidate 术语。
|
|
16
16
|
|
|
17
|
+
const CODEMODE_SANDBOX_TIMEOUT_MS = 55_000;
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* 把宿主的 Worker Loader、出站网络和 Workspace 组装成 Pi 代码执行工具候选项。
|
|
19
21
|
*
|
|
@@ -32,6 +34,8 @@ export function workspaceCodeExecutionPiToolCandidate(options: {
|
|
|
32
34
|
executor: new DynamicWorkerExecutor({
|
|
33
35
|
loader: options.loader,
|
|
34
36
|
globalOutbound: options.outbound,
|
|
37
|
+
// 先于外层 60s 截止结束,给 Runtime RPC 结算和 Worker 释放留出时间。
|
|
38
|
+
timeout: CODEMODE_SANDBOX_TIMEOUT_MS,
|
|
35
39
|
}),
|
|
36
40
|
connectors: [
|
|
37
41
|
new StateConnector(
|
package/src/pi/tool/core.ts
CHANGED
|
@@ -205,6 +205,7 @@ const executeParameters = Type.Object({
|
|
|
205
205
|
"Plain JavaScript async function. TypeScript annotations are not supported.",
|
|
206
206
|
}),
|
|
207
207
|
});
|
|
208
|
+
const CODEMODE_EXECUTE_TIMEOUT_MS = 60_000;
|
|
208
209
|
|
|
209
210
|
/**
|
|
210
211
|
* 把 Cloudflare Codemode Runtime handle 包装为高风险 Pi 代码执行工具候选项。
|
|
@@ -235,7 +236,33 @@ export function codeExecutionPiToolCandidate(
|
|
|
235
236
|
// 必须通过 Runtime handle 而不是直接调用 executor,因为 Cloudflare Codemode 把重放、审批和执行日志放在持久化 Runtime 层。
|
|
236
237
|
async execute(_toolCallId, input, signal) {
|
|
237
238
|
signal?.throwIfAborted();
|
|
238
|
-
|
|
239
|
+
// ponytail: 外层截止只保证 Agent 继续;Codemode 支持 AbortSignal 或宿主 dispose 后再终止底层执行。
|
|
240
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
241
|
+
const deadline = new Promise<{
|
|
242
|
+
status: "error";
|
|
243
|
+
code: "timeout";
|
|
244
|
+
error: string;
|
|
245
|
+
retryable: false;
|
|
246
|
+
outcome: "unknown";
|
|
247
|
+
}>((resolve) => {
|
|
248
|
+
timeout = setTimeout(() => resolve({
|
|
249
|
+
status: "error",
|
|
250
|
+
code: "timeout",
|
|
251
|
+
error:
|
|
252
|
+
`Code Mode execute exceeded ${CODEMODE_EXECUTE_TIMEOUT_MS} ms; ` +
|
|
253
|
+
"use existing results and state the missing evidence.",
|
|
254
|
+
retryable: false,
|
|
255
|
+
outcome: "unknown",
|
|
256
|
+
}), CODEMODE_EXECUTE_TIMEOUT_MS);
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
return result(await Promise.race([
|
|
260
|
+
runtime.execute(input),
|
|
261
|
+
deadline,
|
|
262
|
+
]));
|
|
263
|
+
} finally {
|
|
264
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
265
|
+
}
|
|
239
266
|
},
|
|
240
267
|
};
|
|
241
268
|
return {
|
|
@@ -1106,6 +1106,8 @@ export interface WebSearchOptions {
|
|
|
1106
1106
|
|
|
1107
1107
|
function apiFor(endpoint: RuntimeModelEndpoint): NativeApi {
|
|
1108
1108
|
switch (endpoint.protocol) {
|
|
1109
|
+
case "openrouter-chat":
|
|
1110
|
+
throw new Error("Native web search is unavailable for openrouter-chat");
|
|
1109
1111
|
case "google-generative-ai":
|
|
1110
1112
|
return "google-generative-ai";
|
|
1111
1113
|
case "openai-chat":
|
package/src/plugins.ts
CHANGED
|
@@ -863,12 +863,22 @@ class RuntimeBuilder implements RuntimeContributionContext {
|
|
|
863
863
|
);
|
|
864
864
|
}
|
|
865
865
|
const resolvedModel = resolvePiModel(provider, profile.model);
|
|
866
|
-
const
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
866
|
+
const endpoint = endpoints[0]!;
|
|
867
|
+
const webSearch = endpoint.protocol === "openrouter-chat"
|
|
868
|
+
? undefined
|
|
869
|
+
: createWebSearch({
|
|
870
|
+
endpoint,
|
|
871
|
+
model: profile.model,
|
|
872
|
+
maxTokens: resolvedModel.maxTokens,
|
|
873
|
+
reasoning: resolvedModel.reasoning,
|
|
874
|
+
});
|
|
875
|
+
if (!webSearch) {
|
|
876
|
+
this.reportDegradation({
|
|
877
|
+
capability: "web_search",
|
|
878
|
+
reason: "unavailable",
|
|
879
|
+
detail: "Native web search is unavailable for openrouter-chat",
|
|
880
|
+
});
|
|
881
|
+
}
|
|
872
882
|
for (const candidate of basePiToolCandidates(webSearch)) {
|
|
873
883
|
this.addPiTool(candidate);
|
|
874
884
|
}
|