@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/README.md +156 -43
- package/dist/index.cjs +2192 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +812 -0
- package/dist/index.d.ts +109 -12
- package/dist/index.js +253 -67
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -88,6 +88,10 @@ var BackendConfigSchema = z.object({
|
|
|
88
88
|
appendSystemPrompt: z.string().optional(),
|
|
89
89
|
proxy: ProxyConfigSchema.optional(),
|
|
90
90
|
cwd: z.string().optional(),
|
|
91
|
+
serverUrl: z.string().optional(),
|
|
92
|
+
apiKey: z.string().optional(),
|
|
93
|
+
interactive: z.boolean().optional(),
|
|
94
|
+
cliPath: z.string().optional(),
|
|
91
95
|
options: z.record(z.string(), z.unknown()).optional()
|
|
92
96
|
});
|
|
93
97
|
var PorygonConfigSchema = z.object({
|
|
@@ -305,12 +309,18 @@ var EphemeralProcess = class {
|
|
|
305
309
|
throw new Error("Process aborted before start");
|
|
306
310
|
}
|
|
307
311
|
this.aborted = false;
|
|
312
|
+
const useStdin = options.stdinData !== void 0;
|
|
308
313
|
const child = spawn(options.command, options.args, {
|
|
309
314
|
cwd: options.cwd,
|
|
310
315
|
env: options.env ?? void 0,
|
|
311
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
316
|
+
stdio: [useStdin ? "pipe" : "ignore", "pipe", "pipe"]
|
|
312
317
|
});
|
|
313
318
|
this.childProcess = child;
|
|
319
|
+
if (useStdin && child.stdin) {
|
|
320
|
+
child.stdin.write(options.stdinData, () => {
|
|
321
|
+
child.stdin.end();
|
|
322
|
+
});
|
|
323
|
+
}
|
|
314
324
|
let timeoutTimer;
|
|
315
325
|
if (options.timeoutMs !== void 0 && options.timeoutMs > 0) {
|
|
316
326
|
timeoutTimer = setTimeout(() => {
|
|
@@ -780,15 +790,16 @@ function isResultEvent(event) {
|
|
|
780
790
|
// src/adapters/claude/message-mapper.ts
|
|
781
791
|
function mapClaudeEvent(event, sessionId) {
|
|
782
792
|
const timestamp = Date.now();
|
|
783
|
-
const
|
|
793
|
+
const resolvedSessionId = event.session_id ?? sessionId;
|
|
794
|
+
const baseFields = { timestamp, sessionId: resolvedSessionId, raw: event };
|
|
784
795
|
if (isSystemEvent(event)) {
|
|
785
|
-
return {
|
|
796
|
+
return [{
|
|
786
797
|
...baseFields,
|
|
787
798
|
type: "system",
|
|
788
799
|
model: event.model,
|
|
789
800
|
tools: event.tools,
|
|
790
801
|
cwd: event.cwd
|
|
791
|
-
};
|
|
802
|
+
}];
|
|
792
803
|
}
|
|
793
804
|
if (isAssistantEvent(event)) {
|
|
794
805
|
const content = event.message.content;
|
|
@@ -797,16 +808,16 @@ function mapClaudeEvent(event, sessionId) {
|
|
|
797
808
|
if (isStreamEvent(event)) {
|
|
798
809
|
const delta = event.event.delta;
|
|
799
810
|
if (delta?.type === "text_delta" && delta.text) {
|
|
800
|
-
return {
|
|
811
|
+
return [{
|
|
801
812
|
...baseFields,
|
|
802
813
|
type: "stream_chunk",
|
|
803
814
|
text: delta.text
|
|
804
|
-
};
|
|
815
|
+
}];
|
|
805
816
|
}
|
|
806
|
-
return
|
|
817
|
+
return [];
|
|
807
818
|
}
|
|
808
819
|
if (isResultEvent(event)) {
|
|
809
|
-
return {
|
|
820
|
+
return [{
|
|
810
821
|
...baseFields,
|
|
811
822
|
type: "result",
|
|
812
823
|
text: event.result,
|
|
@@ -814,38 +825,52 @@ function mapClaudeEvent(event, sessionId) {
|
|
|
814
825
|
durationMs: event.duration_ms,
|
|
815
826
|
inputTokens: event.input_tokens,
|
|
816
827
|
outputTokens: event.output_tokens
|
|
817
|
-
};
|
|
828
|
+
}];
|
|
818
829
|
}
|
|
819
|
-
return
|
|
830
|
+
return [];
|
|
820
831
|
}
|
|
821
832
|
function mapAssistantContent(blocks, baseFields) {
|
|
822
|
-
if (!blocks || blocks.length === 0) return
|
|
833
|
+
if (!blocks || blocks.length === 0) return [];
|
|
834
|
+
const messages = [];
|
|
823
835
|
const textParts = [];
|
|
824
|
-
let firstToolUse = null;
|
|
825
836
|
for (const block of blocks) {
|
|
826
837
|
if (block.type === "text" && block.text) {
|
|
827
838
|
textParts.push(block.text);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
839
|
+
messages.push({
|
|
840
|
+
...baseFields,
|
|
841
|
+
type: "stream_chunk",
|
|
842
|
+
text: block.text
|
|
843
|
+
});
|
|
831
844
|
}
|
|
832
845
|
}
|
|
833
846
|
if (textParts.length > 0) {
|
|
834
|
-
|
|
847
|
+
messages.push({
|
|
835
848
|
...baseFields,
|
|
836
849
|
type: "assistant",
|
|
837
|
-
text: textParts.join("\n")
|
|
838
|
-
|
|
850
|
+
text: textParts.join("\n"),
|
|
851
|
+
turnComplete: true
|
|
852
|
+
});
|
|
839
853
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
854
|
+
for (const block of blocks) {
|
|
855
|
+
if (block.type === "tool_use") {
|
|
856
|
+
messages.push({
|
|
857
|
+
...baseFields,
|
|
858
|
+
type: "tool_use",
|
|
859
|
+
toolName: block.name,
|
|
860
|
+
input: block.input
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (block.type === "tool_result") {
|
|
864
|
+
messages.push({
|
|
865
|
+
...baseFields,
|
|
866
|
+
type: "tool_use",
|
|
867
|
+
toolName: block.tool_use_id,
|
|
868
|
+
input: {},
|
|
869
|
+
output: block.content
|
|
870
|
+
});
|
|
871
|
+
}
|
|
847
872
|
}
|
|
848
|
-
return
|
|
873
|
+
return messages;
|
|
849
874
|
}
|
|
850
875
|
|
|
851
876
|
// src/adapters/claude/index.ts
|
|
@@ -857,6 +882,10 @@ var CLAUDE_MODELS = [
|
|
|
857
882
|
];
|
|
858
883
|
var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
859
884
|
backend = "claude";
|
|
885
|
+
/** CLI 命令名或路径 */
|
|
886
|
+
get cliCommand() {
|
|
887
|
+
return this.config?.cliPath ?? "claude";
|
|
888
|
+
}
|
|
860
889
|
/**
|
|
861
890
|
* 检查 Claude CLI 是否可用
|
|
862
891
|
*/
|
|
@@ -865,7 +894,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
865
894
|
const proc = new EphemeralProcess();
|
|
866
895
|
const result = await proc.execute({
|
|
867
896
|
command: "which",
|
|
868
|
-
args: [
|
|
897
|
+
args: [this.cliCommand]
|
|
869
898
|
});
|
|
870
899
|
return result.exitCode === 0;
|
|
871
900
|
} catch {
|
|
@@ -878,7 +907,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
878
907
|
async getVersion() {
|
|
879
908
|
const proc = new EphemeralProcess();
|
|
880
909
|
const result = await proc.execute({
|
|
881
|
-
command:
|
|
910
|
+
command: this.cliCommand,
|
|
882
911
|
args: ["--version"]
|
|
883
912
|
});
|
|
884
913
|
if (result.exitCode !== 0) {
|
|
@@ -903,8 +932,10 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
903
932
|
"tool-restriction",
|
|
904
933
|
"mcp",
|
|
905
934
|
"subagents",
|
|
906
|
-
"worktree"
|
|
935
|
+
"worktree",
|
|
936
|
+
"interactive-session"
|
|
907
937
|
]),
|
|
938
|
+
streamingMode: "chunked",
|
|
908
939
|
outputFormats: ["text", "json", "stream-json"],
|
|
909
940
|
testedVersionRange: ">=1.0.0"
|
|
910
941
|
};
|
|
@@ -935,14 +966,16 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
935
966
|
if (v) cleanEnv[k] = v;
|
|
936
967
|
}
|
|
937
968
|
}
|
|
969
|
+
const stdinData = this.buildStdinData(request);
|
|
938
970
|
const streamOptions = {
|
|
939
|
-
command:
|
|
971
|
+
command: this.cliCommand,
|
|
940
972
|
args,
|
|
941
973
|
...cwd ? { cwd } : {},
|
|
942
974
|
env: cleanEnv,
|
|
943
|
-
timeoutMs: request.timeoutMs
|
|
975
|
+
timeoutMs: request.timeoutMs,
|
|
976
|
+
stdinData
|
|
944
977
|
};
|
|
945
|
-
const cmdStr = [
|
|
978
|
+
const cmdStr = [this.cliCommand, ...args.map((a) => /[\s"']/.test(a) ? JSON.stringify(a) : a)].join(" ");
|
|
946
979
|
const debugCmd = cwd ? `cd ${JSON.stringify(cwd)} && ${cmdStr}` : cmdStr;
|
|
947
980
|
yield {
|
|
948
981
|
type: "system",
|
|
@@ -963,8 +996,8 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
963
996
|
} catch {
|
|
964
997
|
continue;
|
|
965
998
|
}
|
|
966
|
-
const
|
|
967
|
-
|
|
999
|
+
const messages = mapClaudeEvent(parsed, sessionId);
|
|
1000
|
+
for (const message of messages) {
|
|
968
1001
|
yield message;
|
|
969
1002
|
}
|
|
970
1003
|
}
|
|
@@ -984,7 +1017,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
984
1017
|
args.push("--limit", String(options.limit));
|
|
985
1018
|
}
|
|
986
1019
|
const result = await proc.execute({
|
|
987
|
-
command:
|
|
1020
|
+
command: this.cliCommand,
|
|
988
1021
|
args,
|
|
989
1022
|
cwd: options?.cwd
|
|
990
1023
|
});
|
|
@@ -1073,10 +1106,16 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1073
1106
|
env["NO_PROXY"] = noProxy;
|
|
1074
1107
|
}
|
|
1075
1108
|
}
|
|
1109
|
+
/**
|
|
1110
|
+
* 构建通过 stdin 传递给 Claude CLI 的数据。
|
|
1111
|
+
* 使用 stdin 而非 CLI 参数传递 prompt,避免超长参数导致的 403 错误。
|
|
1112
|
+
*/
|
|
1113
|
+
buildStdinData(request) {
|
|
1114
|
+
return request.prompt;
|
|
1115
|
+
}
|
|
1076
1116
|
buildArgs(request) {
|
|
1077
1117
|
const args = [
|
|
1078
|
-
"
|
|
1079
|
-
request.prompt,
|
|
1118
|
+
"--print",
|
|
1080
1119
|
"--output-format",
|
|
1081
1120
|
"stream-json",
|
|
1082
1121
|
"--verbose"
|
|
@@ -1108,6 +1147,11 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1108
1147
|
if (request.maxTurns !== void 0) {
|
|
1109
1148
|
args.push("--max-turns", String(request.maxTurns));
|
|
1110
1149
|
}
|
|
1150
|
+
const interactive = this.config?.interactive;
|
|
1151
|
+
const skipPerms = interactive === false || request.backendOptions?.dangerouslySkipPermissions || this.config?.options?.dangerouslySkipPermissions;
|
|
1152
|
+
if (skipPerms) {
|
|
1153
|
+
args.push("--dangerously-skip-permissions");
|
|
1154
|
+
}
|
|
1111
1155
|
if (request.mcpServers) {
|
|
1112
1156
|
for (const [name, config] of Object.entries(request.mcpServers)) {
|
|
1113
1157
|
const serverSpec = {
|
|
@@ -1144,11 +1188,14 @@ var OpenCodeApiError = class extends Error {
|
|
|
1144
1188
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1145
1189
|
var OpenCodeApiClient = class {
|
|
1146
1190
|
baseUrl;
|
|
1191
|
+
apiKey;
|
|
1147
1192
|
/**
|
|
1148
1193
|
* @param baseUrl opencode serve 服务的基础 URL,例如 http://localhost:39393
|
|
1194
|
+
* @param apiKey 可选的 API Key,用于 Basic Auth 认证(username: opencode, password: apiKey)
|
|
1149
1195
|
*/
|
|
1150
|
-
constructor(baseUrl) {
|
|
1196
|
+
constructor(baseUrl, apiKey) {
|
|
1151
1197
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
1198
|
+
this.apiKey = apiKey;
|
|
1152
1199
|
}
|
|
1153
1200
|
/**
|
|
1154
1201
|
* 创建新会话
|
|
@@ -1206,7 +1253,8 @@ var OpenCodeApiClient = class {
|
|
|
1206
1253
|
method: "GET",
|
|
1207
1254
|
headers: {
|
|
1208
1255
|
Accept: "text/event-stream",
|
|
1209
|
-
"Cache-Control": "no-cache"
|
|
1256
|
+
"Cache-Control": "no-cache",
|
|
1257
|
+
...this.buildAuthHeaders()
|
|
1210
1258
|
},
|
|
1211
1259
|
signal: abortSignal
|
|
1212
1260
|
});
|
|
@@ -1257,6 +1305,7 @@ var OpenCodeApiClient = class {
|
|
|
1257
1305
|
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
1258
1306
|
const response = await fetch(`${this.baseUrl}/api/sessions`, {
|
|
1259
1307
|
method: "GET",
|
|
1308
|
+
headers: this.buildAuthHeaders(),
|
|
1260
1309
|
signal: controller.signal
|
|
1261
1310
|
});
|
|
1262
1311
|
clearTimeout(timer);
|
|
@@ -1294,6 +1343,17 @@ var OpenCodeApiClient = class {
|
|
|
1294
1343
|
* @param init fetch 请求选项
|
|
1295
1344
|
* @returns 解析后的 JSON 响应
|
|
1296
1345
|
*/
|
|
1346
|
+
/**
|
|
1347
|
+
* 构建包含认证信息的请求 headers
|
|
1348
|
+
*/
|
|
1349
|
+
buildAuthHeaders() {
|
|
1350
|
+
const headers = {};
|
|
1351
|
+
if (this.apiKey) {
|
|
1352
|
+
const credentials = Buffer.from(`opencode:${this.apiKey}`).toString("base64");
|
|
1353
|
+
headers["Authorization"] = `Basic ${credentials}`;
|
|
1354
|
+
}
|
|
1355
|
+
return headers;
|
|
1356
|
+
}
|
|
1297
1357
|
async request(path, init) {
|
|
1298
1358
|
const controller = new AbortController();
|
|
1299
1359
|
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
@@ -1302,6 +1362,7 @@ var OpenCodeApiClient = class {
|
|
|
1302
1362
|
...init,
|
|
1303
1363
|
headers: {
|
|
1304
1364
|
"Content-Type": "application/json",
|
|
1365
|
+
...this.buildAuthHeaders(),
|
|
1305
1366
|
...init.headers
|
|
1306
1367
|
},
|
|
1307
1368
|
signal: controller.signal
|
|
@@ -1395,18 +1456,36 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1395
1456
|
backend = "opencode";
|
|
1396
1457
|
apiClient = null;
|
|
1397
1458
|
servePort;
|
|
1459
|
+
/** 远程服务器 URL(提供时跳过自启 serve) */
|
|
1460
|
+
remoteServerUrl;
|
|
1461
|
+
/** API Key(用于 Basic Auth) */
|
|
1462
|
+
apiKey;
|
|
1398
1463
|
/**
|
|
1399
1464
|
* @param processManager 进程管理器实例
|
|
1400
|
-
* @param
|
|
1465
|
+
* @param config 可选的后端配置
|
|
1466
|
+
* - serverUrl: 远程 OpenCode 服务地址,提供时跳过自启 serve
|
|
1467
|
+
* - apiKey: API Key,用于 Basic Auth 认证
|
|
1468
|
+
* - options.servePort: 本地 serve 端口(仅自启模式)
|
|
1401
1469
|
*/
|
|
1402
|
-
constructor(processManager,
|
|
1403
|
-
super(processManager);
|
|
1404
|
-
|
|
1470
|
+
constructor(processManager, config) {
|
|
1471
|
+
super(processManager, config);
|
|
1472
|
+
const opts = config?.options ?? {};
|
|
1473
|
+
this.remoteServerUrl = config?.serverUrl ?? opts.serverUrl;
|
|
1474
|
+
this.apiKey = config?.apiKey ?? opts.apiKey;
|
|
1475
|
+
this.servePort = opts.servePort ?? DEFAULT_SERVE_PORT;
|
|
1405
1476
|
}
|
|
1406
1477
|
/**
|
|
1407
1478
|
* 检查 opencode 命令是否可用
|
|
1408
1479
|
*/
|
|
1409
1480
|
async isAvailable() {
|
|
1481
|
+
if (this.remoteServerUrl) {
|
|
1482
|
+
try {
|
|
1483
|
+
const client = new OpenCodeApiClient(this.remoteServerUrl, this.apiKey);
|
|
1484
|
+
return await client.healthCheck();
|
|
1485
|
+
} catch {
|
|
1486
|
+
return false;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1410
1489
|
try {
|
|
1411
1490
|
const proc = new EphemeralProcess();
|
|
1412
1491
|
const result = await proc.execute({
|
|
@@ -1445,6 +1524,7 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1445
1524
|
"system-prompt",
|
|
1446
1525
|
"serve-mode"
|
|
1447
1526
|
]),
|
|
1527
|
+
streamingMode: "delta",
|
|
1448
1528
|
outputFormats: ["text"],
|
|
1449
1529
|
testedVersionRange: TESTED_VERSION_RANGE
|
|
1450
1530
|
};
|
|
@@ -1582,8 +1662,20 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1582
1662
|
const healthy = await this.apiClient.healthCheck();
|
|
1583
1663
|
if (healthy) return this.apiClient;
|
|
1584
1664
|
}
|
|
1665
|
+
if (this.remoteServerUrl) {
|
|
1666
|
+
const client2 = new OpenCodeApiClient(this.remoteServerUrl, this.apiKey);
|
|
1667
|
+
const healthy = await client2.healthCheck();
|
|
1668
|
+
if (!healthy) {
|
|
1669
|
+
throw new Error(
|
|
1670
|
+
`\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B OpenCode \u670D\u52A1: ${this.remoteServerUrl}`
|
|
1671
|
+
);
|
|
1672
|
+
}
|
|
1673
|
+
this.apiClient = client2;
|
|
1674
|
+
return client2;
|
|
1675
|
+
}
|
|
1585
1676
|
const client = new OpenCodeApiClient(
|
|
1586
|
-
`http://localhost:${this.servePort}
|
|
1677
|
+
`http://localhost:${this.servePort}`,
|
|
1678
|
+
this.apiKey
|
|
1587
1679
|
);
|
|
1588
1680
|
const alreadyRunning = await client.healthCheck();
|
|
1589
1681
|
if (alreadyRunning) {
|
|
@@ -1635,6 +1727,54 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1635
1727
|
}
|
|
1636
1728
|
};
|
|
1637
1729
|
|
|
1730
|
+
// src/session/interactive-session.ts
|
|
1731
|
+
var InteractiveSession = class {
|
|
1732
|
+
initialSessionId;
|
|
1733
|
+
resolvedSessionId;
|
|
1734
|
+
adapter;
|
|
1735
|
+
baseRequest;
|
|
1736
|
+
firstSent = false;
|
|
1737
|
+
closed = false;
|
|
1738
|
+
constructor(initialSessionId, adapter, baseRequest) {
|
|
1739
|
+
this.initialSessionId = initialSessionId;
|
|
1740
|
+
this.adapter = adapter;
|
|
1741
|
+
this.baseRequest = baseRequest;
|
|
1742
|
+
}
|
|
1743
|
+
/** 当前生效的 sessionId(首次 send 后反映 CLI 返回的真实 ID) */
|
|
1744
|
+
get sessionId() {
|
|
1745
|
+
return this.resolvedSessionId ?? this.initialSessionId;
|
|
1746
|
+
}
|
|
1747
|
+
/** 会话是否仍然活跃 */
|
|
1748
|
+
get isActive() {
|
|
1749
|
+
return !this.closed;
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* 发送一条消息,返回流式响应。
|
|
1753
|
+
* 首次调用使用 initialSessionId,后续自动附加 resume。
|
|
1754
|
+
*/
|
|
1755
|
+
async *send(prompt) {
|
|
1756
|
+
if (this.closed) {
|
|
1757
|
+
throw new SessionNotFoundError(this.sessionId);
|
|
1758
|
+
}
|
|
1759
|
+
const request = {
|
|
1760
|
+
...this.baseRequest,
|
|
1761
|
+
prompt,
|
|
1762
|
+
...this.firstSent ? { resume: this.sessionId } : {}
|
|
1763
|
+
};
|
|
1764
|
+
for await (const msg of this.adapter.query(request)) {
|
|
1765
|
+
if (!this.firstSent && msg.sessionId) {
|
|
1766
|
+
this.resolvedSessionId = msg.sessionId;
|
|
1767
|
+
}
|
|
1768
|
+
yield msg;
|
|
1769
|
+
}
|
|
1770
|
+
this.firstSent = true;
|
|
1771
|
+
}
|
|
1772
|
+
/** 关闭会话(仅清理内部状态,无进程需要释放) */
|
|
1773
|
+
close() {
|
|
1774
|
+
this.closed = true;
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
|
|
1638
1778
|
// src/porygon.ts
|
|
1639
1779
|
var Porygon = class extends EventEmitter2 {
|
|
1640
1780
|
config;
|
|
@@ -1661,7 +1801,12 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1661
1801
|
};
|
|
1662
1802
|
const claudeAdapter = new ClaudeAdapter(this.processManager, mergedClaudeConfig);
|
|
1663
1803
|
this.registerAdapter(claudeAdapter);
|
|
1664
|
-
const
|
|
1804
|
+
const opencodeConfig = this.config.backends?.["opencode"];
|
|
1805
|
+
const mergedOpencodeConfig = {
|
|
1806
|
+
...opencodeConfig,
|
|
1807
|
+
proxy: opencodeConfig?.proxy ?? this.config.proxy
|
|
1808
|
+
};
|
|
1809
|
+
const opencodeAdapter = new OpenCodeAdapter(this.processManager, mergedOpencodeConfig);
|
|
1665
1810
|
this.registerAdapter(opencodeAdapter);
|
|
1666
1811
|
}
|
|
1667
1812
|
/**
|
|
@@ -1729,6 +1874,21 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1729
1874
|
}
|
|
1730
1875
|
return resultText;
|
|
1731
1876
|
}
|
|
1877
|
+
/**
|
|
1878
|
+
* 创建交互式多轮对话会话。
|
|
1879
|
+
* 自动管理 sessionId 和 resume,对调用方透明。
|
|
1880
|
+
*/
|
|
1881
|
+
session(options) {
|
|
1882
|
+
const backend = options?.backend ?? this.config.defaultBackend ?? "claude";
|
|
1883
|
+
const adapter = this.getAdapter(backend);
|
|
1884
|
+
const merged = this.mergeRequest({ ...options, prompt: "" }, backend);
|
|
1885
|
+
const { prompt: _, ...baseRequest } = merged;
|
|
1886
|
+
return new InteractiveSession(
|
|
1887
|
+
crypto.randomUUID(),
|
|
1888
|
+
adapter,
|
|
1889
|
+
baseRequest
|
|
1890
|
+
);
|
|
1891
|
+
}
|
|
1732
1892
|
/**
|
|
1733
1893
|
* 注册拦截器
|
|
1734
1894
|
* @param direction 拦截方向
|
|
@@ -1753,29 +1913,45 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1753
1913
|
return this.getAdapter(backend).listModels();
|
|
1754
1914
|
}
|
|
1755
1915
|
/**
|
|
1756
|
-
*
|
|
1916
|
+
* 检查单个后端的健康状态
|
|
1917
|
+
* @param backend 后端名称
|
|
1757
1918
|
*/
|
|
1758
|
-
async
|
|
1759
|
-
const
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
if (available) {
|
|
1765
|
-
compatibility = await adapter.checkCompatibility();
|
|
1766
|
-
if (!compatibility.supported) {
|
|
1767
|
-
this.emit(
|
|
1768
|
-
"health:degraded",
|
|
1769
|
-
name,
|
|
1770
|
-
compatibility.warnings.join("; ")
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
return [name, { available, compatibility }];
|
|
1775
|
-
} catch (err) {
|
|
1776
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1777
|
-
return [name, { available: false, compatibility: null, error: errorMsg }];
|
|
1919
|
+
async checkBackend(backend) {
|
|
1920
|
+
const adapter = this.getAdapter(backend);
|
|
1921
|
+
try {
|
|
1922
|
+
const available = await adapter.isAvailable();
|
|
1923
|
+
if (!available) {
|
|
1924
|
+
return { available: false };
|
|
1778
1925
|
}
|
|
1926
|
+
const compat = await adapter.checkCompatibility();
|
|
1927
|
+
const result = {
|
|
1928
|
+
available: true,
|
|
1929
|
+
version: compat.version,
|
|
1930
|
+
supported: compat.supported
|
|
1931
|
+
};
|
|
1932
|
+
if (compat.warnings.length > 0) {
|
|
1933
|
+
result.warnings = compat.warnings;
|
|
1934
|
+
}
|
|
1935
|
+
if (!compat.supported) {
|
|
1936
|
+
this.emit("health:degraded", backend, compat.warnings.join("; "));
|
|
1937
|
+
}
|
|
1938
|
+
return result;
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
return {
|
|
1941
|
+
available: false,
|
|
1942
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1943
|
+
};
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* 对所有已注册后端进行健康检查。
|
|
1948
|
+
* 返回扁平化结构,包含 version/supported/warnings 等字段。
|
|
1949
|
+
*/
|
|
1950
|
+
async healthCheck() {
|
|
1951
|
+
const entries = Array.from(this.adapters.keys());
|
|
1952
|
+
const checks = entries.map(async (name) => {
|
|
1953
|
+
const result = await this.checkBackend(name);
|
|
1954
|
+
return [name, result];
|
|
1779
1955
|
});
|
|
1780
1956
|
const settled = await Promise.allSettled(checks);
|
|
1781
1957
|
const results = {};
|
|
@@ -1831,7 +2007,16 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1831
2007
|
this.sessionManager.clearCache();
|
|
1832
2008
|
}
|
|
1833
2009
|
/**
|
|
1834
|
-
*
|
|
2010
|
+
* 合并请求参数与配置默认值。
|
|
2011
|
+
*
|
|
2012
|
+
* 合并策略:
|
|
2013
|
+
* - model: request > backendConfig > 不设置(后端使用自身默认值)
|
|
2014
|
+
* - timeoutMs: request > defaults
|
|
2015
|
+
* - maxTurns: request > defaults
|
|
2016
|
+
* - cwd: request > backendConfig
|
|
2017
|
+
* - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
|
|
2018
|
+
* 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
|
|
2019
|
+
*
|
|
1835
2020
|
* @param request 原始请求
|
|
1836
2021
|
* @param backend 后端名称
|
|
1837
2022
|
* @returns 合并后的请求
|
|
@@ -1951,6 +2136,7 @@ export {
|
|
|
1951
2136
|
AgentTimeoutError,
|
|
1952
2137
|
ClaudeAdapter,
|
|
1953
2138
|
ConfigValidationError,
|
|
2139
|
+
InteractiveSession,
|
|
1954
2140
|
InterceptorRejectedError,
|
|
1955
2141
|
OpenCodeAdapter,
|
|
1956
2142
|
Porygon,
|