ai-fly 0.1.1 → 0.3.0-alpha.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.
Files changed (44) hide show
  1. package/dist/ai-fly.js +48 -22
  2. package/dist/args-CSxXW9eD.mjs +69 -0
  3. package/dist/common-CgxCXeTt.mjs +83 -0
  4. package/dist/common-CqxqIg6D.mjs +86 -0
  5. package/dist/config-BlZnFqx7.mjs +59 -0
  6. package/dist/daemon-DklsEniO.mjs +291 -0
  7. package/dist/{forget-BEbNwPip.mjs → forget-B17zGwj4.mjs} +3 -2
  8. package/dist/{sdk-C6xDRbzg.mjs → frames-BDqvhB2Y.mjs} +1 -10
  9. package/dist/{group-Csy9VmFb.mjs → group-B96IKEDF.mjs} +24 -4
  10. package/dist/{import-BVAFw5Tx.mjs → import-tilxdbTH.mjs} +8 -6
  11. package/dist/{join-E1_-Rt-7.mjs → join-DsKUAibH.mjs} +7 -5
  12. package/dist/{join-B9op0juF.mjs → join-hyqiwEek.mjs} +3 -3
  13. package/dist/{key-CK77IYXp.mjs → key-BiEp995L.mjs} +2 -2
  14. package/dist/{key-Bt4EWiuO.mjs → key-CNufbB__.mjs} +4 -3
  15. package/dist/local-test-C3HGjGY3.mjs +101 -0
  16. package/dist/models-dev-2UfQoIpQ.mjs +244 -0
  17. package/dist/{mux-xHl9Jrbi.mjs → mux-CS1zhF4W.mjs} +2 -2
  18. package/dist/{ports-BQukx7wS.mjs → ports-rRppuBgo.mjs} +3 -2
  19. package/dist/presets-C73CAMZ6.mjs +55 -0
  20. package/dist/proxy-D7VMUVEg.mjs +21 -0
  21. package/dist/{revoke-CyrOzc8t.mjs → revoke-CkpvK33Q.mjs} +5 -4
  22. package/dist/rewrite-xZHWnyJk.mjs +313 -0
  23. package/dist/route-test-Oew0CO1U.mjs +60 -0
  24. package/dist/rpc-contract-5qyWzT5t.mjs +638 -0
  25. package/dist/{run-Be8H-IRf.mjs → run-DbPXAm6h.mjs} +8 -5
  26. package/dist/{runtime-Dg4en1D8.mjs → runtime-D_nxUEjd.mjs} +3 -3
  27. package/dist/secret-SA37VND5.mjs +95 -0
  28. package/dist/secrets-BYI6ooTJ.mjs +113 -0
  29. package/dist/secrets-u19-8EyW.mjs +2 -0
  30. package/dist/{serve-DuyQMeJh.mjs → serve-Be4zdKVj.mjs} +14 -425
  31. package/dist/{serve-R03mDBwd.mjs → serve-DSBlodTF.mjs} +18 -6
  32. package/dist/service-B5uORhl3.mjs +232 -0
  33. package/dist/settings-C1Hb4zwd.mjs +34 -0
  34. package/dist/settings-DU0YqNDt.mjs +95 -0
  35. package/dist/{share-DGM2TOft.mjs → share-Cug0944N.mjs} +8 -5
  36. package/dist/{status-DRaKNvqz.mjs → status-B-j454u2.mjs} +7 -5
  37. package/dist/{status-W2OS7WB6.mjs → status-SkNsxGnk.mjs} +6 -4
  38. package/dist/{common-4VeA_3wo.mjs → store-BiTS2FTl.mjs} +3 -73
  39. package/dist/store-CrTUwskE.mjs +629 -0
  40. package/dist/test-Bc0OKBAi.mjs +64 -0
  41. package/package.json +1 -1
  42. package/dist/common-BULbPtYn.mjs +0 -1295
  43. package/dist/config-CO3WyW2H.mjs +0 -173
  44. package/dist/service-Cd4R47n4.mjs +0 -94
