@springbrand/agent-runtime 0.1.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.
Files changed (75) hide show
  1. package/package.json +28 -0
  2. package/src/db/approval.repo.ts +291 -0
  3. package/src/db/ext-context.repo.ts +34 -0
  4. package/src/db/index.ts +83 -0
  5. package/src/db/message-ui.repo.ts +39 -0
  6. package/src/db/milestone.repo.ts +96 -0
  7. package/src/db/runtime-event-outbox.repo.ts +89 -0
  8. package/src/db/schema.ts +164 -0
  9. package/src/db/settlement.repo.ts +104 -0
  10. package/src/db/steer.repo.ts +73 -0
  11. package/src/db/submission.repo.ts +323 -0
  12. package/src/index.ts +133 -0
  13. package/src/kernel/approval-lifecycle.ts +552 -0
  14. package/src/kernel/bindings.ts +898 -0
  15. package/src/kernel/degradation.ts +15 -0
  16. package/src/kernel/extensions.ts +108 -0
  17. package/src/kernel/profile.ts +116 -0
  18. package/src/kernel/public-contracts.ts +17 -0
  19. package/src/kernel/receipts.ts +124 -0
  20. package/src/kernel/recoverable-chat-agent.ts +899 -0
  21. package/src/kernel/state.ts +76 -0
  22. package/src/kernel/submission-lifecycle.ts +600 -0
  23. package/src/layers/context/budget/gate.ts +88 -0
  24. package/src/layers/orchestration/subagents/agent-types/contract.ts +78 -0
  25. package/src/layers/orchestration/subagents/agent-types/extract/index.ts +47 -0
  26. package/src/layers/orchestration/subagents/agent-types/fanout/index.ts +53 -0
  27. package/src/layers/orchestration/subagents/agent-types/registry.ts +16 -0
  28. package/src/layers/orchestration/temporary-agent/core.ts +152 -0
  29. package/src/layers/orchestration/temporary-agent/runner.ts +133 -0
  30. package/src/layers/orchestration/temporary-agent/workspace.ts +154 -0
  31. package/src/lib/artifacts.ts +54 -0
  32. package/src/lib/egress.ts +44 -0
  33. package/src/lib/execution-level.ts +27 -0
  34. package/src/lib/extension-name.ts +18 -0
  35. package/src/lib/host-actions.ts +57 -0
  36. package/src/lib/mcp.ts +86 -0
  37. package/src/lib/model-catalog.ts +7 -0
  38. package/src/lib/prompt.ts +139 -0
  39. package/src/lib/telemetry-dev.ts +44 -0
  40. package/src/pi/assembly/context.ts +510 -0
  41. package/src/pi/assembly/extensions.ts +661 -0
  42. package/src/pi/assembly/index.ts +19 -0
  43. package/src/pi/assembly/snapshot.ts +200 -0
  44. package/src/pi/message/contract.ts +8 -0
  45. package/src/pi/message/conversion.ts +73 -0
  46. package/src/pi/message/index.ts +3 -0
  47. package/src/pi/message/projection.ts +604 -0
  48. package/src/pi/runtime-adapter/assembly.ts +552 -0
  49. package/src/pi/runtime-adapter/execution.ts +683 -0
  50. package/src/pi/runtime-adapter/index.ts +232 -0
  51. package/src/pi/runtime-adapter/models.ts +243 -0
  52. package/src/pi/runtime-adapter/recovery.ts +805 -0
  53. package/src/pi/runtime-adapter/transcript.ts +825 -0
  54. package/src/pi/session/index.ts +24 -0
  55. package/src/pi/session/storage.ts +353 -0
  56. package/src/pi/tool/ai-adapter.ts +100 -0
  57. package/src/pi/tool/base.ts +110 -0
  58. package/src/pi/tool/compiler.ts +444 -0
  59. package/src/pi/tool/core-host.ts +48 -0
  60. package/src/pi/tool/core.ts +251 -0
  61. package/src/pi/tool/index.ts +32 -0
  62. package/src/pi/tool/mcp.ts +319 -0
  63. package/src/pi/tool/schedule.ts +198 -0
  64. package/src/pi/tool/skill.ts +455 -0
  65. package/src/pi/tool/subagent.ts +148 -0
  66. package/src/pi/tool/web-search/api.ts +1292 -0
  67. package/src/pi/tool/web-search/index.ts +2 -0
  68. package/src/pi/tool/web-search/web-search.ts +127 -0
  69. package/src/pi/tool/workspace-sandbox.ts +664 -0
  70. package/src/pi/turn/approval.ts +181 -0
  71. package/src/pi/turn/index.ts +62 -0
  72. package/src/pi/turn/tool-recovery.ts +792 -0
  73. package/src/plugins.ts +1024 -0
  74. package/src/runtime-agent.ts +654 -0
  75. package/src/runtime.ts +2880 -0
