@qoder-ai/qoder-agent-sdk 1.0.4 → 1.0.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.
@@ -4,8 +4,10 @@
4
4
  * provide options.pathToQoderCLIExecutable or QODERCLI_PATH.
5
5
  *
6
6
  * Environment variables:
7
- * - QODER_SKIP_DOWNLOAD=1|true: skip download
7
+ * - QODER_SKIP_DOWNLOAD=1|true: skip postinstall runtime downloads
8
+ * - QODER_INSTALL_BUNDLED_CLI=1|true: download process fallback even when the packaged default runtime is Worker
8
9
  * - QODER_CLI_MIRROR: CDN base URL. Accepts .../qodercli, .../qodercli/dev, or .../releases
10
+ * - QODER_WORKER_RUNTIME_URL: explicit worker runtime tgz URL override
9
11
  * - QODER_CLI_VERSION: override package.json qoderCliVersion
10
12
  * - QODER_CLI_SITE: download site. Supports global and cn
11
13
  * - QODER_CLI_PLATFORM: override target artifact, e.g. darwin-arm64, linux-x64-musl
@@ -24,6 +26,10 @@ const GLOBAL_PROD_DOWNLOAD_BASE_URL =
24
26
  'https://download.qoder.com/qodercli/releases';
25
27
  const GLOBAL_DEV_DOWNLOAD_BASE_URL =
26
28
  'https://qs-cli-dev.oss-cn-hangzhou.aliyuncs.com/qodercli/dev/releases';
29
+ const CN_PROD_DOWNLOAD_BASE_URL =
30
+ 'https://static.qoder.com.cn/qoder-cli-cn/releases';
31
+ const CN_DEV_DOWNLOAD_BASE_URL =
32
+ 'https://static.qoder.com.cn/qoder-cli-cn/dev/releases';
27
33
  const LOG_PREFIX = '[@qoder-ai/qoder-agent-sdk]';
28
34
 