@@ -0,0 +1,60 @@
1
+ import { n as ROUTE_LOCAL_PREFIX } from "./rpc-contract-5qyWzT5t.mjs";
2
+ import { t as formProbe } from "./local-test-C3HGjGY3.mjs";
3
+ import { i as buildUpstreamRequest, t as PathNotOfferedError } from "./rewrite-xZHWnyJk.mjs";
4
+ //#region src/provider/route-test.ts
5
+ const DEFAULT_TIMEOUT_MS = 2e4;
6
+ const BODY_EXCERPT_MAX = 2e3;
7
+ async function testServiceRoute(input) {
8
+ const fetchImpl = input.fetchImpl ?? fetch;
9
+ const now = input.now ?? Date.now;
10
+ const started = now();
11
+ const probe = formProbe(input.form, input.localPrefix ?? ROUTE_LOCAL_PREFIX[input.form], input.model, input.content?.trim() !== "" && input.content !== void 0 ? input.content : "hi");
12
+ const baseRequest = {
13
+ method: "POST",
14
+ url: probe.path,
15
+ ...input.model !== void 0 ? { model: input.model } : {}
16
+ };
17
+ const req = {
18
+ v: 1,
19
+ id: "provider-route-test",
20
+ serviceId: input.service.serviceId,
21
+ method: "POST",
22
+ path: probe.path,
23
+ headers: probe.headers,
24
+ contentType: "application/json",
25
+ bodyLen: 0
26
+ };
27
+ try {
28
+ const plan = buildUpstreamRequest(input.service, req, input.env, input.secrets);
29
+ const response = await fetchImpl(plan.url, {
30
+ method: "POST",
31
+ headers: plan.headers,
32
+ body: JSON.stringify(probe.body),
33
+ signal: AbortSignal.timeout(input.timeoutMs ?? DEFAULT_TIMEOUT_MS)
34
+ });
35
+ const latencyMs = Math.max(0, now() - started);
36
+ const ok = response.status >= 200 && response.status < 300;
37
+ const text = await response.text().catch(() => "");
38
+ const bodyExcerpt = text === "" ? void 0 : text.slice(0, BODY_EXCERPT_MAX);
39
+ return {
40
+ ok,
41
+ latencyMs,
42
+ request: {
43
+ ...baseRequest,
44
+ url: plan.url.toString()
45
+ },
46
+ httpStatus: response.status,
47
+ ...!ok && response.status >= 500 ? { error: `upstream error (HTTP ${response.status})` } : {},
48
+ ...bodyExcerpt !== void 0 ? { bodyExcerpt } : {}
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ ok: false,
53
+ latencyMs: Math.max(0, now() - started),
54
+ request: baseRequest,
55
+ error: error instanceof PathNotOfferedError ? `path not offered - ${probe.path} matches no declared route (whitelist)` : error instanceof Error ? error.message : String(error)
56
+ };
57
+ }
58
+ }
59
+ //#endregion
60
+ export { testServiceRoute };
@@ -0,0 +1,638 @@
1
+ import { z } from "zod";
2
+ import { randomBytes } from "node:crypto";
3
+ import { oc } from "@orpc/contract";
4
+ //#region src/wire/z32.ts
5
+ /** z-base-32 字母表(fabric 同款,ybndrfg8…h769)。 */
6
+ const Z32_ALPHABET = "ybndrfg8ejkmcpqxot1uwisza345h769";
7
+ const CHAR_VALUE = (/* @__PURE__ */ new Uint8Array(128)).fill(255);
8
+ for (let i = 0; i < 32; i++) CHAR_VALUE[Z32_ALPHABET.charCodeAt(i)] = i;
9
+ /** 编码:高位在前、5 bit 一组;末组不足 5 bit 左移补零,不输出填充符。 */
10
+ function encodeZ32(bytes) {
11
+ let out = "";
12
+ let acc = 0;
13
+ let bits = 0;
14
+ for (let i = 0; i < bytes.length; i++) {
15
+ acc = acc << 8 | bytes[i];
16
+ bits += 8;
17
+ while (bits >= 5) {
18
+ bits -= 5;
19
+ out += Z32_ALPHABET.charAt(acc >>> bits & 31);
20
+ }
21
+ acc &= (1 << bits) - 1;
22
+ }
23
+ if (bits > 0) out += Z32_ALPHABET.charAt(acc << 5 - bits & 31);
24
+ return out;
25
+ }
26
+ /** 解码:严格规范形校验(非法字符、尾位 padding 非零、非最短长度均抛错)。 */
27
+ function decodeZ32(text) {
28
+ const out = [];
29
+ let acc = 0;
30
+ let bits = 0;
31
+ for (let i = 0; i < text.length; i++) {
32
+ const code = text.charCodeAt(i);
33
+ if (code >= 128 || CHAR_VALUE[code] === 255) throw new Error(`z32: invalid character at index ${i}`);
34
+ acc = acc << 5 | CHAR_VALUE[code];
35
+ bits += 5;
36
+ if (bits >= 8) {
37
+ bits -= 8;
38
+ out.push(acc >>> bits & 255);
39
+ acc &= (1 << bits) - 1;
40
+ }
41
+ }
42
+ if (bits > 0 && acc !== 0) throw new Error("z32: non-canonical trailing bits");
43
+ const bytes = Uint8Array.from(out);
44
+ if (encodeZ32(bytes).length !== text.length) throw new Error("z32: non-canonical length");
45
+ return bytes;
46
+ }
47
+ /** nBytes 字节密码学随机数的 z32 编码(request-id / serviceId / 密钥体共用)。 */
48
+ function randomZ32(nBytes) {
49
+ return encodeZ32(randomBytes(nBytes));
50
+ }
51
+ z.enum([
52
+ "NOT_FOUND",
53
+ "CONFLICT",
54
+ "INVALID_INPUT",
55
+ "INVALID_STATE",
56
+ "UNAVAILABLE",
57
+ "INTERNAL"
58
+ ]);
59
+ /** 与错误码绑定的稳定传输状态(skill-creator-v2 同款 ErrorMap 形状)。 */
60
+ const RpcErrorDefinitions = {
61
+ NOT_FOUND: {
62
+ status: 404,
63
+ message: "The requested resource was not found."
64
+ },
65
+ CONFLICT: {
66
+ status: 409,
67
+ message: "The operation conflicts with current state."
68
+ },
69
+ INVALID_INPUT: {
70
+ status: 422,
71
+ message: "The input failed validation."
72
+ },
73
+ INVALID_STATE: {
74
+ status: 409,
75
+ message: "The operation is not valid in the current state."
76
+ },
77
+ UNAVAILABLE: {
78
+ status: 503,
79
+ message: "The required engine is not running."
80
+ },
81
+ INTERNAL: {
82
+ status: 500,
83
+ message: "Internal error."
84
+ }
85
+ };
86
+ const SERVICE_MATCH_SCHEMA = z.strictObject({
87
+ type: z.enum([
88
+ "exact",
89
+ "suffix",
90
+ "regex"
91
+ ]),
92
+ value: z.string().min(1).max(2048)
93
+ });
94
+ const SERVICE_REWRITE_SCHEMA = z.strictObject({
95
+ hostHeader: z.string().min(1).max(2048).optional(),
96
+ pathPrefixStrip: z.string().min(1).max(2048).optional(),
97
+ pathPrefixAppend: z.string().min(1).max(2048).optional(),
98
+ headerSet: z.record(z.string().min(1).max(1024), z.string().max(8192)).optional(),
99
+ headerRemove: z.array(z.string().min(1).max(1024)).max(32).optional()
100
+ });
101
+ const ROUTE_FORM_SCHEMA = z.enum([
102
+ "openai-chat",
103
+ "openai-responses",
104
+ "anthropic"
105
+ ]);
106
+ /** 路由匹配模式(M3-r7):prefix = 前缀替换(默认);pattern = URLPattern
107
+ * 匹配 + RFC 6570 URI Template 拼装。 */
108
+ const ROUTE_MODE_SCHEMA = z.enum(["prefix", "pattern"]);
109
+ /**
110
+ * 路径路由(M3-r7 定形):**按声明顺序命中**(先声明先匹配)。
111
+ * - prefix 模式(默认):localPrefix(版本段粒度如 /v1)替换为 upstreamPrefix;
112
+ * - pattern 模式:matchPattern(URLPattern pathname 表达式,`:name`/`{name}`
113
+ * 组、`*` 通配)匹配请求路径,template(RFC 6570 URI Template)以捕获组
114
+ * + 查询参数拼装 upstream 路径。
115
+ * forms 标注承载的 API 标准(AI 层;引擎转发与 forms 无关,可为空)。
116
+ */
117
+ const SERVICE_ROUTE_SCHEMA = z.strictObject({
118
+ forms: z.array(ROUTE_FORM_SCHEMA).max(3),
119
+ mode: ROUTE_MODE_SCHEMA.optional(),
120
+ /** prefix 模式:本地端口前缀。缺省 = 首个 form 的规范前缀。 */
121
+ localPrefix: z.string().min(1).max(2048).optional(),
122
+ /** prefix 模式:替换本地前缀的 upstream 路径前缀("" = upstream 根)。 */
123
+ upstreamPrefix: z.string().max(2048).optional(),
124
+ /** pattern 模式:URLPattern pathname 表达式。 */
125
+ matchPattern: z.string().min(1).max(2048).optional(),
126
+ /** pattern 模式:RFC 6570 URI Template(变量 = 捕获组 + 查询参数)。 */
127
+ template: z.string().min(1).max(2048).optional()
128
+ });
129
+ /** 各 API 标准在本地端口上的规范前缀(缺省 localPrefix 与 agent 配置基准)。 */
130
+ const ROUTE_LOCAL_PREFIX = {
131
+ "openai-chat": "/v1",
132
+ "openai-responses": "/v1",
133
+ anthropic: "/anthropic"
134
+ };
135
+ /** 路由的生效本地前缀(缺省派生规范前缀)。 */
136
+ function routeLocalPrefix(route) {
137
+ return route.localPrefix ?? ROUTE_LOCAL_PREFIX[route.forms[0] ?? "openai-chat"];
138
+ }
139
+ const SERVICE_SCHEMA = z.strictObject({
140
+ serviceId: z.string().min(1).max(128),
141
+ name: z.string().min(1).max(256),
142
+ match: z.array(SERVICE_MATCH_SCHEMA).max(64),
143
+ upstream: z.string().min(1).max(2048),
144
+ rewrite: SERVICE_REWRITE_SCHEMA.optional(),
145
+ routes: z.array(SERVICE_ROUTE_SCHEMA).max(3).optional(),
146
+ defaultPort: z.number().int().min(1).max(65535)
147
+ });
148
+ /** services.add 输入(defaultPort 缺省规则的校验在 store,同 CLI)。 */
149
+ const SERVICE_INPUT_SCHEMA = z.strictObject({
150
+ name: z.string().min(1).max(256),
151
+ upstream: z.string().min(1).max(2048),
152
+ match: z.array(SERVICE_MATCH_SCHEMA).min(1).max(64),
153
+ defaultPort: z.number().int().min(1).max(65535).optional(),
154
+ rewrite: SERVICE_REWRITE_SCHEMA.optional(),
155
+ routes: z.array(SERVICE_ROUTE_SCHEMA).max(3).optional()
156
+ });
157
+ const GROUP_LIMITS_SCHEMA = z.strictObject({
158
+ maxConcurrency: z.number().int().min(1).optional(),
159
+ dailyRequests: z.number().int().min(1).optional()
160
+ });
161
+ const GROUP_SCHEMA = z.strictObject({
162
+ name: z.string().min(1).max(256),
163
+ serviceIds: z.array(z.string().min(1).max(128)).max(256),
164
+ limits: GROUP_LIMITS_SCHEMA.optional()
165
+ });
166
+ /** 密钥视图:keyId/分组/时间/状态;哈希与原文一律不进契约(原文仅 issue 时一次性返回)。 */
167
+ const KEY_VIEW_SCHEMA = z.strictObject({
168
+ keyId: z.string().min(1).max(128),
169
+ group: z.string().min(1).max(256),
170
+ createdAt: z.number().int().min(0),
171
+ revokedAt: z.number().int().min(0).optional()
172
+ });
173
+ /** 使用方服务目录条目(AUTH_OK / import 视图;detail 为脱敏披露,$env 值显示 ●)。 */
174
+ const SERVICE_ENTRY_SCHEMA = z.object({
175
+ serviceId: z.string(),
176
+ name: z.string(),
177
+ match: z.array(z.strictObject({
178
+ type: z.string(),
179
+ value: z.string()
180
+ })),
181
+ defaultPort: z.number().int(),
182
+ detail: z.object({
183
+ upstream: z.string(),
184
+ match: z.array(z.strictObject({
185
+ type: z.string(),
186
+ value: z.string()
187
+ })),
188
+ rewrite: z.object({
189
+ host: z.string().optional(),
190
+ prefix: z.string().optional(),
191
+ headerSet: z.array(z.strictObject({
192
+ name: z.string(),
193
+ value: z.string()
194
+ })).optional()
195
+ }).optional(),
196
+ /** 按标准路由披露(消费侧呈现各标准本地 base 与可用性判定)。 */
197
+ routes: z.array(z.strictObject({
198
+ forms: z.array(ROUTE_FORM_SCHEMA).max(3),
199
+ mode: ROUTE_MODE_SCHEMA.optional(),
200
+ localPrefix: z.string().optional(),
201
+ upstreamPrefix: z.string().optional(),
202
+ matchPattern: z.string().optional(),
203
+ template: z.string().optional()
204
+ })).max(4).optional()
205
+ }).optional()
206
+ });
207
+ /** 消费侧提供者连接状态(M1 六态 + 网关未运行时的 stopped)。 */
208
+ const PROVIDER_STATE_SCHEMA = z.enum([
209
+ "stopped",
210
+ "not-connected",
211
+ "connected-unauthed",
212
+ "direct",
213
+ "relay",
214
+ "offline",
215
+ "key-all-invalid"
216
+ ]);
217
+ const CONSUMER_PROVIDER_STATUS_SCHEMA = z.object({
218
+ endpointId: z.string(),
219
+ alias: z.string(),
220
+ state: PROVIDER_STATE_SCHEMA,
221
+ /** 该提供者授权目录内的服务(脱敏 detail)。 */
222
+ services: z.array(SERVICE_ENTRY_SCHEMA),
223
+ /** serviceId -> 本地端口(pinned 或 default;网关运行时为实际监听端口)。 */
224
+ ports: z.record(z.string(), z.number().int().min(0).max(65535)),
225
+ servedCount: z.number().int().min(0),
226
+ bufferOverflows: z.number().int().min(0),
227
+ lastError: z.string().optional()
228
+ });
229
+ const API_FORM_SCHEMA = z.enum([
230
+ "openai-completions",
231
+ "anthropic-messages",
232
+ "gemini-native"
233
+ ]);
234
+ const PRESET_SCHEMA = z.strictObject({
235
+ id: z.string().min(1).max(128),
236
+ label: z.string().min(1).max(256),
237
+ apiForm: API_FORM_SCHEMA,
238
+ baseUrl: z.string().min(1).max(2048),
239
+ /** 图标源 id(models.dev logos 覆写;缺省取 id)。 */
240
+ iconId: z.string().min(1).max(128).optional(),
241
+ /** 惯用环境变量名(仅用于 $env 注入建议与文档;本地运行时模板无此字段)。 */
242
+ keyEnv: z.string().min(1).max(256).optional(),
243
+ /** 使用方本地端口建议(避开 <1024 特权段)。 */
244
+ defaultPort: z.number().int().min(1024).max(65535),
245
+ /** 官方域名集(exact/suffix 建议的生成源)。 */
246
+ matchDomains: z.array(z.string().min(1).max(256)).min(1).max(16),
247
+ /** 按标准路由(展开进服务;本地前缀由 form 派生)。 */
248
+ routes: z.array(SERVICE_ROUTE_SCHEMA).max(3).optional(),
249
+ notes: z.string().max(2048).optional(),
250
+ /** 出处(精选集为调研 URL;models.dev 长尾为 "models.dev")。 */
251
+ source: z.string().min(1).max(512),
252
+ /** apiForm 未经厂商文档核实(models.dev npm 推断)时长尾条目置 true。 */
253
+ unverified: z.boolean().optional()
254
+ });
255
+ const SECRET_NAME_SCHEMA = z.string().min(1).max(128).regex(/^[a-z0-9][a-z0-9._-]*$/, "lowercase letters, digits, dot, dash, underscore");
256
+ /** 密钥库清单条目(仅名称与开关——值由设计不跨 RPC)。 */
257
+ const SECRET_ENTRY_SCHEMA = z.strictObject({
258
+ name: SECRET_NAME_SCHEMA,
259
+ createdAt: z.number().int().min(0),
260
+ updatedAt: z.number().int().min(0),
261
+ /** 注入时自动拼 "Bearer "(默认 true;Owner 2026-09-10:值默认是裸 key)。 */
262
+ bearerPrefix: z.boolean()
263
+ });
264
+ const WRITER_AGENT_SCHEMA = z.enum([
265
+ "codex",
266
+ "claude-code",
267
+ "cursor",
268
+ "cline",
269
+ "continue"
270
+ ]);
271
+ /** 写手目标描述:serviceId(从消费方存储解析端口)或显式 port,二选一。 */
272
+ const WRITER_TARGET_SCHEMA = z.strictObject({
273
+ serviceId: z.string().min(1).max(128).optional(),
274
+ port: z.number().int().min(1).max(65535).optional()
275
+ }).refine((v) => v.serviceId !== void 0 !== (v.port !== void 0), { message: "exactly one of serviceId or port is required" });
276
+ const WRITER_PREVIEW_SCHEMA = z.strictObject({
277
+ agent: WRITER_AGENT_SCHEMA,
278
+ target: WRITER_TARGET_SCHEMA
279
+ });
280
+ const WRITER_APPLY_SCHEMA = z.strictObject({
281
+ agent: WRITER_AGENT_SCHEMA,
282
+ target: WRITER_TARGET_SCHEMA,
283
+ /** preview 返回的确认令牌(sha256(diff)):apply 只写入用户看过的那份 diff。 */
284
+ confirmToken: z.string().regex(/^[0-9a-f]{64}$/)
285
+ });
286
+ /** 按标准测试的结果(消费侧 wire 链路 / 提供方 route 直打共用形状)。 */
287
+ const SERVICE_TEST_RESULT_SCHEMA = z.strictObject({
288
+ ok: z.boolean(),
289
+ latencyMs: z.number().int().min(0),
290
+ request: z.strictObject({
291
+ method: z.literal("POST"),
292
+ url: z.string(),
293
+ model: z.string().optional()
294
+ }),
295
+ modelSource: z.enum([
296
+ "explicit",
297
+ "models.dev",
298
+ "none"
299
+ ]).optional(),
300
+ /** upstream 响应状态码(拿到响应即有;传输失败缺席)。 */
301
+ httpStatus: z.number().int().min(100).max(599).optional(),
302
+ error: z.string().optional(),
303
+ /** 响应正文摘录(成功=模型回复/错误体;截断)。 */
304
+ bodyExcerpt: z.string().optional()
305
+ });
306
+ const SETTINGS_SCHEMA = z.strictObject({
307
+ theme: z.enum([
308
+ "dark",
309
+ "light",
310
+ "system"
311
+ ]),
312
+ /** false 时预设列表不含 models.dev 长尾(断网/隐私偏好)。 */
313
+ modelsDevEnabled: z.boolean(),
314
+ /** relay 入口列表(provider/consumer 共用;null = 未配置,走 SDK 默认)。 */
315
+ relayUrls: z.array(z.string().min(1).max(2048)).max(8).nullable()
316
+ });
317
+ const SHARE_CREATE_INPUT_SCHEMA = z.strictObject({
318
+ group: z.string().min(1).max(256),
319
+ /** TTL 毫秒(1s..30d;缺省 60min——范围校验复用 link.ts 常量语义)。 */
320
+ ttlMs: z.number().int().min(1e3).max(2592e6).optional()
321
+ });
322
+ const PRESET_APPLY_INPUT_SCHEMA = z.strictObject({
323
+ presetId: z.string().min(1).max(128),
324
+ /** 服务名(缺省取 preset.id)。 */
325
+ name: z.string().min(1).max(256).optional(),
326
+ /** 显式端口(缺省取 preset.defaultPort;上游特权端口时必填——store 校验兜底)。 */
327
+ port: z.number().int().min(1).max(65535).optional(),
328
+ /** 密钥库名(选中时 rewrite 写入 $secret:<name>,优先于 keyEnv)。 */
329
+ secretName: SECRET_NAME_SCHEMA.optional(),
330
+ /** 注入建议的 $env 变量名(缺省取 preset.keyEnv;无 keyEnv 且未显式给出则不注入)。 */
331
+ keyEnv: z.string().min(1).max(256).optional()
332
+ });
333
+ /** 提供方状态快照(引擎运行值 + 磁盘存储摘要,二合一)。 */
334
+ const PROVIDER_STATUS_SCHEMA = z.object({
335
+ running: z.boolean(),
336
+ alias: z.string().optional(),
337
+ endpointId: z.string().optional(),
338
+ fabricIdHex: z.string().optional(),
339
+ relayMode: z.string().optional(),
340
+ relayUrls: z.array(z.string()),
341
+ sessionCount: z.number().int().min(0),
342
+ services: z.number().int().min(0),
343
+ groups: z.number().int().min(0),
344
+ activeKeys: z.number().int().min(0),
345
+ revokedKeys: z.number().int().min(0)
346
+ });
347
+ oc.errors(RpcErrorDefinitions).router({
348
+ provider: {
349
+ services: {
350
+ /** 全量服务配置(本机控制面:含 rewrite 原始 $env 引用,无脱敏需要)。 */
351
+ list: oc.input(z.object({})).output(z.object({ services: z.array(SERVICE_SCHEMA) })),
352
+ /** 按 name 精确定位单个服务。 */
353
+ get: oc.input(z.strictObject({ name: z.string().min(1).max(256) })).output(SERVICE_SCHEMA),
354
+ /** 新增服务(校验同 CLI:defaultPort 特权规则、match 编译检查、重名拒绝)。 */
355
+ add: oc.input(SERVICE_INPUT_SCHEMA).output(z.object({ service: SERVICE_SCHEMA })),
356
+ /** 删除服务(连带清出分组引用)。 */
357
+ remove: oc.input(z.strictObject({ name: z.string().min(1).max(256) })).output(z.object({ removed: z.literal(true) })),
358
+ /** 上游连通性测试(草稿或已存服务形状;provider-local、不落盘、不计限额)。 */
359
+ test: oc.input(z.strictObject({
360
+ upstream: z.string().min(1).max(2048),
361
+ apiForm: API_FORM_SCHEMA.optional(),
362
+ secretName: SECRET_NAME_SCHEMA.optional(),
363
+ model: z.string().min(1).max(256).optional()
364
+ })).output(z.strictObject({
365
+ ok: z.boolean(),
366
+ httpStatus: z.number().int().min(0).max(599).optional(),
367
+ latencyMs: z.number().int().min(0),
368
+ model: z.string(),
369
+ error: z.string().optional(),
370
+ /** 请求详情(发起过即有):UI 呈现「发了什么」。 */
371
+ request: z.strictObject({
372
+ method: z.literal("POST"),
373
+ url: z.string(),
374
+ model: z.string()
375
+ }).optional(),
376
+ /** 模型选择来源(explicit / models.dev / upstream-probe)。 */
377
+ modelSource: z.enum([
378
+ "explicit",
379
+ "models.dev",
380
+ "upstream-probe"
381
+ ]).optional()
382
+ })),
383
+ /**
384
+ * 提供方侧按标准路由测试(Owner 裁决 2026-09-11:与 connect ③ 同形态):
385
+ * 最小 AI-API 请求 → 本地路由命中(prefix/pattern + 白名单)→ rewrite
386
+ * 注入($secret/$env)→ 直打 upstream。request.url = 改写后的上游 URL。
387
+ */
388
+ testRoute: oc.input(z.strictObject({
389
+ name: z.string().min(1).max(256),
390
+ form: ROUTE_FORM_SCHEMA,
391
+ model: z.string().min(1).max(256).optional(),
392
+ content: z.string().max(8192).optional(),
393
+ localPrefix: z.string().min(1).max(2048).optional()
394
+ })).output(SERVICE_TEST_RESULT_SCHEMA)
395
+ },
396
+ secrets: {
397
+ /** 密钥库清单(仅名称与时间戳;值由设计不跨 RPC)。 */
398
+ list: oc.input(z.object({})).output(z.object({ secrets: z.array(SECRET_ENTRY_SCHEMA) })),
399
+ /** 新增/覆写(value 为裸密钥——bearerPrefix 默认 true 时注入自动拼 "Bearer ")。 */
400
+ set: oc.input(z.strictObject({
401
+ name: SECRET_NAME_SCHEMA,
402
+ value: z.string().min(1).max(8192),
403
+ /** 关闭后按原样注入(非 Bearer 站点)。 */
404
+ bearerPrefix: z.boolean().optional()
405
+ })).output(z.object({ secret: SECRET_ENTRY_SCHEMA })),
406
+ /** 删除(不存在报 NOT_FOUND)。 */
407
+ remove: oc.input(z.strictObject({ name: SECRET_NAME_SCHEMA })).output(z.object({ removed: z.literal(true) }))
408
+ },
409
+ groups: {
410
+ /** 分组列表(含 limits 与 serviceIds)。 */
411
+ list: oc.input(z.object({})).output(z.object({ groups: z.array(GROUP_SCHEMA) })),
412
+ /** 新建分组(可带限额;未知服务名报错——store 校验)。 */
413
+ add: oc.input(z.strictObject({
414
+ name: z.string().min(1).max(256),
415
+ serviceNames: z.array(z.string().min(1).max(256)).max(256),
416
+ limits: GROUP_LIMITS_SCHEMA.optional()
417
+ })).output(z.object({ group: GROUP_SCHEMA })),
418
+ /** 整表替换分组服务引用。 */
419
+ setServices: oc.input(z.strictObject({
420
+ name: z.string().min(1).max(256),
421
+ serviceNames: z.array(z.string().min(1).max(256)).max(256)
422
+ })).output(z.object({ group: GROUP_SCHEMA })),
423
+ /** 更新分组限额(省略 limits = 清除为无限)。 */
424
+ setLimits: oc.input(z.strictObject({
425
+ name: z.string().min(1).max(256),
426
+ limits: GROUP_LIMITS_SCHEMA.optional()
427
+ })).output(z.object({ group: GROUP_SCHEMA })),
428
+ /** 删除分组(仍有未撤销密钥时 CONFLICT——先 revoke)。 */
429
+ remove: oc.input(z.strictObject({ name: z.string().min(1).max(256) })).output(z.object({ removed: z.literal(true) }))
430
+ },
431
+ keys: {
432
+ /** 签发(原文仅本次返回;此后只余哈希)。 */
433
+ issue: oc.input(z.strictObject({ group: z.string().min(1).max(256) })).output(z.object({
434
+ keyId: z.string(),
435
+ key: z.string(),
436
+ createdAt: z.number().int()
437
+ })),
438
+ /** 密钥清单(无哈希、无原文)。 */
439
+ list: oc.input(z.object({})).output(z.object({ keys: z.array(KEY_VIEW_SCHEMA) })),
440
+ /** 撤销(幂等)。 */
441
+ revoke: oc.input(z.strictObject({ keyId: z.string().min(1).max(128) })).output(z.object({ key: KEY_VIEW_SCHEMA }))
442
+ },
443
+ share: {
444
+ /** 组合分享链接(需要 provider daemon 在运行以签发 fabric invite)。 */
445
+ create: oc.input(SHARE_CREATE_INPUT_SCHEMA).output(z.object({
446
+ link: z.string().min(1),
447
+ keyId: z.string(),
448
+ warnings: z.array(z.string())
449
+ })) },
450
+ /** 提供方状态(引擎运行值 + 磁盘摘要)。 */
451
+ status: oc.input(z.object({})).output(PROVIDER_STATUS_SCHEMA),
452
+ daemon: {
453
+ /** 启动提供方 daemon(幂等;按数据目录现状装配)。 */
454
+ start: oc.input(z.object({})).output(z.object({ running: z.literal(true) })),
455
+ /** 停止提供方 daemon(幂等;在途请求按 M1 语义收敛)。 */
456
+ stop: oc.input(z.object({})).output(z.object({ running: z.literal(false) }))
457
+ }
458
+ },
459
+ consumer: {
460
+ import: {
461
+ /** 分享链接离线预览(不发起任何网络请求)。 */
462
+ preview: oc.input(z.strictObject({ link: z.string().min(1) })).output(z.object({
463
+ alias: z.string(),
464
+ endpointId: z.string(),
465
+ group: z.string(),
466
+ keyId: z.string(),
467
+ relayUrls: z.array(z.string()),
468
+ services: z.array(z.object({
469
+ serviceId: z.string(),
470
+ name: z.string(),
471
+ defaultPort: z.number().int(),
472
+ matchCount: z.number().int().min(0)
473
+ }))
474
+ })),
475
+ /** 兑换令牌并入环(老设备复用既有身份,不消耗令牌)。 */
476
+ apply: oc.input(z.strictObject({ link: z.string().min(1) })).output(z.object({
477
+ alias: z.string(),
478
+ endpointId: z.string(),
479
+ redeemed: z.boolean(),
480
+ keyAdded: z.boolean(),
481
+ services: z.array(z.object({
482
+ serviceId: z.string(),
483
+ name: z.string(),
484
+ defaultPort: z.number().int()
485
+ }))
486
+ }))
487
+ },
488
+ /** fabric 邀请令牌入网(裸 join,不带密钥)。 */
489
+ join: oc.input(z.strictObject({ invite: z.string().min(1) })).output(z.object({
490
+ alias: z.string(),
491
+ endpointId: z.string(),
492
+ alreadyJoined: z.boolean()
493
+ })),
494
+ key: {
495
+ /** 裸密钥入环(keyId/group 由下次 AUTH_OK 回填)。 */
496
+ add: oc.input(z.strictObject({
497
+ key: z.string().min(8).max(256),
498
+ providerRef: z.string().min(1).max(256)
499
+ })).output(z.object({
500
+ alias: z.string(),
501
+ added: z.boolean()
502
+ })) },
503
+ services: {
504
+ /**
505
+ * 消费侧连通测试(M3-r8:③ 步 = test——选协议、选端点、单轮输入框发
506
+ * 真实 AI 请求):对本机网关端口按 API 标准发最小请求,走完整 wire 链路;
507
+ * 凭据由提供方 rewrite 注入,本请求不携带 authorization。
508
+ * model 缺省时经 models.dev 缓存按 detail.upstream 选最便宜 chat 模型;
509
+ * content 为单轮提示词(缺省 "ping");localPrefix 显式指定端点路径
510
+ * (缺省取该标准路由规则的本地前缀)。
511
+ */
512
+ test: oc.input(z.strictObject({
513
+ serviceId: z.string().min(1).max(128),
514
+ form: ROUTE_FORM_SCHEMA,
515
+ model: z.string().min(1).max(256).optional(),
516
+ content: z.string().max(8192).optional(),
517
+ localPrefix: z.string().min(1).max(2048).optional()
518
+ })).output(SERVICE_TEST_RESULT_SCHEMA) },
519
+ ports: {
520
+ /** 全部已导入服务的本地端口清单(pinned/default 标注)。 */
521
+ list: oc.input(z.object({})).output(z.object({ providers: z.array(z.object({
522
+ alias: z.string(),
523
+ endpointId: z.string(),
524
+ services: z.array(z.object({
525
+ serviceId: z.string(),
526
+ name: z.string(),
527
+ port: z.number().int(),
528
+ defaultPort: z.number().int(),
529
+ pinned: z.boolean()
530
+ }))
531
+ })) })),
532
+ /** 持久化端口偏好(运行中网关下次启动生效)。 */
533
+ set: oc.input(z.strictObject({
534
+ serviceId: z.string().min(1).max(128),
535
+ port: z.number().int().min(1).max(65535)
536
+ })).output(z.object({
537
+ alias: z.string(),
538
+ serviceId: z.string(),
539
+ port: z.number().int()
540
+ }))
541
+ },
542
+ /** 消费侧状态(网关运行时为实时状态机;停止时为 stopped + 存储投影)。 */
543
+ status: oc.input(z.object({})).output(z.object({
544
+ gatewayRunning: z.boolean(),
545
+ providers: z.array(CONSUMER_PROVIDER_STATUS_SCHEMA)
546
+ })),
547
+ /** 整环删除(钥环 + fabric 身份目录)。 */
548
+ forget: oc.input(z.strictObject({ ref: z.string().min(1).max(256) })).output(z.object({
549
+ alias: z.string(),
550
+ removed: z.literal(true)
551
+ })),
552
+ gateway: {
553
+ /** 启动本地网关(幂等;物化既有目录监听)。 */
554
+ start: oc.input(z.object({})).output(z.object({ running: z.literal(true) })),
555
+ /** 停止本地网关(幂等)。 */
556
+ stop: oc.input(z.object({})).output(z.object({ running: z.literal(false) }))
557
+ }
558
+ },
559
+ presets: {
560
+ /** 预设列表(精选 + 可选 models.dev 长尾合流)。 */
561
+ list: oc.input(z.strictObject({ includeModelsDev: z.boolean().optional() })).output(z.object({
562
+ curated: z.array(PRESET_SCHEMA),
563
+ modelsDev: z.array(PRESET_SCHEMA),
564
+ /** 长尾不可用原因(断网/禁用/无缓存;精选集仍可用)。 */
565
+ modelsDevError: z.string().optional()
566
+ })),
567
+ /** 预设 → 提供方服务(展开 upstream/match/defaultPort/$env 注入;走 services.add 同一校验)。 */
568
+ applyAsService: oc.input(PRESET_APPLY_INPUT_SCHEMA).output(z.object({
569
+ service: SERVICE_SCHEMA,
570
+ /** $env 注入建议(导出提示;值需含完整 header 形态如 "Bearer <key>")。 */
571
+ envHint: z.string().optional()
572
+ })),
573
+ /** 模型清单(models.dev 缓存;按价格升序,chat 优先,未知价尾排)。
574
+ * presetId 二选一:已知预设;或 custom(自定义上游)实时探测 {upstream}/models。 */
575
+ models: oc.input(z.strictObject({
576
+ presetId: z.string().min(1).max(128).optional(),
577
+ upstream: z.string().min(1).max(2048).optional(),
578
+ secretName: SECRET_NAME_SCHEMA.optional()
579
+ }).refine((v) => v.presetId !== void 0 !== (v.upstream !== void 0), { message: "exactly one of presetId or upstream is required" })).output(z.strictObject({
580
+ models: z.array(z.strictObject({
581
+ id: z.string().min(1).max(256),
582
+ name: z.string().max(512).optional(),
583
+ /** input+output 合计 USD/Mtok;未知价省略。 */
584
+ pricePerMTok: z.number().min(0).optional(),
585
+ /** 价格已知(排序依据;未知价条目 false)。 */
586
+ priced: z.boolean(),
587
+ /** false = embed/image/tts 等非对话模型(id 启发式)。 */
588
+ chat: z.boolean()
589
+ })),
590
+ /** 清单不可用原因(无缓存且拉取失败等)。 */
591
+ error: z.string().optional()
592
+ }))
593
+ },
594
+ writers: {
595
+ /** 生成目标 agent 配置的统一 diff 与落盘路径(不写盘)。 */
596
+ preview: oc.input(WRITER_PREVIEW_SCHEMA).output(z.object({
597
+ agent: WRITER_AGENT_SCHEMA,
598
+ path: z.string().min(1),
599
+ /** 目标文件当前是否已存在(新建 vs 更新)。 */
600
+ exists: z.boolean(),
601
+ /** 解析后的本地端点(http://127.0.0.1:<port>)。 */
602
+ baseUrl: z.string(),
603
+ /** 统一 diff 文本(无上下文行数的完整 unified diff)。 */
604
+ diff: z.string(),
605
+ /** apply 必须原样带回(sha256(diff))。 */
606
+ confirmToken: z.string().regex(/^[0-9a-f]{64}$/)
607
+ })),
608
+ /** 确认后原子写(confirmToken 不匹配即拒绝;保留其余配置字段)。 */
609
+ apply: oc.input(WRITER_APPLY_SCHEMA).output(z.object({
610
+ agent: WRITER_AGENT_SCHEMA,
611
+ path: z.string(),
612
+ written: z.literal(true)
613
+ }))
614
+ },
615
+ system: {
616
+ settings: {
617
+ /** 读取应用设置(主题/models.dev 开关/relay)。 */
618
+ get: oc.input(z.object({})).output(SETTINGS_SCHEMA),
619
+ /** 补丁式更新(仅提交的字段变更)。 */
620
+ set: oc.input(z.strictObject({
621
+ theme: z.enum([
622
+ "dark",
623
+ "light",
624
+ "system"
625
+ ]).optional(),
626
+ modelsDevEnabled: z.boolean().optional(),
627
+ relayUrls: z.array(z.string().min(1).max(2048)).max(8).nullable().optional()
628
+ })).output(SETTINGS_SCHEMA)
629
+ },
630
+ /** 通知通道常量(前端 ws 订阅地址;与 web-server 实现保持同源)。 */
631
+ notifyChannels: oc.input(z.object({})).output(z.object({
632
+ rpcPath: z.literal("/ws/rpc"),
633
+ notifyPath: z.literal("/ws/notify")
634
+ }))
635
+ }
636
+ });
637
+ //#endregion
638
+ export { routeLocalPrefix as a, SETTINGS_SCHEMA as i, ROUTE_LOCAL_PREFIX as n, decodeZ32 as o, SECRET_NAME_SCHEMA as r, randomZ32 as s, PRESET_SCHEMA as t };