aicodeswitch 6.0.0 → 6.0.1
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/bin/restore.js +67 -4
- package/bin/utils/managed-fields.js +12 -0
- package/dist/server/agent-map/activity-extractor.js +8 -0
- package/dist/server/agent-map/agent-map-service.js +10 -15
- package/dist/server/coding-plan.js +3 -0
- package/dist/server/config-managed-fields.js +20 -1
- package/dist/server/config-metadata.js +78 -1
- package/dist/server/fs-database.js +4 -1
- package/dist/server/main.js +405 -7
- package/dist/server/original-config-reader.js +71 -1
- package/dist/server/proxy-server.js +55 -8
- package/dist/server/session-migration.js +1 -0
- package/dist/ui/assets/index-BeM4AIzn.js +1187 -0
- package/dist/ui/assets/index-DqRzbMIU.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-CQVhBlBB.css +0 -1
- package/dist/ui/assets/index-QZBX5HHX.js +0 -1186
|
@@ -66,7 +66,7 @@ const compact_1 = require("./conversions/compact");
|
|
|
66
66
|
const coding_plan_1 = require("./coding-plan");
|
|
67
67
|
const coding_plan_headers_1 = require("./coding-plan-headers");
|
|
68
68
|
const auth_1 = require("./auth");
|
|
69
|
-
const SUPPORTED_TARGETS = ['claude-code', 'codex'];
|
|
69
|
+
const SUPPORTED_TARGETS = ['claude-code', 'codex', 'opencode'];
|
|
70
70
|
/**
|
|
71
71
|
* Fallback(回退原始配置)路径的虚拟供应商归属。
|
|
72
72
|
* 该路径转发的请求不属于任何用户配置的供应商/服务,故用一组固定 ID + 名称
|
|
@@ -129,6 +129,19 @@ function apiPathToClientFormat(apiPath) {
|
|
|
129
129
|
case '/v1/models': return null;
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* 根据客户端工具类型推断其原生 API 格式(Format)。
|
|
134
|
+
* - claude-code → claude(Anthropic Messages)
|
|
135
|
+
* - codex → responses(OpenAI Responses)
|
|
136
|
+
* - opencode → completions(OpenAI Chat Completions,经由 @ai-sdk/openai-compatible)
|
|
137
|
+
*/
|
|
138
|
+
function clientFormatForTool(tool) {
|
|
139
|
+
if (tool === 'codex')
|
|
140
|
+
return 'responses';
|
|
141
|
+
if (tool === 'opencode')
|
|
142
|
+
return 'completions';
|
|
143
|
+
return 'claude';
|
|
144
|
+
}
|
|
132
145
|
class ClaudeCompactResponseSanitizer extends stream_1.Transform {
|
|
133
146
|
constructor() {
|
|
134
147
|
super({ objectMode: true });
|
|
@@ -379,6 +392,9 @@ class ProxyServer {
|
|
|
379
392
|
if (path === '/codex' || path.startsWith('/codex/')) {
|
|
380
393
|
return 'codex';
|
|
381
394
|
}
|
|
395
|
+
if (path === '/opencode' || path.startsWith('/opencode/')) {
|
|
396
|
+
return 'opencode';
|
|
397
|
+
}
|
|
382
398
|
return undefined;
|
|
383
399
|
}
|
|
384
400
|
inferToolFromRequest(req) {
|
|
@@ -387,6 +403,8 @@ class ProxyServer {
|
|
|
387
403
|
return 'claude-code';
|
|
388
404
|
if (path.startsWith('/codex'))
|
|
389
405
|
return 'codex';
|
|
406
|
+
if (path.startsWith('/opencode'))
|
|
407
|
+
return 'opencode';
|
|
390
408
|
return 'claude-code';
|
|
391
409
|
}
|
|
392
410
|
buildRelayTags(relayed, useOriginalConfig = false) {
|
|
@@ -747,7 +765,7 @@ class ProxyServer {
|
|
|
747
765
|
tags: this.buildRelayTags(hasRelayAttempt),
|
|
748
766
|
});
|
|
749
767
|
// 确定目标类型
|
|
750
|
-
const targetType =
|
|
768
|
+
const targetType = this.inferToolFromRequest(req);
|
|
751
769
|
// 记录错误日志 - 包含请求详情和最后失败的服务信息
|
|
752
770
|
const _lastFailedVendor = lastFailedService ? this.dbManager.getVendorByServiceId(lastFailedService.id) : undefined;
|
|
753
771
|
yield this.dbManager.addErrorLog({
|
|
@@ -813,7 +831,7 @@ class ProxyServer {
|
|
|
813
831
|
}
|
|
814
832
|
// Add error log - 包含请求详情
|
|
815
833
|
if (!accessKeyCtx) {
|
|
816
|
-
const targetType =
|
|
834
|
+
const targetType = this.inferToolFromRequest(req);
|
|
817
835
|
yield this.dbManager.addErrorLog({
|
|
818
836
|
timestamp: Date.now(),
|
|
819
837
|
method: req.method,
|
|
@@ -855,6 +873,8 @@ class ProxyServer {
|
|
|
855
873
|
this.app.use('/claude-code', this.createFixedRouteHandler('claude-code'));
|
|
856
874
|
this.app.use('/codex/', this.createFixedRouteHandler('codex'));
|
|
857
875
|
this.app.use('/codex', this.createFixedRouteHandler('codex'));
|
|
876
|
+
this.app.use('/opencode/', this.createFixedRouteHandler('opencode'));
|
|
877
|
+
this.app.use('/opencode', this.createFixedRouteHandler('opencode'));
|
|
858
878
|
}
|
|
859
879
|
createFixedRouteHandler(targetType) {
|
|
860
880
|
return (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1184,6 +1204,9 @@ class ProxyServer {
|
|
|
1184
1204
|
else if (req.path.startsWith('/codex/')) {
|
|
1185
1205
|
tool = 'codex';
|
|
1186
1206
|
}
|
|
1207
|
+
else if (req.path.startsWith('/opencode/')) {
|
|
1208
|
+
tool = 'opencode';
|
|
1209
|
+
}
|
|
1187
1210
|
if (!tool)
|
|
1188
1211
|
return undefined;
|
|
1189
1212
|
// 优先检查会话级路由绑定
|
|
@@ -1224,6 +1247,9 @@ class ProxyServer {
|
|
|
1224
1247
|
else if (req.path.startsWith('/codex/')) {
|
|
1225
1248
|
targetType = 'codex';
|
|
1226
1249
|
}
|
|
1250
|
+
else if (req.path.startsWith('/opencode/')) {
|
|
1251
|
+
targetType = 'opencode';
|
|
1252
|
+
}
|
|
1227
1253
|
if (!targetType) {
|
|
1228
1254
|
return false;
|
|
1229
1255
|
}
|
|
@@ -1279,7 +1305,7 @@ class ProxyServer {
|
|
|
1279
1305
|
apiUrl: originalConfig.apiUrl,
|
|
1280
1306
|
apiKey: originalConfig.apiKey,
|
|
1281
1307
|
authType: originalConfig.authType,
|
|
1282
|
-
sourceType: originalConfig.sourceType || (targetType === 'claude-code' ? 'claude' : 'openai'),
|
|
1308
|
+
sourceType: originalConfig.sourceType || (targetType === 'claude-code' ? 'claude' : targetType === 'opencode' ? 'openai-chat' : 'openai'),
|
|
1283
1309
|
createdAt: Date.now(),
|
|
1284
1310
|
updatedAt: Date.now(),
|
|
1285
1311
|
};
|
|
@@ -3110,6 +3136,16 @@ class ProxyServer {
|
|
|
3110
3136
|
return sessionId[0] || null;
|
|
3111
3137
|
}
|
|
3112
3138
|
}
|
|
3139
|
+
else if (type === 'opencode') {
|
|
3140
|
+
// OpenCode 经 @ai-sdk/openai-compatible 发送 chat completions,尝试从 headers 提取 session 标识
|
|
3141
|
+
const sessionId = request.headers['session-id'] || request.headers['session_id'] || request.headers['x-session-id'];
|
|
3142
|
+
if (typeof sessionId === 'string') {
|
|
3143
|
+
return sessionId;
|
|
3144
|
+
}
|
|
3145
|
+
if (Array.isArray(sessionId)) {
|
|
3146
|
+
return sessionId[0] || null;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3113
3149
|
return null;
|
|
3114
3150
|
}
|
|
3115
3151
|
/**
|
|
@@ -3299,6 +3335,14 @@ class ProxyServer {
|
|
|
3299
3335
|
if (tool === 'codex' && this.isClaudeSource(source)) {
|
|
3300
3336
|
return `${apiUrl}/v1/messages`;
|
|
3301
3337
|
}
|
|
3338
|
+
// opencode 请求 claude 类型接口,使用 claude messages 接口处理
|
|
3339
|
+
if (tool === 'opencode' && this.isClaudeSource(source)) {
|
|
3340
|
+
return `${apiUrl}/v1/messages`;
|
|
3341
|
+
}
|
|
3342
|
+
// opencode 请求 openai(responses) 类型接口,使用 responses 接口处理
|
|
3343
|
+
if (tool === 'opencode' && this.isOpenAISource(source)) {
|
|
3344
|
+
return `${apiUrl}/v1/responses`;
|
|
3345
|
+
}
|
|
3302
3346
|
// 透传路径
|
|
3303
3347
|
return `${apiUrl}${originalPath}`;
|
|
3304
3348
|
}
|
|
@@ -3312,7 +3356,7 @@ class ProxyServer {
|
|
|
3312
3356
|
* @returns 转换后往服务商API接口的数据
|
|
3313
3357
|
*/
|
|
3314
3358
|
transformRequestToUpstream(tool, source, payloadData, targetModel, providerConfig, serverToolConfig, sanitizeBody) {
|
|
3315
|
-
const clientFormat = tool
|
|
3359
|
+
const clientFormat = clientFormatForTool(tool);
|
|
3316
3360
|
const upstreamFormat = (0, source_type_mapping_1.sourceTypeToFormat)(source);
|
|
3317
3361
|
const result = (0, index_1.transformRequest)({ fromFormat: clientFormat, toFormat: upstreamFormat, body: payloadData, providerConfig, serverToolConfig, sanitizeBody });
|
|
3318
3362
|
const body = result.body;
|
|
@@ -3332,7 +3376,7 @@ class ProxyServer {
|
|
|
3332
3376
|
* @param responseData
|
|
3333
3377
|
*/
|
|
3334
3378
|
transformResponseToTool(tool, source, responseData) {
|
|
3335
|
-
const clientFormat = tool
|
|
3379
|
+
const clientFormat = clientFormatForTool(tool);
|
|
3336
3380
|
const upstreamFormat = (0, source_type_mapping_1.sourceTypeToFormat)(source);
|
|
3337
3381
|
return (0, index_1.transformResponse)({ fromFormat: upstreamFormat, toFormat: clientFormat, response: responseData });
|
|
3338
3382
|
}
|
|
@@ -3343,7 +3387,7 @@ class ProxyServer {
|
|
|
3343
3387
|
* @returns 转换器实例和相关信息
|
|
3344
3388
|
*/
|
|
3345
3389
|
transformSSEToTool(targetType, sourceType) {
|
|
3346
|
-
const clientFormat = targetType
|
|
3390
|
+
const clientFormat = clientFormatForTool(targetType);
|
|
3347
3391
|
const upstreamFormat = (0, source_type_mapping_1.sourceTypeToFormat)(sourceType);
|
|
3348
3392
|
if (upstreamFormat === clientFormat) {
|
|
3349
3393
|
return { converter: null };
|
|
@@ -3514,7 +3558,7 @@ class ProxyServer {
|
|
|
3514
3558
|
let logged = false;
|
|
3515
3559
|
const extraTagsForLog = [];
|
|
3516
3560
|
// 编程套餐限制检查
|
|
3517
|
-
const clientFormat = targetType
|
|
3561
|
+
const clientFormat = clientFormatForTool(targetType);
|
|
3518
3562
|
if (!this.checkCodingPlan(req, res, service, clientFormat))
|
|
3519
3563
|
return;
|
|
3520
3564
|
// Compact 请求消息清理:确保 tool_use/tool_result 配对完整
|
|
@@ -4077,6 +4121,9 @@ class ProxyServer {
|
|
|
4077
4121
|
else if (targetType === 'codex' && req.path.startsWith('/codex')) {
|
|
4078
4122
|
pathToRequest = req.path.slice('/codex'.length);
|
|
4079
4123
|
}
|
|
4124
|
+
else if (targetType === 'opencode' && req.path.startsWith('/opencode')) {
|
|
4125
|
+
pathToRequest = req.path.slice('/opencode'.length);
|
|
4126
|
+
}
|
|
4080
4127
|
// 使用 mapRequestPathToUpstreamUrl 统一构建上游 URL
|
|
4081
4128
|
const model = rule.targetModel || (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model);
|
|
4082
4129
|
const apiUrl = this.resolveEffectiveApiUrl(service);
|
|
@@ -276,6 +276,7 @@ function generateMigrationPrompt(content, targetTool) {
|
|
|
276
276
|
const toolLabels = {
|
|
277
277
|
'claude-code': 'Claude Code',
|
|
278
278
|
'codex': 'Codex',
|
|
279
|
+
'opencode': 'OpenCode',
|
|
279
280
|
};
|
|
280
281
|
const sourceLabel = toolLabels[content.sourceTool];
|
|
281
282
|
const targetLabel = toolLabels[targetTool];
|