aws-runtime-bridge 1.6.5 → 1.6.6
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/adapter/OpencodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/OpencodeSdkAdapter.js +5 -6
- package/dist/services/tool-installer.d.ts +19 -0
- package/dist/services/tool-installer.d.ts.map +1 -1
- package/dist/services/tool-installer.js +72 -20
- package/dist/services/tool-installer.test.js +20 -1
- package/dist/utils/sdk-package-loader.d.ts +12 -0
- package/dist/utils/sdk-package-loader.d.ts.map +1 -0
- package/dist/utils/sdk-package-loader.js +78 -0
- package/dist/utils/sdk-package-loader.test.d.ts +2 -0
- package/dist/utils/sdk-package-loader.test.d.ts.map +1 -0
- package/dist/utils/sdk-package-loader.test.js +66 -0
- package/package.json +2 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpencodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/OpencodeSdkAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"OpencodeSdkAdapter.d.ts","sourceRoot":"","sources":["../../src/adapter/OpencodeSdkAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgB3C,OAAO,KAAK,EAEV,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EAEnB,aAAa,EACd,MAAM,YAAY,CAAC;AA2GpB,qBAAa,kBAAmB,SAAQ,YAAa,YAAW,mBAAmB;IACjF,QAAQ,CAAC,UAAU,cAAc;IACjC,QAAQ,CAAC,WAAW,cAAc;IAElC,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,SAAS,CAAC,CAAoB;IAEhC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IA+I5E,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwC9D,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBnE,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7F,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,gBAAgB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAUnG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAclD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BxD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,EAAE;IAIzD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAItC,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAI3D,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI9D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAOpD,OAAO,IAAI,IAAI;IAaf;;;OAGG;YACW,OAAO;YA2BP,aAAa;IAmB3B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAahC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAY7B,OAAO,CAAC,YAAY;YAYN,UAAU;IAuBxB,OAAO,CAAC,mBAAmB;IA6H3B,OAAO,CAAC,gBAAgB;IA6GxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,2BAA2B;CAOpC"}
|
|
@@ -17,6 +17,7 @@ import * as net from 'node:net';
|
|
|
17
17
|
import * as os from 'node:os';
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { v4 as uuidv4 } from 'uuid';
|
|
20
|
+
import { BRIDGE_PACKAGE_ROOT, importBridgeSdkPackage } from '../utils/sdk-package-loader.js';
|
|
20
21
|
import { getToolActionInfo } from './types.js';
|
|
21
22
|
// ============ 可执行文件查找 ============
|
|
22
23
|
/**
|
|
@@ -359,7 +360,7 @@ export class OpencodeSdkAdapter extends EventEmitter {
|
|
|
359
360
|
async loadSdk() {
|
|
360
361
|
if (!this.sdkModule) {
|
|
361
362
|
try {
|
|
362
|
-
this.sdkModule = await
|
|
363
|
+
this.sdkModule = await importBridgeSdkPackage('@opencode-ai/sdk');
|
|
363
364
|
}
|
|
364
365
|
catch (error) {
|
|
365
366
|
if (isMissingPackageError(error, '@opencode-ai/sdk')) {
|
|
@@ -367,13 +368,11 @@ export class OpencodeSdkAdapter extends EventEmitter {
|
|
|
367
368
|
'OpenCode SDK provider is not installed.',
|
|
368
369
|
'',
|
|
369
370
|
'aws-runtime-bridge keeps Codex/OpenCode provider SDKs optional to reduce default install size.',
|
|
370
|
-
'To use SDK mode with command "opencode", install:',
|
|
371
|
+
'To use SDK mode with command "opencode", install the SDK into the aws-runtime-bridge package root:',
|
|
371
372
|
'',
|
|
372
|
-
|
|
373
|
+
` npm install --prefix "${BRIDGE_PACKAGE_ROOT}" @opencode-ai/sdk@latest opencode-ai@latest`,
|
|
373
374
|
'',
|
|
374
|
-
'If aws-runtime-bridge is installed locally,
|
|
375
|
-
'',
|
|
376
|
-
' npm install @opencode-ai/sdk',
|
|
375
|
+
'If aws-runtime-bridge is installed locally, run the same command with that local package path.',
|
|
377
376
|
'',
|
|
378
377
|
`Original error: ${error instanceof Error ? error.message : String(error)}`,
|
|
379
378
|
].join('\n'));
|
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import type { ToolInstallStatus } from "../types.js";
|
|
2
|
+
type ToolCommand = {
|
|
3
|
+
kind: "execFile";
|
|
4
|
+
command: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
display: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "shell";
|
|
9
|
+
display: string;
|
|
10
|
+
};
|
|
2
11
|
export declare const SUPPORTED_INSTALLABLE_TOOLS: readonly string[];
|
|
3
12
|
export declare const SUPPORTED_UNINSTALLABLE_TOOLS: readonly string[];
|
|
4
13
|
/**
|
|
5
14
|
* 返回工具对应的全局卸载命令副本,用于诊断与测试卸载覆盖范围。
|
|
6
15
|
*/
|
|
7
16
|
export declare function getToolUninstallCommands(tool: string): string[];
|
|
17
|
+
/**
|
|
18
|
+
* 返回工具对应的安装/卸载执行参数副本,用于验证 Windows 下不会把路径引号传给 npm。
|
|
19
|
+
*/
|
|
20
|
+
export declare function getToolCommandSpecs(tool: string, phase: "install" | "uninstall"): Array<{
|
|
21
|
+
command: string;
|
|
22
|
+
args: string[];
|
|
23
|
+
display: string;
|
|
24
|
+
kind: ToolCommand["kind"];
|
|
25
|
+
}>;
|
|
8
26
|
export declare function isVoltaShimPath(commandPath: string): boolean;
|
|
9
27
|
/**
|
|
10
28
|
* 检查单个工具的 CLI 可执行状态,供实例状态展示与初始化前判断使用。
|
|
@@ -22,4 +40,5 @@ export declare function ensureToolsInstalled(tools: string[]): Promise<Record<st
|
|
|
22
40
|
* 按工具定义执行全局卸载命令,随后重新检测并返回最新安装状态。
|
|
23
41
|
*/
|
|
24
42
|
export declare function uninstallTools(tools: string[]): Promise<Record<string, ToolInstallStatus>>;
|
|
43
|
+
export {};
|
|
25
44
|
//# sourceMappingURL=tool-installer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-installer.d.ts","sourceRoot":"","sources":["../../src/services/tool-installer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tool-installer.d.ts","sourceRoot":"","sources":["../../src/services/tool-installer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAwBrD,KAAK,WAAW,GACZ;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAiHN,eAAO,MAAM,2BAA2B,mBAEvC,CAAC;AAEF,eAAO,MAAM,6BAA6B,mBAIzC,CAAC;AAEF;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAO/D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,SAAS,GAAG,WAAW,GAC7B,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAaxF;AAoDD,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAM5D;AAgPD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAsC5B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAkB5C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAsF5C;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAiD5C"}
|
|
@@ -2,12 +2,12 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { access, readFile } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
5
|
import { promisify } from "node:util";
|
|
7
6
|
import { createLogger } from "../utils/logger.js";
|
|
7
|
+
import { BRIDGE_PACKAGE_ROOT, importBridgeSdkPackage } from "../utils/sdk-package-loader.js";
|
|
8
8
|
const execFileAsync = promisify(execFile);
|
|
9
9
|
const log = createLogger("tool-installer");
|
|
10
|
-
const bridgePackageRoot =
|
|
10
|
+
const bridgePackageRoot = BRIDGE_PACKAGE_ROOT;
|
|
11
11
|
const isWindows = process.platform === "win32";
|
|
12
12
|
function quoteCommandArg(value) {
|
|
13
13
|
if (isWindows) {
|
|
@@ -21,6 +21,27 @@ function npmInstallIntoBridgeCommand(packages) {
|
|
|
21
21
|
function npmUninstallFromBridgeCommand(packages) {
|
|
22
22
|
return `npm uninstall --prefix ${quoteCommandArg(bridgePackageRoot)} ${packages.join(" ")}`;
|
|
23
23
|
}
|
|
24
|
+
function npmExecutableName() {
|
|
25
|
+
return isWindows ? "npm.cmd" : "npm";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 构建 bridge 本地 npm 包安装/卸载命令。
|
|
29
|
+
* 主流程:展示层保留可读命令;执行层使用参数数组,避免 Windows shell 引号被 npm 当作路径内容。
|
|
30
|
+
*/
|
|
31
|
+
function npmBridgeCommand(action, packages) {
|
|
32
|
+
const display = action === "install"
|
|
33
|
+
? npmInstallIntoBridgeCommand(packages)
|
|
34
|
+
: npmUninstallFromBridgeCommand(packages);
|
|
35
|
+
return {
|
|
36
|
+
kind: "execFile",
|
|
37
|
+
command: npmExecutableName(),
|
|
38
|
+
args: [action, "--prefix", bridgePackageRoot, ...packages],
|
|
39
|
+
display,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function shellToolCommand(command) {
|
|
43
|
+
return { kind: "shell", display: command };
|
|
44
|
+
}
|
|
24
45
|
const TOOL_DEFINITIONS = {
|
|
25
46
|
claude: {
|
|
26
47
|
key: "claude",
|
|
@@ -29,10 +50,10 @@ const TOOL_DEFINITIONS = {
|
|
|
29
50
|
aliases: [],
|
|
30
51
|
versionArgs: [],
|
|
31
52
|
installCommands: [
|
|
32
|
-
|
|
53
|
+
npmBridgeCommand("install", ["@anthropic-ai/claude-agent-sdk@latest"]),
|
|
33
54
|
],
|
|
34
55
|
uninstallCommands: [
|
|
35
|
-
|
|
56
|
+
npmBridgeCommand("uninstall", ["@anthropic-ai/claude-agent-sdk"]),
|
|
36
57
|
],
|
|
37
58
|
},
|
|
38
59
|
claudecode: {
|
|
@@ -42,10 +63,10 @@ const TOOL_DEFINITIONS = {
|
|
|
42
63
|
aliases: [],
|
|
43
64
|
versionArgs: [],
|
|
44
65
|
installCommands: [
|
|
45
|
-
|
|
66
|
+
npmBridgeCommand("install", ["@anthropic-ai/claude-agent-sdk@latest"]),
|
|
46
67
|
],
|
|
47
68
|
uninstallCommands: [
|
|
48
|
-
|
|
69
|
+
npmBridgeCommand("uninstall", ["@anthropic-ai/claude-agent-sdk"]),
|
|
49
70
|
],
|
|
50
71
|
},
|
|
51
72
|
opencode: {
|
|
@@ -57,16 +78,16 @@ const TOOL_DEFINITIONS = {
|
|
|
57
78
|
: ["opencode"],
|
|
58
79
|
versionArgs: ["--version"],
|
|
59
80
|
installCommands: [
|
|
60
|
-
|
|
81
|
+
npmBridgeCommand("install", ["@opencode-ai/sdk@latest", "opencode-ai@latest"]),
|
|
61
82
|
],
|
|
62
83
|
uninstallCommands: isWindows
|
|
63
84
|
? [
|
|
64
|
-
|
|
85
|
+
npmBridgeCommand("uninstall", ["@opencode-ai/sdk", "opencode-ai"]),
|
|
65
86
|
]
|
|
66
87
|
: [
|
|
67
|
-
|
|
68
|
-
"opencode uninstall --force",
|
|
69
|
-
"rm -f ~/.opencode/bin/opencode",
|
|
88
|
+
npmBridgeCommand("uninstall", ["@opencode-ai/sdk", "opencode-ai"]),
|
|
89
|
+
shellToolCommand("opencode uninstall --force"),
|
|
90
|
+
shellToolCommand("rm -f ~/.opencode/bin/opencode"),
|
|
70
91
|
],
|
|
71
92
|
extraSearchPaths: () => {
|
|
72
93
|
const home = os.homedir();
|
|
@@ -85,8 +106,8 @@ const TOOL_DEFINITIONS = {
|
|
|
85
106
|
sdkPackageName: "@openai/codex-sdk",
|
|
86
107
|
aliases: [],
|
|
87
108
|
versionArgs: [],
|
|
88
|
-
installCommands: [
|
|
89
|
-
uninstallCommands: [
|
|
109
|
+
installCommands: [npmBridgeCommand("install", ["@openai/codex-sdk@latest"])],
|
|
110
|
+
uninstallCommands: [npmBridgeCommand("uninstall", ["@openai/codex-sdk"])],
|
|
90
111
|
},
|
|
91
112
|
};
|
|
92
113
|
export const SUPPORTED_INSTALLABLE_TOOLS = Object.freeze(Object.keys(TOOL_DEFINITIONS));
|
|
@@ -98,7 +119,24 @@ export function getToolUninstallCommands(tool) {
|
|
|
98
119
|
const normalizedTool = String(tool || "")
|
|
99
120
|
.trim()
|
|
100
121
|
.toLowerCase();
|
|
101
|
-
return
|
|
122
|
+
return (TOOL_DEFINITIONS[normalizedTool]?.uninstallCommands || []).map((command) => command.display);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 返回工具对应的安装/卸载执行参数副本,用于验证 Windows 下不会把路径引号传给 npm。
|
|
126
|
+
*/
|
|
127
|
+
export function getToolCommandSpecs(tool, phase) {
|
|
128
|
+
const normalizedTool = String(tool || "")
|
|
129
|
+
.trim()
|
|
130
|
+
.toLowerCase();
|
|
131
|
+
const commands = phase === "install"
|
|
132
|
+
? TOOL_DEFINITIONS[normalizedTool]?.installCommands
|
|
133
|
+
: TOOL_DEFINITIONS[normalizedTool]?.uninstallCommands;
|
|
134
|
+
return (commands || []).map((command) => ({
|
|
135
|
+
command: command.kind === "execFile" ? command.command : command.display,
|
|
136
|
+
args: command.kind === "execFile" ? [...command.args] : [],
|
|
137
|
+
display: command.display,
|
|
138
|
+
kind: command.kind,
|
|
139
|
+
}));
|
|
102
140
|
}
|
|
103
141
|
function parseVersion(output) {
|
|
104
142
|
const normalized = String(output || "").trim();
|
|
@@ -288,7 +326,7 @@ async function resolveSdkPackageCandidate(definition) {
|
|
|
288
326
|
return resolveExecutableCandidate(definition);
|
|
289
327
|
}
|
|
290
328
|
try {
|
|
291
|
-
await
|
|
329
|
+
await importBridgeSdkPackage(sdkPackageName);
|
|
292
330
|
return {
|
|
293
331
|
executable: sdkPackageName,
|
|
294
332
|
version: await readInstalledPackageVersion(sdkPackageName),
|
|
@@ -320,13 +358,26 @@ function describeCommandFailure(error) {
|
|
|
320
358
|
};
|
|
321
359
|
}
|
|
322
360
|
async function runToolCommand(command) {
|
|
361
|
+
if (command.kind === "execFile") {
|
|
362
|
+
const needsWindowsCmdShim = isWindows && /\.(?:cmd|bat)$/i.test(command.command);
|
|
363
|
+
const result = needsWindowsCmdShim
|
|
364
|
+
? await execFileAsync("cmd.exe", ["/d", "/s", "/c", command.command, ...command.args], {
|
|
365
|
+
timeout: 10 * 60 * 1000,
|
|
366
|
+
windowsVerbatimArguments: false,
|
|
367
|
+
})
|
|
368
|
+
: await execFileAsync(command.command, command.args, {
|
|
369
|
+
timeout: 10 * 60 * 1000,
|
|
370
|
+
windowsVerbatimArguments: false,
|
|
371
|
+
});
|
|
372
|
+
return { stdout: result.stdout || "", stderr: result.stderr || "" };
|
|
373
|
+
}
|
|
323
374
|
if (isWindows) {
|
|
324
|
-
const result = await execFileAsync("cmd.exe", ["/d", "/s", "/c", command], {
|
|
375
|
+
const result = await execFileAsync("cmd.exe", ["/d", "/s", "/c", command.display], {
|
|
325
376
|
timeout: 10 * 60 * 1000,
|
|
326
377
|
});
|
|
327
378
|
return { stdout: result.stdout || "", stderr: result.stderr || "" };
|
|
328
379
|
}
|
|
329
|
-
const result = await execFileAsync("/bin/sh", ["-lc", command], {
|
|
380
|
+
const result = await execFileAsync("/bin/sh", ["-lc", command.display], {
|
|
330
381
|
timeout: 10 * 60 * 1000,
|
|
331
382
|
});
|
|
332
383
|
return { stdout: result.stdout || "", stderr: result.stderr || "" };
|
|
@@ -420,14 +471,14 @@ export async function ensureToolsInstalled(tools) {
|
|
|
420
471
|
try {
|
|
421
472
|
log.info("Running install command", {
|
|
422
473
|
tool,
|
|
423
|
-
command,
|
|
474
|
+
command: command.display,
|
|
424
475
|
sdkPackageName: definition.sdkPackageName || null,
|
|
425
476
|
packageName: definition.packageName,
|
|
426
477
|
});
|
|
427
478
|
const commandResult = await runToolCommand(command);
|
|
428
479
|
log.info("Install command completed", {
|
|
429
480
|
tool,
|
|
430
|
-
command,
|
|
481
|
+
command: command.display,
|
|
431
482
|
stdout: commandResult.stdout,
|
|
432
483
|
stderr: commandResult.stderr,
|
|
433
484
|
});
|
|
@@ -443,7 +494,7 @@ export async function ensureToolsInstalled(tools) {
|
|
|
443
494
|
catch (error) {
|
|
444
495
|
log.error("Install command failed", {
|
|
445
496
|
tool,
|
|
446
|
-
command,
|
|
497
|
+
command: command.display,
|
|
447
498
|
failure: describeCommandFailure(error),
|
|
448
499
|
});
|
|
449
500
|
lastError =
|
|
@@ -464,6 +515,7 @@ export async function ensureToolsInstalled(tools) {
|
|
|
464
515
|
tool,
|
|
465
516
|
status: nextStatuses[tool],
|
|
466
517
|
attemptedCommands: definition.installCommands,
|
|
518
|
+
attemptedCommandDisplays: definition.installCommands.map((command) => command.display),
|
|
467
519
|
});
|
|
468
520
|
}
|
|
469
521
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, detectToolInstallStatus, detectToolStatuses, getToolCommandSpecs, getToolUninstallCommands, isVoltaShimPath } from './tool-installer.js';
|
|
3
3
|
describe('tool installer service', () => {
|
|
4
4
|
it('returns structured status for supported tools', async () => {
|
|
5
5
|
const statuses = await detectToolStatuses(['claude', 'opencode', 'codex']);
|
|
@@ -115,6 +115,25 @@ describe('tool installer service', () => {
|
|
|
115
115
|
expect(commands.some(command => command.includes('npm uninstall --prefix'))).toBe(true);
|
|
116
116
|
expect(commands.some(command => command.includes('@openai/codex-sdk'))).toBe(true);
|
|
117
117
|
});
|
|
118
|
+
it('executes npm-backed install and uninstall commands with prefix as an unquoted argument', () => {
|
|
119
|
+
const expectedPackagesByTool = {
|
|
120
|
+
claude: '@anthropic-ai/claude-agent-sdk',
|
|
121
|
+
claudecode: '@anthropic-ai/claude-agent-sdk',
|
|
122
|
+
opencode: '@opencode-ai/sdk',
|
|
123
|
+
codex: '@openai/codex-sdk'
|
|
124
|
+
};
|
|
125
|
+
for (const [tool, packageName] of Object.entries(expectedPackagesByTool)) {
|
|
126
|
+
for (const phase of ['install', 'uninstall']) {
|
|
127
|
+
const [command] = getToolCommandSpecs(tool, phase);
|
|
128
|
+
expect(command.kind).toBe('execFile');
|
|
129
|
+
expect(command.command).toMatch(/^npm(?:\.cmd)?$/);
|
|
130
|
+
expect(command.args.slice(0, 3)).toEqual([phase, '--prefix', expect.any(String)]);
|
|
131
|
+
expect(command.args[2]).not.toMatch(/^"|"$/);
|
|
132
|
+
expect(command.args.some(arg => arg.startsWith(packageName))).toBe(true);
|
|
133
|
+
expect(command.display).toContain(`npm ${phase} --prefix`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
118
137
|
it('recognizes Volta shim paths for package-manager-aware detection', () => {
|
|
119
138
|
expect(isVoltaShimPath('C:\\Users\\tester\\AppData\\Local\\Volta\\bin\\codex.cmd')).toBe(true);
|
|
120
139
|
expect(isVoltaShimPath('/home/tester/.volta/bin/codex')).toBe(true);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const BRIDGE_PACKAGE_ROOT: string;
|
|
2
|
+
/**
|
|
3
|
+
* 主干流程:从 bridge 自身安装根下的 node_modules 读取包元数据,
|
|
4
|
+
* 按 package.json 的 exports/module/main 定位真实 ESM 入口,避免猜测根目录 index.js。
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveBridgePackageEntryPath(packageName: string, packageRoot?: string): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* 具体实现逻辑:优先从 bridge-local 安装目录显式导入真实入口;
|
|
9
|
+
* 若没有本地 package.json,则退回 Node 的包名解析以兼容开发环境。
|
|
10
|
+
*/
|
|
11
|
+
export declare function importBridgeSdkPackage<TModule>(packageName: string): Promise<TModule>;
|
|
12
|
+
//# sourceMappingURL=sdk-package-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-package-loader.d.ts","sourceRoot":"","sources":["../../src/utils/sdk-package-loader.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,mBAAmB,QAI/B,CAAC;AA4DF;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,EACnB,WAAW,SAAsB,GAChC,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAClD,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC,CAUlB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
export const BRIDGE_PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
function getPackageDirectory(packageName, packageRoot) {
|
|
9
|
+
return path.join(packageRoot, 'node_modules', ...packageName.split('/'));
|
|
10
|
+
}
|
|
11
|
+
function resolveConditionalExport(value) {
|
|
12
|
+
if (typeof value === 'string') {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
if (!isRecord(value)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
for (const condition of ['import', 'module', 'default', 'node']) {
|
|
19
|
+
const resolved = resolveConditionalExport(value[condition]);
|
|
20
|
+
if (resolved) {
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
function resolvePackageRootExport(exportsField) {
|
|
27
|
+
if (typeof exportsField === 'string') {
|
|
28
|
+
return exportsField;
|
|
29
|
+
}
|
|
30
|
+
if (!isRecord(exportsField)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const rootExport = resolveConditionalExport(exportsField['.']);
|
|
34
|
+
return rootExport ?? resolveConditionalExport(exportsField);
|
|
35
|
+
}
|
|
36
|
+
function resolveEntryFromPackageJson(packageJson) {
|
|
37
|
+
const exportedEntry = resolvePackageRootExport(packageJson.exports);
|
|
38
|
+
if (exportedEntry) {
|
|
39
|
+
return exportedEntry;
|
|
40
|
+
}
|
|
41
|
+
if (typeof packageJson.module === 'string') {
|
|
42
|
+
return packageJson.module;
|
|
43
|
+
}
|
|
44
|
+
if (typeof packageJson.main === 'string') {
|
|
45
|
+
return packageJson.main;
|
|
46
|
+
}
|
|
47
|
+
return 'index.js';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 主干流程:从 bridge 自身安装根下的 node_modules 读取包元数据,
|
|
51
|
+
* 按 package.json 的 exports/module/main 定位真实 ESM 入口,避免猜测根目录 index.js。
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveBridgePackageEntryPath(packageName, packageRoot = BRIDGE_PACKAGE_ROOT) {
|
|
54
|
+
const packageDirectory = getPackageDirectory(packageName, packageRoot);
|
|
55
|
+
const packageJsonPath = path.join(packageDirectory, 'package.json');
|
|
56
|
+
const rawPackageJson = await readFile(packageJsonPath, 'utf8');
|
|
57
|
+
const packageJson = JSON.parse(rawPackageJson);
|
|
58
|
+
const entry = resolveEntryFromPackageJson(packageJson);
|
|
59
|
+
const entryPath = path.resolve(packageDirectory, entry);
|
|
60
|
+
await access(entryPath);
|
|
61
|
+
return entryPath;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 具体实现逻辑:优先从 bridge-local 安装目录显式导入真实入口;
|
|
65
|
+
* 若没有本地 package.json,则退回 Node 的包名解析以兼容开发环境。
|
|
66
|
+
*/
|
|
67
|
+
export async function importBridgeSdkPackage(packageName) {
|
|
68
|
+
try {
|
|
69
|
+
const entryPath = await resolveBridgePackageEntryPath(packageName);
|
|
70
|
+
return (await import(pathToFileURL(entryPath).href));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
74
|
+
return (await import(packageName));
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-package-loader.test.d.ts","sourceRoot":"","sources":["../../src/utils/sdk-package-loader.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
+
import { resolveBridgePackageEntryPath } from './sdk-package-loader.js';
|
|
6
|
+
const tempRoots = [];
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
9
|
+
});
|
|
10
|
+
async function createTempPackageRoot() {
|
|
11
|
+
const root = await mkdtemp(path.join(os.tmpdir(), 'aws-sdk-package-loader-'));
|
|
12
|
+
tempRoots.push(root);
|
|
13
|
+
return root;
|
|
14
|
+
}
|
|
15
|
+
describe('sdk package loader', () => {
|
|
16
|
+
it('resolves package exports instead of assuming a root index.js file', async () => {
|
|
17
|
+
const packageRoot = await createTempPackageRoot();
|
|
18
|
+
const packageDirectory = path.join(packageRoot, 'node_modules', '@opencode-ai', 'sdk');
|
|
19
|
+
await mkdir(path.join(packageDirectory, 'dist'), { recursive: true });
|
|
20
|
+
await writeFile(path.join(packageDirectory, 'package.json'), JSON.stringify({
|
|
21
|
+
name: '@opencode-ai/sdk',
|
|
22
|
+
type: 'module',
|
|
23
|
+
exports: {
|
|
24
|
+
'.': {
|
|
25
|
+
import: './dist/index.js',
|
|
26
|
+
types: './dist/index.d.ts',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
}));
|
|
30
|
+
await writeFile(path.join(packageDirectory, 'dist', 'index.js'), 'export const ok = true;');
|
|
31
|
+
await expect(resolveBridgePackageEntryPath('@opencode-ai/sdk', packageRoot)).resolves.toBe(path.join(packageDirectory, 'dist', 'index.js'));
|
|
32
|
+
});
|
|
33
|
+
it('resolves default conditional exports', async () => {
|
|
34
|
+
const packageRoot = await createTempPackageRoot();
|
|
35
|
+
const packageDirectory = path.join(packageRoot, 'node_modules', 'default-only-sdk');
|
|
36
|
+
await mkdir(path.join(packageDirectory, 'dist'), { recursive: true });
|
|
37
|
+
await writeFile(path.join(packageDirectory, 'package.json'), JSON.stringify({ exports: { '.': { default: './dist/default.js' } } }));
|
|
38
|
+
await writeFile(path.join(packageDirectory, 'dist', 'default.js'), 'export const ok = true;');
|
|
39
|
+
await expect(resolveBridgePackageEntryPath('default-only-sdk', packageRoot)).resolves.toBe(path.join(packageDirectory, 'dist', 'default.js'));
|
|
40
|
+
});
|
|
41
|
+
it('falls back to module when exports is absent', async () => {
|
|
42
|
+
const packageRoot = await createTempPackageRoot();
|
|
43
|
+
const packageDirectory = path.join(packageRoot, 'node_modules', 'module-sdk');
|
|
44
|
+
await mkdir(path.join(packageDirectory, 'esm'), { recursive: true });
|
|
45
|
+
await writeFile(path.join(packageDirectory, 'package.json'), JSON.stringify({ module: './esm/index.js' }));
|
|
46
|
+
await writeFile(path.join(packageDirectory, 'esm', 'index.js'), 'export const ok = true;');
|
|
47
|
+
await expect(resolveBridgePackageEntryPath('module-sdk', packageRoot)).resolves.toBe(path.join(packageDirectory, 'esm', 'index.js'));
|
|
48
|
+
});
|
|
49
|
+
it('falls back to main when exports and module are absent', async () => {
|
|
50
|
+
const packageRoot = await createTempPackageRoot();
|
|
51
|
+
const packageDirectory = path.join(packageRoot, 'node_modules', 'main-sdk');
|
|
52
|
+
await mkdir(path.join(packageDirectory, 'lib'), { recursive: true });
|
|
53
|
+
await writeFile(path.join(packageDirectory, 'package.json'), JSON.stringify({ main: './lib/main.js' }));
|
|
54
|
+
await writeFile(path.join(packageDirectory, 'lib', 'main.js'), 'export const ok = true;');
|
|
55
|
+
await expect(resolveBridgePackageEntryPath('main-sdk', packageRoot)).resolves.toBe(path.join(packageDirectory, 'lib', 'main.js'));
|
|
56
|
+
});
|
|
57
|
+
it('rejects when the resolved package entry is missing', async () => {
|
|
58
|
+
const packageRoot = await createTempPackageRoot();
|
|
59
|
+
const packageDirectory = path.join(packageRoot, 'node_modules', 'broken-sdk');
|
|
60
|
+
await mkdir(packageDirectory, { recursive: true });
|
|
61
|
+
await writeFile(path.join(packageDirectory, 'package.json'), JSON.stringify({ exports: './dist/missing.js' }));
|
|
62
|
+
await expect(resolveBridgePackageEntryPath('broken-sdk', packageRoot)).rejects.toMatchObject({
|
|
63
|
+
code: 'ENOENT',
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aws-runtime-bridge",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.6",
|
|
4
4
|
"description": "AgentsWorkStudio runtime bridge service for machine-level agent runtime integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"@anthropic-ai/claude-agent-sdk": "^0.2.87",
|
|
50
50
|
"@cc-switch/sdk": "file:package/cc-switch-sdk",
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
52
|
+
"@openai/codex-sdk": "^0.130.0",
|
|
52
53
|
"archiver": "^8.0.0",
|
|
53
54
|
"axios": "^1.7.9",
|
|
54
55
|
"cors": "^2.8.5",
|
|
@@ -64,7 +65,6 @@
|
|
|
64
65
|
"zod": "^4.1.12"
|
|
65
66
|
},
|
|
66
67
|
"peerDependencies": {
|
|
67
|
-
"@openai/codex-sdk": "^0.125.0",
|
|
68
68
|
"@opencode-ai/sdk": "^1.3.13"
|
|
69
69
|
},
|
|
70
70
|
"peerDependenciesMeta": {
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@eslint/js": "^9.0.0",
|
|
80
|
-
"@openai/codex-sdk": "^0.125.0",
|
|
81
80
|
"@opencode-ai/sdk": "^1.3.13",
|
|
82
81
|
"@types/archiver": "^7.0.0",
|
|
83
82
|
"@types/cors": "^2.8.19",
|