@snack-kit/porygon 0.1.0 → 0.3.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/dist/index.cjs ADDED
@@ -0,0 +1,2192 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AbstractAgentAdapter: () => AbstractAgentAdapter,
24
+ AdapterIncompatibleError: () => AdapterIncompatibleError,
25
+ AdapterNotAvailableError: () => AdapterNotAvailableError,
26
+ AdapterNotFoundError: () => AdapterNotFoundError,
27
+ AgentExecutionError: () => AgentExecutionError,
28
+ AgentTimeoutError: () => AgentTimeoutError,
29
+ ClaudeAdapter: () => ClaudeAdapter,
30
+ ConfigValidationError: () => ConfigValidationError,
31
+ InteractiveSession: () => InteractiveSession,
32
+ InterceptorRejectedError: () => InterceptorRejectedError,
33
+ OpenCodeAdapter: () => OpenCodeAdapter,
34
+ Porygon: () => Porygon,
35
+ PorygonError: () => PorygonError,
36
+ SessionNotFoundError: () => SessionNotFoundError,
37
+ createInputGuard: () => createInputGuard,
38
+ createOutputGuard: () => createOutputGuard,
39
+ createPorygon: () => createPorygon
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+
43
+ // src/porygon.ts
44
+ var import_node_events2 = require("events");
45
+
46
+ // src/config/defaults.ts
47
+ var DEFAULT_CONFIG = {
48
+ defaultBackend: "claude",
49
+ backends: {},
50
+ defaults: {
51
+ timeoutMs: 3e5,
52
+ // 5 minutes
53
+ maxTurns: 50
54
+ },
55
+ proxy: void 0
56
+ };
57
+
58
+ // src/config/schema.ts
59
+ var import_zod = require("zod");
60
+
61
+ // src/errors/index.ts
62
+ var PorygonError = class extends Error {
63
+ code;
64
+ constructor(code, message, options) {
65
+ super(message, options);
66
+ this.name = "PorygonError";
67
+ this.code = code;
68
+ }
69
+ };
70
+ var AdapterNotFoundError = class extends PorygonError {
71
+ constructor(backend, options) {
72
+ super("ADAPTER_NOT_FOUND", `Adapter not found: ${backend}`, options);
73
+ this.name = "AdapterNotFoundError";
74
+ }
75
+ };
76
+ var AdapterNotAvailableError = class extends PorygonError {
77
+ constructor(backend, options) {
78
+ super("ADAPTER_NOT_AVAILABLE", `Adapter not available: ${backend}`, options);
79
+ this.name = "AdapterNotAvailableError";
80
+ }
81
+ };
82
+ var AdapterIncompatibleError = class extends PorygonError {
83
+ constructor(backend, version, options) {
84
+ super(
85
+ "ADAPTER_INCOMPATIBLE",
86
+ `Adapter incompatible: ${backend} version ${version}`,
87
+ options
88
+ );
89
+ this.name = "AdapterIncompatibleError";
90
+ }
91
+ };
92
+ var SessionNotFoundError = class extends PorygonError {
93
+ constructor(sessionId, options) {
94
+ super("SESSION_NOT_FOUND", `Session not found: ${sessionId}`, options);
95
+ this.name = "SessionNotFoundError";
96
+ }
97
+ };
98
+ var InterceptorRejectedError = class extends PorygonError {
99
+ constructor(reason, options) {
100
+ super("INTERCEPTOR_REJECTED", `Interceptor rejected: ${reason}`, options);
101
+ this.name = "InterceptorRejectedError";
102
+ }
103
+ };
104
+ var AgentExecutionError = class extends PorygonError {
105
+ constructor(message, options) {
106
+ super("AGENT_EXECUTION_ERROR", message, options);
107
+ this.name = "AgentExecutionError";
108
+ }
109
+ };
110
+ var AgentTimeoutError = class extends PorygonError {
111
+ constructor(timeoutMs, options) {
112
+ super("AGENT_TIMEOUT", `Agent timed out after ${timeoutMs}ms`, options);
113
+ this.name = "AgentTimeoutError";
114
+ }
115
+ };
116
+ var ConfigValidationError = class extends PorygonError {
117
+ constructor(message, options) {
118
+ super("CONFIG_VALIDATION_ERROR", message, options);
119
+ this.name = "ConfigValidationError";
120
+ }
121
+ };
122
+
123
+ // src/config/schema.ts
124
+ var ProxyConfigSchema = import_zod.z.object({
125
+ url: import_zod.z.string().url(),
126
+ noProxy: import_zod.z.string().optional()
127
+ });
128
+ var BackendConfigSchema = import_zod.z.object({
129
+ model: import_zod.z.string().optional(),
130
+ appendSystemPrompt: import_zod.z.string().optional(),
131
+ proxy: ProxyConfigSchema.optional(),
132
+ cwd: import_zod.z.string().optional(),
133
+ serverUrl: import_zod.z.string().optional(),
134
+ apiKey: import_zod.z.string().optional(),
135
+ interactive: import_zod.z.boolean().optional(),
136
+ cliPath: import_zod.z.string().optional(),
137
+ options: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
138
+ });
139
+ var PorygonConfigSchema = import_zod.z.object({
140
+ defaultBackend: import_zod.z.string().optional(),
141
+ backends: import_zod.z.record(import_zod.z.string(), BackendConfigSchema).optional(),
142
+ defaults: import_zod.z.object({
143
+ appendSystemPrompt: import_zod.z.string().optional(),
144
+ timeoutMs: import_zod.z.number().positive().optional(),
145
+ maxTurns: import_zod.z.number().int().positive().optional()
146
+ }).optional(),
147
+ proxy: ProxyConfigSchema.optional()
148
+ });
149
+ function validateConfig(config) {
150
+ const result = PorygonConfigSchema.safeParse(config);
151
+ if (!result.success) {
152
+ const issues = result.error.issues.map((issue) => ({
153
+ path: issue.path.join("."),
154
+ message: issue.message
155
+ }));
156
+ throw new ConfigValidationError(
157
+ `\u914D\u7F6E\u6821\u9A8C\u5931\u8D25: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`
158
+ );
159
+ }
160
+ return result.data;
161
+ }
162
+
163
+ // src/config/config-loader.ts
164
+ function isPlainObject(value) {
165
+ return typeof value === "object" && value !== null && !Array.isArray(value);
166
+ }
167
+ function deepMerge(target, source) {
168
+ const result = { ...target };
169
+ for (const key of Object.keys(source)) {
170
+ const sourceVal = source[key];
171
+ const targetVal = result[key];
172
+ if (sourceVal === void 0) {
173
+ continue;
174
+ }
175
+ if (isPlainObject(targetVal) && isPlainObject(sourceVal)) {
176
+ result[key] = deepMerge(
177
+ targetVal,
178
+ sourceVal
179
+ );
180
+ } else {
181
+ result[key] = sourceVal;
182
+ }
183
+ }
184
+ return result;
185
+ }
186
+ function loadEnvOverrides() {
187
+ const config = {};
188
+ const defaultBackend = process.env["PORYGON_DEFAULT_BACKEND"];
189
+ if (defaultBackend) {
190
+ config.defaultBackend = defaultBackend;
191
+ }
192
+ const proxyUrl = process.env["PORYGON_PROXY_URL"];
193
+ if (proxyUrl) {
194
+ config.proxy = {
195
+ url: proxyUrl,
196
+ noProxy: process.env["PORYGON_PROXY_NO_PROXY"]
197
+ };
198
+ }
199
+ return config;
200
+ }
201
+ var ConfigLoader = class {
202
+ /**
203
+ * 加载并合并配置
204
+ * @param userConfig - 用户传入的配置(可选)
205
+ * @returns 校验通过的最终配置
206
+ * @throws ConfigValidationError 校验失败时抛出
207
+ */
208
+ static load(userConfig) {
209
+ let merged = { ...DEFAULT_CONFIG };
210
+ const envOverrides = loadEnvOverrides();
211
+ merged = deepMerge(merged, envOverrides);
212
+ if (userConfig) {
213
+ merged = deepMerge(merged, userConfig);
214
+ }
215
+ return validateConfig(merged);
216
+ }
217
+ };
218
+
219
+ // src/interceptor/interceptor.ts
220
+ var InterceptorManager = class {
221
+ inputInterceptors = [];
222
+ outputInterceptors = [];
223
+ /**
224
+ * 注册拦截器
225
+ * @param direction - 拦截方向
226
+ * @param fn - 拦截器函数
227
+ * @returns 取消注册的函数
228
+ */
229
+ use(direction, fn) {
230
+ const list = direction === "input" ? this.inputInterceptors : this.outputInterceptors;
231
+ list.push(fn);
232
+ return () => {
233
+ const idx = list.indexOf(fn);
234
+ if (idx !== -1) {
235
+ list.splice(idx, 1);
236
+ }
237
+ };
238
+ }
239
+ /**
240
+ * 执行输入拦截器流水线
241
+ * @param text - 原始输入文本
242
+ * @param context - 拦截器上下文(不含 direction)
243
+ * @returns 处理后的文本
244
+ * @throws InterceptorRejectedError 拦截器返回 false 时抛出
245
+ */
246
+ async processInput(text, context) {
247
+ return this.runPipeline(text, "input", context, this.inputInterceptors);
248
+ }
249
+ /**
250
+ * 执行输出拦截器流水线
251
+ * @param text - 原始输出文本
252
+ * @param context - 拦截器上下文(不含 direction)
253
+ * @returns 处理后的文本
254
+ * @throws InterceptorRejectedError 拦截器返回 false 时抛出
255
+ */
256
+ async processOutput(text, context) {
257
+ return this.runPipeline(text, "output", context, this.outputInterceptors);
258
+ }
259
+ /**
260
+ * 执行拦截器流水线
261
+ */
262
+ async runPipeline(text, direction, context, interceptors) {
263
+ const fullContext = { ...context, direction };
264
+ let current = text;
265
+ for (const fn of interceptors) {
266
+ const result = await fn(current, fullContext);
267
+ if (result === false) {
268
+ throw new InterceptorRejectedError(direction);
269
+ }
270
+ if (typeof result === "string") {
271
+ current = result;
272
+ }
273
+ }
274
+ return current;
275
+ }
276
+ };
277
+
278
+ // src/process/process-handle.ts
279
+ var import_node_child_process = require("child_process");
280
+ var import_node_events = require("events");
281
+ var import_node_readline = require("readline");
282
+ var GRACE_PERIOD_MS = 5e3;
283
+ var EphemeralProcess = class {
284
+ childProcess = null;
285
+ aborted = false;
286
+ /**
287
+ * 执行命令并收集输出。支持 AbortController 进行取消操作。
288
+ * @param options 进程启动选项
289
+ * @param abortSignal 可选的中止信号
290
+ * @returns 进程执行结果
291
+ */
292
+ async execute(options, abortSignal) {
293
+ return new Promise((resolve, reject) => {
294
+ if (abortSignal?.aborted) {
295
+ reject(new Error("Process aborted before start"));
296
+ return;
297
+ }
298
+ this.aborted = false;
299
+ const child = (0, import_node_child_process.spawn)(options.command, options.args, {
300
+ cwd: options.cwd,
301
+ env: options.env ? { ...process.env, ...options.env } : void 0,
302
+ stdio: ["pipe", "pipe", "pipe"]
303
+ });
304
+ this.childProcess = child;
305
+ const stdoutChunks = [];
306
+ const stderrChunks = [];
307
+ child.stdout?.on("data", (chunk) => {
308
+ stdoutChunks.push(chunk);
309
+ });
310
+ child.stderr?.on("data", (chunk) => {
311
+ stderrChunks.push(chunk);
312
+ });
313
+ let timeoutTimer;
314
+ if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
315
+ timeoutTimer = setTimeout(() => {
316
+ this.terminate();
317
+ reject(new Error(`Process timed out after ${options.timeoutMs}ms`));
318
+ }, options.timeoutMs);
319
+ }
320
+ const onAbort = () => {
321
+ this.terminate();
322
+ reject(new Error("Process aborted"));
323
+ };
324
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
325
+ child.on("error", (err) => {
326
+ if (timeoutTimer) clearTimeout(timeoutTimer);
327
+ abortSignal?.removeEventListener("abort", onAbort);
328
+ this.childProcess = null;
329
+ reject(err);
330
+ });
331
+ child.on("close", (code, signal) => {
332
+ if (timeoutTimer) clearTimeout(timeoutTimer);
333
+ abortSignal?.removeEventListener("abort", onAbort);
334
+ this.childProcess = null;
335
+ resolve({
336
+ exitCode: code,
337
+ signal,
338
+ stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
339
+ stderr: Buffer.concat(stderrChunks).toString("utf-8")
340
+ });
341
+ });
342
+ });
343
+ }
344
+ /**
345
+ * 以流式方式执行命令,逐行输出 stdout 内容。
346
+ * @param options 进程启动选项
347
+ * @param abortSignal 可选的中止信号
348
+ */
349
+ async *executeStreaming(options, abortSignal) {
350
+ if (abortSignal?.aborted) {
351
+ throw new Error("Process aborted before start");
352
+ }
353
+ this.aborted = false;
354
+ const useStdin = options.stdinData !== void 0;
355
+ const child = (0, import_node_child_process.spawn)(options.command, options.args, {
356
+ cwd: options.cwd,
357
+ env: options.env ?? void 0,
358
+ stdio: [useStdin ? "pipe" : "ignore", "pipe", "pipe"]
359
+ });
360
+ this.childProcess = child;
361
+ if (useStdin && child.stdin) {
362
+ child.stdin.write(options.stdinData, () => {
363
+ child.stdin.end();
364
+ });
365
+ }
366
+ let timeoutTimer;
367
+ if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
368
+ timeoutTimer = setTimeout(() => {
369
+ this.terminate();
370
+ }, options.timeoutMs);
371
+ }
372
+ const onAbort = () => {
373
+ this.terminate();
374
+ };
375
+ abortSignal?.addEventListener("abort", onAbort, { once: true });
376
+ const stderrChunks = [];
377
+ child.stderr?.on("data", (chunk) => {
378
+ stderrChunks.push(chunk);
379
+ });
380
+ let exitCode = null;
381
+ const exitPromise = new Promise((resolve) => {
382
+ child.on("close", (code) => {
383
+ exitCode = code;
384
+ resolve();
385
+ });
386
+ });
387
+ const rl = (0, import_node_readline.createInterface)({ input: child.stdout });
388
+ let yieldedLines = 0;
389
+ try {
390
+ for await (const line of rl) {
391
+ yieldedLines++;
392
+ yield line;
393
+ }
394
+ await exitPromise;
395
+ if (exitCode !== 0 && yieldedLines === 0 && !this.aborted) {
396
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
397
+ throw new Error(
398
+ stderr || `Process exited with code ${exitCode}`
399
+ );
400
+ }
401
+ } finally {
402
+ if (timeoutTimer) clearTimeout(timeoutTimer);
403
+ abortSignal?.removeEventListener("abort", onAbort);
404
+ rl.close();
405
+ this.childProcess = null;
406
+ }
407
+ }
408
+ /** 获取底层子进程的 PID */
409
+ get pid() {
410
+ return this.childProcess?.pid;
411
+ }
412
+ /** 终止进程:先发送 SIGTERM,超时后发送 SIGKILL */
413
+ terminate() {
414
+ if (!this.childProcess || this.aborted) return;
415
+ this.aborted = true;
416
+ this.childProcess.kill("SIGTERM");
417
+ const ref = this.childProcess;
418
+ setTimeout(() => {
419
+ if (!ref.killed) {
420
+ ref.kill("SIGKILL");
421
+ }
422
+ }, GRACE_PERIOD_MS);
423
+ }
424
+ };
425
+ var PersistentProcess = class extends import_node_events.EventEmitter {
426
+ process = null;
427
+ restartCount = 0;
428
+ stopped = false;
429
+ healthCheckTimer = null;
430
+ options;
431
+ constructor(options) {
432
+ super();
433
+ this.options = options;
434
+ }
435
+ /** 启动持久进程 */
436
+ async start() {
437
+ this.stopped = false;
438
+ const child = (0, import_node_child_process.spawn)(this.options.command, this.options.args, {
439
+ cwd: this.options.cwd,
440
+ env: this.options.env ? { ...process.env, ...this.options.env } : void 0,
441
+ stdio: ["pipe", "pipe", "pipe"]
442
+ });
443
+ this.process = child;
444
+ child.on("error", (err) => {
445
+ this.emit("error", err);
446
+ });
447
+ child.on("exit", (code) => {
448
+ this.process = null;
449
+ this.stopHealthCheck();
450
+ this.handleCrash(code);
451
+ });
452
+ if (this.options.healthCheckFn && this.options.healthCheckIntervalMs > 0) {
453
+ this.healthCheckTimer = setInterval(() => {
454
+ this.options.healthCheckFn().catch((err) => {
455
+ this.emit("healthCheckFailed", err);
456
+ });
457
+ }, this.options.healthCheckIntervalMs);
458
+ }
459
+ this.emit("started", child.pid);
460
+ }
461
+ /** 处理进程崩溃,按指数退避策略重启 */
462
+ handleCrash(code) {
463
+ if (this.stopped) return;
464
+ this.emit("exit", code);
465
+ if (this.restartCount < this.options.maxRestarts) {
466
+ const delay = this.options.restartIntervalMs * Math.pow(2, this.restartCount);
467
+ this.restartCount++;
468
+ this.emit("restart", this.restartCount);
469
+ setTimeout(() => {
470
+ if (!this.stopped) {
471
+ this.start().catch((err) => {
472
+ this.emit("fatal", err);
473
+ });
474
+ }
475
+ }, delay);
476
+ } else {
477
+ this.emit(
478
+ "fatal",
479
+ new Error(
480
+ `Max restarts (${this.options.maxRestarts}) exceeded, last exit code: ${code}`
481
+ )
482
+ );
483
+ }
484
+ }
485
+ /** 停止健康检查定时器 */
486
+ stopHealthCheck() {
487
+ if (this.healthCheckTimer) {
488
+ clearInterval(this.healthCheckTimer);
489
+ this.healthCheckTimer = null;
490
+ }
491
+ }
492
+ /** 停止持久进程 */
493
+ async stop() {
494
+ this.stopped = true;
495
+ this.stopHealthCheck();
496
+ if (this.process) {
497
+ const child = this.process;
498
+ child.kill("SIGTERM");
499
+ await new Promise((resolve) => {
500
+ const timer = setTimeout(() => {
501
+ if (!child.killed) {
502
+ child.kill("SIGKILL");
503
+ }
504
+ resolve();
505
+ }, GRACE_PERIOD_MS);
506
+ child.on("exit", () => {
507
+ clearTimeout(timer);
508
+ resolve();
509
+ });
510
+ });
511
+ this.process = null;
512
+ }
513
+ }
514
+ /** 获取底层子进程的 PID */
515
+ get pid() {
516
+ return this.process?.pid;
517
+ }
518
+ /** 当前进程是否正在运行 */
519
+ get isRunning() {
520
+ return this.process !== null && !this.process.killed;
521
+ }
522
+ };
523
+
524
+ // src/process/process-manager.ts
525
+ var ProcessManager = class _ProcessManager {
526
+ ephemeral = /* @__PURE__ */ new Map();
527
+ persistent = /* @__PURE__ */ new Map();
528
+ /** 全局静态标记,确保信号处理器只注册一次 */
529
+ static cleanupRegistered = false;
530
+ /** 所有活跃的 ProcessManager 实例(弱引用避免阻止 GC) */
531
+ static instances = /* @__PURE__ */ new Set();
532
+ constructor() {
533
+ _ProcessManager.instances.add(this);
534
+ _ProcessManager.registerCleanup();
535
+ }
536
+ /** 注册进程退出清理钩子(仅清理资源,不劫持宿主退出行为) */
537
+ static registerCleanup() {
538
+ if (_ProcessManager.cleanupRegistered) return;
539
+ _ProcessManager.cleanupRegistered = true;
540
+ const cleanup = () => {
541
+ for (const instance of _ProcessManager.instances) {
542
+ instance.terminateAllSync();
543
+ }
544
+ };
545
+ process.on("exit", cleanup);
546
+ process.on("SIGTERM", cleanup);
547
+ process.on("SIGINT", cleanup);
548
+ }
549
+ /**
550
+ * 创建一次性进程实例并注册到管理器
551
+ * @param sessionId 会话标识
552
+ * @returns 一次性进程实例
553
+ */
554
+ createEphemeral(sessionId) {
555
+ const proc = new EphemeralProcess();
556
+ this.ephemeral.set(sessionId, proc);
557
+ return proc;
558
+ }
559
+ /**
560
+ * 从管理器中移除一次性进程
561
+ * @param sessionId 会话标识
562
+ */
563
+ removeEphemeral(sessionId) {
564
+ this.ephemeral.delete(sessionId);
565
+ }
566
+ /**
567
+ * 启动或获取持久进程
568
+ * @param backend 后端标识
569
+ * @param options 持久进程选项
570
+ * @returns 持久进程实例
571
+ */
572
+ async startPersistent(backend, options) {
573
+ const existing = this.persistent.get(backend);
574
+ if (existing?.isRunning) return existing;
575
+ const proc = new PersistentProcess(options);
576
+ this.persistent.set(backend, proc);
577
+ await proc.start();
578
+ return proc;
579
+ }
580
+ /**
581
+ * 获取指定后端的持久进程
582
+ * @param backend 后端标识
583
+ */
584
+ getPersistent(backend) {
585
+ return this.persistent.get(backend);
586
+ }
587
+ /**
588
+ * 获取指定会话的一次性进程
589
+ * @param sessionId 会话标识
590
+ */
591
+ getEphemeral(sessionId) {
592
+ return this.ephemeral.get(sessionId);
593
+ }
594
+ /** 异步终止所有托管进程 */
595
+ async terminateAll() {
596
+ const promises = [];
597
+ for (const [, proc] of this.ephemeral) {
598
+ proc.terminate();
599
+ }
600
+ for (const [, proc] of this.persistent) {
601
+ promises.push(proc.stop());
602
+ }
603
+ this.ephemeral.clear();
604
+ await Promise.allSettled(promises);
605
+ this.persistent.clear();
606
+ }
607
+ /** 同步终止所有托管进程(用于 exit 钩子) */
608
+ terminateAllSync() {
609
+ for (const [, proc] of this.ephemeral) {
610
+ proc.terminate();
611
+ }
612
+ for (const [, proc] of this.persistent) {
613
+ const pid = proc.pid;
614
+ if (pid) {
615
+ try {
616
+ process.kill(pid, "SIGTERM");
617
+ } catch {
618
+ }
619
+ }
620
+ }
621
+ }
622
+ /**
623
+ * 销毁实例,从全局实例列表中移除
624
+ */
625
+ destroy() {
626
+ _ProcessManager.instances.delete(this);
627
+ }
628
+ };
629
+
630
+ // src/session/session-manager.ts
631
+ var SessionManager = class {
632
+ cache = /* @__PURE__ */ new Map();
633
+ adapters = /* @__PURE__ */ new Map();
634
+ /**
635
+ * 注册适配器用于会话管理
636
+ * @param backend 后端标识
637
+ * @param adapter 适配器实例
638
+ */
639
+ registerAdapter(backend, adapter) {
640
+ this.adapters.set(backend, adapter);
641
+ }
642
+ /**
643
+ * 注销适配器
644
+ * @param backend 后端标识
645
+ */
646
+ unregisterAdapter(backend) {
647
+ this.adapters.delete(backend);
648
+ }
649
+ /**
650
+ * 获取指定后端的适配器,不存在时抛出错误
651
+ * @param backend 后端标识
652
+ */
653
+ getAdapter(backend) {
654
+ const adapter = this.adapters.get(backend);
655
+ if (!adapter) {
656
+ throw new AdapterNotFoundError(backend);
657
+ }
658
+ return adapter;
659
+ }
660
+ /**
661
+ * 列出指定后端的会话,结果会写入缓存
662
+ * @param backend 后端标识
663
+ * @param options 查询选项
664
+ */
665
+ async list(backend, options) {
666
+ const adapter = this.getAdapter(backend);
667
+ const sessions = await adapter.listSessions(options);
668
+ for (const session of sessions) {
669
+ this.cache.set(session.sessionId, session);
670
+ }
671
+ return sessions;
672
+ }
673
+ /**
674
+ * 从缓存中获取会话信息
675
+ * @param sessionId 会话 ID
676
+ */
677
+ get(sessionId) {
678
+ return this.cache.get(sessionId);
679
+ }
680
+ /**
681
+ * 恢复会话,委托适配器以 resume 标志执行查询
682
+ * @param backend 后端标识
683
+ * @param sessionId 要恢复的会话 ID
684
+ * @param prompt 提示词
685
+ */
686
+ async *resume(backend, sessionId, prompt) {
687
+ const adapter = this.getAdapter(backend);
688
+ yield* adapter.query({ prompt, resume: sessionId });
689
+ }
690
+ /**
691
+ * 从缓存中移除指定会话
692
+ * @param sessionId 会话 ID
693
+ */
694
+ evict(sessionId) {
695
+ this.cache.delete(sessionId);
696
+ }
697
+ /**
698
+ * 清空全部会话缓存
699
+ */
700
+ clearCache() {
701
+ this.cache.clear();
702
+ }
703
+ };
704
+
705
+ // src/adapters/claude/index.ts
706
+ var import_promises = require("fs/promises");
707
+ var import_node_os = require("os");
708
+ var import_node_path = require("path");
709
+ var import_node_crypto = require("crypto");
710
+
711
+ // src/adapters/base.ts
712
+ function satisfiesRange(version, range) {
713
+ const parseVersion = (v) => {
714
+ const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
715
+ if (!match) return null;
716
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
717
+ };
718
+ const trimmed = range.trim();
719
+ if (trimmed.startsWith(">=")) {
720
+ const rangeVersion2 = parseVersion(trimmed.slice(2));
721
+ const currentVersion2 = parseVersion(version);
722
+ if (!rangeVersion2 || !currentVersion2) return false;
723
+ for (let i = 0; i < 3; i++) {
724
+ if (currentVersion2[i] > rangeVersion2[i]) return true;
725
+ if (currentVersion2[i] < rangeVersion2[i]) return false;
726
+ }
727
+ return true;
728
+ }
729
+ if (trimmed.startsWith("<=")) {
730
+ const rangeVersion2 = parseVersion(trimmed.slice(2));
731
+ const currentVersion2 = parseVersion(version);
732
+ if (!rangeVersion2 || !currentVersion2) return false;
733
+ for (let i = 0; i < 3; i++) {
734
+ if (currentVersion2[i] < rangeVersion2[i]) return true;
735
+ if (currentVersion2[i] > rangeVersion2[i]) return false;
736
+ }
737
+ return true;
738
+ }
739
+ const rangeVersion = parseVersion(trimmed);
740
+ const currentVersion = parseVersion(version);
741
+ if (!rangeVersion || !currentVersion) return false;
742
+ return currentVersion[0] === rangeVersion[0] && currentVersion[1] === rangeVersion[1] && currentVersion[2] === rangeVersion[2];
743
+ }
744
+ var AbstractAgentAdapter = class {
745
+ /** 活跃的 AbortController 映射,按 sessionId 索引 */
746
+ abortControllers = /* @__PURE__ */ new Map();
747
+ processManager;
748
+ config;
749
+ constructor(processManager, config) {
750
+ this.processManager = processManager;
751
+ this.config = config;
752
+ }
753
+ /**
754
+ * 默认兼容性检查:获取当前版本并与 testedVersionRange 进行比较
755
+ */
756
+ async checkCompatibility() {
757
+ const warnings = [];
758
+ let version;
759
+ try {
760
+ version = await this.getVersion();
761
+ } catch {
762
+ return {
763
+ version: "unknown",
764
+ supported: false,
765
+ warnings: [`\u65E0\u6CD5\u83B7\u53D6 ${this.backend} \u7248\u672C\u4FE1\u606F`]
766
+ };
767
+ }
768
+ const capabilities = this.getCapabilities();
769
+ const supported = satisfiesRange(version, capabilities.testedVersionRange);
770
+ if (!supported) {
771
+ warnings.push(
772
+ `${this.backend} \u7248\u672C ${version} \u4E0D\u5728\u6D4B\u8BD5\u8303\u56F4 ${capabilities.testedVersionRange} \u5185\uFF0C\u53EF\u80FD\u5B58\u5728\u517C\u5BB9\u6027\u95EE\u9898`
773
+ );
774
+ }
775
+ return { version, supported, warnings };
776
+ }
777
+ /**
778
+ * 中止指定会话的执行
779
+ * @param sessionId 会话标识
780
+ */
781
+ abort(sessionId) {
782
+ const controller = this.abortControllers.get(sessionId);
783
+ if (controller) {
784
+ controller.abort();
785
+ this.abortControllers.delete(sessionId);
786
+ }
787
+ }
788
+ /**
789
+ * 创建并注册 AbortController
790
+ * @param sessionId 会话标识
791
+ * @returns AbortController 实例
792
+ */
793
+ createAbortController(sessionId) {
794
+ const existing = this.abortControllers.get(sessionId);
795
+ if (existing) {
796
+ existing.abort();
797
+ }
798
+ const controller = new AbortController();
799
+ this.abortControllers.set(sessionId, controller);
800
+ return controller;
801
+ }
802
+ /**
803
+ * 清理指定会话的 AbortController
804
+ * @param sessionId 会话标识
805
+ */
806
+ removeAbortController(sessionId) {
807
+ this.abortControllers.delete(sessionId);
808
+ }
809
+ };
810
+
811
+ // src/adapters/claude/types.ts
812
+ function isSystemEvent(event) {
813
+ if (typeof event !== "object" || event === null) return false;
814
+ return event["type"] === "system";
815
+ }
816
+ function isAssistantEvent(event) {
817
+ if (typeof event !== "object" || event === null) return false;
818
+ const obj = event;
819
+ return obj["type"] === "assistant" && typeof obj["message"] === "object" && obj["message"] !== null;
820
+ }
821
+ function isStreamEvent(event) {
822
+ if (typeof event !== "object" || event === null) return false;
823
+ const obj = event;
824
+ return obj["type"] === "stream_event" && typeof obj["event"] === "object" && obj["event"] !== null;
825
+ }
826
+ function isResultEvent(event) {
827
+ if (typeof event !== "object" || event === null) return false;
828
+ const obj = event;
829
+ return obj["type"] === "result" && "result" in obj;
830
+ }
831
+
832
+ // src/adapters/claude/message-mapper.ts
833
+ function mapClaudeEvent(event, sessionId) {
834
+ const timestamp = Date.now();
835
+ const resolvedSessionId = event.session_id ?? sessionId;
836
+ const baseFields = { timestamp, sessionId: resolvedSessionId, raw: event };
837
+ if (isSystemEvent(event)) {
838
+ return [{
839
+ ...baseFields,
840
+ type: "system",
841
+ model: event.model,
842
+ tools: event.tools,
843
+ cwd: event.cwd
844
+ }];
845
+ }
846
+ if (isAssistantEvent(event)) {
847
+ const content = event.message.content;
848
+ return mapAssistantContent(content, baseFields);
849
+ }
850
+ if (isStreamEvent(event)) {
851
+ const delta = event.event.delta;
852
+ if (delta?.type === "text_delta" && delta.text) {
853
+ return [{
854
+ ...baseFields,
855
+ type: "stream_chunk",
856
+ text: delta.text
857
+ }];
858
+ }
859
+ return [];
860
+ }
861
+ if (isResultEvent(event)) {
862
+ return [{
863
+ ...baseFields,
864
+ type: "result",
865
+ text: event.result,
866
+ costUsd: event.cost_usd,
867
+ durationMs: event.duration_ms,
868
+ inputTokens: event.input_tokens,
869
+ outputTokens: event.output_tokens
870
+ }];
871
+ }
872
+ return [];
873
+ }
874
+ function mapAssistantContent(blocks, baseFields) {
875
+ if (!blocks || blocks.length === 0) return [];
876
+ const messages = [];
877
+ const textParts = [];
878
+ for (const block of blocks) {
879
+ if (block.type === "text" && block.text) {
880
+ textParts.push(block.text);
881
+ messages.push({
882
+ ...baseFields,
883
+ type: "stream_chunk",
884
+ text: block.text
885
+ });
886
+ }
887
+ }
888
+ if (textParts.length > 0) {
889
+ messages.push({
890
+ ...baseFields,
891
+ type: "assistant",
892
+ text: textParts.join("\n"),
893
+ turnComplete: true
894
+ });
895
+ }
896
+ for (const block of blocks) {
897
+ if (block.type === "tool_use") {
898
+ messages.push({
899
+ ...baseFields,
900
+ type: "tool_use",
901
+ toolName: block.name,
902
+ input: block.input
903
+ });
904
+ }
905
+ if (block.type === "tool_result") {
906
+ messages.push({
907
+ ...baseFields,
908
+ type: "tool_use",
909
+ toolName: block.tool_use_id,
910
+ input: {},
911
+ output: block.content
912
+ });
913
+ }
914
+ }
915
+ return messages;
916
+ }
917
+
918
+ // src/adapters/claude/index.ts
919
+ var CLAUDE_SETTINGS_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "settings.json");
920
+ var CLAUDE_MODELS = [
921
+ { id: "sonnet", name: "Sonnet", provider: "anthropic" },
922
+ { id: "opus", name: "Opus", provider: "anthropic" },
923
+ { id: "haiku", name: "Haiku", provider: "anthropic" }
924
+ ];
925
+ var ClaudeAdapter = class extends AbstractAgentAdapter {
926
+ backend = "claude";
927
+ /** CLI 命令名或路径 */
928
+ get cliCommand() {
929
+ return this.config?.cliPath ?? "claude";
930
+ }
931
+ /**
932
+ * 检查 Claude CLI 是否可用
933
+ */
934
+ async isAvailable() {
935
+ try {
936
+ const proc = new EphemeralProcess();
937
+ const result = await proc.execute({
938
+ command: "which",
939
+ args: [this.cliCommand]
940
+ });
941
+ return result.exitCode === 0;
942
+ } catch {
943
+ return false;
944
+ }
945
+ }
946
+ /**
947
+ * 获取 Claude CLI 版本号
948
+ */
949
+ async getVersion() {
950
+ const proc = new EphemeralProcess();
951
+ const result = await proc.execute({
952
+ command: this.cliCommand,
953
+ args: ["--version"]
954
+ });
955
+ if (result.exitCode !== 0) {
956
+ throw new Error(`\u83B7\u53D6 Claude \u7248\u672C\u5931\u8D25: ${result.stderr}`);
957
+ }
958
+ const output = result.stdout.trim();
959
+ const match = /(\d+\.\d+\.\d+)/.exec(output);
960
+ if (!match) {
961
+ throw new Error(`\u65E0\u6CD5\u89E3\u6790 Claude \u7248\u672C\u53F7: ${output}`);
962
+ }
963
+ return match[1];
964
+ }
965
+ /**
966
+ * 获取 Claude 适配器能力声明
967
+ */
968
+ getCapabilities() {
969
+ return {
970
+ features: /* @__PURE__ */ new Set([
971
+ "streaming",
972
+ "session-resume",
973
+ "system-prompt",
974
+ "tool-restriction",
975
+ "mcp",
976
+ "subagents",
977
+ "worktree",
978
+ "interactive-session"
979
+ ]),
980
+ streamingMode: "chunked",
981
+ outputFormats: ["text", "json", "stream-json"],
982
+ testedVersionRange: ">=1.0.0"
983
+ };
984
+ }
985
+ /**
986
+ * 向 Claude 发送提示并以流式方式接收响应
987
+ * @param request 提示请求参数
988
+ */
989
+ async *query(request) {
990
+ const sessionId = request.resume ?? (0, import_node_crypto.randomUUID)();
991
+ const controller = this.createAbortController(sessionId);
992
+ const proc = this.processManager.createEphemeral(sessionId);
993
+ try {
994
+ const args = this.buildArgs(request);
995
+ const cwd = request.cwd ?? this.config?.cwd;
996
+ const cleanEnv = {};
997
+ for (const [k, v] of Object.entries(process.env)) {
998
+ if (k !== "CLAUDECODE" && v !== void 0) {
999
+ cleanEnv[k] = v;
1000
+ }
1001
+ }
1002
+ const proxyUrl = this.config?.proxy?.url;
1003
+ if (proxyUrl) {
1004
+ this.applyProxyEnv(cleanEnv, proxyUrl, this.config?.proxy?.noProxy);
1005
+ }
1006
+ if (request.envVars) {
1007
+ for (const [k, v] of Object.entries(request.envVars)) {
1008
+ if (v) cleanEnv[k] = v;
1009
+ }
1010
+ }
1011
+ const stdinData = this.buildStdinData(request);
1012
+ const streamOptions = {
1013
+ command: this.cliCommand,
1014
+ args,
1015
+ ...cwd ? { cwd } : {},
1016
+ env: cleanEnv,
1017
+ timeoutMs: request.timeoutMs,
1018
+ stdinData
1019
+ };
1020
+ const cmdStr = [this.cliCommand, ...args.map((a) => /[\s"']/.test(a) ? JSON.stringify(a) : a)].join(" ");
1021
+ const debugCmd = cwd ? `cd ${JSON.stringify(cwd)} && ${cmdStr}` : cmdStr;
1022
+ yield {
1023
+ type: "system",
1024
+ timestamp: Date.now(),
1025
+ sessionId,
1026
+ cwd,
1027
+ raw: { debug_command: debugCmd }
1028
+ };
1029
+ for await (const line of proc.executeStreaming(
1030
+ streamOptions,
1031
+ controller.signal
1032
+ )) {
1033
+ const trimmed = line.trim();
1034
+ if (!trimmed) continue;
1035
+ let parsed;
1036
+ try {
1037
+ parsed = JSON.parse(trimmed);
1038
+ } catch {
1039
+ continue;
1040
+ }
1041
+ const messages = mapClaudeEvent(parsed, sessionId);
1042
+ for (const message of messages) {
1043
+ yield message;
1044
+ }
1045
+ }
1046
+ } finally {
1047
+ this.removeAbortController(sessionId);
1048
+ this.processManager.removeEphemeral(sessionId);
1049
+ }
1050
+ }
1051
+ /**
1052
+ * 列出 Claude 会话
1053
+ * @param options 查询选项
1054
+ */
1055
+ async listSessions(options) {
1056
+ const proc = new EphemeralProcess();
1057
+ const args = ["sessions", "list", "--output", "json"];
1058
+ if (options?.limit) {
1059
+ args.push("--limit", String(options.limit));
1060
+ }
1061
+ const result = await proc.execute({
1062
+ command: this.cliCommand,
1063
+ args,
1064
+ cwd: options?.cwd
1065
+ });
1066
+ if (result.exitCode !== 0) {
1067
+ throw new Error(`\u83B7\u53D6 Claude \u4F1A\u8BDD\u5217\u8868\u5931\u8D25: ${result.stderr}`);
1068
+ }
1069
+ const output = result.stdout.trim();
1070
+ if (!output) return [];
1071
+ let rawSessions;
1072
+ try {
1073
+ rawSessions = JSON.parse(output);
1074
+ } catch {
1075
+ throw new Error(`\u89E3\u6790 Claude \u4F1A\u8BDD\u5217\u8868\u5931\u8D25: ${output.slice(0, 200)}`);
1076
+ }
1077
+ if (!Array.isArray(rawSessions)) return [];
1078
+ return rawSessions.map((session) => {
1079
+ const s = session;
1080
+ return {
1081
+ sessionId: String(s["id"] ?? s["session_id"] ?? ""),
1082
+ backend: this.backend,
1083
+ summary: typeof s["summary"] === "string" ? s["summary"] : void 0,
1084
+ lastModified: typeof s["last_modified"] === "number" ? s["last_modified"] : typeof s["last_modified"] === "string" ? new Date(s["last_modified"]).getTime() : Date.now(),
1085
+ cwd: typeof s["cwd"] === "string" ? s["cwd"] : void 0,
1086
+ metadata: s
1087
+ };
1088
+ });
1089
+ }
1090
+ /**
1091
+ * 获取 Claude 可用模型列表。
1092
+ * Claude CLI 支持使用简短别名(sonnet/opus/haiku)自动选择当前最新版本。
1093
+ */
1094
+ async listModels() {
1095
+ return CLAUDE_MODELS;
1096
+ }
1097
+ /**
1098
+ * 读取 Claude 设置
1099
+ */
1100
+ async getSettings() {
1101
+ try {
1102
+ const content = await (0, import_promises.readFile)(CLAUDE_SETTINGS_PATH, "utf-8");
1103
+ const parsed = JSON.parse(content);
1104
+ if (typeof parsed === "object" && parsed !== null) {
1105
+ return parsed;
1106
+ }
1107
+ return {};
1108
+ } catch {
1109
+ return {};
1110
+ }
1111
+ }
1112
+ /**
1113
+ * 更新 Claude 设置(读取-合并-写入)
1114
+ * @param settings 要合并的设置项
1115
+ */
1116
+ async updateSettings(settings) {
1117
+ const current = await this.getSettings();
1118
+ const merged = { ...current, ...settings };
1119
+ await (0, import_promises.writeFile)(
1120
+ CLAUDE_SETTINGS_PATH,
1121
+ JSON.stringify(merged, null, 2),
1122
+ "utf-8"
1123
+ );
1124
+ }
1125
+ /**
1126
+ * 释放适配器资源
1127
+ */
1128
+ async dispose() {
1129
+ for (const [sessionId] of this.abortControllers) {
1130
+ this.abort(sessionId);
1131
+ }
1132
+ }
1133
+ /**
1134
+ * 将代理配置注入到环境变量中
1135
+ * @param env 环境变量对象
1136
+ * @param proxyUrl 代理地址
1137
+ * @param noProxy 不走代理的地址列表
1138
+ */
1139
+ applyProxyEnv(env, proxyUrl, noProxy) {
1140
+ env["http_proxy"] = proxyUrl;
1141
+ env["https_proxy"] = proxyUrl;
1142
+ env["HTTP_PROXY"] = proxyUrl;
1143
+ env["HTTPS_PROXY"] = proxyUrl;
1144
+ env["all_proxy"] = proxyUrl;
1145
+ env["ALL_PROXY"] = proxyUrl;
1146
+ if (noProxy) {
1147
+ env["no_proxy"] = noProxy;
1148
+ env["NO_PROXY"] = noProxy;
1149
+ }
1150
+ }
1151
+ /**
1152
+ * 构建通过 stdin 传递给 Claude CLI 的数据。
1153
+ * 使用 stdin 而非 CLI 参数传递 prompt,避免超长参数导致的 403 错误。
1154
+ */
1155
+ buildStdinData(request) {
1156
+ return request.prompt;
1157
+ }
1158
+ buildArgs(request) {
1159
+ const args = [
1160
+ "--print",
1161
+ "--output-format",
1162
+ "stream-json",
1163
+ "--verbose"
1164
+ ];
1165
+ if (request.resume) {
1166
+ args.push("--resume", request.resume);
1167
+ }
1168
+ const model = request.model ?? this.config?.model;
1169
+ if (model) {
1170
+ args.push("--model", model);
1171
+ }
1172
+ if (request.systemPrompt) {
1173
+ args.push("--system-prompt", request.systemPrompt);
1174
+ }
1175
+ const appendPrompt = request.appendSystemPrompt ?? this.config?.appendSystemPrompt;
1176
+ if (appendPrompt) {
1177
+ args.push("--append-system-prompt", appendPrompt);
1178
+ }
1179
+ if (request.allowedTools && request.allowedTools.length > 0) {
1180
+ for (const tool of request.allowedTools) {
1181
+ args.push("--allowedTools", tool);
1182
+ }
1183
+ }
1184
+ if (request.disallowedTools && request.disallowedTools.length > 0) {
1185
+ for (const tool of request.disallowedTools) {
1186
+ args.push("--disallowedTools", tool);
1187
+ }
1188
+ }
1189
+ if (request.maxTurns !== void 0) {
1190
+ args.push("--max-turns", String(request.maxTurns));
1191
+ }
1192
+ const interactive = this.config?.interactive;
1193
+ const skipPerms = interactive === false || request.backendOptions?.dangerouslySkipPermissions || this.config?.options?.dangerouslySkipPermissions;
1194
+ if (skipPerms) {
1195
+ args.push("--dangerously-skip-permissions");
1196
+ }
1197
+ if (request.mcpServers) {
1198
+ for (const [name, config] of Object.entries(request.mcpServers)) {
1199
+ const serverSpec = {
1200
+ command: config.command,
1201
+ ...config.args ? { args: config.args } : {},
1202
+ ...config.env ? { env: config.env } : {},
1203
+ ...config.url ? { url: config.url } : {}
1204
+ };
1205
+ args.push("--mcp-server", `${name}=${JSON.stringify(serverSpec)}`);
1206
+ }
1207
+ }
1208
+ return args;
1209
+ }
1210
+ };
1211
+
1212
+ // src/adapters/opencode/index.ts
1213
+ var import_promises2 = require("fs/promises");
1214
+ var import_node_os2 = require("os");
1215
+ var import_node_path2 = require("path");
1216
+
1217
+ // src/adapters/opencode/types.ts
1218
+ var OpenCodeApiError = class extends Error {
1219
+ statusCode;
1220
+ responseBody;
1221
+ constructor(statusCode, responseBody) {
1222
+ super(`OpenCode API error (${statusCode}): ${responseBody}`);
1223
+ this.name = "OpenCodeApiError";
1224
+ this.statusCode = statusCode;
1225
+ this.responseBody = responseBody;
1226
+ }
1227
+ };
1228
+
1229
+ // src/adapters/opencode/api-client.ts
1230
+ var DEFAULT_TIMEOUT_MS = 3e4;
1231
+ var OpenCodeApiClient = class {
1232
+ baseUrl;
1233
+ apiKey;
1234
+ /**
1235
+ * @param baseUrl opencode serve 服务的基础 URL,例如 http://localhost:39393
1236
+ * @param apiKey 可选的 API Key,用于 Basic Auth 认证(username: opencode, password: apiKey)
1237
+ */
1238
+ constructor(baseUrl, apiKey) {
1239
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
1240
+ this.apiKey = apiKey;
1241
+ }
1242
+ /**
1243
+ * 创建新会话
1244
+ * @param cwd 可选的工作目录
1245
+ * @returns 包含会话 ID 的对象
1246
+ */
1247
+ async createSession(cwd) {
1248
+ const body = {};
1249
+ if (cwd) {
1250
+ body.cwd = cwd;
1251
+ }
1252
+ const response = await this.request(
1253
+ "/api/session",
1254
+ {
1255
+ method: "POST",
1256
+ body: JSON.stringify(body)
1257
+ }
1258
+ );
1259
+ return response;
1260
+ }
1261
+ /**
1262
+ * 获取所有会话列表
1263
+ * @returns 会话数组
1264
+ */
1265
+ async listSessions() {
1266
+ const response = await this.request(
1267
+ "/api/sessions",
1268
+ { method: "GET" }
1269
+ );
1270
+ return response.sessions;
1271
+ }
1272
+ /**
1273
+ * 向指定会话发送消息
1274
+ * @param sessionId 会话 ID
1275
+ * @param content 消息内容
1276
+ */
1277
+ async sendMessage(sessionId, content) {
1278
+ await this.request(
1279
+ `/api/session/${encodeURIComponent(sessionId)}/message`,
1280
+ {
1281
+ method: "POST",
1282
+ body: JSON.stringify({ content })
1283
+ }
1284
+ );
1285
+ }
1286
+ /**
1287
+ * 以 SSE 流式方式订阅会话事件
1288
+ * @param sessionId 会话 ID
1289
+ * @param abortSignal 可选的中止信号
1290
+ * @yields OpenCode SSE 事件
1291
+ */
1292
+ async *streamEvents(sessionId, abortSignal) {
1293
+ const url = `${this.baseUrl}/api/session/${encodeURIComponent(sessionId)}/events`;
1294
+ const response = await fetch(url, {
1295
+ method: "GET",
1296
+ headers: {
1297
+ Accept: "text/event-stream",
1298
+ "Cache-Control": "no-cache",
1299
+ ...this.buildAuthHeaders()
1300
+ },
1301
+ signal: abortSignal
1302
+ });
1303
+ if (!response.ok) {
1304
+ const body = await response.text().catch(() => "");
1305
+ throw new OpenCodeApiError(response.status, body);
1306
+ }
1307
+ if (!response.body) {
1308
+ throw new Error("Response body is null, SSE streaming unavailable");
1309
+ }
1310
+ const reader = response.body.getReader();
1311
+ const decoder = new TextDecoder();
1312
+ let buffer = "";
1313
+ try {
1314
+ while (true) {
1315
+ const { done, value } = await reader.read();
1316
+ if (done) break;
1317
+ buffer += decoder.decode(value, { stream: true });
1318
+ const parts = buffer.split("\n\n");
1319
+ buffer = parts.pop() ?? "";
1320
+ for (const part of parts) {
1321
+ const event = this.parseSseEvent(part);
1322
+ if (event) {
1323
+ yield event;
1324
+ if (event.type === "done") {
1325
+ return;
1326
+ }
1327
+ }
1328
+ }
1329
+ }
1330
+ if (buffer.trim()) {
1331
+ const event = this.parseSseEvent(buffer);
1332
+ if (event) {
1333
+ yield event;
1334
+ }
1335
+ }
1336
+ } finally {
1337
+ reader.releaseLock();
1338
+ }
1339
+ }
1340
+ /**
1341
+ * 健康检查:验证 opencode serve 服务是否可达
1342
+ * @returns 服务是否健康
1343
+ */
1344
+ async healthCheck() {
1345
+ try {
1346
+ const controller = new AbortController();
1347
+ const timer = setTimeout(() => controller.abort(), 5e3);
1348
+ const response = await fetch(`${this.baseUrl}/api/sessions`, {
1349
+ method: "GET",
1350
+ headers: this.buildAuthHeaders(),
1351
+ signal: controller.signal
1352
+ });
1353
+ clearTimeout(timer);
1354
+ return response.ok;
1355
+ } catch {
1356
+ return false;
1357
+ }
1358
+ }
1359
+ /**
1360
+ * 解析单个 SSE 事件文本块
1361
+ * @param raw 原始 SSE 文本块
1362
+ * @returns 解析后的事件对象,解析失败返回 null
1363
+ */
1364
+ parseSseEvent(raw) {
1365
+ const lines = raw.split("\n");
1366
+ let data = "";
1367
+ for (const line of lines) {
1368
+ if (line.startsWith(":")) continue;
1369
+ if (line.startsWith("data: ")) {
1370
+ data += line.slice(6);
1371
+ } else if (line.startsWith("data:")) {
1372
+ data += line.slice(5);
1373
+ }
1374
+ }
1375
+ if (!data) return null;
1376
+ try {
1377
+ return JSON.parse(data);
1378
+ } catch {
1379
+ return null;
1380
+ }
1381
+ }
1382
+ /**
1383
+ * 发送 HTTP 请求的通用方法
1384
+ * @param path API 路径
1385
+ * @param init fetch 请求选项
1386
+ * @returns 解析后的 JSON 响应
1387
+ */
1388
+ /**
1389
+ * 构建包含认证信息的请求 headers
1390
+ */
1391
+ buildAuthHeaders() {
1392
+ const headers = {};
1393
+ if (this.apiKey) {
1394
+ const credentials = Buffer.from(`opencode:${this.apiKey}`).toString("base64");
1395
+ headers["Authorization"] = `Basic ${credentials}`;
1396
+ }
1397
+ return headers;
1398
+ }
1399
+ async request(path, init) {
1400
+ const controller = new AbortController();
1401
+ const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
1402
+ try {
1403
+ const response = await fetch(`${this.baseUrl}${path}`, {
1404
+ ...init,
1405
+ headers: {
1406
+ "Content-Type": "application/json",
1407
+ ...this.buildAuthHeaders(),
1408
+ ...init.headers
1409
+ },
1410
+ signal: controller.signal
1411
+ });
1412
+ if (!response.ok) {
1413
+ const body = await response.text().catch(() => "");
1414
+ throw new OpenCodeApiError(response.status, body);
1415
+ }
1416
+ const text = await response.text();
1417
+ if (!text) return {};
1418
+ return JSON.parse(text);
1419
+ } finally {
1420
+ clearTimeout(timer);
1421
+ }
1422
+ }
1423
+ };
1424
+
1425
+ // src/adapters/opencode/message-mapper.ts
1426
+ function mapOpenCodeEventToMessage(event) {
1427
+ const base = {
1428
+ timestamp: event.timestamp ?? Date.now(),
1429
+ sessionId: event.sessionId,
1430
+ raw: event
1431
+ };
1432
+ switch (event.type) {
1433
+ case "session.created":
1434
+ return {
1435
+ ...base,
1436
+ type: "system",
1437
+ model: event.model,
1438
+ tools: event.tools,
1439
+ cwd: event.cwd
1440
+ };
1441
+ case "message.delta":
1442
+ return {
1443
+ ...base,
1444
+ type: "stream_chunk",
1445
+ text: event.text
1446
+ };
1447
+ case "message.complete":
1448
+ return {
1449
+ ...base,
1450
+ type: "result",
1451
+ text: event.text,
1452
+ durationMs: event.durationMs,
1453
+ costUsd: event.costUsd,
1454
+ inputTokens: event.inputTokens,
1455
+ outputTokens: event.outputTokens
1456
+ };
1457
+ case "tool_use.start":
1458
+ return {
1459
+ ...base,
1460
+ type: "tool_use",
1461
+ toolName: event.toolName,
1462
+ input: event.input
1463
+ };
1464
+ case "tool_use.complete":
1465
+ return {
1466
+ ...base,
1467
+ type: "tool_use",
1468
+ toolName: event.toolName,
1469
+ input: event.input,
1470
+ output: event.output
1471
+ };
1472
+ case "error":
1473
+ return {
1474
+ ...base,
1475
+ type: "error",
1476
+ message: event.message,
1477
+ code: event.code
1478
+ };
1479
+ // message.start / tool_use.delta / done 事件不需要映射为独立消息
1480
+ case "message.start":
1481
+ case "tool_use.delta":
1482
+ case "done":
1483
+ return null;
1484
+ default:
1485
+ return null;
1486
+ }
1487
+ }
1488
+
1489
+ // src/adapters/opencode/index.ts
1490
+ var DEFAULT_SERVE_PORT = 39393;
1491
+ var SERVE_READY_TIMEOUT_MS = 3e4;
1492
+ var SERVE_POLL_INTERVAL_MS = 500;
1493
+ var TESTED_VERSION_RANGE = ">=0.1.0";
1494
+ var MAX_RESTARTS = 3;
1495
+ var RESTART_INTERVAL_MS = 2e3;
1496
+ var HEALTH_CHECK_INTERVAL_MS = 3e4;
1497
+ var OpenCodeAdapter = class extends AbstractAgentAdapter {
1498
+ backend = "opencode";
1499
+ apiClient = null;
1500
+ servePort;
1501
+ /** 远程服务器 URL(提供时跳过自启 serve) */
1502
+ remoteServerUrl;
1503
+ /** API Key(用于 Basic Auth) */
1504
+ apiKey;
1505
+ /**
1506
+ * @param processManager 进程管理器实例
1507
+ * @param config 可选的后端配置
1508
+ * - serverUrl: 远程 OpenCode 服务地址,提供时跳过自启 serve
1509
+ * - apiKey: API Key,用于 Basic Auth 认证
1510
+ * - options.servePort: 本地 serve 端口(仅自启模式)
1511
+ */
1512
+ constructor(processManager, config) {
1513
+ super(processManager, config);
1514
+ const opts = config?.options ?? {};
1515
+ this.remoteServerUrl = config?.serverUrl ?? opts.serverUrl;
1516
+ this.apiKey = config?.apiKey ?? opts.apiKey;
1517
+ this.servePort = opts.servePort ?? DEFAULT_SERVE_PORT;
1518
+ }
1519
+ /**
1520
+ * 检查 opencode 命令是否可用
1521
+ */
1522
+ async isAvailable() {
1523
+ if (this.remoteServerUrl) {
1524
+ try {
1525
+ const client = new OpenCodeApiClient(this.remoteServerUrl, this.apiKey);
1526
+ return await client.healthCheck();
1527
+ } catch {
1528
+ return false;
1529
+ }
1530
+ }
1531
+ try {
1532
+ const proc = new EphemeralProcess();
1533
+ const result = await proc.execute({
1534
+ command: "opencode",
1535
+ args: ["--version"],
1536
+ timeoutMs: 1e4
1537
+ });
1538
+ return result.exitCode === 0;
1539
+ } catch {
1540
+ return false;
1541
+ }
1542
+ }
1543
+ /**
1544
+ * 获取 opencode 版本号
1545
+ */
1546
+ async getVersion() {
1547
+ const proc = new EphemeralProcess();
1548
+ const result = await proc.execute({
1549
+ command: "opencode",
1550
+ args: ["version"],
1551
+ timeoutMs: 1e4
1552
+ });
1553
+ if (result.exitCode !== 0) {
1554
+ throw new Error(`Failed to get opencode version: ${result.stderr}`);
1555
+ }
1556
+ return result.stdout.trim();
1557
+ }
1558
+ /**
1559
+ * 获取适配器能力声明
1560
+ */
1561
+ getCapabilities() {
1562
+ return {
1563
+ features: /* @__PURE__ */ new Set([
1564
+ "streaming",
1565
+ "session-resume",
1566
+ "system-prompt",
1567
+ "serve-mode"
1568
+ ]),
1569
+ streamingMode: "delta",
1570
+ outputFormats: ["text"],
1571
+ testedVersionRange: TESTED_VERSION_RANGE
1572
+ };
1573
+ }
1574
+ /**
1575
+ * 执行查询:启动 serve 进程(如未启动),创建/恢复会话,发送消息并流式返回结果
1576
+ * @param request 请求参数
1577
+ * @yields AgentMessage 统一消息流
1578
+ */
1579
+ async *query(request) {
1580
+ const client = await this.ensureServeRunning();
1581
+ let sessionId;
1582
+ if (request.resume) {
1583
+ sessionId = request.resume;
1584
+ } else {
1585
+ const session = await client.createSession(request.cwd);
1586
+ sessionId = session.id;
1587
+ }
1588
+ const controller = this.createAbortController(sessionId);
1589
+ try {
1590
+ await client.sendMessage(sessionId, request.prompt);
1591
+ for await (const event of client.streamEvents(
1592
+ sessionId,
1593
+ controller.signal
1594
+ )) {
1595
+ const message = mapOpenCodeEventToMessage(event);
1596
+ if (message) {
1597
+ yield message;
1598
+ }
1599
+ }
1600
+ } finally {
1601
+ this.removeAbortController(sessionId);
1602
+ }
1603
+ }
1604
+ /**
1605
+ * 列出历史会话
1606
+ * @param options 查询选项
1607
+ */
1608
+ async listSessions(options) {
1609
+ const client = await this.ensureServeRunning();
1610
+ const sessions = await client.listSessions();
1611
+ let filtered = sessions;
1612
+ if (options?.cwd) {
1613
+ filtered = filtered.filter((s) => s.cwd === options.cwd);
1614
+ }
1615
+ if (options?.limit !== void 0 && options.limit > 0) {
1616
+ filtered = filtered.slice(0, options.limit);
1617
+ }
1618
+ return filtered.map((s) => ({
1619
+ sessionId: s.id,
1620
+ backend: this.backend,
1621
+ summary: s.title,
1622
+ lastModified: s.updatedAt,
1623
+ cwd: s.cwd
1624
+ }));
1625
+ }
1626
+ /**
1627
+ * 获取 OpenCode 可用模型列表(通过 `opencode models` 命令)
1628
+ */
1629
+ async listModels() {
1630
+ try {
1631
+ const proc = new EphemeralProcess();
1632
+ const result = await proc.execute({
1633
+ command: "opencode",
1634
+ args: ["models"],
1635
+ timeoutMs: 15e3
1636
+ });
1637
+ if (result.exitCode !== 0) return [];
1638
+ const lines = result.stdout.trim().split("\n").filter(Boolean);
1639
+ return lines.map((line) => {
1640
+ const trimmed = line.trim();
1641
+ const slashIndex = trimmed.indexOf("/");
1642
+ if (slashIndex >= 0) {
1643
+ return {
1644
+ id: trimmed,
1645
+ name: trimmed.slice(slashIndex + 1),
1646
+ provider: trimmed.slice(0, slashIndex)
1647
+ };
1648
+ }
1649
+ return { id: trimmed, name: trimmed };
1650
+ });
1651
+ } catch {
1652
+ return [];
1653
+ }
1654
+ }
1655
+ /**
1656
+ * 读取 opencode 配置文件
1657
+ */
1658
+ async getSettings() {
1659
+ const configPath = this.getConfigPath();
1660
+ try {
1661
+ const content = await (0, import_promises2.readFile)(configPath, "utf-8");
1662
+ return JSON.parse(content);
1663
+ } catch {
1664
+ return {};
1665
+ }
1666
+ }
1667
+ /**
1668
+ * 更新 opencode 配置文件(读取-合并-写入)
1669
+ * @param settings 要合并的配置项
1670
+ */
1671
+ async updateSettings(settings) {
1672
+ const configPath = this.getConfigPath();
1673
+ const configDir = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".config", "opencode");
1674
+ let existing;
1675
+ try {
1676
+ const content = await (0, import_promises2.readFile)(configPath, "utf-8");
1677
+ existing = JSON.parse(content);
1678
+ } catch {
1679
+ existing = {};
1680
+ }
1681
+ const merged = { ...existing, ...settings };
1682
+ await (0, import_promises2.mkdir)(configDir, { recursive: true });
1683
+ await (0, import_promises2.writeFile)(configPath, JSON.stringify(merged, null, 2), "utf-8");
1684
+ }
1685
+ /**
1686
+ * 释放资源:停止 serve 进程,中止所有活跃流
1687
+ */
1688
+ async dispose() {
1689
+ for (const [sessionId] of this.abortControllers) {
1690
+ this.abort(sessionId);
1691
+ }
1692
+ const persistent = this.processManager.getPersistent(this.backend);
1693
+ if (persistent) {
1694
+ await persistent.stop();
1695
+ }
1696
+ this.apiClient = null;
1697
+ }
1698
+ /**
1699
+ * 确保 opencode serve 进程已启动并可用
1700
+ * @returns 可用的 API 客户端
1701
+ */
1702
+ async ensureServeRunning() {
1703
+ if (this.apiClient) {
1704
+ const healthy = await this.apiClient.healthCheck();
1705
+ if (healthy) return this.apiClient;
1706
+ }
1707
+ if (this.remoteServerUrl) {
1708
+ const client2 = new OpenCodeApiClient(this.remoteServerUrl, this.apiKey);
1709
+ const healthy = await client2.healthCheck();
1710
+ if (!healthy) {
1711
+ throw new Error(
1712
+ `\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B OpenCode \u670D\u52A1: ${this.remoteServerUrl}`
1713
+ );
1714
+ }
1715
+ this.apiClient = client2;
1716
+ return client2;
1717
+ }
1718
+ const client = new OpenCodeApiClient(
1719
+ `http://localhost:${this.servePort}`,
1720
+ this.apiKey
1721
+ );
1722
+ const alreadyRunning = await client.healthCheck();
1723
+ if (alreadyRunning) {
1724
+ this.apiClient = client;
1725
+ return client;
1726
+ }
1727
+ await this.processManager.startPersistent(this.backend, {
1728
+ command: "opencode",
1729
+ args: ["serve", "--port", String(this.servePort)],
1730
+ maxRestarts: MAX_RESTARTS,
1731
+ restartIntervalMs: RESTART_INTERVAL_MS,
1732
+ healthCheckIntervalMs: HEALTH_CHECK_INTERVAL_MS,
1733
+ healthCheckFn: () => client.healthCheck().then((ok) => {
1734
+ if (!ok) throw new Error("Health check failed");
1735
+ return ok;
1736
+ })
1737
+ });
1738
+ await this.waitForServeReady(client);
1739
+ this.apiClient = client;
1740
+ return client;
1741
+ }
1742
+ /**
1743
+ * 轮询等待 serve 服务可达
1744
+ * @param client API 客户端
1745
+ */
1746
+ async waitForServeReady(client) {
1747
+ const deadline = Date.now() + SERVE_READY_TIMEOUT_MS;
1748
+ while (Date.now() < deadline) {
1749
+ const healthy = await client.healthCheck();
1750
+ if (healthy) return;
1751
+ await this.delay(SERVE_POLL_INTERVAL_MS);
1752
+ }
1753
+ throw new Error(
1754
+ `opencode serve did not become ready within ${SERVE_READY_TIMEOUT_MS}ms`
1755
+ );
1756
+ }
1757
+ /**
1758
+ * 延迟指定毫秒
1759
+ * @param ms 毫秒数
1760
+ */
1761
+ delay(ms) {
1762
+ return new Promise((resolve) => setTimeout(resolve, ms));
1763
+ }
1764
+ /**
1765
+ * 获取 opencode 配置文件路径
1766
+ */
1767
+ getConfigPath() {
1768
+ return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".config", "opencode", "config.json");
1769
+ }
1770
+ };
1771
+
1772
+ // src/session/interactive-session.ts
1773
+ var InteractiveSession = class {
1774
+ initialSessionId;
1775
+ resolvedSessionId;
1776
+ adapter;
1777
+ baseRequest;
1778
+ firstSent = false;
1779
+ closed = false;
1780
+ constructor(initialSessionId, adapter, baseRequest) {
1781
+ this.initialSessionId = initialSessionId;
1782
+ this.adapter = adapter;
1783
+ this.baseRequest = baseRequest;
1784
+ }
1785
+ /** 当前生效的 sessionId(首次 send 后反映 CLI 返回的真实 ID) */
1786
+ get sessionId() {
1787
+ return this.resolvedSessionId ?? this.initialSessionId;
1788
+ }
1789
+ /** 会话是否仍然活跃 */
1790
+ get isActive() {
1791
+ return !this.closed;
1792
+ }
1793
+ /**
1794
+ * 发送一条消息,返回流式响应。
1795
+ * 首次调用使用 initialSessionId,后续自动附加 resume。
1796
+ */
1797
+ async *send(prompt) {
1798
+ if (this.closed) {
1799
+ throw new SessionNotFoundError(this.sessionId);
1800
+ }
1801
+ const request = {
1802
+ ...this.baseRequest,
1803
+ prompt,
1804
+ ...this.firstSent ? { resume: this.sessionId } : {}
1805
+ };
1806
+ for await (const msg of this.adapter.query(request)) {
1807
+ if (!this.firstSent && msg.sessionId) {
1808
+ this.resolvedSessionId = msg.sessionId;
1809
+ }
1810
+ yield msg;
1811
+ }
1812
+ this.firstSent = true;
1813
+ }
1814
+ /** 关闭会话(仅清理内部状态,无进程需要释放) */
1815
+ close() {
1816
+ this.closed = true;
1817
+ }
1818
+ };
1819
+
1820
+ // src/porygon.ts
1821
+ var Porygon = class extends import_node_events2.EventEmitter {
1822
+ config;
1823
+ adapters = /* @__PURE__ */ new Map();
1824
+ interceptors;
1825
+ processManager;
1826
+ sessionManager;
1827
+ constructor(config) {
1828
+ super();
1829
+ this.config = ConfigLoader.load(config);
1830
+ this.interceptors = new InterceptorManager();
1831
+ this.processManager = new ProcessManager();
1832
+ this.sessionManager = new SessionManager();
1833
+ this.registerBuiltinAdapters();
1834
+ }
1835
+ /**
1836
+ * 注册内置适配器
1837
+ */
1838
+ registerBuiltinAdapters() {
1839
+ const claudeConfig = this.config.backends?.["claude"];
1840
+ const mergedClaudeConfig = {
1841
+ ...claudeConfig,
1842
+ proxy: claudeConfig?.proxy ?? this.config.proxy
1843
+ };
1844
+ const claudeAdapter = new ClaudeAdapter(this.processManager, mergedClaudeConfig);
1845
+ this.registerAdapter(claudeAdapter);
1846
+ const opencodeConfig = this.config.backends?.["opencode"];
1847
+ const mergedOpencodeConfig = {
1848
+ ...opencodeConfig,
1849
+ proxy: opencodeConfig?.proxy ?? this.config.proxy
1850
+ };
1851
+ const opencodeAdapter = new OpenCodeAdapter(this.processManager, mergedOpencodeConfig);
1852
+ this.registerAdapter(opencodeAdapter);
1853
+ }
1854
+ /**
1855
+ * 注册自定义适配器
1856
+ * @param adapter 适配器实例
1857
+ */
1858
+ registerAdapter(adapter) {
1859
+ this.adapters.set(adapter.backend, adapter);
1860
+ this.sessionManager.registerAdapter(adapter.backend, adapter);
1861
+ }
1862
+ /**
1863
+ * 获取指定后端的适配器
1864
+ * @param backend 后端名称,默认使用配置中的 defaultBackend
1865
+ */
1866
+ getAdapter(backend) {
1867
+ const name = backend ?? this.config.defaultBackend ?? "claude";
1868
+ const adapter = this.adapters.get(name);
1869
+ if (!adapter) throw new AdapterNotFoundError(name);
1870
+ return adapter;
1871
+ }
1872
+ /**
1873
+ * 流式查询,返回 AgentMessage 异步生成器。
1874
+ * 作为与 LLM 后端交互的主要入口。
1875
+ * @param request 提示请求参数
1876
+ * @yields AgentMessage 消息流
1877
+ */
1878
+ async *query(request) {
1879
+ const adapter = this.getAdapter(request.backend);
1880
+ const backendName = adapter.backend;
1881
+ const mergedRequest = this.mergeRequest(request, backendName);
1882
+ const processedPrompt = await this.interceptors.processInput(
1883
+ mergedRequest.prompt,
1884
+ { backend: backendName, sessionId: mergedRequest.resume }
1885
+ );
1886
+ mergedRequest.prompt = processedPrompt;
1887
+ for await (const message of adapter.query(mergedRequest)) {
1888
+ if (message.type === "assistant" || message.type === "result") {
1889
+ const processedText = await this.interceptors.processOutput(
1890
+ message.text,
1891
+ {
1892
+ backend: backendName,
1893
+ sessionId: message.sessionId,
1894
+ messageType: message.type
1895
+ }
1896
+ );
1897
+ yield { ...message, text: processedText };
1898
+ } else {
1899
+ yield message;
1900
+ }
1901
+ }
1902
+ }
1903
+ /**
1904
+ * 简单运行模式,收集所有消息并返回最终结果文本
1905
+ * @param request 提示请求参数
1906
+ * @returns 最终结果文本
1907
+ */
1908
+ async run(request) {
1909
+ let resultText = "";
1910
+ for await (const msg of this.query(request)) {
1911
+ if (msg.type === "result") {
1912
+ resultText = msg.text;
1913
+ } else if (msg.type === "assistant") {
1914
+ resultText = msg.text;
1915
+ }
1916
+ }
1917
+ return resultText;
1918
+ }
1919
+ /**
1920
+ * 创建交互式多轮对话会话。
1921
+ * 自动管理 sessionId 和 resume,对调用方透明。
1922
+ */
1923
+ session(options) {
1924
+ const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
1925
+ const adapter = this.getAdapter(backend);
1926
+ const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
1927
+ const { prompt: _, ...baseRequest } = merged;
1928
+ return new InteractiveSession(
1929
+ crypto.randomUUID(),
1930
+ adapter,
1931
+ baseRequest
1932
+ );
1933
+ }
1934
+ /**
1935
+ * 注册拦截器
1936
+ * @param direction 拦截方向
1937
+ * @param fn 拦截器函数
1938
+ * @returns 取消注册的函数
1939
+ */
1940
+ use(direction, fn) {
1941
+ return this.interceptors.use(direction, fn);
1942
+ }
1943
+ /**
1944
+ * 获取后端能力声明
1945
+ * @param backend 后端名称
1946
+ */
1947
+ getCapabilities(backend) {
1948
+ return this.getAdapter(backend).getCapabilities();
1949
+ }
1950
+ /**
1951
+ * 获取指定后端的可用模型列表
1952
+ * @param backend 后端名称
1953
+ */
1954
+ async listModels(backend) {
1955
+ return this.getAdapter(backend).listModels();
1956
+ }
1957
+ /**
1958
+ * 检查单个后端的健康状态
1959
+ * @param backend 后端名称
1960
+ */
1961
+ async checkBackend(backend) {
1962
+ const adapter = this.getAdapter(backend);
1963
+ try {
1964
+ const available = await adapter.isAvailable();
1965
+ if (!available) {
1966
+ return { available: false };
1967
+ }
1968
+ const compat = await adapter.checkCompatibility();
1969
+ const result = {
1970
+ available: true,
1971
+ version: compat.version,
1972
+ supported: compat.supported
1973
+ };
1974
+ if (compat.warnings.length > 0) {
1975
+ result.warnings = compat.warnings;
1976
+ }
1977
+ if (!compat.supported) {
1978
+ this.emit("health:degraded", backend, compat.warnings.join("; "));
1979
+ }
1980
+ return result;
1981
+ } catch (err) {
1982
+ return {
1983
+ available: false,
1984
+ error: err instanceof Error ? err.message : String(err)
1985
+ };
1986
+ }
1987
+ }
1988
+ /**
1989
+ * 对所有已注册后端进行健康检查。
1990
+ * 返回扁平化结构,包含 version/supported/warnings 等字段。
1991
+ */
1992
+ async healthCheck() {
1993
+ const entries = Array.from(this.adapters.keys());
1994
+ const checks = entries.map(async (name) => {
1995
+ const result = await this.checkBackend(name);
1996
+ return [name, result];
1997
+ });
1998
+ const settled = await Promise.allSettled(checks);
1999
+ const results = {};
2000
+ for (const item of settled) {
2001
+ if (item.status === "fulfilled") {
2002
+ const [name, result] = item.value;
2003
+ results[name] = result;
2004
+ }
2005
+ }
2006
+ return results;
2007
+ }
2008
+ /**
2009
+ * 读取或更新后端设置
2010
+ * @param backend 后端名称
2011
+ * @param newSettings 要更新的设置项(可选)
2012
+ * @returns 当前设置
2013
+ */
2014
+ async settings(backend, newSettings) {
2015
+ const adapter = this.getAdapter(backend);
2016
+ if (newSettings) {
2017
+ await adapter.updateSettings(newSettings);
2018
+ }
2019
+ return adapter.getSettings();
2020
+ }
2021
+ /**
2022
+ * 列出指定后端的会话
2023
+ * @param backend 后端名称
2024
+ * @param options 查询选项
2025
+ */
2026
+ async listSessions(backend, options) {
2027
+ const name = backend ?? this.config.defaultBackend ?? "claude";
2028
+ return this.sessionManager.list(name, options);
2029
+ }
2030
+ /**
2031
+ * 中止正在运行的查询
2032
+ * @param backend 后端名称
2033
+ * @param sessionId 会话 ID
2034
+ */
2035
+ abort(backend, sessionId) {
2036
+ this.getAdapter(backend).abort(sessionId);
2037
+ }
2038
+ /**
2039
+ * 释放所有资源
2040
+ */
2041
+ async dispose() {
2042
+ const promises = [];
2043
+ for (const [, adapter] of this.adapters) {
2044
+ promises.push(adapter.dispose());
2045
+ }
2046
+ await Promise.allSettled(promises);
2047
+ await this.processManager.terminateAll();
2048
+ this.adapters.clear();
2049
+ this.sessionManager.clearCache();
2050
+ }
2051
+ /**
2052
+ * 合并请求参数与配置默认值。
2053
+ *
2054
+ * 合并策略:
2055
+ * - model: request > backendConfig > 不设置(后端使用自身默认值)
2056
+ * - timeoutMs: request > defaults
2057
+ * - maxTurns: request > defaults
2058
+ * - cwd: request > backendConfig
2059
+ * - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
2060
+ * 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
2061
+ *
2062
+ * @param request 原始请求
2063
+ * @param backend 后端名称
2064
+ * @returns 合并后的请求
2065
+ */
2066
+ mergeRequest(request, backend) {
2067
+ const backendConfig = this.config.backends?.[backend];
2068
+ const defaults = this.config.defaults;
2069
+ const appendParts = [];
2070
+ if (defaults?.appendSystemPrompt) {
2071
+ appendParts.push(defaults.appendSystemPrompt);
2072
+ }
2073
+ if (backendConfig?.appendSystemPrompt) {
2074
+ appendParts.push(backendConfig.appendSystemPrompt);
2075
+ }
2076
+ if (request.appendSystemPrompt) {
2077
+ appendParts.push(request.appendSystemPrompt);
2078
+ }
2079
+ return {
2080
+ ...request,
2081
+ model: request.model ?? backendConfig?.model,
2082
+ timeoutMs: request.timeoutMs ?? defaults?.timeoutMs,
2083
+ maxTurns: request.maxTurns ?? defaults?.maxTurns,
2084
+ cwd: request.cwd ?? backendConfig?.cwd,
2085
+ appendSystemPrompt: request.systemPrompt ? void 0 : appendParts.length > 0 ? appendParts.join("\n") : void 0
2086
+ };
2087
+ }
2088
+ };
2089
+ function createPorygon(config) {
2090
+ return new Porygon(config);
2091
+ }
2092
+
2093
+ // src/interceptor/guard.ts
2094
+ var BUILTIN_INPUT_PATTERNS = [
2095
+ // 中文
2096
+ /忽略.{0,10}(之前|上面|以上|所有).{0,10}(指令|规则|提示|约束)/i,
2097
+ /重复.{0,10}(系统|完整).{0,10}(提示|指令|prompt)/i,
2098
+ /输出.{0,10}(系统|完整).{0,10}(提示|指令|prompt)/i,
2099
+ /告诉我.{0,10}(系统|你的).{0,10}(提示|指令|prompt)/i,
2100
+ /显示.{0,10}(系统|隐藏|完整).{0,10}(提示|指令|prompt)/i,
2101
+ /你的(系统|初始).{0,10}(提示|指令|设定)/i,
2102
+ // English
2103
+ /ignore.{0,15}(previous|above|all|prior).{0,15}(instructions?|rules?|prompts?|constraints?)/i,
2104
+ /repeat.{0,15}(system|full|entire|complete).{0,15}(prompt|instructions?|message)/i,
2105
+ /reveal.{0,15}(system|hidden|full).{0,15}(prompt|instructions?)/i,
2106
+ /show.{0,15}(system|hidden|full).{0,15}(prompt|instructions?)/i,
2107
+ /what.{0,15}(is|are).{0,15}(your|the).{0,15}(system|initial).{0,15}(prompt|instructions?)/i,
2108
+ /print.{0,15}(system|full|entire).{0,15}(prompt|instructions?|message)/i,
2109
+ /disregard.{0,15}(previous|above|all|prior).{0,15}(instructions?|rules?)/i
2110
+ ];
2111
+ function createInputGuard(options) {
2112
+ const patterns = [
2113
+ ...BUILTIN_INPUT_PATTERNS,
2114
+ ...options?.blockedPatterns ?? []
2115
+ ];
2116
+ const action = options?.action ?? "reject";
2117
+ const backends = options?.backends;
2118
+ return (text, context) => {
2119
+ if (backends && !backends.includes(context.backend)) {
2120
+ return void 0;
2121
+ }
2122
+ for (const pattern of patterns) {
2123
+ if (pattern.test(text)) {
2124
+ if (action === "reject") {
2125
+ return false;
2126
+ }
2127
+ return text.replace(pattern, "[REDACTED]");
2128
+ }
2129
+ }
2130
+ if (options?.customCheck?.(text, context)) {
2131
+ if (action === "reject") {
2132
+ return false;
2133
+ }
2134
+ return "[REDACTED]";
2135
+ }
2136
+ return void 0;
2137
+ };
2138
+ }
2139
+ function createOutputGuard(options) {
2140
+ const keywords = options?.sensitiveKeywords ?? [];
2141
+ const action = options?.action ?? "redact";
2142
+ const backends = options?.backends;
2143
+ return (text, context) => {
2144
+ if (backends && !backends.includes(context.backend)) {
2145
+ return void 0;
2146
+ }
2147
+ let matched = false;
2148
+ let result = text;
2149
+ for (const keyword of keywords) {
2150
+ if (text.includes(keyword)) {
2151
+ matched = true;
2152
+ if (action === "redact") {
2153
+ result = result.split(keyword).join("[REDACTED]");
2154
+ }
2155
+ }
2156
+ }
2157
+ if (!matched && options?.customCheck?.(text, context)) {
2158
+ matched = true;
2159
+ if (action === "redact") {
2160
+ result = "[REDACTED]";
2161
+ }
2162
+ }
2163
+ if (!matched) {
2164
+ return void 0;
2165
+ }
2166
+ if (action === "reject") {
2167
+ return false;
2168
+ }
2169
+ return result;
2170
+ };
2171
+ }
2172
+ // Annotate the CommonJS export names for ESM import in node:
2173
+ 0 && (module.exports = {
2174
+ AbstractAgentAdapter,
2175
+ AdapterIncompatibleError,
2176
+ AdapterNotAvailableError,
2177
+ AdapterNotFoundError,
2178
+ AgentExecutionError,
2179
+ AgentTimeoutError,
2180
+ ClaudeAdapter,
2181
+ ConfigValidationError,
2182
+ InteractiveSession,
2183
+ InterceptorRejectedError,
2184
+ OpenCodeAdapter,
2185
+ Porygon,
2186
+ PorygonError,
2187
+ SessionNotFoundError,
2188
+ createInputGuard,
2189
+ createOutputGuard,
2190
+ createPorygon
2191
+ });
2192
+ //# sourceMappingURL=index.cjs.map