29
35
  function isTruthy(value) {
@@ -38,7 +44,9 @@ function getCliVersion() {
38
44
  const packageJson = require('../package.json');
39
45
  const version = packageJson.qoderCliVersion ?? packageJson.version;
40
46
  if (!version) {
41
- throw new Error('package.json must define "qoderCliVersion" or "version".');
47
+ throw new Error(
48
+ 'package.json must define "qoderCliVersion" or "version".',
49
+ );
42
50
  }
43
51
  return String(version).replace(/^v/, '');
44
52
  } catch (error) {
@@ -83,6 +91,21 @@ function getDownloadBaseUrl(version = getCliVersion()) {
83
91
  return GLOBAL_PROD_DOWNLOAD_BASE_URL;
84
92
  }
85
93
 
94
+ function getWorkerRuntimeDownloadBaseUrl(version = getCliVersion()) {
95
+ if (process.env.QODER_CLI_MIRROR) {
96
+ return process.env.QODER_CLI_MIRROR;
97
+ }
98
+ const site = getCliSite();
99
+ if (site === 'cn') {
100
+ return isDevCliVersion(version)
101
+ ? CN_DEV_DOWNLOAD_BASE_URL
102
+ : CN_PROD_DOWNLOAD_BASE_URL;
103
+ }
104
+ return isDevCliVersion(version)
105
+ ? GLOBAL_DEV_DOWNLOAD_BASE_URL
106
+ : GLOBAL_PROD_DOWNLOAD_BASE_URL;
107
+ }
108
+
86
109
  function normalizeDownloadBaseUrl(baseUrl, version = getCliVersion()) {
87
110
  const trimmed = baseUrl.replace(/\/+$/, '');
88
111
  if (trimmed.endsWith(`/${version}`)) return trimmed;
@@ -119,9 +142,7 @@ function linuxX64HasAvx2() {
119
142
  }
120
143
 
121
144
  function normalizePlatformOverride(value) {
122
- const normalized = value
123
- .replace(/_/g, '-')
124
- .replace(/-amd64(?=-|$)/, '-x64');
145
+ const normalized = value.replace(/_/g, '-').replace(/-amd64(?=-|$)/, '-x64');
125
146
  const supported = new Set([
126
147
  'darwin-arm64',
127
148
  'darwin-x64',
@@ -181,10 +202,89 @@ function getArtifactName(target = getPlatformTarget()) {
181
202
 
182
203
  function getDownloadUrl() {
183
204
  const version = getCliVersion();
184
- const baseUrl = normalizeDownloadBaseUrl(getDownloadBaseUrl(version), version);
205
+ const baseUrl = normalizeDownloadBaseUrl(
206
+ getDownloadBaseUrl(version),
207
+ version,
208
+ );
185
209
  return `${baseUrl}/${getArtifactName()}`;
186
210
  }
187
211
 
212
+ function getWorkerRuntimeArtifactName(site = getCliSite()) {
213
+ return `${getCliArtifactPrefix(site)}-worker-runtime-universal.tgz`;
214
+ }
215
+
216
+ function hasWorkerRuntimeDownloadSiteOverride() {
217
+ return Boolean(process.env.QODER_CLI_SITE || process.env.QODER_CLI_MIRROR);
218
+ }
219
+
220
+ function getManifestWorkerRuntime(manifest = readRuntimeManifest()) {
221
+ const workerRuntime = manifest?.workerRuntime;
222
+ return workerRuntime && typeof workerRuntime === 'object'
223
+ ? workerRuntime
224
+ : null;
225
+ }
226
+
227
+ function getManifestWorkerRuntimeDownloadUrl(manifest = readRuntimeManifest()) {
228
+ const workerRuntime = getManifestWorkerRuntime(manifest);
229
+ if (!workerRuntime || workerRuntime.packaged === true) return null;
230
+
231
+ const candidates = [
232
+ workerRuntime.url,
233
+ workerRuntime.artifact?.url,
234
+ workerRuntime.source?.url,
235
+ ];
236
+ const url = candidates.find(
237
+ (candidate) => typeof candidate === 'string' && candidate.length > 0,
238
+ );
239
+ return url ?? null;
240
+ }
241
+
242
+ function getManifestWorkerRuntimeSha256(manifest = readRuntimeManifest()) {
243
+ const workerRuntime = getManifestWorkerRuntime(manifest);
244
+ if (!workerRuntime || workerRuntime.packaged === true) return null;
245
+
246
+ const candidates = [
247
+ workerRuntime.sha256,
248
+ workerRuntime.artifact?.sha256,
249
+ workerRuntime.source?.sha256,
250
+ ];
251
+ const checksum = candidates.find(
252
+ (candidate) =>
253
+ typeof candidate === 'string' &&
254
+ /^[a-fA-F0-9]{64}$/.test(candidate.trim()),
255
+ );
256
+ return checksum ? checksum.trim().toLowerCase() : null;
257
+ }
258
+
259
+ function getWorkerRuntimeDownloadUrl() {
260
+ if (process.env.QODER_WORKER_RUNTIME_URL) {
261
+ return process.env.QODER_WORKER_RUNTIME_URL;
262
+ }
263
+
264
+ const manifestUrl = hasWorkerRuntimeDownloadSiteOverride()
265
+ ? null
266
+ : getManifestWorkerRuntimeDownloadUrl();
267
+ if (manifestUrl) {
268
+ return manifestUrl;
269
+ }
270
+
271
+ const version = getCliVersion();
272
+ const baseUrl = normalizeDownloadBaseUrl(
273
+ getWorkerRuntimeDownloadBaseUrl(version),
274
+ version,
275
+ );
276
+ return `${baseUrl}/${getWorkerRuntimeArtifactName()}`;
277
+ }
278
+
279
+ function getWorkerRuntimeExpectedSha256(url = getWorkerRuntimeDownloadUrl()) {
280
+ const manifestUrl = getManifestWorkerRuntimeDownloadUrl();
281
+ if (manifestUrl && manifestUrl === url) {
282
+ return getManifestWorkerRuntimeSha256();
283
+ }
284
+
285
+ return null;
286
+ }
287
+
188
288
  function getBundledDir() {
189
289
  return path.join(__dirname, '..', 'dist', '_bundled');
190
290
  }
@@ -194,6 +294,105 @@ function getBundledCliPath() {
194
294
  return path.join(getBundledDir(), cliName);
195
295
  }
196
296
 
297
+ function getPackagedWorkerRuntimePath() {
298
+ return path.join(
299
+ __dirname,
300
+ '..',
301
+ 'dist',
302
+ '_worker',
303
+ 'qoder-worker-runtime.obf.mjs',
304
+ );
305
+ }
306
+
307
+ function getPackagedWorkerRuntimeInfoPath() {
308
+ return path.join(path.dirname(getPackagedWorkerRuntimePath()), 'runtime-info.json');
309
+ }
310
+
311
+ function hasPackagedWorkerRuntime() {
312
+ return fs.existsSync(getPackagedWorkerRuntimePath());
313
+ }
314
+
315
+ function readPackagedWorkerRuntimeInfo() {
316
+ const runtimeInfoPath = getPackagedWorkerRuntimeInfoPath();
317
+ if (!fs.existsSync(runtimeInfoPath)) {
318
+ return null;
319
+ }
320
+ try {
321
+ const parsed = JSON.parse(fs.readFileSync(runtimeInfoPath, 'utf8'));
322
+ return parsed && typeof parsed === 'object' ? parsed : null;
323
+ } catch {
324
+ return null;
325
+ }
326
+ }
327
+
328
+ function isPackagedWorkerRuntimeCompatibleWithSite(site = getCliSite()) {
329
+ if (!hasPackagedWorkerRuntime()) return false;
330
+ if (process.env.QODER_WORKER_RUNTIME_URL) return false;
331
+
332
+ const runtimeInfo = readPackagedWorkerRuntimeInfo();
333
+ if (!runtimeInfo) return false;
334
+
335
+ const expectedProductName = getCliArtifactPrefix(site);
336
+ const buildInfo =
337
+ runtimeInfo.build && typeof runtimeInfo.build === 'object'
338
+ ? runtimeInfo.build
339
+ : {};
340
+ const actualProductName = buildInfo.productName;
341
+ const actualSite = buildInfo.site;
342
+
343
+ if (
344
+ typeof actualProductName === 'string' &&
345
+ actualProductName.length > 0 &&
346
+ actualProductName !== expectedProductName
347
+ ) {
348
+ return false;
349
+ }
350
+
351
+ if (
352
+ typeof actualSite === 'string' &&
353
+ actualSite.length > 0 &&
354
+ actualSite !== site
355
+ ) {
356
+ return false;
357
+ }
358
+
359
+ return true;
360
+ }
361
+
362
+ function getRuntimeManifestPath() {
363
+ return path.join(__dirname, '..', 'dist', 'runtime-manifest.json');
364
+ }
365
+
366
+ function readRuntimeManifest() {
367
+ const manifestPath = getRuntimeManifestPath();
368
+ if (!fs.existsSync(manifestPath)) {
369
+ return null;
370
+ }
371
+ try {
372
+ const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
373
+ return parsed && typeof parsed === 'object' ? parsed : null;
374
+ } catch {
375
+ return null;
376
+ }
377
+ }
378
+
379
+ function getDefaultRuntimeTransport() {
380
+ const manifest = readRuntimeManifest();
381
+ return manifest?.defaultTransport === 'worker' ? 'worker' : 'process';
382
+ }
383
+
384
+ function shouldInstallWorkerRuntimeForDefaultWorker() {
385
+ return (
386
+ getDefaultRuntimeTransport() === 'worker' &&
387
+ !isPackagedWorkerRuntimeCompatibleWithSite()
388
+ );
389
+ }
390
+
391
+ function shouldSkipBundledCliDownloadForWorkerRuntime() {
392
+ if (getDefaultRuntimeTransport() !== 'worker') return false;
393
+ return !isTruthy(process.env.QODER_INSTALL_BUNDLED_CLI);
394
+ }
395
+
197
396
  function requestUrl(url, redirectCount = 0) {
198
397
  return new Promise((resolve, reject) => {
199
398
  if (redirectCount > 5) {
@@ -241,7 +440,10 @@ async function downloadFile(url, destPath) {
241
440
  }
242
441
 
243
442
  await new Promise((resolve, reject) => {
244
- const totalSize = Number.parseInt(response.headers['content-length'] || '0', 10);
443
+ const totalSize = Number.parseInt(
444
+ response.headers['content-length'] || '0',
445
+ 10,
446
+ );
245
447
  let downloadedSize = 0;
246
448
  let lastProgress = 0;
247
449
  const file = fs.createWriteStream(destPath);
@@ -299,9 +501,26 @@ function sha256File(filePath) {
299
501
  return hash.digest('hex');
300
502
  }
301
503
 
302
- async function verifyChecksum(archiveUrl, archivePath) {
504
+ async function verifyChecksum(archiveUrl, archivePath, expectedSha256) {
303
505
  if (isTruthy(process.env.QODER_SKIP_CHECKSUM)) {
304
- console.log(`${LOG_PREFIX} QODER_SKIP_CHECKSUM is set, skipping checksum verification.`);
506
+ console.log(
507
+ `${LOG_PREFIX} QODER_SKIP_CHECKSUM is set, skipping checksum verification.`,
508
+ );
509
+ return;
510
+ }
511
+
512
+ if (expectedSha256) {
513
+ const expected = String(expectedSha256).trim().toLowerCase();
514
+ if (!/^[a-fA-F0-9]{64}$/.test(expected)) {
515
+ throw new Error(`Invalid expected sha256: ${expectedSha256}`);
516
+ }
517
+ const actual = sha256File(archivePath);
518
+ if (actual !== expected) {
519
+ throw new Error(
520
+ `Checksum verification failed. Expected ${expected}, actual ${actual}`,
521
+ );
522
+ }
523
+ console.log(`${LOG_PREFIX} Checksum verified.`);
305
524
  return;
306
525
  }
307
526
 
@@ -310,14 +529,16 @@ async function verifyChecksum(archiveUrl, archivePath) {
310
529
  const expected = parseSha256(fs.readFileSync(checksumPath, 'utf8'));
311
530
  const actual = sha256File(archivePath);
312
531
  if (actual !== expected) {
313
- throw new Error(`Checksum verification failed. Expected ${expected}, actual ${actual}`);
532
+ throw new Error(
533
+ `Checksum verification failed. Expected ${expected}, actual ${actual}`,
534
+ );
314
535
  }
315
536
  console.log(`${LOG_PREFIX} Checksum verified.`);
316
537
  }
317
538
 
318
- function execFilePromise(command, args) {
539
+ function execFilePromise(command, args, options = {}) {
319
540
  return new Promise((resolve, reject) => {
320
- execFile(command, args, (error, stdout, stderr) => {
541
+ execFile(command, args, options, (error, stdout, stderr) => {
321
542
  if (error) {
322
543
  reject(
323
544
  new Error(
@@ -331,9 +552,28 @@ function execFilePromise(command, args) {
331
552
  });
332
553
  }
333
554
 
555
+ function getPathApi(filePath) {
556
+ return /^[a-zA-Z]:[\\/]/.test(filePath) ? path.win32 : path;
557
+ }
558
+
559
+ function getTarExtractInvocation(archivePath, destDir) {
560
+ const pathApi = getPathApi(archivePath);
561
+ const cwd = pathApi.dirname(archivePath);
562
+ return {
563
+ cwd,
564
+ args: [
565
+ '-xzf',
566
+ pathApi.basename(archivePath),
567
+ '-C',
568
+ pathApi.relative(cwd, destDir) || '.',
569
+ ],
570
+ };
571
+ }
572
+
334
573
  async function extractArchive(archivePath, destDir) {
335
- if (archivePath.endsWith('.tar.gz')) {
336
- await execFilePromise('tar', ['-xzf', archivePath, '-C', destDir]);
574
+ if (archivePath.endsWith('.tar.gz') || archivePath.endsWith('.tgz')) {
575
+ const invocation = getTarExtractInvocation(archivePath, destDir);
576
+ await execFilePromise('tar', invocation.args, { cwd: invocation.cwd });
337
577
  return;
338
578
  }
339
579
 
@@ -423,13 +663,102 @@ function installExtractedContents(extractDir, bundledDir) {
423
663
  }
424
664
 
425
665
  const files = fs.readdirSync(bundledDir);
426
- console.log(`${LOG_PREFIX} Copied ${files.length} file(s) to _bundled: ${files.join(', ')}`);
666
+ console.log(
667
+ `${LOG_PREFIX} Copied ${files.length} file(s) to _bundled: ${files.join(', ')}`,
668
+ );
427
669
  return target;
428
670
  }
429
671
 
672
+ function findWorkerRuntimeRoot(dir) {
673
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
674
+ const hasWorkerRuntime = entries.some(
675
+ (entry) =>
676
+ entry.isFile() &&
677
+ (entry.name === 'qoder-worker-runtime.obf.mjs' ||
678
+ entry.name === 'qoder-worker-runtime.mjs'),
679
+ );
680
+ if (hasWorkerRuntime) return dir;
681
+
682
+ for (const entry of entries) {
683
+ if (!entry.isDirectory()) continue;
684
+ const nested = findWorkerRuntimeRoot(path.join(dir, entry.name));
685
+ if (nested) return nested;
686
+ }
687
+ return null;
688
+ }
689
+
690
+ function installExtractedWorkerRuntime(extractDir, workerDir) {
691
+ const contentRoot = findWorkerRuntimeRoot(extractDir);
692
+ if (!contentRoot) {
693
+ throw new Error(
694
+ 'Worker runtime not found in extracted archive. Expected qoder-worker-runtime.obf.mjs or qoder-worker-runtime.mjs.',
695
+ );
696
+ }
697
+
698
+ fs.rmSync(workerDir, { recursive: true, force: true });
699
+ copyDirContents(contentRoot, workerDir);
700
+
701
+ const obfuscatedRuntime = path.join(
702
+ workerDir,
703
+ 'qoder-worker-runtime.obf.mjs',
704
+ );
705
+ const plainRuntime = path.join(workerDir, 'qoder-worker-runtime.mjs');
706
+ const runtimePath = fs.existsSync(obfuscatedRuntime)
707
+ ? obfuscatedRuntime
708
+ : plainRuntime;
709
+ if (!fs.existsSync(runtimePath)) {
710
+ throw new Error(`Worker runtime copy failed: ${runtimePath}`);
711
+ }
712
+
713
+ const files = fs.readdirSync(workerDir);
714
+ console.log(
715
+ `${LOG_PREFIX} Copied ${files.length} file(s) to _worker: ${files.join(', ')}`,
716
+ );
717
+ return runtimePath;
718
+ }
719
+
720
+ async function installWorkerRuntimeFromDownload() {
721
+ const url = getWorkerRuntimeDownloadUrl();
722
+ const expectedSha256 = getWorkerRuntimeExpectedSha256(url);
723
+ const tempDir = fs.mkdtempSync(
724
+ path.join(os.tmpdir(), 'qoder-worker-runtime-'),
725
+ );
726
+ const archivePath = path.join(tempDir, path.basename(new URL(url).pathname));
727
+ const extractDir = path.join(tempDir, 'extract');
728
+
729
+ try {
730
+ await downloadWithRetry(url, archivePath);
731
+ await verifyChecksum(url, archivePath, expectedSha256);
732
+ fs.mkdirSync(extractDir, { recursive: true });
733
+ await extractArchive(archivePath, extractDir);
734
+ return installExtractedWorkerRuntime(
735
+ extractDir,
736
+ path.dirname(getPackagedWorkerRuntimePath()),
737
+ );
738
+ } finally {
739
+ fs.rmSync(tempDir, { recursive: true, force: true });
740
+ }
741
+ }
742
+
430
743
  async function main() {
431
744
  if (isTruthy(process.env.QODER_SKIP_DOWNLOAD)) {
432
- console.log(`${LOG_PREFIX} QODER_SKIP_DOWNLOAD is set, skipping CLI download.`);
745
+ console.log(
746
+ `${LOG_PREFIX} QODER_SKIP_DOWNLOAD is set, skipping runtime downloads.`,
747
+ );
748
+ return;
749
+ }
750
+
751
+ if (shouldInstallWorkerRuntimeForDefaultWorker()) {
752
+ console.log(
753
+ `${LOG_PREFIX} Package default runtime is Worker; downloading Worker runtime.`,
754
+ );
755
+ await installWorkerRuntimeFromDownload();
756
+ }
757
+
758
+ if (shouldSkipBundledCliDownloadForWorkerRuntime()) {
759
+ console.log(
760
+ `${LOG_PREFIX} Package default runtime is Worker; skipping bundled CLI binary download. Set QODER_INSTALL_BUNDLED_CLI=1 to install the process fallback as well.`,
761
+ );
433
762
  return;
434
763
  }
435
764
 
@@ -448,7 +777,9 @@ async function main() {
448
777
  const archivePath = path.join(tempDir, archiveName);
449
778
  const extractDir = path.join(tempDir, 'extract');
450
779
 
451
- console.log(`${LOG_PREFIX} Installing ${bundleName} ${version} for ${target}.`);
780
+ console.log(
781
+ `${LOG_PREFIX} Installing ${bundleName} ${version} for ${target}.`,
782
+ );
452
783
 
453
784
  try {
454
785
  fs.mkdirSync(extractDir, { recursive: true });
@@ -461,8 +792,12 @@ async function main() {
461
792
  `${LOG_PREFIX} CLI binary installed: ${installedPath} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`,
462
793
  );
463
794
  } catch (error) {
464
- console.error(`${LOG_PREFIX} ERROR: Failed to install bundled CLI: ${error.message}`);
465
- console.error(`${LOG_PREFIX} Set QODER_SKIP_DOWNLOAD=1 to skip download and use a system qodercli.`);
795
+ console.error(
796
+ `${LOG_PREFIX} ERROR: Failed to install bundled CLI: ${error.message}`,
797
+ );
798
+ console.error(
799
+ `${LOG_PREFIX} Set QODER_SKIP_DOWNLOAD=1 to skip download and use a system qodercli.`,
800
+ );
466
801
  process.exit(1);
467
802
  } finally {
468
803
  fs.rmSync(tempDir, { recursive: true, force: true });
@@ -477,15 +812,37 @@ if (require.main === module) {
477
812
  }
478
813
 
479
814
  module.exports = {
815
+ downloadWithRetry,
816
+ extractArchive,
817
+ getTarExtractInvocation,
480
818
  getArtifactName,
481
819
  getArchiveExtension,
482
820
  getCliSite,
483
821
  getCliVersion,
484
822
  getDownloadBaseUrl,
485
823
  getDownloadUrl,
824
+ getManifestWorkerRuntimeDownloadUrl,
825
+ getManifestWorkerRuntimeSha256,
826
+ getPackagedWorkerRuntimeInfoPath,
827
+ getPackagedWorkerRuntimePath,
828
+ getRuntimeManifestPath,
486
829
  getPlatformTarget,
830
+ getDefaultRuntimeTransport,
831
+ getWorkerRuntimeExpectedSha256,
832
+ getWorkerRuntimeArtifactName,
833
+ getWorkerRuntimeDownloadBaseUrl,
834
+ getWorkerRuntimeDownloadUrl,
835
+ hasPackagedWorkerRuntime,
487
836
  installExtractedContents,
837
+ installExtractedWorkerRuntime,
838
+ installWorkerRuntimeFromDownload,
839
+ isPackagedWorkerRuntimeCompatibleWithSite,
488
840
  isDevCliVersion,
489
841
  normalizeDownloadBaseUrl,
490
842
  normalizePlatformOverride,
843
+ readPackagedWorkerRuntimeInfo,
844
+ readRuntimeManifest,
845
+ shouldInstallWorkerRuntimeForDefaultWorker,
846
+ shouldSkipBundledCliDownloadForWorkerRuntime,
847
+ verifyChecksum,
491
848
  };