@@ -0,0 +1,232 @@
1
+ import {
2
+ PreparedPiTurnAdapter,
3
+ type CreatePreparedPiTurnOptions,
4
+ } from "./execution";
5
+ import {
6
+ uiUserMessageToPi,
7
+ } from "../message";
8
+ import type { UIMessage } from "ai";
9
+ import { createModels, type MutableModels } from "@earendil-works/pi-ai";
10
+ import { configurePiModels, resolvePiApiKey } from "./models";
11
+ export { withProviderRetry } from "./models";
12
+ import {
13
+ pinPiRuntime,
14
+ preparePiRuntime,
15
+ type PinPiRuntimeOptions,
16
+ type PinnedPiRuntime,
17
+ type PreparePiRuntimeOptions,
18
+ type PreparedPiRuntime,
19
+ } from "./assembly";
20
+ import {
21
+ decidePiRecovery,
22
+ PiRuntimeRecoveryAdapter,
23
+ type PiRecoveryHost,
24
+ type PiRecoveryInput,
25
+ } from "./recovery";
26
+ import {
27
+ PiRuntimeTranscript,
28
+ type PiTranscriptDurability,
29
+ } from "./transcript";
30
+ import type { SqlTaggedTemplate } from "agents/chat";
31
+
32
+ /**
33
+ * Runtime 只通过这个入口使用 Pi 的装配、执行、消息和恢复能力。
34
+ *
35
+ * @remarks
36
+ * Runtime、Pi、Turn、Tool Candidate 和 canonical transcript 等核心术语见包入口 `../../index.ts`。
37
+ * 本适配器只补充以下局部术语。
38
+ *
39
+ * - Prepared Runtime 是配置加载期产生的不透明句柄。
40
+ * 它包含已加载 Extension、Tool candidate 和快照,但 Runtime 只能读取 revision descriptor 与 degradation。
41
+ * - revision descriptor 是影响执行行为的已排序 JSON 描述。
42
+ * Runtime 会对它取哈希,用来识别当前 Prepared Runtime 的基础版本。
43
+ * - Pinned Runtime 是 Submission 准入时固定的描述符。
44
+ * 它在 Prepared Runtime 基础上加入动态 system context、approval mode 和 base revision。
45
+ * - recovery milestone 是可重放的持久化恢复记录。
46
+ * recovery effect 是重放后交给 Runtime 执行的唯一下一步动作。
47
+ *
48
+ * 调用方:`AgentRuntimeKernel` 在配置加载、请求准入、Turn 执行和中断恢复时调用这些粗粒度能力。
49
+ *
50
+ * 实现理由:较早的入口曾直接公开 assembly、message、session、tool 和 recovery 命名空间。
51
+ * 当前边界刻意隐藏这些细节,避免 Runtime 同时维护第二套 Pi 状态或绕过统一校验。
52
+ * 不要为了调用某个内部 helper 重新导出子模块;应先判断它是否属于 Runtime 真正需要的边界能力。
53
+ */
54
+ export class PiRuntimeAdapter {
55
+ private readonly owner = {};
56
+ private readonly models: MutableModels;
57
+ private readonly managesModels: boolean;
58
+
59
+ /**
60
+ * 创建一个持有独立 Pi 模型目录的 Runtime 适配器。
61
+ *
62
+ * @remarks
63
+ * 调用方:生产 Runtime 使用零参数构造,测试或专用宿主可以注入已经配置好的模型目录。
64
+ *
65
+ * 实现理由:适配器只管理自己创建的目录;注入目录的生命周期和配置权仍属于注入方。
66
+ * 不要让 `activate()` 重配注入目录,否则会覆盖调用方准备的 provider 和模型替身。
67
+ */
68
+ constructor(options: { models?: MutableModels } = {}) {
69
+ this.models = options.models ?? createModels();
70
+ this.managesModels = !options.models;
71
+ }
72
+
73
+ /**
74
+ * 创建负责 canonical transcript、浏览器投影和上下文压缩的对象。
75
+ *
76
+ * @remarks
77
+ * 调用方:Runtime 构造函数为当前实例创建一次,并提供 SQLite、事务和 Submission 投影回调。
78
+ *
79
+ * 实现理由:transcript 与 Turn 共用同一个模型目录,让上下文压缩和实际执行看到相同的 provider 配置。
80
+ * 不要把 RuntimeDatabase 直接传进 Pi 层;持久化职责应继续通过这里的窄回调提供。
81
+ */
82
+ createTranscript(options: {
83
+ readonly sql: SqlTaggedTemplate;
84
+ readonly durability: PiTranscriptDurability;
85
+ readonly hasActiveTurn: () => boolean;
86
+ }): PiRuntimeTranscript {
87
+ return new PiRuntimeTranscript(
88
+ options.sql,
89
+ options.durability,
90
+ this.models,
91
+ options.hasActiveTurn,
92
+ );
93
+ }
94
+
95
+ /**
96
+ * 把已校验的浏览器用户消息转换成 Pi 可以写入 transcript 的用户消息。
97
+ *
98
+ * @remarks
99
+ * 调用方:Runtime 收到 submit 或 regenerate 请求并完成结构校验后,在持久化 Submission 前调用。
100
+ *
101
+ * 实现理由:转换只生成 Pi UserMessage 的 content 和 timestamp,原始浏览器视图由 transcript sidecar 另行保存。
102
+ * 不要把 UI id、metadata 或原始 part 结构并入 canonical message。
103
+ */
104
+ normalizeUserInput(input: UIMessage & { role: "user" }) {
105
+ return uiUserMessageToPi(input);
106
+ }
107
+
108
+ resolveApiKey(
109
+ ...args: Parameters<typeof resolvePiApiKey>
110
+ ): ReturnType<typeof resolvePiApiKey> {
111
+ return resolvePiApiKey(...args);
112
+ }
113
+
114
+ /**
115
+ * 加载并预检一份可供后续准入使用的 Prepared Runtime。
116
+ *
117
+ * @remarks
118
+ * 调用方:Runtime 的 `initConfig()` 在取得新快照后调用,再用返回的 revision descriptor 判断是否可以切换配置。
119
+ *
120
+ * 实现理由:Extension 加载、Tool candidate 汇总和 Tool 编译预检在配置切换前一次完成。
121
+ * 返回值绑定当前 Adapter 的 owner,不能交给另一个 Adapter 创建 Turn。
122
+ */
123
+ prepare(
124
+ options: PreparePiRuntimeOptions,
125
+ ): Promise<PreparedPiRuntime> {
126
+ return preparePiRuntime(options, this.owner);
127
+ }
128
+
129
+ /**
130
+ * 用已经通过加载检查的快照启用当前部署声明的模型目录。
131
+ *
132
+ * @remarks
133
+ * 调用方:Runtime 的 `initConfig()` 在 Prepared Runtime 和 revision 检查成功后、发布新快照前调用。
134
+ *
135
+ * 实现理由:激活会校验并注册所有 endpoint 中声明的模型,而不只校验当前选中的模型。
136
+ * 这解释了为什么一个未被选中的无效模型也会让配置启动失败;不要把激活误当成首次推理请求。
137
+ * 注入模型目录时跳过此步骤,以保留调用方对目录的配置权。
138
+ */
139
+ activate(snapshot: PreparePiRuntimeOptions["snapshot"]): void {
140
+ if (this.managesModels) {
141
+ configurePiModels(this.models, snapshot.bindings.provider);
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 在接受 Submission 前固定本次执行使用的动态上下文和审批模式。
147
+ *
148
+ * @remarks
149
+ * 调用方:Runtime 的准入流程为每个新 Submission 调用,并把 descriptor 与其哈希一起持久化。
150
+ *
151
+ * 实现理由:memory、workspace 和 Extension context 可能随时间变化,恢复必须使用准入时的描述符检查兼容性。
152
+ * 不要把 pin 推迟到 Turn 启动后,否则已持久化的 Submission 将失去可验证的执行基线。
153
+ */
154
+ pin(options: PinPiRuntimeOptions): Promise<PinnedPiRuntime> {
155
+ return pinPiRuntime(options, this.owner);
156
+ }
157
+
158
+ /**
159
+ * 根据持久化 milestone 和一个命令算出恢复时要写什么、下一步做什么。
160
+ *
161
+ * @remarks
162
+ * 调用方:Runtime 和 approval 生命周期在检查状态、记录 Tool 输入结果、处理审批或提交终态时调用。
163
+ *
164
+ * 实现理由:该方法只返回 durable mutations 和一个 effect,实际写库与调度仍由 Runtime 完成。
165
+ * replay、plan、stage、commit 和 encode 必须保持在同一决策入口,避免宿主与 Pi 各自推导出不同状态。
166
+ */
167
+ decideRecovery(options: PiRecoveryInput) {
168
+ return decidePiRecovery(options);
169
+ }
170
+
171
+ /**
172
+ * 把 Runtime 提供的持久化和重试回调包装成通用聊天恢复端口。
173
+ *
174
+ * @remarks
175
+ * 调用方:`AgentRuntimeKernel.createRecoveryPort()` 在恢复引擎需要分类、重试、终结或读取部分输出时创建并复用它。
176
+ *
177
+ * 实现理由:Durable Object 被驱逐或休眠后内存状态不会保留,因此恢复判断必须从宿主持久化状态和 stream chunk 重建。
178
+ * Pi 负责解释 milestone,Runtime 仍负责数据库写入、Submission 重试和终态提交;不要合并这两个所有权边界。
179
+ */
180
+ createRecovery(host: PiRecoveryHost) {
181
+ return new PiRuntimeRecoveryAdapter(host);
182
+ }
183
+
184
+ /**
185
+ * 为一个已准入的 Submission 创建一次可执行的 Pi Turn。
186
+ *
187
+ * @remarks
188
+ * 调用方:Runtime 在首次执行和恢复继续时各创建一个新 Turn,并提供 canonical messages、durable Tool 回调和流事件回调。
189
+ *
190
+ * 实现理由:构造时会校验 Prepared Runtime 的 owner、pinned descriptor 和 base revision,再建立本 Turn 独享的 Tool governance 与执行状态。
191
+ * 不要跨 Turn 复用返回对象;abort、steer、assistant ordinal 和 Tool governance 都是单次执行状态。
192
+ */
193
+ createTurn(
194
+ options: CreatePreparedPiTurnOptions,
195
+ ): PreparedPiTurnAdapter {
196
+ return new PreparedPiTurnAdapter(options, {
197
+ owner: this.owner,
198
+ models: this.models,
199
+ });
200
+ }
201
+ }
202
+
203
+ export type {
204
+ UIChatRequestBody,
205
+ } from "../message";
206
+ export type { PiToolApproval } from "../turn";
207
+ export type {
208
+ CreatePreparedPiTurnOptions,
209
+ PiCanonicalUserInput,
210
+ PiStoredToolSettlement,
211
+ PiToolSettlement,
212
+ PiToolInputRecord,
213
+ PreparedPiTurnAdapter,
214
+ } from "./execution";
215
+ export type {
216
+ PinPiRuntimeOptions,
217
+ PinnedPiRuntime,
218
+ PreparePiRuntimeOptions,
219
+ PreparedPiRuntime,
220
+ } from "./assembly";
221
+ export type {
222
+ PiChatRecoveryData,
223
+ PiDurableMutation,
224
+ PiDurableRecoveryState,
225
+ PiRecoveryCommand,
226
+ PiRecoveryDecision,
227
+ PiRecoveryHost,
228
+ } from "./recovery";
229
+ export type {
230
+ PiRuntimeTranscript,
231
+ PiCanonicalTranscriptSnapshot,
232
+ } from "./transcript";
@@ -0,0 +1,243 @@
1
+ import type { StreamFn } from "@earendil-works/pi-agent-core";
2
+ import {
3
+ createModels,
4
+ createProvider,
5
+ type Api,
6
+ type Model,
7
+ type MutableModels,
8
+ } from "@earendil-works/pi-ai";
9
+ import {
10
+ anthropicMessagesApi,
11
+ } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
12
+ import {
13
+ openAICompletionsApi,
14
+ } from "@earendil-works/pi-ai/api/openai-completions.lazy";
15
+ import {
16
+ googleGenerativeAIApi,
17
+ } from "@earendil-works/pi-ai/api/google-generative-ai.lazy";
18
+ import {
19
+ openAICodexResponsesApi,
20
+ } from "@earendil-works/pi-ai/api/openai-codex-responses.lazy";
21
+ import {
22
+ anthropicProvider,
23
+ } from "@earendil-works/pi-ai/providers/anthropic";
24
+ import {
25
+ openaiProvider,
26
+ } from "@earendil-works/pi-ai/providers/openai";
27
+ import {
28
+ googleProvider,
29
+ } from "@earendil-works/pi-ai/providers/google";
30
+ import {
31
+ openaiCodexProvider,
32
+ } from "@earendil-works/pi-ai/providers/openai-codex";
33
+ import type {
34
+ RuntimeModelEndpoint,
35
+ RuntimeModelProtocol,
36
+ RuntimeProviderPort,
37
+ } from "../../kernel/bindings";
38
+
39
+ const CATALOGS = {
40
+ "openai-chat": openaiProvider().getModels(),
41
+ "anthropic-messages": anthropicProvider().getModels(),
42
+ "google-generative-ai": googleProvider().getModels(),
43
+ "openai-codex-responses": openaiCodexProvider().getModels(),
44
+ } satisfies Record<RuntimeModelProtocol, readonly Model<Api>[]>;
45
+
46
+ const PROVIDER_MAX_RETRIES = 2;
47
+
48
+ /**
49
+ * 给模型请求补上可中断的 provider 瞬时错误重试。
50
+ *
51
+ * Runtime Turn 和 SubAgent 在把 `Models.streamSimple` 交给 Pi 前调用;显式传入的重试次数优先。
52
+ *
53
+ * Pi 0.83 默认 `maxRetries` 为 0,这里只补 2 次默认值,避免瞬时网络错误直接终止,同时不覆盖调用方策略。
54
+ */
55
+ export function withProviderRetry(streamFn: StreamFn): StreamFn {
56
+ return (model, context, options) =>
57
+ streamFn(model, context, {
58
+ ...options,
59
+ maxRetries: options?.maxRetries ?? PROVIDER_MAX_RETRIES,
60
+ });
61
+ }
62
+
63
+ // 找出部署把这个模型放在哪个端点,并保留该端点在配置中的位置。
64
+ // resolvePiModel 和 resolvePiApiKey 在组装运行快照、发起模型请求前调用它,调用方只需传部署配置和 modelId。
65
+ // 这里复用同一条查找路径,避免模型与密钥落到不同端点;同一 modelId 若配置多次会取第一个,是否应改为拒绝重复配置仍待确认。
66
+ function endpointFor(
67
+ provider: RuntimeProviderPort,
68
+ modelId: string,
69
+ ): { endpoint: RuntimeModelEndpoint; index: number } {
70
+ const index = provider.endpoints.findIndex((endpoint) =>
71
+ endpoint.models.includes(modelId),
72
+ );
73
+ if (index < 0) {
74
+ throw new Error(`Model is not configured by this deployment: ${modelId}`);
75
+ }
76
+ return { endpoint: provider.endpoints[index], index };
77
+ }
78
+
79
+ // 给一个部署端点生成 Pi 内部使用的 provider 标识。
80
+ // configuredModel 和 configurePiModels 必须用相同参数调用它,让模型上的 provider 与注册进 MutableModels 的 provider 对得上。
81
+ // Pi 当前按 provider.id 存取和替换 provider,所以编号用于区分同协议端点;格式和端点顺序会影响路由身份,不能只改其中一处。
82
+ function providerId(endpoint: RuntimeModelEndpoint, index: number): string {
83
+ return `model-api-${endpoint.protocol}-${index}`;
84
+ }
85
+
86
+ function apiFor(protocol: RuntimeModelProtocol): Api {
87
+ switch (protocol) {
88
+ case "openai-chat":
89
+ return "openai-completions";
90
+ case "anthropic-messages":
91
+ return "anthropic-messages";
92
+ case "google-generative-ai":
93
+ return "google-generative-ai";
94
+ case "openai-codex-responses":
95
+ return "openai-codex-responses";
96
+ }
97
+ }
98
+
99
+ function piApiFor(protocol: RuntimeModelProtocol) {
100
+ switch (protocol) {
101
+ case "openai-chat":
102
+ return openAICompletionsApi();
103
+ case "anthropic-messages":
104
+ return anthropicMessagesApi();
105
+ case "google-generative-ai":
106
+ return googleGenerativeAIApi();
107
+ case "openai-codex-responses":
108
+ return openAICodexResponsesApi();
109
+ }
110
+ }
111
+
112
+ // 把部署声明的模型改造成可由对应自定义端点执行的 Pi 模型。
113
+ // resolvePiModel 为单个运行快照调用它,configurePiModels 则在注册端点时为端点中的每个 modelId 调用它。
114
+ // Pi 的内置 OpenAI、Anthropic provider 提供上下文窗口、费用和推理能力等元数据;这里保留这些元数据,但以部署协议、provider 标识和 baseURL 覆盖内置路由,未知模型会在初始化阶段直接报错,而不是拖到请求阶段。
115
+ function configuredModel(
116
+ endpoint: RuntimeModelEndpoint,
117
+ index: number,
118
+ modelId: string,
119
+ ): Model<Api> {
120
+ const catalogModel = CATALOGS[endpoint.protocol].find(
121
+ (model) => model.id === modelId,
122
+ );
123
+ if (!catalogModel) {
124
+ throw new Error(
125
+ `Model is not in Pi's built-in catalog for ${endpoint.protocol}: ${modelId}`,
126
+ );
127
+ }
128
+ // 待确认:当前统一丢弃 catalogModel.compat;现有提交历史没有说明这样做对 Anthropic 兼容参数的影响,修改前需先验证 Pi 的流式请求行为。
129
+ const {
130
+ api: _api,
131
+ provider: _provider,
132
+ baseUrl: _baseUrl,
133
+ compat: _compat,
134
+ headers: catalogHeaders,
135
+ ...metadata
136
+ } = catalogModel;
137
+ const headers = {
138
+ ...catalogHeaders,
139
+ ...endpoint.headers,
140
+ };
141
+ return {
142
+ ...metadata,
143
+ api: apiFor(endpoint.protocol),
144
+ provider: providerId(endpoint, index),
145
+ baseUrl: endpoint.baseURL,
146
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
147
+ };
148
+ }
149
+
150
+ /**
151
+ * 把部署允许的 modelId 解析成 Pi 可以直接执行的模型。
152
+ *
153
+ * Runtime 组装不可变运行快照时调用它;标题生成器和临时 Agent 也会先解析默认模型,再把返回值交给 Pi 的 Models 实例。
154
+ * 调用方应传入同一份 `RuntimeProviderPort`,随后用 {@link configurePiModels} 或 {@link createPiModels} 注册与该模型匹配的 provider。
155
+ *
156
+ * 解析先检查部署端点,再借用 Pi 当前内置 OpenAI、Anthropic catalog 的能力和计费元数据,同时把实际协议、provider 与 baseURL 改成部署配置。
157
+ * 这两道检查不能随便跳过,否则未获部署授权的模型或 Pi 不认识的模型会延迟到执行阶段才失败。
158
+ *
159
+ * @throws 当部署未配置该 modelId,或 Pi 内置 catalog 中没有该 modelId 时抛出错误。
160
+ */
161
+ export function resolvePiModel(
162
+ provider: RuntimeProviderPort,
163
+ modelId: string,
164
+ ): Model<Api> {
165
+ const { endpoint, index } = endpointFor(provider, modelId);
166
+ return configuredModel(endpoint, index, modelId);
167
+ }
168
+
169
+ /**
170
+ * 取出负责指定模型的部署端点密钥。
171
+ *
172
+ * Runtime 在创建一次模型执行或压缩上下文前调用它;辅助的标题生成器也在请求 Pi 时使用它。
173
+ * 调用方应传入与 {@link resolvePiModel} 相同的 provider 和 modelId,并只把返回值用于当前请求。
174
+ *
175
+ * 它与模型解析共用 endpointFor,确保密钥和模型总是选择同一个端点,也让未配置的模型在密钥离开部署边界前失败。
176
+ * 不要另写按协议选密钥的分支,否则同协议的多个端点会拿错凭据。
177
+ *
178
+ * @throws 当部署未配置该 modelId 时抛出错误。
179
+ */
180
+ export function resolvePiApiKey(
181
+ provider: RuntimeProviderPort,
182
+ modelId: string,
183
+ ): string {
184
+ return endpointFor(provider, modelId).endpoint.apiKey;
185
+ }
186
+
187
+ /**
188
+ * 用当前部署端点完整重建一个已有的 Pi 模型集合。
189
+ *
190
+ * PiRuntimeAdapter 激活新运行快照时调用它;需要复用 MutableModels 的调用方也可以调用,并继续使用返回的同一实例。
191
+ * 该函数会先清空集合,所以调用方必须把传入对象视为由 Runtime 独占管理,不能期待自定义 provider 被保留。
192
+ *
193
+ * Pi 当前用 provider.id 在 Map 中注册和路由请求,因此每个部署端点都必须同时注册唯一 provider、对应模型、认证解析器和协议实现。
194
+ * 先 clearProviders 是为了避免配置更新后旧端点继续可用;所有模型在注册时都会经过 catalog 校验,因此任一未知 modelId 会让整次配置失败。
195
+ *
196
+ * @returns 传入并已重建完成的 `models` 实例。
197
+ * @throws 当任一部署模型不在 Pi 内置 OpenAI、Anthropic catalog 中时抛出错误。
198
+ */
199
+ export function configurePiModels(
200
+ models: MutableModels,
201
+ provider: RuntimeProviderPort,
202
+ ): MutableModels {
203
+ models.clearProviders();
204
+ provider.endpoints.forEach((endpoint, index) => {
205
+ models.setProvider(createProvider({
206
+ id: providerId(endpoint, index),
207
+ name: endpoint.protocol,
208
+ baseUrl: endpoint.baseURL,
209
+ auth: {
210
+ apiKey: {
211
+ name: `${endpoint.protocol} API key`,
212
+ resolve: async () => ({
213
+ auth: { apiKey: endpoint.apiKey },
214
+ source: "deployment binding",
215
+ }),
216
+ },
217
+ },
218
+ models: endpoint.models.map((modelId) =>
219
+ configuredModel(endpoint, index, modelId),
220
+ ),
221
+ api: piApiFor(endpoint.protocol),
222
+ }));
223
+ });
224
+ return models;
225
+ }
226
+
227
+ /**
228
+ * 新建一个只包含当前部署端点的 Pi 模型集合。
229
+ *
230
+ * 标题生成器和临时 Agent 在需要独立、短生命周期的 Models 实例时调用它;长期 Runtime 通常复用自身集合并调用 {@link configurePiModels}。
231
+ * 调用方拿到实例后可直接配合 {@link resolvePiModel} 执行请求。
232
+ *
233
+ * Pi 的 createModels 当前只创建空集合,这里立即复用 configurePiModels 填充它,保证新建和重配只有一套注册规则。
234
+ * 不要在此重复 provider 组装,否则两条初始化路径会逐渐产生不同的模型、认证或协议行为。
235
+ *
236
+ * @returns 已按部署配置完成的全新 Pi 模型集合。
237
+ * @throws 当任一部署模型不在 Pi 内置 OpenAI、Anthropic catalog 中时抛出错误。
238
+ */
239
+ export function createPiModels(
240
+ provider: RuntimeProviderPort,
241
+ ): MutableModels {
242
+ return configurePiModels(createModels(), provider);
243
+ }