aws-runtime-bridge 1.6.2 → 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.
@@ -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;AAc3C,OAAO,KAAK,EACV,oBAAoB,EAEpB,mBAAmB,EACnB,mBAAmB,EAEnB,aAAa,EACd,MAAM,YAAY,CAAC;AA4GpB,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;YA6BP,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"}
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 import('@opencode-ai/sdk');
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
- ' npm install -g @opencode-ai/sdk',
373
+ ` npm install --prefix "${BRIDGE_PACKAGE_ROOT}" @opencode-ai/sdk@latest opencode-ai@latest`,
373
374
  '',
374
- 'If aws-runtime-bridge is installed locally, install the SDK in the same project:',
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'));
@@ -47,5 +47,6 @@ export declare function buildToolStatusDetectionTargets(enabledTools: string[]):
47
47
  * 汇总卸载后仍可执行的工具,用于避免把部分卸载误报为成功。
48
48
  */
49
49
  export declare function buildUninstallFailureMessage(requestedTools: string[], toolStatus: Record<string, ToolInstallStatus>): string;
50
+ export declare function summarizeToolStatus(toolStatus: Record<string, ToolInstallStatus>): Record<string, Pick<ToolInstallStatus, "installed" | "version" | "executable" | "error">>;
50
51
  export {};
51
52
  //# sourceMappingURL=instance.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR"}
1
+ {"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/routes/instance.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYrD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAC/D,IAAI,CAEN;AAED,eAAO,MAAM,cAAc,4CAAW,CAAC;AAyBvC,wBAAgB,4BAA4B,CAAC,gBAAgB,EAAE,MAAM,GAAG;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE;QACJ,EAAE,EAAE,OAAO,CAAC;QACZ,aAAa,CAAC,EAAE,SAAS,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;QAC9B,qBAAqB,EAAE,OAAO,CAAC;QAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAqCA;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,iCAAiC;IACzC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,8BAA8B,CAAC;IACtC,YAAY,EAAE,gBAAgB,CAAC;IAC/B,aAAa,EAAE,SAAS,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,KAAK,EAAE,KAAK,EACZ,0BAA0B,EAAE,MAAM,GACjC,4BAA4B,CAiB9B;AAED;;;GAGG;AACH,wBAAgB,sCAAsC,CACpD,UAAU,EAAE,MAAM,EAAE,GACnB,iCAAiC,CAUnC;AAED;;;GAGG;AACH,wBAAgB,+BAA+B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhF;AAED;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,cAAc,EAAE,MAAM,EAAE,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAaR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAC5C,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,CAAC,CAY3F"}
@@ -132,6 +132,17 @@ export function buildUninstallFailureMessage(requestedTools, toolStatus) {
132
132
  })
133
133
  .join("; ");
134
134
  }
