@snack-kit/porygon 0.1.0 → 0.2.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 +146 -43
- package/dist/index.cjs +2112 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +768 -0
- package/dist/index.d.ts +64 -11
- package/dist/index.js +169 -62
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,15 @@ interface BackendConfig {
|
|
|
15
15
|
appendSystemPrompt?: string;
|
|
16
16
|
proxy?: ProxyConfig;
|
|
17
17
|
cwd?: string;
|
|
18
|
-
/**
|
|
18
|
+
/** 远程服务地址(OpenCode serve 模式) */
|
|
19
|
+
serverUrl?: string;
|
|
20
|
+
/** API Key 认证 */
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
/** 是否为交互模式,false 时 Claude 会添加 --dangerously-skip-permissions */
|
|
23
|
+
interactive?: boolean;
|
|
24
|
+
/** CLI 可执行文件路径(如 Claude CLI 的自定义安装路径) */
|
|
25
|
+
cliPath?: string;
|
|
26
|
+
/** 透传给 CLI 的后端特定选项(向后兼容,推荐使用上方的显式字段) */
|
|
19
27
|
options?: Record<string, unknown>;
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
@@ -84,6 +92,13 @@ interface AgentSystemMessage extends BaseAgentMessage {
|
|
|
84
92
|
interface AgentAssistantMessage extends BaseAgentMessage {
|
|
85
93
|
type: "assistant";
|
|
86
94
|
text: string;
|
|
95
|
+
/**
|
|
96
|
+
* 标记此消息为一个 turn 的完整文本汇总。
|
|
97
|
+
* 当为 true 时,text 内容与之前发出的 stream_chunk 消息完全重复,
|
|
98
|
+
* 调用方如果已通过 stream_chunk 累加文本,应忽略此消息的 text 以避免重复。
|
|
99
|
+
* run() 和拦截器仍会使用此消息。
|
|
100
|
+
*/
|
|
101
|
+
turnComplete?: boolean;
|
|
87
102
|
}
|
|
88
103
|
interface AgentToolUseMessage extends BaseAgentMessage {
|
|
89
104
|
type: "tool_use";
|
|
@@ -114,6 +129,12 @@ interface AgentErrorMessage extends BaseAgentMessage {
|
|
|
114
129
|
*/
|
|
115
130
|
interface AdapterCapabilities {
|
|
116
131
|
features: Set<"streaming" | "session-resume" | "system-prompt" | "tool-restriction" | "mcp" | "subagents" | "worktree" | "budget-limit" | "serve-mode">;
|
|
132
|
+
/**
|
|
133
|
+
* 流式输出模式:
|
|
134
|
+
* - 'delta': 后端原生产生增量 stream_chunk 事件(如 OpenCode)
|
|
135
|
+
* - 'chunked': 适配器将完整 assistant 消息拆分为 stream_chunk + assistant(如 Claude)
|
|
136
|
+
*/
|
|
137
|
+
streamingMode: "delta" | "chunked";
|
|
117
138
|
outputFormats: string[];
|
|
118
139
|
testedVersionRange: string;
|
|
119
140
|
}
|
|
@@ -159,6 +180,8 @@ interface IAgentAdapter {
|
|
|
159
180
|
/** 获取可用模型列表 */
|
|
160
181
|
listModels(): Promise<ModelInfo[]>;
|
|
161
182
|
abort(sessionId: string): void;
|
|
183
|
+
/** 删除会话及其资源(可选实现) */
|
|
184
|
+
deleteSession?(sessionId: string): Promise<void>;
|
|
162
185
|
dispose(): Promise<void>;
|
|
163
186
|
}
|
|
164
187
|
/**
|
|
@@ -186,6 +209,16 @@ interface InterceptorContext {
|
|
|
186
209
|
*/
|
|
187
210
|
type InterceptorFn = (text: string, context: InterceptorContext) => string | boolean | undefined | Promise<string | boolean | undefined>;
|
|
188
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 健康检查结果(扁平化结构)
|
|
214
|
+
*/
|
|
215
|
+
interface HealthCheckResult {
|
|
216
|
+
available: boolean;
|
|
217
|
+
version?: string;
|
|
218
|
+
supported?: boolean;
|
|
219
|
+
warnings?: string[];
|
|
220
|
+
error?: string;
|
|
221
|
+
}
|
|
189
222
|
/**
|
|
190
223
|
* Porygon 事件类型定义
|
|
191
224
|
*/
|
|
@@ -255,13 +288,15 @@ declare class Porygon extends EventEmitter {
|
|
|
255
288
|
*/
|
|
256
289
|
listModels(backend?: string): Promise<ModelInfo[]>;
|
|
257
290
|
/**
|
|
258
|
-
*
|
|
291
|
+
* 检查单个后端的健康状态
|
|
292
|
+
* @param backend 后端名称
|
|
259
293
|
*/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
checkBackend(backend: string): Promise<HealthCheckResult>;
|
|
295
|
+
/**
|
|
296
|
+
* 对所有已注册后端进行健康检查。
|
|
297
|
+
* 返回扁平化结构,包含 version/supported/warnings 等字段。
|
|
298
|
+
*/
|
|
299
|
+
healthCheck(): Promise<Record<string, HealthCheckResult>>;
|
|
265
300
|
/**
|
|
266
301
|
* 读取或更新后端设置
|
|
267
302
|
* @param backend 后端名称
|
|
@@ -286,7 +321,16 @@ declare class Porygon extends EventEmitter {
|
|
|
286
321
|
*/
|
|
287
322
|
dispose(): Promise<void>;
|
|
288
323
|
/**
|
|
289
|
-
*
|
|
324
|
+
* 合并请求参数与配置默认值。
|
|
325
|
+
*
|
|
326
|
+
* 合并策略:
|
|
327
|
+
* - model: request > backendConfig > 不设置(后端使用自身默认值)
|
|
328
|
+
* - timeoutMs: request > defaults
|
|
329
|
+
* - maxTurns: request > defaults
|
|
330
|
+
* - cwd: request > backendConfig
|
|
331
|
+
* - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
|
|
332
|
+
* 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
|
|
333
|
+
*
|
|
290
334
|
* @param request 原始请求
|
|
291
335
|
* @param backend 后端名称
|
|
292
336
|
* @returns 合并后的请求
|
|
@@ -588,6 +632,8 @@ declare abstract class AbstractAgentAdapter implements IAgentAdapter {
|
|
|
588
632
|
*/
|
|
589
633
|
declare class ClaudeAdapter extends AbstractAgentAdapter {
|
|
590
634
|
readonly backend = "claude";
|
|
635
|
+
/** CLI 命令名或路径 */
|
|
636
|
+
private get cliCommand();
|
|
591
637
|
/**
|
|
592
638
|
* 检查 Claude CLI 是否可用
|
|
593
639
|
*/
|
|
@@ -646,11 +692,18 @@ declare class OpenCodeAdapter extends AbstractAgentAdapter {
|
|
|
646
692
|
readonly backend = "opencode";
|
|
647
693
|
private apiClient;
|
|
648
694
|
private servePort;
|
|
695
|
+
/** 远程服务器 URL(提供时跳过自启 serve) */
|
|
696
|
+
private remoteServerUrl?;
|
|
697
|
+
/** API Key(用于 Basic Auth) */
|
|
698
|
+
private apiKey?;
|
|
649
699
|
/**
|
|
650
700
|
* @param processManager 进程管理器实例
|
|
651
|
-
* @param
|
|
701
|
+
* @param config 可选的后端配置
|
|
702
|
+
* - serverUrl: 远程 OpenCode 服务地址,提供时跳过自启 serve
|
|
703
|
+
* - apiKey: API Key,用于 Basic Auth 认证
|
|
704
|
+
* - options.servePort: 本地 serve 端口(仅自启模式)
|
|
652
705
|
*/
|
|
653
|
-
constructor(processManager: ProcessManager,
|
|
706
|
+
constructor(processManager: ProcessManager, config?: BackendConfig);
|
|
654
707
|
/**
|
|
655
708
|
* 检查 opencode 命令是否可用
|
|
656
709
|
*/
|
|
@@ -712,4 +765,4 @@ declare class OpenCodeAdapter extends AbstractAgentAdapter {
|
|
|
712
765
|
private getConfigPath;
|
|
713
766
|
}
|
|
714
767
|
|
|
715
|
-
export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type IAgentAdapter, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
|
|
768
|
+
export { AbstractAgentAdapter, type AdapterCapabilities, AdapterIncompatibleError, AdapterNotAvailableError, AdapterNotFoundError, type AgentAssistantMessage, type AgentErrorMessage, AgentExecutionError, type AgentMessage, type AgentResultMessage, type AgentStreamChunkMessage, type AgentSystemMessage, AgentTimeoutError, type AgentToolUseMessage, type BackendConfig, ClaudeAdapter, type CompatibilityResult, ConfigValidationError, type GuardAction, type GuardOptions, type HealthCheckResult, type IAgentAdapter, type InterceptorContext, type InterceptorDirection, type InterceptorFn, InterceptorRejectedError, type McpServerConfig, type ModelInfo, OpenCodeAdapter, Porygon, type PorygonConfig, PorygonError, type PorygonEvents, type PromptRequest, type ProxyConfig, type SessionInfo, type SessionListOptions, SessionNotFoundError, createInputGuard, createOutputGuard, createPorygon };
|
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({
|
|
@@ -780,15 +784,16 @@ function isResultEvent(event) {
|
|
|
780
784
|
// src/adapters/claude/message-mapper.ts
|
|
781
785
|
function mapClaudeEvent(event, sessionId) {
|
|
782
786
|
const timestamp = Date.now();
|
|
783
|
-
const
|
|
787
|
+
const resolvedSessionId = event.session_id ?? sessionId;
|
|
788
|
+
const baseFields = { timestamp, sessionId: resolvedSessionId, raw: event };
|
|
784
789
|
if (isSystemEvent(event)) {
|
|
785
|
-
return {
|
|
790
|
+
return [{
|
|
786
791
|
...baseFields,
|
|
787
792
|
type: "system",
|
|
788
793
|
model: event.model,
|
|
789
794
|
tools: event.tools,
|
|
790
795
|
cwd: event.cwd
|
|
791
|
-
};
|
|
796
|
+
}];
|
|
792
797
|
}
|
|
793
798
|
if (isAssistantEvent(event)) {
|
|
794
799
|
const content = event.message.content;
|
|
@@ -797,16 +802,16 @@ function mapClaudeEvent(event, sessionId) {
|
|
|
797
802
|
if (isStreamEvent(event)) {
|
|
798
803
|
const delta = event.event.delta;
|
|
799
804
|
if (delta?.type === "text_delta" && delta.text) {
|
|
800
|
-
return {
|
|
805
|
+
return [{
|
|
801
806
|
...baseFields,
|
|
802
807
|
type: "stream_chunk",
|
|
803
808
|
text: delta.text
|
|
804
|
-
};
|
|
809
|
+
}];
|
|
805
810
|
}
|
|
806
|
-
return
|
|
811
|
+
return [];
|
|
807
812
|
}
|
|
808
813
|
if (isResultEvent(event)) {
|
|
809
|
-
return {
|
|
814
|
+
return [{
|
|
810
815
|
...baseFields,
|
|
811
816
|
type: "result",
|
|
812
817
|
text: event.result,
|
|
@@ -814,38 +819,52 @@ function mapClaudeEvent(event, sessionId) {
|
|
|
814
819
|
durationMs: event.duration_ms,
|
|
815
820
|
inputTokens: event.input_tokens,
|
|
816
821
|
outputTokens: event.output_tokens
|
|
817
|
-
};
|
|
822
|
+
}];
|
|
818
823
|
}
|
|
819
|
-
return
|
|
824
|
+
return [];
|
|
820
825
|
}
|
|
821
826
|
function mapAssistantContent(blocks, baseFields) {
|
|
822
|
-
if (!blocks || blocks.length === 0) return
|
|
827
|
+
if (!blocks || blocks.length === 0) return [];
|
|
828
|
+
const messages = [];
|
|
823
829
|
const textParts = [];
|
|
824
|
-
let firstToolUse = null;
|
|
825
830
|
for (const block of blocks) {
|
|
826
831
|
if (block.type === "text" && block.text) {
|
|
827
832
|
textParts.push(block.text);
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
833
|
+
messages.push({
|
|
834
|
+
...baseFields,
|
|
835
|
+
type: "stream_chunk",
|
|
836
|
+
text: block.text
|
|
837
|
+
});
|
|
831
838
|
}
|
|
832
839
|
}
|
|
833
840
|
if (textParts.length > 0) {
|
|
834
|
-
|
|
841
|
+
messages.push({
|
|
835
842
|
...baseFields,
|
|
836
843
|
type: "assistant",
|
|
837
|
-
text: textParts.join("\n")
|
|
838
|
-
|
|
844
|
+
text: textParts.join("\n"),
|
|
845
|
+
turnComplete: true
|
|
846
|
+
});
|
|
839
847
|
}
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
848
|
+
for (const block of blocks) {
|
|
849
|
+
if (block.type === "tool_use") {
|
|
850
|
+
messages.push({
|
|
851
|
+
...baseFields,
|
|
852
|
+
type: "tool_use",
|
|
853
|
+
toolName: block.name,
|
|
854
|
+
input: block.input
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
if (block.type === "tool_result") {
|
|
858
|
+
messages.push({
|
|
859
|
+
...baseFields,
|
|
860
|
+
type: "tool_use",
|
|
861
|
+
toolName: block.tool_use_id,
|
|
862
|
+
input: {},
|
|
863
|
+
output: block.content
|
|
864
|
+
});
|
|
865
|
+
}
|
|
847
866
|
}
|
|
848
|
-
return
|
|
867
|
+
return messages;
|
|
849
868
|
}
|
|
850
869
|
|
|
851
870
|
// src/adapters/claude/index.ts
|
|
@@ -857,6 +876,10 @@ var CLAUDE_MODELS = [
|
|
|
857
876
|
];
|
|
858
877
|
var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
859
878
|
backend = "claude";
|
|
879
|
+
/** CLI 命令名或路径 */
|
|
880
|
+
get cliCommand() {
|
|
881
|
+
return this.config?.cliPath ?? "claude";
|
|
882
|
+
}
|
|
860
883
|
/**
|
|
861
884
|
* 检查 Claude CLI 是否可用
|
|
862
885
|
*/
|
|
@@ -865,7 +888,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
865
888
|
const proc = new EphemeralProcess();
|
|
866
889
|
const result = await proc.execute({
|
|
867
890
|
command: "which",
|
|
868
|
-
args: [
|
|
891
|
+
args: [this.cliCommand]
|
|
869
892
|
});
|
|
870
893
|
return result.exitCode === 0;
|
|
871
894
|
} catch {
|
|
@@ -878,7 +901,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
878
901
|
async getVersion() {
|
|
879
902
|
const proc = new EphemeralProcess();
|
|
880
903
|
const result = await proc.execute({
|
|
881
|
-
command:
|
|
904
|
+
command: this.cliCommand,
|
|
882
905
|
args: ["--version"]
|
|
883
906
|
});
|
|
884
907
|
if (result.exitCode !== 0) {
|
|
@@ -905,6 +928,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
905
928
|
"subagents",
|
|
906
929
|
"worktree"
|
|
907
930
|
]),
|
|
931
|
+
streamingMode: "chunked",
|
|
908
932
|
outputFormats: ["text", "json", "stream-json"],
|
|
909
933
|
testedVersionRange: ">=1.0.0"
|
|
910
934
|
};
|
|
@@ -936,13 +960,13 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
936
960
|
}
|
|
937
961
|
}
|
|
938
962
|
const streamOptions = {
|
|
939
|
-
command:
|
|
963
|
+
command: this.cliCommand,
|
|
940
964
|
args,
|
|
941
965
|
...cwd ? { cwd } : {},
|
|
942
966
|
env: cleanEnv,
|
|
943
967
|
timeoutMs: request.timeoutMs
|
|
944
968
|
};
|
|
945
|
-
const cmdStr = [
|
|
969
|
+
const cmdStr = [this.cliCommand, ...args.map((a) => /[\s"']/.test(a) ? JSON.stringify(a) : a)].join(" ");
|
|
946
970
|
const debugCmd = cwd ? `cd ${JSON.stringify(cwd)} && ${cmdStr}` : cmdStr;
|
|
947
971
|
yield {
|
|
948
972
|
type: "system",
|
|
@@ -963,8 +987,8 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
963
987
|
} catch {
|
|
964
988
|
continue;
|
|
965
989
|
}
|
|
966
|
-
const
|
|
967
|
-
|
|
990
|
+
const messages = mapClaudeEvent(parsed, sessionId);
|
|
991
|
+
for (const message of messages) {
|
|
968
992
|
yield message;
|
|
969
993
|
}
|
|
970
994
|
}
|
|
@@ -984,7 +1008,7 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
984
1008
|
args.push("--limit", String(options.limit));
|
|
985
1009
|
}
|
|
986
1010
|
const result = await proc.execute({
|
|
987
|
-
command:
|
|
1011
|
+
command: this.cliCommand,
|
|
988
1012
|
args,
|
|
989
1013
|
cwd: options?.cwd
|
|
990
1014
|
});
|
|
@@ -1108,6 +1132,11 @@ var ClaudeAdapter = class extends AbstractAgentAdapter {
|
|
|
1108
1132
|
if (request.maxTurns !== void 0) {
|
|
1109
1133
|
args.push("--max-turns", String(request.maxTurns));
|
|
1110
1134
|
}
|
|
1135
|
+
const interactive = this.config?.interactive;
|
|
1136
|
+
const skipPerms = interactive === false || request.backendOptions?.dangerouslySkipPermissions || this.config?.options?.dangerouslySkipPermissions;
|
|
1137
|
+
if (skipPerms) {
|
|
1138
|
+
args.push("--dangerously-skip-permissions");
|
|
1139
|
+
}
|
|
1111
1140
|
if (request.mcpServers) {
|
|
1112
1141
|
for (const [name, config] of Object.entries(request.mcpServers)) {
|
|
1113
1142
|
const serverSpec = {
|
|
@@ -1144,11 +1173,14 @@ var OpenCodeApiError = class extends Error {
|
|
|
1144
1173
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1145
1174
|
var OpenCodeApiClient = class {
|
|
1146
1175
|
baseUrl;
|
|
1176
|
+
apiKey;
|
|
1147
1177
|
/**
|
|
1148
1178
|
* @param baseUrl opencode serve 服务的基础 URL,例如 http://localhost:39393
|
|
1179
|
+
* @param apiKey 可选的 API Key,用于 Basic Auth 认证(username: opencode, password: apiKey)
|
|
1149
1180
|
*/
|
|
1150
|
-
constructor(baseUrl) {
|
|
1181
|
+
constructor(baseUrl, apiKey) {
|
|
1151
1182
|
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
1183
|
+
this.apiKey = apiKey;
|
|
1152
1184
|
}
|
|
1153
1185
|
/**
|
|
1154
1186
|
* 创建新会话
|
|
@@ -1206,7 +1238,8 @@ var OpenCodeApiClient = class {
|
|
|
1206
1238
|
method: "GET",
|
|
1207
1239
|
headers: {
|
|
1208
1240
|
Accept: "text/event-stream",
|
|
1209
|
-
"Cache-Control": "no-cache"
|
|
1241
|
+
"Cache-Control": "no-cache",
|
|
1242
|
+
...this.buildAuthHeaders()
|
|
1210
1243
|
},
|
|
1211
1244
|
signal: abortSignal
|
|
1212
1245
|
});
|
|
@@ -1257,6 +1290,7 @@ var OpenCodeApiClient = class {
|
|
|
1257
1290
|
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
1258
1291
|
const response = await fetch(`${this.baseUrl}/api/sessions`, {
|
|
1259
1292
|
method: "GET",
|
|
1293
|
+
headers: this.buildAuthHeaders(),
|
|
1260
1294
|
signal: controller.signal
|
|
1261
1295
|
});
|
|
1262
1296
|
clearTimeout(timer);
|
|
@@ -1294,6 +1328,17 @@ var OpenCodeApiClient = class {
|
|
|
1294
1328
|
* @param init fetch 请求选项
|
|
1295
1329
|
* @returns 解析后的 JSON 响应
|
|
1296
1330
|
*/
|
|
1331
|
+
/**
|
|
1332
|
+
* 构建包含认证信息的请求 headers
|
|
1333
|
+
*/
|
|
1334
|
+
buildAuthHeaders() {
|
|
1335
|
+
const headers = {};
|
|
1336
|
+
if (this.apiKey) {
|
|
1337
|
+
const credentials = Buffer.from(`opencode:${this.apiKey}`).toString("base64");
|
|
1338
|
+
headers["Authorization"] = `Basic ${credentials}`;
|
|
1339
|
+
}
|
|
1340
|
+
return headers;
|
|
1341
|
+
}
|
|
1297
1342
|
async request(path, init) {
|
|
1298
1343
|
const controller = new AbortController();
|
|
1299
1344
|
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
@@ -1302,6 +1347,7 @@ var OpenCodeApiClient = class {
|
|
|
1302
1347
|
...init,
|
|
1303
1348
|
headers: {
|
|
1304
1349
|
"Content-Type": "application/json",
|
|
1350
|
+
...this.buildAuthHeaders(),
|
|
1305
1351
|
...init.headers
|
|
1306
1352
|
},
|
|
1307
1353
|
signal: controller.signal
|
|
@@ -1395,18 +1441,36 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1395
1441
|
backend = "opencode";
|
|
1396
1442
|
apiClient = null;
|
|
1397
1443
|
servePort;
|
|
1444
|
+
/** 远程服务器 URL(提供时跳过自启 serve) */
|
|
1445
|
+
remoteServerUrl;
|
|
1446
|
+
/** API Key(用于 Basic Auth) */
|
|
1447
|
+
apiKey;
|
|
1398
1448
|
/**
|
|
1399
1449
|
* @param processManager 进程管理器实例
|
|
1400
|
-
* @param
|
|
1450
|
+
* @param config 可选的后端配置
|
|
1451
|
+
* - serverUrl: 远程 OpenCode 服务地址,提供时跳过自启 serve
|
|
1452
|
+
* - apiKey: API Key,用于 Basic Auth 认证
|
|
1453
|
+
* - options.servePort: 本地 serve 端口(仅自启模式)
|
|
1401
1454
|
*/
|
|
1402
|
-
constructor(processManager,
|
|
1403
|
-
super(processManager);
|
|
1404
|
-
|
|
1455
|
+
constructor(processManager, config) {
|
|
1456
|
+
super(processManager, config);
|
|
1457
|
+
const opts = config?.options ?? {};
|
|
1458
|
+
this.remoteServerUrl = config?.serverUrl ?? opts.serverUrl;
|
|
1459
|
+
this.apiKey = config?.apiKey ?? opts.apiKey;
|
|
1460
|
+
this.servePort = opts.servePort ?? DEFAULT_SERVE_PORT;
|
|
1405
1461
|
}
|
|
1406
1462
|
/**
|
|
1407
1463
|
* 检查 opencode 命令是否可用
|
|
1408
1464
|
*/
|
|
1409
1465
|
async isAvailable() {
|
|
1466
|
+
if (this.remoteServerUrl) {
|
|
1467
|
+
try {
|
|
1468
|
+
const client = new OpenCodeApiClient(this.remoteServerUrl, this.apiKey);
|
|
1469
|
+
return await client.healthCheck();
|
|
1470
|
+
} catch {
|
|
1471
|
+
return false;
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1410
1474
|
try {
|
|
1411
1475
|
const proc = new EphemeralProcess();
|
|
1412
1476
|
const result = await proc.execute({
|
|
@@ -1445,6 +1509,7 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1445
1509
|
"system-prompt",
|
|
1446
1510
|
"serve-mode"
|
|
1447
1511
|
]),
|
|
1512
|
+
streamingMode: "delta",
|
|
1448
1513
|
outputFormats: ["text"],
|
|
1449
1514
|
testedVersionRange: TESTED_VERSION_RANGE
|
|
1450
1515
|
};
|
|
@@ -1582,8 +1647,20 @@ var OpenCodeAdapter = class extends AbstractAgentAdapter {
|
|
|
1582
1647
|
const healthy = await this.apiClient.healthCheck();
|
|
1583
1648
|
if (healthy) return this.apiClient;
|
|
1584
1649
|
}
|
|
1650
|
+
if (this.remoteServerUrl) {
|
|
1651
|
+
const client2 = new OpenCodeApiClient(this.remoteServerUrl, this.apiKey);
|
|
1652
|
+
const healthy = await client2.healthCheck();
|
|
1653
|
+
if (!healthy) {
|
|
1654
|
+
throw new Error(
|
|
1655
|
+
`\u65E0\u6CD5\u8FDE\u63A5\u8FDC\u7A0B OpenCode \u670D\u52A1: ${this.remoteServerUrl}`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
this.apiClient = client2;
|
|
1659
|
+
return client2;
|
|
1660
|
+
}
|
|
1585
1661
|
const client = new OpenCodeApiClient(
|
|
1586
|
-
`http://localhost:${this.servePort}
|
|
1662
|
+
`http://localhost:${this.servePort}`,
|
|
1663
|
+
this.apiKey
|
|
1587
1664
|
);
|
|
1588
1665
|
const alreadyRunning = await client.healthCheck();
|
|
1589
1666
|
if (alreadyRunning) {
|
|
@@ -1661,7 +1738,12 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1661
1738
|
};
|
|
1662
1739
|
const claudeAdapter = new ClaudeAdapter(this.processManager, mergedClaudeConfig);
|
|
1663
1740
|
this.registerAdapter(claudeAdapter);
|
|
1664
|
-
const
|
|
1741
|
+
const opencodeConfig = this.config.backends?.["opencode"];
|
|
1742
|
+
const mergedOpencodeConfig = {
|
|
1743
|
+
...opencodeConfig,
|
|
1744
|
+
proxy: opencodeConfig?.proxy ?? this.config.proxy
|
|
1745
|
+
};
|
|
1746
|
+
const opencodeAdapter = new OpenCodeAdapter(this.processManager, mergedOpencodeConfig);
|
|
1665
1747
|
this.registerAdapter(opencodeAdapter);
|
|
1666
1748
|
}
|
|
1667
1749
|
/**
|
|
@@ -1753,29 +1835,45 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1753
1835
|
return this.getAdapter(backend).listModels();
|
|
1754
1836
|
}
|
|
1755
1837
|
/**
|
|
1756
|
-
*
|
|
1838
|
+
* 检查单个后端的健康状态
|
|
1839
|
+
* @param backend 后端名称
|
|
1757
1840
|
*/
|
|
1758
|
-
async
|
|
1759
|
-
const
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1777
|
-
return [name, { available: false, compatibility: null, error: errorMsg }];
|
|
1841
|
+
async checkBackend(backend) {
|
|
1842
|
+
const adapter = this.getAdapter(backend);
|
|
1843
|
+
try {
|
|
1844
|
+
const available = await adapter.isAvailable();
|
|
1845
|
+
if (!available) {
|
|
1846
|
+
return { available: false };
|
|
1847
|
+
}
|
|
1848
|
+
const compat = await adapter.checkCompatibility();
|
|
1849
|
+
const result = {
|
|
1850
|
+
available: true,
|
|
1851
|
+
version: compat.version,
|
|
1852
|
+
supported: compat.supported
|
|
1853
|
+
};
|
|
1854
|
+
if (compat.warnings.length > 0) {
|
|
1855
|
+
result.warnings = compat.warnings;
|
|
1856
|
+
}
|
|
1857
|
+
if (!compat.supported) {
|
|
1858
|
+
this.emit("health:degraded", backend, compat.warnings.join("; "));
|
|
1778
1859
|
}
|
|
1860
|
+
return result;
|
|
1861
|
+
} catch (err) {
|
|
1862
|
+
return {
|
|
1863
|
+
available: false,
|
|
1864
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* 对所有已注册后端进行健康检查。
|
|
1870
|
+
* 返回扁平化结构,包含 version/supported/warnings 等字段。
|
|
1871
|
+
*/
|
|
1872
|
+
async healthCheck() {
|
|
1873
|
+
const entries = Array.from(this.adapters.keys());
|
|
1874
|
+
const checks = entries.map(async (name) => {
|
|
1875
|
+
const result = await this.checkBackend(name);
|
|
1876
|
+
return [name, result];
|
|
1779
1877
|
});
|
|
1780
1878
|
const settled = await Promise.allSettled(checks);
|
|
1781
1879
|
const results = {};
|
|
@@ -1831,7 +1929,16 @@ var Porygon = class extends EventEmitter2 {
|
|
|
1831
1929
|
this.sessionManager.clearCache();
|
|
1832
1930
|
}
|
|
1833
1931
|
/**
|
|
1834
|
-
*
|
|
1932
|
+
* 合并请求参数与配置默认值。
|
|
1933
|
+
*
|
|
1934
|
+
* 合并策略:
|
|
1935
|
+
* - model: request > backendConfig > 不设置(后端使用自身默认值)
|
|
1936
|
+
* - timeoutMs: request > defaults
|
|
1937
|
+
* - maxTurns: request > defaults
|
|
1938
|
+
* - cwd: request > backendConfig
|
|
1939
|
+
* - appendSystemPrompt: **追加模式** — defaults + backendConfig + request 三层拼接(换行分隔)
|
|
1940
|
+
* 但如果 request.systemPrompt 已设置(替换模式),则忽略所有 appendSystemPrompt
|
|
1941
|
+
*
|
|
1835
1942
|
* @param request 原始请求
|
|
1836
1943
|
* @param backend 后端名称
|
|
1837
1944
|
* @returns 合并后的请求
|