@vellumai/cli 0.10.4-dev.202607012040.649b529 → 0.10.4-dev.202607012234.a9be056
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/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
fetchLatestVersion,
|
|
4
5
|
fetchReleases,
|
|
5
6
|
resolveImageRefsDetailed,
|
|
6
7
|
} from "../lib/platform-releases.js";
|
|
@@ -114,4 +115,45 @@ describe("resolveImageRefsDetailed", () => {
|
|
|
114
115
|
const result = await resolveImageRefsDetailed("0.7.0");
|
|
115
116
|
expect(result.status).toBe("dockerhub-fallback");
|
|
116
117
|
});
|
|
118
|
+
|
|
119
|
+
test("requests the preview channel when channel=preview", async () => {
|
|
120
|
+
const fetchMock = mockFetchJson([RELEASE]);
|
|
121
|
+
await resolveImageRefsDetailed("v0.7.0", undefined, "preview");
|
|
122
|
+
const url = String(fetchMock.mock.calls[0][0]);
|
|
123
|
+
expect(url).toContain("channel=preview");
|
|
124
|
+
expect(url).not.toContain("stable=true");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("keeps the stable filter when channel=stable (default)", async () => {
|
|
128
|
+
const fetchMock = mockFetchJson([RELEASE]);
|
|
129
|
+
await resolveImageRefsDetailed("v0.7.0", undefined, "stable");
|
|
130
|
+
const url = String(fetchMock.mock.calls[0][0]);
|
|
131
|
+
expect(url).toContain("stable=true");
|
|
132
|
+
expect(url).not.toContain("channel=");
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("fetchLatestVersion", () => {
|
|
137
|
+
test("defaults to the stable channel", async () => {
|
|
138
|
+
const fetchMock = mockFetchJson([RELEASE]);
|
|
139
|
+
const version = await fetchLatestVersion();
|
|
140
|
+
expect(version).toBe("0.7.0");
|
|
141
|
+
const url = String(fetchMock.mock.calls[0][0]);
|
|
142
|
+
expect(url).toContain("stable=true");
|
|
143
|
+
expect(url).not.toContain("channel=");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("requests the preview channel when asked", async () => {
|
|
147
|
+
const preview = { ...RELEASE, version: "0.8.0-preview.3" };
|
|
148
|
+
const fetchMock = mockFetchJson([preview]);
|
|
149
|
+
const version = await fetchLatestVersion("preview");
|
|
150
|
+
expect(version).toBe("0.8.0-preview.3");
|
|
151
|
+
const url = String(fetchMock.mock.calls[0][0]);
|
|
152
|
+
expect(url).toContain("channel=preview");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("returns null when the channel has no releases", async () => {
|
|
156
|
+
mockFetchJson([]);
|
|
157
|
+
expect(await fetchLatestVersion("preview")).toBeNull();
|
|
158
|
+
});
|
|
117
159
|
});
|
package/src/commands/hatch.ts
CHANGED
|
@@ -178,6 +178,7 @@ interface HatchArgs {
|
|
|
178
178
|
remote: RemoteHost;
|
|
179
179
|
watch: boolean;
|
|
180
180
|
sourcePath: string | null;
|
|
181
|
+
preview: boolean;
|
|
181
182
|
configValues: Record<string, string>;
|
|
182
183
|
flagEnvVars: Record<string, string>;
|
|
183
184
|
analyze: boolean;
|
|
@@ -203,6 +204,7 @@ function parseArgs(): HatchArgs {
|
|
|
203
204
|
let remote: RemoteHost = DEFAULT_REMOTE;
|
|
204
205
|
let watch = false;
|
|
205
206
|
let sourcePath: string | null = null;
|
|
207
|
+
let preview = false;
|
|
206
208
|
const configValues: Record<string, string> = {};
|
|
207
209
|
let analyze = false;
|
|
208
210
|
let netnsContainer: string | null = null;
|
|
@@ -231,6 +233,7 @@ function parseArgs(): HatchArgs {
|
|
|
231
233
|
);
|
|
232
234
|
console.log(
|
|
233
235
|
" --source <path> Build images from a local source tree at <path> (no watcher). Useful for callers (e.g. evals) that want each run to pick up local CLI changes.",
|
|
236
|
+
" --preview When pulling published images (no local source), resolve from the preview channel (latest preview release) instead of latest-stable. Also settable via VELLUM_HATCH_CHANNEL=preview.",
|
|
234
237
|
);
|
|
235
238
|
console.log(
|
|
236
239
|
" --keep-alive Stay alive after hatch, exit when gateway stops",
|
|
@@ -263,6 +266,8 @@ function parseArgs(): HatchArgs {
|
|
|
263
266
|
watch = true;
|
|
264
267
|
} else if (arg === "--analyze") {
|
|
265
268
|
analyze = true;
|
|
269
|
+
} else if (arg === "--preview") {
|
|
270
|
+
preview = true;
|
|
266
271
|
} else if (arg === "--source") {
|
|
267
272
|
const next = args[i + 1];
|
|
268
273
|
if (!next || next.startsWith("-")) {
|
|
@@ -349,7 +354,7 @@ function parseArgs(): HatchArgs {
|
|
|
349
354
|
species = arg as Species;
|
|
350
355
|
} else {
|
|
351
356
|
console.error(
|
|
352
|
-
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --watch, --source <path>, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>, --config <key=value>, --flag <key=value>, --analyze, --disable-platform, --netns-container <name>, --gateway-port <port>, --assistant-ca-cert <path>`,
|
|
357
|
+
`Error: Unknown argument '${arg}'. Valid options: ${VALID_SPECIES.join(", ")}, -d, --watch, --source <path>, --preview, --keep-alive, --name <name>, --remote <${VALID_REMOTE_HOSTS.join("|")}>, --config <key=value>, --flag <key=value>, --analyze, --disable-platform, --netns-container <name>, --gateway-port <port>, --assistant-ca-cert <path>`,
|
|
353
358
|
);
|
|
354
359
|
process.exit(1);
|
|
355
360
|
}
|
|
@@ -363,6 +368,7 @@ function parseArgs(): HatchArgs {
|
|
|
363
368
|
remote,
|
|
364
369
|
watch,
|
|
365
370
|
sourcePath,
|
|
371
|
+
preview,
|
|
366
372
|
configValues,
|
|
367
373
|
flagEnvVars,
|
|
368
374
|
analyze,
|
|
@@ -602,6 +608,7 @@ export async function hatch(): Promise<void> {
|
|
|
602
608
|
remote,
|
|
603
609
|
watch,
|
|
604
610
|
sourcePath,
|
|
611
|
+
preview,
|
|
605
612
|
configValues,
|
|
606
613
|
flagEnvVars,
|
|
607
614
|
analyze,
|
|
@@ -681,6 +688,7 @@ export async function hatch(): Promise<void> {
|
|
|
681
688
|
flagEnvVars,
|
|
682
689
|
sourcePath,
|
|
683
690
|
analyze,
|
|
691
|
+
channel: preview ? "preview" : undefined,
|
|
684
692
|
netnsContainer: netnsContainer ?? undefined,
|
|
685
693
|
gatewayPort: gatewayPort ?? undefined,
|
|
686
694
|
assistantCaCertPath: assistantCaCert ?? undefined,
|
package/src/lib/docker.ts
CHANGED
|
@@ -35,8 +35,9 @@ import {
|
|
|
35
35
|
loadImageViaHost,
|
|
36
36
|
} from "./host-image-loader.js";
|
|
37
37
|
import {
|
|
38
|
-
|
|
38
|
+
fetchLatestVersion,
|
|
39
39
|
resolveImageRefs,
|
|
40
|
+
type ReleaseChannel,
|
|
40
41
|
} from "./platform-releases.js";
|
|
41
42
|
import {
|
|
42
43
|
configureHatchProviderApiKey,
|
|
@@ -321,6 +322,15 @@ export interface HatchDockerParams {
|
|
|
321
322
|
* connection.
|
|
322
323
|
*/
|
|
323
324
|
assistantCaCertPath?: string;
|
|
325
|
+
/**
|
|
326
|
+
* Release channel to resolve published images from when hatching without a
|
|
327
|
+
* local source tree (the image-pull fallback). `stable` (default) keeps the
|
|
328
|
+
* latest-stable behavior; `preview` pulls the latest preview release. Only
|
|
329
|
+
* affects the pull path — a local source build ignores it. Falls back to
|
|
330
|
+
* the `VELLUM_HATCH_CHANNEL` env var when unset, so callers that hatch via
|
|
331
|
+
* env (e.g. evals) can opt in without changing the invocation.
|
|
332
|
+
*/
|
|
333
|
+
channel?: ReleaseChannel;
|
|
324
334
|
}
|
|
325
335
|
|
|
326
336
|
export type DockerProviderCredentialSetupAction =
|
|
@@ -1093,6 +1103,14 @@ export async function hatchDocker(params: HatchDockerParams): Promise<void> {
|
|
|
1093
1103
|
flagEnvVars = {},
|
|
1094
1104
|
} = params;
|
|
1095
1105
|
let watch = params.watch ?? false;
|
|
1106
|
+
// Resolve the release channel for the image-pull fallback: explicit param
|
|
1107
|
+
// wins, then the VELLUM_HATCH_CHANNEL env var, else stable. Any value other
|
|
1108
|
+
// than "preview" (case-insensitive) is treated as stable.
|
|
1109
|
+
const channel: ReleaseChannel =
|
|
1110
|
+
params.channel ??
|
|
1111
|
+
(process.env.VELLUM_HATCH_CHANNEL?.trim().toLowerCase() === "preview"
|
|
1112
|
+
? "preview"
|
|
1113
|
+
: "stable");
|
|
1096
1114
|
|
|
1097
1115
|
resetLogFile("hatch.log");
|
|
1098
1116
|
const provider =
|
|
@@ -1250,8 +1268,8 @@ export async function hatchDocker(params: HatchDockerParams): Promise<void> {
|
|
|
1250
1268
|
// Resolve image refs from a remote source that may have dev/local
|
|
1251
1269
|
// builds. If resolution is unavailable, fall back to the CLI's own
|
|
1252
1270
|
// version so a default tag can still be resolved.
|
|
1253
|
-
log(
|
|
1254
|
-
const latestVersion = await
|
|
1271
|
+
log(`🔍 Fetching latest ${channel} release...`);
|
|
1272
|
+
const latestVersion = await fetchLatestVersion(channel);
|
|
1255
1273
|
let versionTag: string;
|
|
1256
1274
|
if (latestVersion) {
|
|
1257
1275
|
versionTag = latestVersion.startsWith("v")
|
|
@@ -1265,7 +1283,7 @@ export async function hatchDocker(params: HatchDockerParams): Promise<void> {
|
|
|
1265
1283
|
);
|
|
1266
1284
|
}
|
|
1267
1285
|
log("🔍 Resolving image references...");
|
|
1268
|
-
const resolved = await resolveImageRefs(versionTag, log);
|
|
1286
|
+
const resolved = await resolveImageRefs(versionTag, log, channel);
|
|
1269
1287
|
imageTags.assistant = resolved.imageTags.assistant;
|
|
1270
1288
|
imageTags.gateway = resolved.imageTags.gateway;
|
|
1271
1289
|
imageTags["credential-executor"] =
|
|
@@ -9,6 +9,25 @@ export interface ResolvedImageRefs {
|
|
|
9
9
|
source: "platform" | "dockerhub";
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Release channel to resolve images from. `stable` is the default and maps
|
|
14
|
+
* to the server's `stable=true` filter (unchanged behavior). `preview` opts
|
|
15
|
+
* into the platform's preview channel — the newest release regardless of
|
|
16
|
+
* stability — used by callers (e.g. evals) that want to always hatch the
|
|
17
|
+
* latest preview image instead of latest-stable.
|
|
18
|
+
*/
|
|
19
|
+
export type ReleaseChannel = "stable" | "preview";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Map a channel to `fetchReleases` options. `stable` passes no `channel`
|
|
23
|
+
* param so the server keeps its `stable=true` default (we don't rely on an
|
|
24
|
+
* unverified `channel=stable` filter); `preview` requests the preview
|
|
25
|
+
* channel explicitly.
|
|
26
|
+
*/
|
|
27
|
+
function releasesOptsFor(channel: ReleaseChannel): { channel?: "preview" } {
|
|
28
|
+
return channel === "preview" ? { channel: "preview" } : {};
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
export interface ReleaseListItem {
|
|
13
32
|
version: string;
|
|
14
33
|
is_stable?: boolean;
|
|
@@ -51,15 +70,26 @@ export async function fetchReleases(opts?: {
|
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
/**
|
|
54
|
-
* Fetch the latest
|
|
73
|
+
* Fetch the latest release version for a channel from the platform API.
|
|
55
74
|
* Returns the version string (e.g. "0.7.0") or null if unavailable.
|
|
56
|
-
* The releases endpoint returns entries ordered newest-first
|
|
75
|
+
* The releases endpoint returns entries ordered newest-first, so the first
|
|
76
|
+
* entry is the latest for the requested channel.
|
|
57
77
|
*/
|
|
58
|
-
export async function
|
|
59
|
-
|
|
78
|
+
export async function fetchLatestVersion(
|
|
79
|
+
channel: ReleaseChannel = "stable",
|
|
80
|
+
): Promise<string | null> {
|
|
81
|
+
const releases = await fetchReleases(releasesOptsFor(channel));
|
|
60
82
|
return releases?.[0]?.version ?? null;
|
|
61
83
|
}
|
|
62
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the latest stable release version. Thin back-compat wrapper around
|
|
87
|
+
* {@link fetchLatestVersion} for existing callers.
|
|
88
|
+
*/
|
|
89
|
+
export async function fetchLatestStableVersion(): Promise<string | null> {
|
|
90
|
+
return fetchLatestVersion("stable");
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
export type ImageRefResolution =
|
|
64
94
|
| { status: "platform"; imageTags: Record<ServiceName, string> }
|
|
65
95
|
| { status: "dockerhub-fallback"; imageTags: Record<ServiceName, string> }
|
|
@@ -87,10 +117,11 @@ function dockerhubImageTags(version: string): Record<ServiceName, string> {
|
|
|
87
117
|
export async function resolveImageRefsDetailed(
|
|
88
118
|
version: string,
|
|
89
119
|
log?: (msg: string) => void,
|
|
120
|
+
channel: ReleaseChannel = "stable",
|
|
90
121
|
): Promise<ImageRefResolution> {
|
|
91
122
|
log?.("Resolving image references...");
|
|
92
123
|
|
|
93
|
-
const releases = await fetchReleases();
|
|
124
|
+
const releases = await fetchReleases(releasesOptsFor(channel));
|
|
94
125
|
if (releases === null) {
|
|
95
126
|
log?.("Platform unreachable — falling back to DockerHub tags");
|
|
96
127
|
return {
|
|
@@ -151,8 +182,9 @@ export async function resolveImageRefsDetailed(
|
|
|
151
182
|
export async function resolveImageRefs(
|
|
152
183
|
version: string,
|
|
153
184
|
log?: (msg: string) => void,
|
|
185
|
+
channel: ReleaseChannel = "stable",
|
|
154
186
|
): Promise<ResolvedImageRefs> {
|
|
155
|
-
const resolution = await resolveImageRefsDetailed(version, log);
|
|
187
|
+
const resolution = await resolveImageRefsDetailed(version, log, channel);
|
|
156
188
|
if (resolution.status === "platform") {
|
|
157
189
|
log?.("Resolved image refs from platform API");
|
|
158
190
|
return { imageTags: resolution.imageTags, source: "platform" };
|