aws-runtime-bridge 1.7.45 → 1.7.46
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/routes/ai-sources.d.ts.map +1 -1
- package/dist/routes/ai-sources.js +16 -32
- package/dist/services/ai-source-apply.d.ts +43 -0
- package/dist/services/ai-source-apply.d.ts.map +1 -0
- package/dist/services/ai-source-apply.js +54 -0
- package/dist/services/ai-source-apply.test.d.ts +2 -0
- package/dist/services/ai-source-apply.test.d.ts.map +1 -0
- package/dist/services/ai-source-apply.test.js +81 -0
- package/dist/services/instance-init-service.d.ts +1 -1
- package/dist/services/instance-init-service.d.ts.map +1 -1
- package/dist/services/instance-init-service.js +25 -23
- package/dist/services/instance-init-service.test.d.ts +2 -0
- package/dist/services/instance-init-service.test.d.ts.map +1 -0
- package/dist/services/instance-init-service.test.js +139 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-sources.d.ts","sourceRoot":"","sources":["../../src/routes/ai-sources.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ai-sources.d.ts","sourceRoot":"","sources":["../../src/routes/ai-sources.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,eAAe,4CAAW,CAAC"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
1
|
import axios from 'axios';
|
|
2
|
+
import { Router } from 'express';
|
|
3
3
|
import { validateToken } from '../middleware/auth.js';
|
|
4
|
+
import { applyCurrentAiConfigToInstalledTools } from '../services/ai-source-apply.js';
|
|
4
5
|
import { getAiConfigService } from '../services/cc-switch-sdk.js';
|
|
5
|
-
import {
|
|
6
|
+
import { detectToolStatuses } from '../services/tool-installer.js';
|
|
6
7
|
import { createLogger } from '../utils/logger.js';
|
|
8
|
+
import { ensureEnabledTools } from '../utils/validation.js';
|
|
7
9
|
const log = createLogger('ai-sources');
|
|
8
10
|
export const aiSourcesRouter = Router();
|
|
9
11
|
function normalizeListedModels(payload) {
|
|
@@ -57,48 +59,30 @@ async function fetchRemoteModelList(baseUrl, apiKey) {
|
|
|
57
59
|
return normalizeListedModels(anthropicResponse.data);
|
|
58
60
|
}
|
|
59
61
|
aiSourcesRouter.post('/cc-switch/apply-ai-sources', validateToken, async (req, res) => {
|
|
60
|
-
const { agentId, template, claudecodeTemplateId, codexTemplateId, opencodeTemplateId, claudecode, codex, opencode, } = req.body || {};
|
|
62
|
+
const { agentId, template, ccSwitchEnabledTools, claudecodeTemplateId, codexTemplateId, opencodeTemplateId, claudecode, codex, opencode, } = req.body || {};
|
|
61
63
|
if (!agentId) {
|
|
62
64
|
res.status(400).json({ error: 'agentId is required' });
|
|
63
65
|
return;
|
|
64
66
|
}
|
|
65
67
|
try {
|
|
66
68
|
const aiConfigService = getAiConfigService();
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
})
|
|
75
|
-
const claudeSource = generatedConfig?.claudecode || claudecode;
|
|
76
|
-
const codexSource = generatedConfig?.codex || codex;
|
|
77
|
-
const opencodeSource = generatedConfig?.opencode || opencode;
|
|
78
|
-
let mergedClaude = null;
|
|
79
|
-
let mergedCodex = null;
|
|
80
|
-
let mergedOpencode = null;
|
|
81
|
-
if (claudeSource && typeof claudeSource === 'object') {
|
|
82
|
-
mergedClaude = await aiConfigService.applyClaudeCodeConfig(claudeSource);
|
|
83
|
-
}
|
|
84
|
-
if (codexSource && typeof codexSource === 'object') {
|
|
85
|
-
mergedCodex = await aiConfigService.applyCodexConfig(codexSource);
|
|
86
|
-
}
|
|
87
|
-
if (opencodeSource && typeof opencodeSource === 'object') {
|
|
88
|
-
mergedOpencode = await aiConfigService.applyOpenCodeConfig(opencodeSource);
|
|
89
|
-
}
|
|
69
|
+
const tools = ensureEnabledTools(ccSwitchEnabledTools);
|
|
70
|
+
const toolStatus = await detectToolStatuses(tools);
|
|
71
|
+
const appliedSummary = await applyCurrentAiConfigToInstalledTools({
|
|
72
|
+
template: template,
|
|
73
|
+
requestedSources: { claudecode, codex, opencode },
|
|
74
|
+
toolStatus,
|
|
75
|
+
aiConfigService,
|
|
76
|
+
});
|
|
90
77
|
res.json({
|
|
91
78
|
ok: true,
|
|
92
79
|
agentId: String(agentId),
|
|
93
80
|
claudecodeTemplateId: String(claudecodeTemplateId || ''),
|
|
94
81
|
codexTemplateId: String(codexTemplateId || ''),
|
|
95
82
|
opencodeTemplateId: String(opencodeTemplateId || ''),
|
|
96
|
-
generatedFromUnifiedTemplate:
|
|
97
|
-
applied:
|
|
98
|
-
|
|
99
|
-
codex: codexSource && typeof codexSource === 'object' ? Object.keys(codexSource) : [],
|
|
100
|
-
opencode: opencodeSource && typeof opencodeSource === 'object' ? Object.keys(opencodeSource) : [],
|
|
101
|
-
},
|
|
83
|
+
generatedFromUnifiedTemplate: appliedSummary.generatedFromUnifiedTemplate,
|
|
84
|
+
applied: appliedSummary.applied,
|
|
85
|
+
skippedTools: appliedSummary.skippedTools,
|
|
102
86
|
});
|
|
103
87
|
}
|
|
104
88
|
catch (error) {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type ClaudeCodeConfig, type CodexConfig, type OpenCodeConfig } from '@cc-switch/sdk';
|
|
2
|
+
import type { ToolInstallStatus } from '../types.js';
|
|
3
|
+
export interface AiSourceTemplateInput {
|
|
4
|
+
baseUrl?: unknown;
|
|
5
|
+
apiKey?: unknown;
|
|
6
|
+
model?: unknown;
|
|
7
|
+
models?: unknown;
|
|
8
|
+
}
|
|
9
|
+
export interface RequestedAiSources {
|
|
10
|
+
claudecode?: unknown;
|
|
11
|
+
codex?: unknown;
|
|
12
|
+
opencode?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface AiConfigApplier {
|
|
15
|
+
applyClaudeCodeConfig(config: ClaudeCodeConfig): Promise<ClaudeCodeConfig>;
|
|
16
|
+
applyCodexConfig(config: CodexConfig): Promise<CodexConfig>;
|
|
17
|
+
applyOpenCodeConfig(config: OpenCodeConfig): Promise<OpenCodeConfig>;
|
|
18
|
+
}
|
|
19
|
+
export interface ApplyCurrentAiConfigOptions {
|
|
20
|
+
template: AiSourceTemplateInput | null | undefined;
|
|
21
|
+
requestedSources?: RequestedAiSources;
|
|
22
|
+
toolStatus: Record<string, ToolInstallStatus>;
|
|
23
|
+
aiConfigService: AiConfigApplier;
|
|
24
|
+
}
|
|
25
|
+
export interface AppliedAiConfigSummary {
|
|
26
|
+
generatedFromUnifiedTemplate: boolean;
|
|
27
|
+
applied: {
|
|
28
|
+
claudecode: string[];
|
|
29
|
+
codex: string[];
|
|
30
|
+
opencode: string[];
|
|
31
|
+
};
|
|
32
|
+
skippedTools: string[];
|
|
33
|
+
merged: {
|
|
34
|
+
claudecode: ClaudeCodeConfig | null;
|
|
35
|
+
codex: CodexConfig | null;
|
|
36
|
+
opencode: OpenCodeConfig | null;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 主流程:用当前统一 AI 配置覆盖各 Agent CLI 配置,并跳过未安装 CLI。
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyCurrentAiConfigToInstalledTools(options: ApplyCurrentAiConfigOptions): Promise<AppliedAiConfigSummary>;
|
|
43
|
+
//# sourceMappingURL=ai-source-apply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-source-apply.d.ts","sourceRoot":"","sources":["../../src/services/ai-source-apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3E,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC5D,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,qBAAqB,GAAG,IAAI,GAAG,SAAS,CAAC;IACnD,gBAAgB,CAAC,EAAE,kBAAkB,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC9C,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,4BAA4B,EAAE,OAAO,CAAC;IACtC,OAAO,EAAE;QACP,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC;IACF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE;QACN,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAC;QACpC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAC;QAC1B,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;KACjC,CAAC;CACH;AAqBD;;GAEG;AACH,wBAAsB,oCAAoC,CACxD,OAAO,EAAE,2BAA2B,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAsCjC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { buildToolSpecificAiConfig, } from '@cc-switch/sdk';
|
|
2
|
+
function normalizeTemplate(template) {
|
|
3
|
+
return {
|
|
4
|
+
baseUrl: String(template?.baseUrl || ''),
|
|
5
|
+
apiKey: String(template?.apiKey || ''),
|
|
6
|
+
model: String(template?.model || ''),
|
|
7
|
+
models: Array.isArray(template?.models)
|
|
8
|
+
? template.models.map((item) => String(item || '').trim()).filter(Boolean)
|
|
9
|
+
: undefined,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function isInstalled(toolStatus, tool) {
|
|
13
|
+
return Boolean(toolStatus[tool]?.installed);
|
|
14
|
+
}
|
|
15
|
+
function isClaudeCodeInstalled(toolStatus) {
|
|
16
|
+
return isInstalled(toolStatus, 'claude') || isInstalled(toolStatus, 'claudecode');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 主流程:用当前统一 AI 配置覆盖各 Agent CLI 配置,并跳过未安装 CLI。
|
|
20
|
+
*/
|
|
21
|
+
export async function applyCurrentAiConfigToInstalledTools(options) {
|
|
22
|
+
const generatedConfig = options.template
|
|
23
|
+
? buildToolSpecificAiConfig(normalizeTemplate(options.template))
|
|
24
|
+
: null;
|
|
25
|
+
const skippedTools = Object.values(options.toolStatus)
|
|
26
|
+
.filter((item) => !item.installed)
|
|
27
|
+
.map((item) => item.tool);
|
|
28
|
+
let mergedClaude = null;
|
|
29
|
+
let mergedCodex = null;
|
|
30
|
+
let mergedOpencode = null;
|
|
31
|
+
if (generatedConfig?.claudecode && isClaudeCodeInstalled(options.toolStatus)) {
|
|
32
|
+
mergedClaude = await options.aiConfigService.applyClaudeCodeConfig(generatedConfig.claudecode);
|
|
33
|
+
}
|
|
34
|
+
if (generatedConfig?.codex && isInstalled(options.toolStatus, 'codex')) {
|
|
35
|
+
mergedCodex = await options.aiConfigService.applyCodexConfig(generatedConfig.codex);
|
|
36
|
+
}
|
|
37
|
+
if (generatedConfig?.opencode && isInstalled(options.toolStatus, 'opencode')) {
|
|
38
|
+
mergedOpencode = await options.aiConfigService.applyOpenCodeConfig(generatedConfig.opencode);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
generatedFromUnifiedTemplate: Boolean(generatedConfig),
|
|
42
|
+
applied: {
|
|
43
|
+
claudecode: generatedConfig?.claudecode && mergedClaude ? Object.keys(generatedConfig.claudecode) : [],
|
|
44
|
+
codex: generatedConfig?.codex && mergedCodex ? Object.keys(generatedConfig.codex) : [],
|
|
45
|
+
opencode: generatedConfig?.opencode && mergedOpencode ? Object.keys(generatedConfig.opencode) : [],
|
|
46
|
+
},
|
|
47
|
+
skippedTools,
|
|
48
|
+
merged: {
|
|
49
|
+
claudecode: mergedClaude,
|
|
50
|
+
codex: mergedCodex,
|
|
51
|
+
opencode: mergedOpencode,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-source-apply.test.d.ts","sourceRoot":"","sources":["../../src/services/ai-source-apply.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
function status(tool, installed) {
|
|
3
|
+
return {
|
|
4
|
+
tool,
|
|
5
|
+
installed,
|
|
6
|
+
executable: installed ? tool : null,
|
|
7
|
+
version: installed ? '1.0.0' : null,
|
|
8
|
+
installing: false,
|
|
9
|
+
error: installed ? null : 'not installed',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
describe('applyCurrentAiConfigToInstalledTools', () => {
|
|
13
|
+
it('forces current unified AI config and skips uninstalled tools', async () => {
|
|
14
|
+
const applyClaudeCodeConfig = vi.fn(async (config) => config);
|
|
15
|
+
const applyCodexConfig = vi.fn(async (config) => config);
|
|
16
|
+
const applyOpenCodeConfig = vi.fn(async (config) => config);
|
|
17
|
+
const { applyCurrentAiConfigToInstalledTools } = await import('./ai-source-apply.js');
|
|
18
|
+
const result = await applyCurrentAiConfigToInstalledTools({
|
|
19
|
+
template: {
|
|
20
|
+
baseUrl: 'https://current.example.com',
|
|
21
|
+
apiKey: 'sk-current',
|
|
22
|
+
model: 'gpt-current',
|
|
23
|
+
},
|
|
24
|
+
requestedSources: {
|
|
25
|
+
claudecode: { env: { ANTHROPIC_BASE_URL: 'https://stale.example.com' } },
|
|
26
|
+
codex: { model: 'stale-codex' },
|
|
27
|
+
opencode: { provider: { default: { options: { baseURL: 'https://stale.example.com/v1' } } } },
|
|
28
|
+
},
|
|
29
|
+
toolStatus: {
|
|
30
|
+
claude: status('claude', true),
|
|
31
|
+
codex: status('codex', false),
|
|
32
|
+
opencode: status('opencode', true),
|
|
33
|
+
},
|
|
34
|
+
aiConfigService: {
|
|
35
|
+
applyClaudeCodeConfig,
|
|
36
|
+
applyCodexConfig,
|
|
37
|
+
applyOpenCodeConfig,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
expect(applyClaudeCodeConfig).toHaveBeenCalledWith({
|
|
41
|
+
env: {
|
|
42
|
+
ANTHROPIC_BASE_URL: 'https://current.example.com',
|
|
43
|
+
ANTHROPIC_AUTH_TOKEN: 'sk-current',
|
|
44
|
+
ANTHROPIC_MODEL: 'gpt-current',
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
expect(applyCodexConfig).not.toHaveBeenCalled();
|
|
48
|
+
expect(applyOpenCodeConfig).toHaveBeenCalledWith(expect.objectContaining({
|
|
49
|
+
provider: expect.objectContaining({
|
|
50
|
+
default: expect.objectContaining({
|
|
51
|
+
options: expect.objectContaining({
|
|
52
|
+
baseURL: 'https://current.example.com/v1',
|
|
53
|
+
apiKey: 'sk-current',
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
}),
|
|
57
|
+
}));
|
|
58
|
+
expect(result.applied.codex).toEqual([]);
|
|
59
|
+
expect(result.skippedTools).toEqual(['codex']);
|
|
60
|
+
});
|
|
61
|
+
it('applies Claude Code config when the installed tool status uses the claudecode alias', async () => {
|
|
62
|
+
const applyClaudeCodeConfig = vi.fn(async (config) => config);
|
|
63
|
+
const { applyCurrentAiConfigToInstalledTools } = await import('./ai-source-apply.js');
|
|
64
|
+
await applyCurrentAiConfigToInstalledTools({
|
|
65
|
+
template: {
|
|
66
|
+
baseUrl: 'https://current.example.com',
|
|
67
|
+
apiKey: 'sk-current',
|
|
68
|
+
model: 'gpt-current',
|
|
69
|
+
},
|
|
70
|
+
toolStatus: {
|
|
71
|
+
claudecode: status('claudecode', true),
|
|
72
|
+
},
|
|
73
|
+
aiConfigService: {
|
|
74
|
+
applyClaudeCodeConfig,
|
|
75
|
+
applyCodexConfig: vi.fn(async (config) => config),
|
|
76
|
+
applyOpenCodeConfig: vi.fn(async (config) => config),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
expect(applyClaudeCodeConfig).toHaveBeenCalledOnce();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CcSwitchSdk } from '@cc-switch/sdk';
|
|
1
|
+
import type { CcSwitchSdk } from '@cc-switch/sdk';
|
|
2
2
|
import type { AdapterResult, InitLogEntry, LegacyMcpStateItem, LegacySkillStateItem, McpPayload, SkillPackage, SyncStateResult } from '../types.js';
|
|
3
3
|
export declare function syncLegacyStateFromSdk(agentId: string, sdk: CcSwitchSdk): Promise<SyncStateResult>;
|
|
4
4
|
export declare function initInstance(agentId: string, workspacePath: string, options: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instance-init-service.d.ts","sourceRoot":"","sources":["../../src/services/instance-init-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"instance-init-service.d.ts","sourceRoot":"","sources":["../../src/services/instance-init-service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAS,YAAY,EAAE,eAAe,EAAqB,MAAM,aAAa,CAAC;AAsB9K,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,eAAe,CAAC,CAyB1B;AAED,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE;IACP,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC;CAC3B,GACA,OAAO,CAAC;IACT,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,IAAI,CAAC,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE;QACL,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAC;KACpC,CAAC;IACF,GAAG,EAAE;QACH,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,oBAAoB,EAAE,CAAC;QAC/B,UAAU,EAAE,kBAAkB,EAAE,CAAC;KAClC,CAAC;CACH,CAAC,CA4MD"}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { promises as fs } from 'node:fs';
|
|
3
|
-
import
|
|
4
|
-
import { loadCcSwitchSdk, runCcSwitchAdapter, toSdkAppsFromEnabledTools } from './cc-switch-sdk.js';
|
|
2
|
+
import path from 'node:path';
|
|
5
3
|
import { ensureDir, installSkillPackageToSdk } from '../utils/file-utils.js';
|
|
6
4
|
import { buildMcpConfigContent, normalizeSdkMcpServerSpec, toLegacyMcpStateItem, toLegacySkillStateItem } from '../utils/mcp-utils.js';
|
|
7
|
-
import { ensureEnabledTools, parseBoolean } from '../utils/validation.js';
|
|
8
5
|
import { normalizeMcpPayload } from '../utils/mcp-utils.js';
|
|
9
|
-
import {
|
|
6
|
+
import { ensureEnabledTools, parseBoolean } from '../utils/validation.js';
|
|
7
|
+
import { loadCcSwitchSdk, runCcSwitchAdapter, toSdkAppsFromEnabledTools } from './cc-switch-sdk.js';
|
|
8
|
+
import { getAgentInitRoot, loadInstanceState, saveInstanceState } from './instance-state.js';
|
|
9
|
+
import { detectToolStatuses } from './tool-installer.js';
|
|
10
|
+
function pickInstalledTools(requestedTools, toolStatus) {
|
|
11
|
+
const installed = new Set(Object.values(toolStatus)
|
|
12
|
+
.filter((item) => item.installed)
|
|
13
|
+
.map((item) => item.tool));
|
|
14
|
+
return requestedTools.filter((tool) => installed.has(tool));
|
|
15
|
+
}
|
|
10
16
|
export async function syncLegacyStateFromSdk(agentId, sdk) {
|
|
11
17
|
const state = await loadInstanceState(agentId);
|
|
12
18
|
const mcpServers = sdk.getAllMcpServers().map((item) => toLegacyMcpStateItem(item));
|
|
@@ -51,12 +57,14 @@ export async function initInstance(agentId, workspacePath, options) {
|
|
|
51
57
|
installedTools: Object.values(detectedToolStatus).filter((item) => item.installed).map((item) => item.tool),
|
|
52
58
|
missingTools: Object.values(detectedToolStatus).filter((item) => !item.installed).map((item) => item.tool),
|
|
53
59
|
});
|
|
60
|
+
const installedTools = pickInstalledTools(tools, detectedToolStatus);
|
|
61
|
+
const missingTools = Object.values(detectedToolStatus).filter((item) => !item.installed);
|
|
54
62
|
const initResult = {
|
|
55
63
|
ok: true,
|
|
56
64
|
agentId: String(agentId),
|
|
57
65
|
workspacePath: normalizedWorkspace,
|
|
58
66
|
initRoot,
|
|
59
|
-
enabledTools:
|
|
67
|
+
enabledTools: installedTools,
|
|
60
68
|
toolStatus: detectedToolStatus,
|
|
61
69
|
logs,
|
|
62
70
|
skill: {
|
|
@@ -69,23 +77,17 @@ export async function initInstance(agentId, workspacePath, options) {
|
|
|
69
77
|
},
|
|
70
78
|
};
|
|
71
79
|
const state = await loadInstanceState(agentId);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const failedTools = Object.values(ensuredToolStatus).filter((item) => !item.installed);
|
|
76
|
-
if (failedTools.length > 0) {
|
|
77
|
-
pushLog('install-tools', 'error', '部分工具自动安装失败', {
|
|
78
|
-
failedTools: failedTools.map((item) => ({ tool: item.tool, error: item.error })),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
pushLog('install-tools', 'success', '工具安装步骤完成', {
|
|
83
|
-
installedTools: Object.values(ensuredToolStatus).map((item) => ({ tool: item.tool, version: item.version })),
|
|
80
|
+
if (missingTools.length > 0) {
|
|
81
|
+
pushLog('skip-uninstalled-tools', 'skipped', '已跳过未安装工具', {
|
|
82
|
+
skippedTools: missingTools.map((item) => ({ tool: item.tool, error: item.error })),
|
|
84
83
|
});
|
|
85
84
|
}
|
|
85
|
+
pushLog('select-installed-tools', 'success', '已选择可初始化工具', {
|
|
86
|
+
installedTools,
|
|
87
|
+
});
|
|
86
88
|
pushLog('load-sdk', 'running', '开始加载 CC Switch SDK');
|
|
87
89
|
const sdk = await loadCcSwitchSdk(agentId);
|
|
88
|
-
const sdkApps = toSdkAppsFromEnabledTools(
|
|
90
|
+
const sdkApps = toSdkAppsFromEnabledTools(installedTools);
|
|
89
91
|
pushLog('load-sdk', 'success', 'CC Switch SDK 加载完成', { sdkApps });
|
|
90
92
|
if (initResult.skill.enabled) {
|
|
91
93
|
pushLog('install-skills', 'running', '开始安装技能包', {
|
|
@@ -125,7 +127,7 @@ export async function initInstance(agentId, workspacePath, options) {
|
|
|
125
127
|
});
|
|
126
128
|
const normalizedMcpServers = (Array.isArray(options.mcpServers) ? options.mcpServers : [])
|
|
127
129
|
.map((item) => normalizeMcpPayload(item))
|
|
128
|
-
.filter((item) => item
|
|
130
|
+
.filter((item) => item !== null);
|
|
129
131
|
const appliedMcp = [];
|
|
130
132
|
for (const item of normalizedMcpServers) {
|
|
131
133
|
const allMcps = sdk.getAllMcpServers();
|
|
@@ -152,8 +154,8 @@ export async function initInstance(agentId, workspacePath, options) {
|
|
|
152
154
|
}
|
|
153
155
|
pushLog('persist-state', 'running', '开始保存实例状态');
|
|
154
156
|
state.initialized = true;
|
|
155
|
-
state.enabledTools =
|
|
156
|
-
state.toolStatus =
|
|
157
|
+
state.enabledTools = installedTools;
|
|
158
|
+
state.toolStatus = detectedToolStatus;
|
|
157
159
|
state.skillEnabled = initResult.skill.enabled;
|
|
158
160
|
state.mcpEnabled = initResult.mcp.enabled;
|
|
159
161
|
await saveInstanceState(agentId, state);
|
|
@@ -178,7 +180,7 @@ export async function initInstance(agentId, workspacePath, options) {
|
|
|
178
180
|
pushLog('adapter', 'success', '适配器回写完成', {
|
|
179
181
|
snapshot: adapterResult.snapshot,
|
|
180
182
|
});
|
|
181
|
-
pushLog('complete',
|
|
183
|
+
pushLog('complete', 'success', missingTools.length > 0 ? '实例初始化完成,未安装工具已跳过' : '实例初始化完成');
|
|
182
184
|
return {
|
|
183
185
|
...initResult,
|
|
184
186
|
adapterResult,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance-init-service.test.d.ts","sourceRoot":"","sources":["../../src/services/instance-init-service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
const detectToolStatusesMock = vi.fn();
|
|
6
|
+
const ensureToolsInstalledMock = vi.fn();
|
|
7
|
+
const installSkillPackageToSdkMock = vi.fn();
|
|
8
|
+
const runCcSwitchAdapterMock = vi.fn();
|
|
9
|
+
vi.mock('./tool-installer.js', () => ({
|
|
10
|
+
detectToolStatuses: detectToolStatusesMock,
|
|
11
|
+
ensureToolsInstalled: ensureToolsInstalledMock,
|
|
12
|
+
}));
|
|
13
|
+
vi.mock('./cc-switch-sdk.js', () => ({
|
|
14
|
+
loadCcSwitchSdk: vi.fn(async () => activeSdk),
|
|
15
|
+
runCcSwitchAdapter: runCcSwitchAdapterMock,
|
|
16
|
+
toSdkAppsFromEnabledTools: (enabledTools) => {
|
|
17
|
+
const tools = Array.isArray(enabledTools) ? enabledTools : [];
|
|
18
|
+
return {
|
|
19
|
+
claude: false,
|
|
20
|
+
claudecode: tools.includes('claude') || tools.includes('claudecode'),
|
|
21
|
+
codex: tools.includes('codex'),
|
|
22
|
+
gemini: tools.includes('gemini'),
|
|
23
|
+
opencode: tools.includes('opencode'),
|
|
24
|
+
openclaw: tools.includes('openclaw'),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
vi.mock('../utils/file-utils.js', async (importOriginal) => {
|
|
29
|
+
const original = await importOriginal();
|
|
30
|
+
return {
|
|
31
|
+
...original,
|
|
32
|
+
installSkillPackageToSdk: installSkillPackageToSdkMock,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
class MockSdk {
|
|
36
|
+
constructor() {
|
|
37
|
+
this.createdMcpInputs = [];
|
|
38
|
+
this.installedSkills = [];
|
|
39
|
+
this.mcpServers = [];
|
|
40
|
+
}
|
|
41
|
+
getAllSkills() {
|
|
42
|
+
return this.installedSkills;
|
|
43
|
+
}
|
|
44
|
+
getAllMcpServers() {
|
|
45
|
+
return this.mcpServers;
|
|
46
|
+
}
|
|
47
|
+
async createMcpServer(input) {
|
|
48
|
+
this.createdMcpInputs.push({ name: input.name, apps: input.apps });
|
|
49
|
+
const server = {
|
|
50
|
+
id: `mcp-${this.mcpServers.length + 1}`,
|
|
51
|
+
name: input.name,
|
|
52
|
+
server: input.server,
|
|
53
|
+
apps: input.apps,
|
|
54
|
+
};
|
|
55
|
+
this.mcpServers.push(server);
|
|
56
|
+
return server;
|
|
57
|
+
}
|
|
58
|
+
async updateMcpServer(id, input) {
|
|
59
|
+
const existing = this.mcpServers.find((server) => server.id === id);
|
|
60
|
+
if (!existing) {
|
|
61
|
+
throw new Error(`missing mcp ${id}`);
|
|
62
|
+
}
|
|
63
|
+
if (input.apps) {
|
|
64
|
+
existing.apps = input.apps;
|
|
65
|
+
}
|
|
66
|
+
if (input.server) {
|
|
67
|
+
existing.server = input.server;
|
|
68
|
+
}
|
|
69
|
+
return existing;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let activeSdk;
|
|
73
|
+
function status(tool, installed) {
|
|
74
|
+
return {
|
|
75
|
+
tool,
|
|
76
|
+
installed,
|
|
77
|
+
executable: installed ? tool : null,
|
|
78
|
+
version: installed ? '1.0.0' : null,
|
|
79
|
+
installing: false,
|
|
80
|
+
error: installed ? null : 'not installed',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
describe('initInstance installed-only tool initialization', () => {
|
|
84
|
+
const originalRuntimeHomeDir = process.env.AWS_RUNTIME_HOME_DIR;
|
|
85
|
+
let runtimeHomeDir;
|
|
86
|
+
beforeEach(async () => {
|
|
87
|
+
runtimeHomeDir = path.join(os.tmpdir(), `aws-init-installed-only-${Date.now()}`);
|
|
88
|
+
process.env.AWS_RUNTIME_HOME_DIR = runtimeHomeDir;
|
|
89
|
+
activeSdk = new MockSdk();
|
|
90
|
+
vi.clearAllMocks();
|
|
91
|
+
runCcSwitchAdapterMock.mockResolvedValue({ ok: true, snapshot: { skills: 0, mcpServers: 0 } });
|
|
92
|
+
installSkillPackageToSdkMock.mockImplementation(async (_sdk, skillPackage, _skillDir, apps) => {
|
|
93
|
+
const installed = {
|
|
94
|
+
id: 'skill-1',
|
|
95
|
+
name: String(skillPackage.skillName || 'demo-skill'),
|
|
96
|
+
directory: '/demo-skill',
|
|
97
|
+
apps,
|
|
98
|
+
installedAt: Date.now(),
|
|
99
|
+
};
|
|
100
|
+
activeSdk.installedSkills.push(installed);
|
|
101
|
+
return installed;
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
afterEach(async () => {
|
|
105
|
+
if (originalRuntimeHomeDir === undefined) {
|
|
106
|
+
delete process.env.AWS_RUNTIME_HOME_DIR;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
process.env.AWS_RUNTIME_HOME_DIR = originalRuntimeHomeDir;
|
|
110
|
+
}
|
|
111
|
+
await fs.rm(runtimeHomeDir, { recursive: true, force: true });
|
|
112
|
+
});
|
|
113
|
+
it('skips uninstalled tools and configures MCP and skills only for installed tools', async () => {
|
|
114
|
+
detectToolStatusesMock.mockResolvedValue({
|
|
115
|
+
claude: status('claude', true),
|
|
116
|
+
opencode: status('opencode', false),
|
|
117
|
+
codex: status('codex', false),
|
|
118
|
+
});
|
|
119
|
+
const { initInstance } = await import('./instance-init-service.js');
|
|
120
|
+
const result = await initInstance('agent-installed-only', runtimeHomeDir, {
|
|
121
|
+
ccSwitchEnabledTools: ['claude', 'opencode', 'codex'],
|
|
122
|
+
skillPackages: [{ skillName: 'demo-skill', downloadUrl: 'https://example.com/skill.zip' }],
|
|
123
|
+
mcpServers: [{ name: 'aws-mcp', command: 'node', args: ['server.js'] }],
|
|
124
|
+
});
|
|
125
|
+
expect(ensureToolsInstalledMock).not.toHaveBeenCalled();
|
|
126
|
+
expect(result.enabledTools).toEqual(['claude']);
|
|
127
|
+
expect(result.ok).toBe(true);
|
|
128
|
+
expect(installSkillPackageToSdkMock).toHaveBeenCalledWith(activeSdk, expect.objectContaining({ skillName: 'demo-skill' }), expect.any(String), expect.objectContaining({ claudecode: true, opencode: false, codex: false }));
|
|
129
|
+
expect(activeSdk.createdMcpInputs).toEqual([
|
|
130
|
+
{
|
|
131
|
+
name: 'aws-mcp',
|
|
132
|
+
apps: expect.objectContaining({ claudecode: true, opencode: false, codex: false }),
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
const statePath = path.join(runtimeHomeDir, '.aws-bridge', 'instances', 'agent-installed-only', 'state.json');
|
|
136
|
+
const state = JSON.parse(await fs.readFile(statePath, 'utf-8'));
|
|
137
|
+
expect(state.enabledTools).toEqual(['claude']);
|
|
138
|
+
});
|
|
139
|
+
});
|