135
+ export function summarizeToolStatus(toolStatus) {
136
+ return Object.fromEntries(Object.entries(toolStatus || {}).map(([tool, status]) => [
137
+ tool,
138
+ {
139
+ installed: Boolean(status?.installed),
140
+ version: status?.version || null,
141
+ executable: status?.executable || null,
142
+ error: status?.error || null,
143
+ },
144
+ ]));
145
+ }
135
146
  instanceRouter.get("/healthz", (_req, res) => {
136
147
  res.json({
137
148
  ok: true,
@@ -180,6 +191,13 @@ instanceRouter.post("/init-instance", validateToken, async (req, res) => {
180
191
  return;
181
192
  }
182
193
  try {
194
+ log.info("[init-instance] Request received", {
195
+ agentId: String(agentId),
196
+ workspacePath: String(workspacePath),
197
+ skillEnabled,
198
+ mcpEnabled,
199
+ ccSwitchEnabledTools,
200
+ });
183
201
  const result = await initInstance(agentId, workspacePath, {
184
202
  skillEnabled,
185
203
  mcpEnabled,
@@ -188,10 +206,22 @@ instanceRouter.post("/init-instance", validateToken, async (req, res) => {
188
206
  skillPackages,
189
207
  mcpServers,
190
208
  });
209
+ log.info("[init-instance] Initialization completed", {
210
+ agentId: String(agentId),
211
+ ok: result.ok,
212
+ logCount: result.logs?.length || 0,
213
+ enabledTools: result.enabledTools,
214
+ });
191
215
  res.json(result);
192
216
  }
193
217
  catch (error) {
194
218
  const err = error;
219
+ log.error("[init-instance] Initialization failed", {
220
+ agentId: String(agentId),
221
+ workspacePath: String(workspacePath),
222
+ error: err?.message || String(error),
223
+ stack: err?.stack || null,
224
+ });
195
225
  res.status(400).json({
196
226
  error: err?.message || "initialize instance failed",
197
227
  });
@@ -204,11 +234,23 @@ instanceRouter.post("/cc-switch/state", validateToken, async (req, res) => {
204
234
  return;
205
235
  }
206
236
  try {
237
+ log.info("[cc-switch/state] Request received", { agentId: String(agentId) });
207
238
  const sdk = await loadCcSwitchSdk(agentId);
208
239
  const imported = await discoverCcSwitchConfiguredItems(sdk);
209
240
  const synced = await syncLegacyStateFromSdk(agentId, sdk);
210
241
  const state = synced.state;
211
- const toolStatus = await detectToolStatuses(buildToolStatusDetectionTargets(state.enabledTools || []));
242
+ const detectionTargets = buildToolStatusDetectionTargets(state.enabledTools || []);
243
+ log.info("[cc-switch/state] Detecting SDK statuses", {
244
+ agentId: String(agentId),
245
+ detectionTargets,
246
+ });
247
+ const toolStatus = await detectToolStatuses(detectionTargets);
248
+ log.info("[cc-switch/state] SDK status detection completed", {
249
+ agentId: String(agentId),
250
+ toolStatus: summarizeToolStatus(toolStatus),
251
+ importedMcpCount: imported.mcpServers,
252
+ importedSkillCount: imported.skills,
253
+ });
212
254
  res.json({
213
255
  ok: true,
214
256
  agentId: String(agentId),
@@ -221,6 +263,11 @@ instanceRouter.post("/cc-switch/state", validateToken, async (req, res) => {
221
263
  }
222
264
  catch (error) {
223
265
  const err = error;
266
+ log.error("[cc-switch/state] Failed to load state", {
267
+ agentId: String(agentId),
268
+ error: err?.message || String(error),
269
+ stack: err?.stack || null,
270
+ });
224
271
  res.status(400).json({ error: err?.message || "load state failed" });
225
272
  }
226
273
  });
@@ -237,9 +284,23 @@ instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res)
237
284
  .toLowerCase())
238
285
  .filter(Boolean)
239
286
  : [];
287
+ log.info("[cc-switch/install-tools] Request received", {
288
+ agentId: String(agentId),
289
+ requestedTools,
290
+ });
240
291
  const supportedInstallableTools = new Set(SUPPORTED_INSTALLABLE_TOOLS);
241
292
  const installableTools = requestedTools.filter((tool) => supportedInstallableTools.has(tool));
293
+ log.info("[cc-switch/install-tools] Request normalized", {
294
+ agentId: String(agentId),
295
+ requestedTools,
296
+ installableTools,
297
+ supportedInstallableTools: SUPPORTED_INSTALLABLE_TOOLS,
298
+ });
242
299
  if (installableTools.length === 0) {
300
+ log.warn("[cc-switch/install-tools] No supported installable tools requested", {
301
+ agentId: String(agentId),
302
+ requestedTools,
303
+ });
243
304
  res
244
305
  .status(400)
245
306
  .json({
@@ -250,8 +311,22 @@ instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res)
250
311
  try {
251
312
  const state = await loadInstanceState(agentId);
252
313
  const enabledTools = Array.from(new Set([...(state.enabledTools || []), ...installableTools]));
314
+ log.info("[cc-switch/install-tools] Starting installer", {
315
+ agentId: String(agentId),
316
+ installableTools,
317
+ previousEnabledTools: state.enabledTools || [],
318
+ nextEnabledTools: enabledTools,
319
+ });
253
320
  const toolStatus = await ensureToolsInstalled(installableTools);
321
+ log.info("[cc-switch/install-tools] Installer completed", {
322
+ agentId: String(agentId),
323
+ toolStatus: summarizeToolStatus(toolStatus),
324
+ });
254
325
  const refreshedToolStatus = await detectToolStatuses(enabledTools);
326
+ log.info("[cc-switch/install-tools] Refreshed enabled SDK statuses", {
327
+ agentId: String(agentId),
328
+ refreshedToolStatus: summarizeToolStatus(refreshedToolStatus),
329
+ });
255
330
  const nextToolStatus = {
256
331
  ...refreshedToolStatus,
257
332
  ...toolStatus,
@@ -261,6 +336,20 @@ instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res)
261
336
  enabledTools,
262
337
  toolStatus: nextToolStatus,
263
338
  });
339
+ const failedTools = Object.entries(nextToolStatus)
340
+ .filter(([, status]) => !status.installed)
341
+ .map(([tool, status]) => ({ tool, error: status.error || null }));
342
+ if (failedTools.length > 0) {
343
+ log.warn("[cc-switch/install-tools] Some SDKs remain unavailable", {
344
+ agentId: String(agentId),
345
+ failedTools,
346
+ });
347
+ }
348
+ log.info("[cc-switch/install-tools] State saved", {
349
+ agentId: String(agentId),
350
+ enabledTools: savedState.enabledTools,
351
+ toolStatus: summarizeToolStatus(nextToolStatus),
352
+ });
264
353
  res.json({
265
354
  ok: true,
266
355
  agentId: String(agentId),
@@ -270,6 +359,13 @@ instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res)
270
359
  }
271
360
  catch (error) {
272
361
  const err = error;
362
+ log.error("[cc-switch/install-tools] Install route failed", {
363
+ agentId: String(agentId),
364
+ requestedTools,
365
+ installableTools,
366
+ error: err?.message || String(error),
367
+ stack: err?.stack || null,
368
+ });
273
369
  res.status(400).json({ error: err?.message || "install tools failed" });
274
370
  }
275
371
  });
@@ -104,6 +104,40 @@ describe('instance route validation', () => {
104
104
  },
105
105
  })).toContain('opencode: uninstall completed but command is still available');
106
106
  });
107
+ it('summarizes tool status for route diagnostics', async () => {
108
+ const { summarizeToolStatus } = await import('./instance.js');
109
+ expect(summarizeToolStatus({
110
+ opencode: {
111
+ tool: 'opencode',
112
+ installed: false,
113
+ executable: null,
114
+ version: null,
115
+ installing: false,
116
+ error: 'SDK package @opencode-ai/sdk is not installed in aws-runtime-bridge',
117
+ },
118
+ claude: {
119
+ tool: 'claude',
120
+ installed: true,
121
+ executable: '@anthropic-ai/claude-agent-sdk',
122
+ version: '0.2.87',
123
+ installing: false,
124
+ error: null,
125
+ },
126
+ })).toEqual({
127
+ opencode: {
128
+ installed: false,
129
+ executable: null,
130
+ version: null,
131
+ error: 'SDK package @opencode-ai/sdk is not installed in aws-runtime-bridge',
132
+ },
133
+ claude: {
134
+ installed: true,
135
+ executable: '@anthropic-ai/claude-agent-sdk',
136
+ version: '0.2.87',
137
+ error: null,
138
+ },
139
+ });
140
+ });
107
141
  it('does not report uninstall failure when requested tools are no longer installed', async () => {
108
142
  const { buildUninstallFailureMessage } = await import('./instance.js');
109
143
  expect(buildUninstallFailureMessage(['claude'], {
@@ -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":"AAMA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA0GrD,eAAO,MAAM,2BAA2B,mBAEvC,CAAC;AAEF,eAAO,MAAM,6BAA6B,mBAIzC,CAAC;AAEF;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAK/D;AAoDD,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAM5D;AAgND;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,iBAAiB,CAAC,CAqC5B;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,CA+C5C;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAiD5C"}
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,10 +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";
6
+ import { createLogger } from "../utils/logger.js";
7
+ import { BRIDGE_PACKAGE_ROOT, importBridgeSdkPackage } from "../utils/sdk-package-loader.js";
7
8
  const execFileAsync = promisify(execFile);
8
- const bridgePackageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
9
+ const log = createLogger("tool-installer");
10
+ const bridgePackageRoot = BRIDGE_PACKAGE_ROOT;
9
11
  const isWindows = process.platform === "win32";
10
12
  function quoteCommandArg(value) {
11
13
  if (isWindows) {
@@ -19,6 +21,27 @@ function npmInstallIntoBridgeCommand(packages) {
19
21
  function npmUninstallFromBridgeCommand(packages) {
20
22
  return `npm uninstall --prefix ${quoteCommandArg(bridgePackageRoot)} ${packages.join(" ")}`;
21
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
+ }
22
45
  const TOOL_DEFINITIONS = {
23
46
  claude: {
24
47
  key: "claude",
@@ -27,10 +50,10 @@ const TOOL_DEFINITIONS = {
27
50
  aliases: [],
28
51
  versionArgs: [],
29
52
  installCommands: [
30
- npmInstallIntoBridgeCommand(["@anthropic-ai/claude-agent-sdk@latest"]),
53
+ npmBridgeCommand("install", ["@anthropic-ai/claude-agent-sdk@latest"]),
31
54
  ],
32
55
  uninstallCommands: [
33
- npmUninstallFromBridgeCommand(["@anthropic-ai/claude-agent-sdk"]),
56
+ npmBridgeCommand("uninstall", ["@anthropic-ai/claude-agent-sdk"]),
34
57
  ],
35
58
  },
36
59
  claudecode: {
@@ -40,10 +63,10 @@ const TOOL_DEFINITIONS = {
40
63
  aliases: [],
41
64
  versionArgs: [],
42
65
  installCommands: [
43
- npmInstallIntoBridgeCommand(["@anthropic-ai/claude-agent-sdk@latest"]),
66
+ npmBridgeCommand("install", ["@anthropic-ai/claude-agent-sdk@latest"]),
44
67
  ],
45
68
  uninstallCommands: [
46
- npmUninstallFromBridgeCommand(["@anthropic-ai/claude-agent-sdk"]),
69
+ npmBridgeCommand("uninstall", ["@anthropic-ai/claude-agent-sdk"]),
47
70
  ],
48
71
  },
49
72
  opencode: {
@@ -55,16 +78,16 @@ const TOOL_DEFINITIONS = {
55
78
  : ["opencode"],
56
79
  versionArgs: ["--version"],
57
80
  installCommands: [
58
- npmInstallIntoBridgeCommand(["@opencode-ai/sdk@latest", "opencode-ai@latest"]),
81
+ npmBridgeCommand("install", ["@opencode-ai/sdk@latest", "opencode-ai@latest"]),
59
82
  ],
60
83
  uninstallCommands: isWindows
61
84
  ? [
62
- npmUninstallFromBridgeCommand(["@opencode-ai/sdk", "opencode-ai"]),
85
+ npmBridgeCommand("uninstall", ["@opencode-ai/sdk", "opencode-ai"]),
63
86
  ]
64
87
  : [
65
- npmUninstallFromBridgeCommand(["@opencode-ai/sdk", "opencode-ai"]),
66
- "opencode uninstall --force",
67
- "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"),
68
91
  ],
69
92
  extraSearchPaths: () => {
70
93
  const home = os.homedir();
@@ -83,8 +106,8 @@ const TOOL_DEFINITIONS = {
83
106
  sdkPackageName: "@openai/codex-sdk",
84
107
  aliases: [],
85
108
  versionArgs: [],
86
- installCommands: [npmInstallIntoBridgeCommand(["@openai/codex-sdk@latest"])],
87
- uninstallCommands: [npmUninstallFromBridgeCommand(["@openai/codex-sdk"])],
109
+ installCommands: [npmBridgeCommand("install", ["@openai/codex-sdk@latest"])],
110
+ uninstallCommands: [npmBridgeCommand("uninstall", ["@openai/codex-sdk"])],
88
111
  },
89
112
  };
90
113
  export const SUPPORTED_INSTALLABLE_TOOLS = Object.freeze(Object.keys(TOOL_DEFINITIONS));
@@ -96,7 +119,24 @@ export function getToolUninstallCommands(tool) {
96
119
  const normalizedTool = String(tool || "")
97
120
  .trim()
98
121
  .toLowerCase();
99
- return [...(TOOL_DEFINITIONS[normalizedTool]?.uninstallCommands || [])];
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
+ }));
100
140
  }
101
141
  function parseVersion(output) {
102
142
  const normalized = String(output || "").trim();
@@ -286,7 +326,7 @@ async function resolveSdkPackageCandidate(definition) {
286
326
  return resolveExecutableCandidate(definition);
287
327
  }
288
328
  try {
289
- await import(sdkPackageName);
329
+ await importBridgeSdkPackage(sdkPackageName);
290
330
  return {
291
331
  executable: sdkPackageName,
292
332
  version: await readInstalledPackageVersion(sdkPackageName),
@@ -304,14 +344,43 @@ async function resolveSdkPackageCandidate(definition) {
304
344
  };
305
345
  }
306
346
  }
347
+ function describeCommandFailure(error) {
348
+ if (!error || typeof error !== "object") {
349
+ return { message: String(error || "command failed") };
350
+ }
351
+ const failure = error;
352
+ return {
353
+ message: typeof failure.message === "string" ? failure.message : String(error),
354
+ code: failure.code ?? null,
355
+ signal: failure.signal ?? null,
356
+ stdout: typeof failure.stdout === "string" ? failure.stdout : "",
357
+ stderr: typeof failure.stderr === "string" ? failure.stderr : "",
358
+ };
359
+ }
307
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
+ }
308
374
  if (isWindows) {
309
- await execFileAsync("cmd.exe", ["/d", "/s", "/c", command], {
375
+ const result = await execFileAsync("cmd.exe", ["/d", "/s", "/c", command.display], {
310
376
  timeout: 10 * 60 * 1000,
311
377
  });
312
- return;
378
+ return { stdout: result.stdout || "", stderr: result.stderr || "" };
313
379
  }
314
- await execFileAsync("/bin/sh", ["-lc", command], { timeout: 10 * 60 * 1000 });
380
+ const result = await execFileAsync("/bin/sh", ["-lc", command.display], {
381
+ timeout: 10 * 60 * 1000,
382
+ });
383
+ return { stdout: result.stdout || "", stderr: result.stderr || "" };
315
384
  }
316
385
  /**
317
386
  * 检查单个工具的 CLI 可执行状态,供实例状态展示与初始化前判断使用。
@@ -322,6 +391,7 @@ export async function detectToolInstallStatus(tool) {
322
391
  .toLowerCase();
323
392
  const definition = TOOL_DEFINITIONS[normalizedTool];
324
393
  if (!definition) {
394
+ log.warn("Unsupported tool status requested", { tool: normalizedTool });
325
395
  return {
326
396
  tool: normalizedTool,
327
397
  installed: false,
@@ -370,25 +440,51 @@ export async function detectToolStatuses(tools) {
370
440
  * 根据勾选工具自动安装缺失 CLI,安装后重新检测状态并返回。
371
441
  */
372
442
  export async function ensureToolsInstalled(tools) {
443
+ log.info("Install request received", {
444
+ requestedTools: tools,
445
+ bridgePackageRoot,
446
+ platform: process.platform,
447
+ node: process.version,
448
+ });
373
449
  const initialStatuses = await detectToolStatuses(tools);
450
+ log.info("Initial install status detected", { statuses: initialStatuses });
374
451
  const nextStatuses = {
375
452
  ...initialStatuses,
376
453
  };
377
454
  for (const tool of Object.keys(initialStatuses)) {
378
455
  const current = initialStatuses[tool];
379
456
  if (current.installed) {
457
+ log.info("Skipping install because tool SDK is already available", {
458
+ tool,
459
+ version: current.version,
460
+ executable: current.executable,
461
+ });
380
462
  continue;
381
463
  }
382
464
  const definition = TOOL_DEFINITIONS[tool];
383
465
  if (!definition) {
466
+ log.warn("Skipping install because tool is unsupported", { tool, current });
384
467
  continue;
385
468
  }
386
469
  let lastError = current.error;
387
470
  for (const command of definition.installCommands) {
388
471
  try {
389
- await runToolCommand(command);
472
+ log.info("Running install command", {
473
+ tool,
474
+ command: command.display,
475
+ sdkPackageName: definition.sdkPackageName || null,
476
+ packageName: definition.packageName,
477
+ });
478
+ const commandResult = await runToolCommand(command);
479
+ log.info("Install command completed", {
480
+ tool,
481
+ command: command.display,
482
+ stdout: commandResult.stdout,
483
+ stderr: commandResult.stderr,
484
+ });
390
485
  const detected = await detectToolInstallStatus(tool);
391
486
  nextStatuses[tool] = detected;
487
+ log.info("Post-install status detected", { tool, status: detected });
392
488
  if (detected.installed) {
393
489
  lastError = null;
394
490
  break;
@@ -396,6 +492,11 @@ export async function ensureToolsInstalled(tools) {
396
492
  lastError = detected.error;
397
493
  }
398
494
  catch (error) {
495
+ log.error("Install command failed", {
496
+ tool,
497
+ command: command.display,
498
+ failure: describeCommandFailure(error),
499
+ });
399
500
  lastError =
400
501
  error instanceof Error
401
502
  ? error.message
@@ -410,8 +511,15 @@ export async function ensureToolsInstalled(tools) {
410
511
  installing: false,
411
512
  error: lastError || "install failed",
412
513
  };
514
+ log.warn("Tool SDK remains unavailable after install attempts", {
515
+ tool,
516
+ status: nextStatuses[tool],
517
+ attemptedCommands: definition.installCommands,
518
+ attemptedCommandDisplays: definition.installCommands.map((command) => command.display),
519
+ });
413
520
  }
414
521
  }
522
+ log.info("Install request completed", { statuses: nextStatuses });
415
523
  return nextStatuses;
416
524
  }
417
525
  /**
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { detectToolInstallStatus, SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, detectToolStatuses, getToolUninstallCommands, isVoltaShimPath } from './tool-installer.js';
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sdk-package-loader.test.d.ts.map
@@ -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.2",
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",