@springbrand/agent-runtime 0.1.1 → 0.1.2

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.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -759,6 +759,7 @@ export interface RuntimeBrowserPort {
759
759
  */
760
760
  export type RuntimeModelProtocol =
761
761
  | "openai-chat"
762
+ | "openrouter-chat"
762
763
  | "anthropic-messages"
763
764
  | "google-generative-ai"
764
765
  | "openai-codex-responses";
@@ -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: _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
- * 该函数会先清空集合,所以调用方必须把传入对象视为由 Runtime 独占管理,不能期待自定义 provider 被保留。
239
+ * 该函数在全部新 provider 校验通过后才清空并替换集合,所以失败时旧集合保持可用。
232
240
  *
233
241
  * Pi 当前用 provider.id 在 Map 中注册和路由请求,因此每个部署端点都必须同时注册唯一 provider、对应模型、认证解析器和协议实现。
234
- * clearProviders 是为了避免配置更新后旧端点继续可用;所有模型在注册时都会经过 catalog 校验,因此任一未知 modelId 会让整次配置失败。
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
- models.clearProviders();
244
- provider.endpoints.forEach((endpoint, index) => {
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
 
@@ -66,9 +66,9 @@ function candidate<T extends TSchema>(tool: AgentTool<T>): PiToolCandidate {
66
66
  };
67
67
  }
68
68
 
69
- /** Runtime-permanent Pi tools, independent of optional Host plugins. */
69
+ /** Runtime base tools, including native web search when the model supports it. */
70
70
  export function basePiToolCandidates(
71
- webSearch: 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
  }
@@ -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 webSearch = createWebSearch({
867
- endpoint: endpoints[0]!,
868
- model: profile.model,
869
- maxTokens: resolvedModel.maxTokens,
870
- reasoning: resolvedModel.reasoning,
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
  }