1
>
/*---------------------------------------------------------------------------------------------
agentSdkDownloader.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import * as fs from 'fs';
7
>
import * as tar from 'tar';
8
>
import { VSBuffer } from '../../../base/common/buffer.js';
9
>
import { CancellationToken } from '../../../base/common/cancellation.js';
10
>
import { CancellationError } from '../../../base/common/errors.js';
11
>
import { Emitter, Event } from '../../../base/common/event.js';
12
>
import { Disposable } from '../../../base/common/lifecycle.js';
13
>
import * as path from '../../../base/common/path.js';
14
>
import { format2 } from '../../../base/common/strings.js';
15
>
import { URI } from '../../../base/common/uri.js';
16
>
import { generateUuid } from '../../../base/common/uuid.js';
17
>
import { detectLibcSync, type LibcFamily } from '../../../base/node/libc.js';
18
>
import { INativeEnvironmentService } from '../../environment/common/environment.js';
19
>
import { FileOperationError, FileOperationResult, IFileService, toFileOperationResult } from '../../files/common/files.js';
20
>
import { createDecorator } from '../../instantiation/common/instantiation.js';
21
>
import { ILogService } from '../../log/common/log.js';
22
>
import { IProductService } from '../../product/common/productService.js';
23
>
import { IRequestService } from '../../request/common/request.js';
24
>
import { IRequestContext } from '../../../base/parts/request/common/request.js';
25
>
26
>
// #region Per-package strategy
27
>
28
>
/**
29
>
* One agent-SDK package the downloader can fetch. Holds the per-package
30
>
* knowledge that varies between Claude, Codex, and any future provider —
31
>
* the package id, the env var that acts as a dev override, and one
32
>
* boolean covering the only mapping detail that differs between SDKs
33
>
* today (Claude has separate `linux-*-musl` SKUs; Codex's Linux binary
34
>
* is statically musl-linked and ships as a single `linux-*` SKU).
35
>
*
36
>
* The downloader itself is package-agnostic: it consumes this interface and
37
>
* never branches on `id`. Concrete `IAgentSdkPackage` instances live in
38
>
* their owning agent module (e.g. `ClaudeSdkPackage` in
39
>
* `claude/claudeAgentSdkService.ts`, `CodexSdkPackage` in
40
>
* `codex/codexAgent.ts`) so Claude-specific / Codex-specific knowledge
41
>
* stays in those modules — the downloader doesn't name the providers it
42
>
* serves.
43
>
*
44
>
* Each shipped `product.json` carries one `{version, urlTemplate}` per
45
>
* SDK. The downloader substitutes `{sdkTarget}` (resolved via
46
>
* `resolveSdkTarget(pkg)`) into the template to get the per-target
47
>
* tarball URL. This shape supports macOS Universal builds, where the
48
>
* same `product.json` is shared by arm64 and x64 launches.
49
>
*/
50
>
export interface IAgentSdkPackage {
51
>
/** Key under `product.agentSdks` — e.g. `'claude'`, `'codex'`. */
52
>
readonly id: string;
53
>
/**
54
>
* Brand display name for user-facing progress, e.g. `'Claude'`, `'Codex'`.
55
>
* The downloader puts this on {@link IAgentSdkDownloadProgress.displayName}
56
>
* so clients can build a localized "Downloading {displayName} agent" label.
57
>
*/
58
>
readonly displayName: string;
59
>
/** Env var that, when set, becomes the SDK root and short-circuits the download. */
60
>
readonly devOverrideEnvVar: string;
61
>
/**
62
>
* True iff this SDK publishes separate `linux-{x64,arm64}-musl`
63
>
* packages alongside the glibc default. Claude does; Codex doesn't
64
>
* (its Linux binary is statically musl-linked and runs on both).
65
>
*/
66
>
readonly hasSeparateMuslLinuxPackage: boolean;
67
>
}
68
>
69
>
/**
70
>
* Per-host info used by `resolveSdkTarget`. Defaulted from the running
71
>
* process; tests inject synthetic values to exercise targets the test
72
>
* host doesn't actually run on (Universal-launch case, musl, etc.).
73
>
*/
74
>
export interface ISdkTargetHost {
75
>
readonly platform: NodeJS.Platform;
76
>
readonly arch: string;
77
>
readonly libc: LibcFamily | undefined;
78
>
}
79
>
80
>
const SUPPORTED_PLATFORMS = new Set<NodeJS.Platform>(['linux', 'darwin', 'win32']);
81
>
const SUPPORTED_ARCHES = new Set<string>(['x64', 'arm64']);
82
>
83
>
/**
84
>
* Resolves the build's `sdkTarget` suffix for the given host. Defaults
85
>
* to the current Node process — production callers omit `host`; tests
86
>
* pass a synthetic host to cover targets the test machine can't reach
87
>
* (Universal launches from a single-arch host, musl Linux on macOS CI,
88
>
* etc.).
89
>
*
90
>
* - claude on glibc Linux: `linux-x64` / `linux-arm64`
91
>
* - claude on musl Linux: `linux-x64-musl` / `linux-arm64-musl`
92
>
* - codex Linux (any libc): `linux-x64` / `linux-arm64`
93
>
* - everywhere else: `<platform>-<arch>`
94
>
*
95
>
* Returns `undefined` when no SDK applies (`armhf`, web, etc.); the
96
>
* downloader treats that the same as "no product config" and never
97
>
* registers the provider.
98
>
*
99
>
* Mirror of the build pipeline's `getSdkTargetForBuild` (in
100
>
* `build/agent-sdk/common.ts`) translated from build-time
101
>
* `vscodePlatform` to runtime `process.platform` + libc detection.
102
>
* Keep the two in sync when adding new target SKUs.
103
>
*/
104
>
export function resolveSdkTarget(
105
pkg: Pick<IAgentSdkPackage, 'hasSeparateMuslLinuxPackage'>,
106
host: ISdkTargetHost = { platform: process.platform, arch: process.arch, libc: detectLibcSync() },