experimental-ash 0.3.0-alpha.37 → 0.3.0-alpha.38

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.
Files changed (60) hide show
  1. package/dist/docs/public/sandbox.md +87 -49
  2. package/dist/src/chunks/{chunk-2R4UWV4G.js → chunk-5BOA7PVK.js} +2 -2
  3. package/dist/src/chunks/{chunk-XQMYWUU7.js → chunk-IKHNAPQA.js} +166 -92
  4. package/dist/src/chunks/{chunk-XQMYWUU7.js.map → chunk-IKHNAPQA.js.map} +2 -2
  5. package/dist/src/chunks/{chunk-XZHB5N7B.js → chunk-W3JJ5DZF.js} +6 -6
  6. package/dist/src/chunks/{chunk-XZHB5N7B.js.map → chunk-W3JJ5DZF.js.map} +2 -2
  7. package/dist/src/chunks/{dev-authored-source-watcher-Y56KM4VR.js → dev-authored-source-watcher-AQSC6QOJ.js} +3 -3
  8. package/dist/src/chunks/{host-VLLCUGVL.js → host-G4LSBCND.js} +4 -4
  9. package/dist/src/cli/commands/info.js +1 -1
  10. package/dist/src/cli/run.js +2 -2
  11. package/dist/src/compiler/model-catalog.d.ts +33 -0
  12. package/dist/src/compiler/model-catalog.d.ts.map +1 -1
  13. package/dist/src/compiler/model-catalog.js +96 -72
  14. package/dist/src/compiler/model-catalog.js.map +1 -1
  15. package/dist/src/compiler/normalize-agent-config.js +13 -2
  16. package/dist/src/compiler/normalize-agent-config.js.map +1 -1
  17. package/dist/src/evals/cli/eval.js +3 -3
  18. package/dist/src/execution/sandbox/bindings/local.js +4 -2
  19. package/dist/src/execution/sandbox/bindings/local.js.map +1 -1
  20. package/dist/src/execution/sandbox/bindings/vercel.d.ts +4 -4
  21. package/dist/src/execution/sandbox/bindings/vercel.d.ts.map +1 -1
  22. package/dist/src/execution/sandbox/bindings/vercel.js +51 -5
  23. package/dist/src/execution/sandbox/bindings/vercel.js.map +1 -1
  24. package/dist/src/execution/sandbox/ensure.d.ts.map +1 -1
  25. package/dist/src/execution/sandbox/ensure.js +2 -4
  26. package/dist/src/execution/sandbox/ensure.js.map +1 -1
  27. package/dist/src/execution/sandbox/prewarm.d.ts.map +1 -1
  28. package/dist/src/execution/sandbox/prewarm.js +2 -2
  29. package/dist/src/execution/sandbox/prewarm.js.map +1 -1
  30. package/dist/src/internal/application/package.js +1 -1
  31. package/dist/src/public/definitions/sandbox-backend.d.ts +7 -6
  32. package/dist/src/public/definitions/sandbox-backend.d.ts.map +1 -1
  33. package/dist/src/public/definitions/sandbox.d.ts +29 -9
  34. package/dist/src/public/definitions/sandbox.d.ts.map +1 -1
  35. package/dist/src/public/definitions/sandbox.js.map +1 -1
  36. package/dist/src/public/sandbox/backends/vercel.d.ts +6 -5
  37. package/dist/src/public/sandbox/backends/vercel.d.ts.map +1 -1
  38. package/dist/src/public/sandbox/backends/vercel.js +7 -6
  39. package/dist/src/public/sandbox/backends/vercel.js.map +1 -1
  40. package/dist/src/public/sandbox/index.d.ts +2 -1
  41. package/dist/src/public/sandbox/index.d.ts.map +1 -1
  42. package/dist/src/public/sandbox/index.js.map +1 -1
  43. package/dist/src/public/sandbox/vercel-sandbox.d.ts +32 -0
  44. package/dist/src/public/sandbox/vercel-sandbox.d.ts.map +1 -0
  45. package/dist/src/public/sandbox/vercel-sandbox.js +2 -0
  46. package/dist/src/public/sandbox/vercel-sandbox.js.map +1 -0
  47. package/dist/src/public/sandboxes/vercel-sandbox.d.ts +42 -0
  48. package/dist/src/public/sandboxes/vercel-sandbox.d.ts.map +1 -0
  49. package/dist/src/public/sandboxes/vercel-sandbox.js +2 -0
  50. package/dist/src/public/sandboxes/vercel-sandbox.js.map +1 -0
  51. package/dist/src/runtime/types.d.ts +3 -3
  52. package/dist/src/runtime/types.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/dist/src/compiler/rich-model-catalog.d.ts +0 -40
  55. package/dist/src/compiler/rich-model-catalog.d.ts.map +0 -1
  56. package/dist/src/compiler/rich-model-catalog.js +0 -66
  57. package/dist/src/compiler/rich-model-catalog.js.map +0 -1
  58. /package/dist/src/chunks/{chunk-2R4UWV4G.js.map → chunk-5BOA7PVK.js.map} +0 -0
  59. /package/dist/src/chunks/{dev-authored-source-watcher-Y56KM4VR.js.map → dev-authored-source-watcher-AQSC6QOJ.js.map} +0 -0
  60. /package/dist/src/chunks/{host-VLLCUGVL.js.map → host-G4LSBCND.js.map} +0 -0
@@ -21,7 +21,7 @@ If you never author a sandbox override, the framework default is what you get.
21
21
 
22
22
  ## Overriding The Sandbox
23
23
 
24
- To customize the sandbox — to add backend options, bootstrap commands, or per-session setup —
24
+ To customize the sandbox — to add bootstrap commands or per-session setup —
25
25
  author a sandbox definition module:
26
26
 
27
27
  ```ts
@@ -29,7 +29,8 @@ author a sandbox definition module:
29
29
  import { defineSandbox } from "experimental-ash/sandbox";
30
30
 
31
31
  export default defineSandbox({
32
- async bootstrap({ sandbox }) {
32
+ async bootstrap({ use }) {
33
+ const sandbox = await use();
33
34
  await sandbox.runCommand("apt-get install -y jq");
34
35
  },
35
36
  });
@@ -49,8 +50,10 @@ When both are present, the folder layout wins and the top-level shorthand is ign
49
50
 
50
51
  The public lifecycle surface is intentionally small:
51
52
 
52
- - `bootstrap` — template-scoped setup (runs once when the template is built)
53
- - `onSession` durable-session-scoped setup (runs once per session)
53
+ - `bootstrap({ use })` — template-scoped setup (runs once when the template is built). Call `use()`
54
+ to get a `SandboxSession` for filesystem setup. Only filesystem state survives snapshotting.
55
+ - `onSession({ use })` — durable-session-scoped setup (runs once per session). Call `use(opts?)` to
56
+ get the backend-specific sandbox (e.g. `VercelSandbox`) with session-level configuration applied.
54
57
  - `sandbox.resolvePath(path)` — translate a logical `/workspace/...` path into the live filesystem
55
58
  - `sandbox.runCommand(command)` — run a shell command inside the sandbox
56
59
 
@@ -182,6 +185,9 @@ Inside a subagent's authored code, `getSandbox()` binds to the subagent's own sa
182
185
  - installing dependencies
183
186
  - seeding files or directories every later session should start with
184
187
 
188
+ Call `use()` to get a `SandboxSession`. Only filesystem state survives — configuration changes
189
+ like network policy or resource allocation do not persist into the snapshot.
190
+
185
191
  Framework-managed seed files (compiled skills, authored `workspace/` entries) are written into the
186
192
  template before the authored `bootstrap` runs, so your bootstrap can read them.
187
193
 
@@ -189,10 +195,14 @@ template before the authored `bootstrap` runs, so your bootstrap can read them.
189
195
 
190
196
  `onSession` is durable-session-scoped. Use it for one-time setup for the current Ash session:
191
197
 
198
+ - configuring network policy, resources, or timeout
192
199
  - creating a session-specific working copy
193
200
  - configuring per-user credentials for CLI tools
194
201
  - writing one-time markers
195
- - lightweight setup that should not be baked into the template
202
+
203
+ Call `use(opts?)` to get the backend-specific sandbox. When using `vercelBackend()`, this returns
204
+ a `VercelSandbox` with `update()` for session-level configuration. Options passed to `use()` are
205
+ applied immediately; you can call `sandbox.update()` again later if needed.
196
206
 
197
207
  Unlike `bootstrap`, `onSession` runs inside the active Ash runtime context, so user-scoped setup can
198
208
  call `getSession()` and derive the current principal without baking credentials into the reusable
@@ -200,10 +210,12 @@ template:
200
210
 
201
211
  ```ts
202
212
  import { getSession } from "experimental-ash/context";
203
- import { defineSandbox } from "experimental-ash/sandbox";
213
+ import { defineSandbox, vercelBackend } from "experimental-ash/sandbox";
204
214
 
205
215
  export default defineSandbox({
206
- async onSession({ sandbox }) {
216
+ backend: vercelBackend(),
217
+ async onSession({ use }) {
218
+ const sandbox = await use({ networkPolicy: "deny-all" });
207
219
  const user = getSession().auth.current;
208
220
  if (user === null) return;
209
221
 
@@ -219,7 +231,8 @@ export default defineSandbox({
219
231
 
220
232
  Ash ships two built-in backends, exposed as factory functions from `experimental-ash/sandbox`:
221
233
 
222
- - `vercelBackend(options?)` — runs the sandbox on Vercel Sandbox via `@vercel/sandbox`.
234
+ - `vercelBackend()` — runs the sandbox on Vercel Sandbox via `@vercel/sandbox`. Returns a
235
+ `VercelSandbox` from `onSession`'s `use()` with full SDK capabilities.
223
236
  - `localBackend()` — runs the sandbox locally via `just-bash`. Default for `pnpm ash dev`.
224
237
 
225
238
  Attach a backend via `defineSandbox({ backend })`:
@@ -229,10 +242,14 @@ Attach a backend via `defineSandbox({ backend })`:
229
242
  import { defineSandbox, vercelBackend } from "experimental-ash/sandbox";
230
243
 
231
244
  export default defineSandbox({
232
- backend: vercelBackend({ runtime: "node24" }),
233
- async bootstrap({ sandbox }) {
245
+ backend: vercelBackend(),
246
+ async bootstrap({ use }) {
247
+ const sandbox = await use();
234
248
  await sandbox.runCommand("git clone https://example.com/repo.git repo");
235
249
  },
250
+ async onSession({ use }) {
251
+ await use({ networkPolicy: "deny-all" });
252
+ },
236
253
  });
237
254
  ```
238
255
 
@@ -251,69 +268,90 @@ export default defineSandbox({
251
268
  });
252
269
  ```
253
270
 
254
- ### Vercel Backend Options
271
+ ### Vercel Backend: Session Configuration
255
272
 
256
- `vercelBackend(options)` accepts the same parameters as `@vercel/sandbox`'s `Sandbox.create`, minus
257
- the four fields Ash owns internally (`name`, `persistent`, `source`, `signal`). The option type is
258
- **inferred** from the SDK so new SDK fields flow through automatically without any ash-side change.
259
- Hover the `options` argument in your editor to see the live set of fields.
273
+ `vercelBackend()` takes no options. All configuration is done through lifecycle hooks:
274
+
275
+ - **Immutable creation config** (runtime, ports, env) passed via `bootstrap`'s `use()`:
260
276
 
261
277
  ```ts
262
- vercelBackend({
263
- networkPolicy: "deny-all",
264
- runtime: "node24",
265
- resources: { vcpus: 4 },
266
- ports: [3000],
267
- env: { NODE_ENV: "production" },
268
- });
278
+ async bootstrap({ use }) {
279
+ const sandbox = await use({ runtime: "node24", ports: [3000] });
280
+ await sandbox.runCommand("npm install");
281
+ }
269
282
  ```
270
283
 
271
- Ash also attaches Vercel Sandbox tags for runtime attribution:
284
+ - **Session-level config** (networkPolicy, resources, timeout, tags) passed via `onSession`'s
285
+ `use()` or `sandbox.update()`:
272
286
 
273
- - `agent` — the active agent or subagent name
274
- - `channel` the active channel adapter kind
275
- - `sessionId` the Ash session id
287
+ ```ts
288
+ async onSession({ use }) {
289
+ const sandbox = await use({
290
+ networkPolicy: "deny-all",
291
+ resources: { vcpus: 4 },
292
+ timeout: 60 * 60 * 1_000,
293
+ });
294
+ // Can also update later:
295
+ await sandbox.update({ tags: { user: "casey" } });
296
+ }
297
+ ```
298
+
299
+ This split ensures that `bootstrap` only does filesystem setup (which survives snapshotting), while
300
+ `onSession` configures the live session (network policy, resources, etc. do not survive snapshots).
301
+
302
+ ### Network Policies
276
303
 
277
- Custom `tags` passed to `vercelBackend(options)` are merged with those Ash-owned tags. Vercel
278
- Sandbox supports at most five tags, so custom tags have two free slots unless they intentionally
279
- share an Ash-owned key.
304
+ Network policies are set per-session via `onSession`. Three forms are supported:
305
+
306
+ ```ts
307
+ await use({ networkPolicy: "allow-all" }); // default
308
+ await use({ networkPolicy: "deny-all" }); // block all egress, including DNS
309
+
310
+ await use({
311
+ networkPolicy: {
312
+ allow: ["ai-gateway.vercel.sh", "*.github.com"],
313
+ subnets: { deny: ["10.0.0.0/8"] },
314
+ },
315
+ });
316
+ ```
317
+
318
+ Since network policy is applied in `onSession` (after the template snapshot), it does not constrain
319
+ `bootstrap`. You can freely `git clone` and `npm install` in bootstrap, then lock down the network
320
+ in `onSession`.
280
321
 
281
322
  ### Timeout
282
323
 
283
324
  The `@vercel/sandbox` SDK shuts down idle VMs after a configurable timeout (default 5 minutes). Ash
284
325
  raises this default to **30 minutes** so the sandbox survives across workflow step boundaries. You can
285
- override it via `vercelBackend()`:
326
+ override it in `onSession`:
286
327
 
287
328
  ```ts
288
- vercelBackend({ timeout: 60 * 60 * 1_000 }); // 1 hour
329
+ async onSession({ use }) {
330
+ await use({ timeout: 60 * 60 * 1_000 }); // 1 hour
331
+ }
289
332
  ```
290
333
 
291
334
  The maximum timeout depends on your Vercel plan: **5 hours** for Pro/Enterprise, **45 minutes** for
292
335
  Hobby. If a sandbox expires between steps, the next step will fail with a `410 Gone` error.
293
336
 
294
- ### Network Policies
337
+ ### Tags
295
338
 
296
- The Vercel backend exposes the underlying SDK's `networkPolicy` field unchanged. Three forms are
297
- supported:
339
+ Ash attaches Vercel Sandbox tags for runtime attribution:
298
340
 
299
- ```ts
300
- vercelBackend({ networkPolicy: "allow-all" }); // default
301
- vercelBackend({ networkPolicy: "deny-all" }); // block all egress, including DNS
341
+ - `agent` — the active agent or subagent name
342
+ - `channel` the active channel adapter kind
343
+ - `sessionId` the Ash session id
302
344
 
303
- vercelBackend({
304
- networkPolicy: {
305
- allow: ["ai-gateway.vercel.sh", "*.github.com"],
306
- subnets: { deny: ["10.0.0.0/8"] },
307
- },
308
- });
345
+ Custom tags can be set via `onSession`:
346
+
347
+ ```ts
348
+ async onSession({ use }) {
349
+ await use({ tags: { team: "infra" } });
350
+ }
309
351
  ```
310
352
 
311
- Important caveat: any options you pass to `vercelBackend()` are applied to **both** the build-time
312
- template snapshot (when `bootstrap()` runs) and per-session sandbox creation. A restrictive
313
- `networkPolicy` will therefore also constrain `bootstrap()`. Make sure your bootstrap works within
314
- whatever constraints you set — for example, a `deny-all` policy will block `git clone` and
315
- `npm install`. If you need a less restrictive bootstrap, allowlist only the hosts your bootstrap
316
- needs (e.g. `["registry.npmjs.org"]`) instead of using `deny-all`.
353
+ Vercel Sandbox supports at most five tags. Ash-owned tags take three slots, leaving two for custom
354
+ tags.
317
355
 
318
356
  ### Custom Backends
319
357
 
@@ -11,7 +11,7 @@ import {
11
11
  loadCompiledManifest,
12
12
  resolveWorkflowBuildDirectory,
13
13
  stringifyEsmImportSpecifier
14
- } from "./chunk-XQMYWUU7.js";
14
+ } from "./chunk-IKHNAPQA.js";
15
15
  import {
16
16
  resolvePackageSourceFilePath
17
17
  } from "./chunk-E7IAIRUB.js";
@@ -589,4 +589,4 @@ export {
589
589
  createAuthoredSourceRuntimeCompiledArtifactsSource,
590
590
  prepareApplicationHost
591
591
  };
592
- //# sourceMappingURL=chunk-2R4UWV4G.js.map
592
+ //# sourceMappingURL=chunk-5BOA7PVK.js.map
@@ -4453,7 +4453,7 @@ function createLocalSandboxBackend() {
4453
4453
  });
4454
4454
  const templateSession = buildSandboxSession(createLocalSessionPrimitives(templateSandbox));
4455
4455
  if (createInput.bootstrap !== void 0) {
4456
- await createInput.bootstrap(templateSession);
4456
+ await createInput.bootstrap({ use: async () => templateSession });
4457
4457
  }
4458
4458
  const templateSeedFiles = await loadSeedFiles();
4459
4459
  for (const file of templateSeedFiles) {
@@ -4592,8 +4592,10 @@ async function createBashSandbox(input) {
4592
4592
  };
4593
4593
  }
4594
4594
  function createHandle(sandbox) {
4595
+ const session = buildSandboxSession(createLocalSessionPrimitives(sandbox));
4595
4596
  return {
4596
- session: buildSandboxSession(createLocalSessionPrimitives(sandbox)),
4597
+ session,
4598
+ useSessionFn: async () => session,
4597
4599
  async captureState() {
4598
4600
  const metadata = await sandbox.captureSnapshot() ?? {};
4599
4601
  return {
@@ -4716,8 +4718,7 @@ function localBackend() {
4716
4718
  function createVercelSandboxBackend(input = {}) {
4717
4719
  const loadSandboxModule = input.loadSandboxModule ?? (async () => await import("@vercel/sandbox"));
4718
4720
  const createOptions = {
4719
- timeout: DEFAULT_SANDBOX_TIMEOUT_MS,
4720
- ...input.createOptions
4721
+ timeout: DEFAULT_SANDBOX_TIMEOUT_MS
4721
4722
  };
4722
4723
  return {
4723
4724
  name: "vercel",
@@ -4798,7 +4799,7 @@ async function ensureTemplate(input) {
4798
4799
  createVercelSessionPrimitives(sandbox, input.templateKey)
4799
4800
  );
4800
4801
  if (input.bootstrap !== void 0) {
4801
- await input.bootstrap(templateSession);
4802
+ await input.bootstrap({ use: async () => templateSession });
4802
4803
  }
4803
4804
  const resolvedSeedFiles = input.seedFiles === void 0 ? [] : typeof input.seedFiles === "function" ? await input.seedFiles() : input.seedFiles;
4804
4805
  for (const file of resolvedSeedFiles) {
@@ -4833,6 +4834,19 @@ async function ensureSession(input) {
4833
4834
  function createHandle2(sandbox, sessionKey) {
4834
4835
  return {
4835
4836
  session: buildSandboxSession(createVercelSessionPrimitives(sandbox, sessionKey)),
4837
+ useSessionFn: async (options) => {
4838
+ if (options !== void 0) {
4839
+ const updateParams = {};
4840
+ if (options.networkPolicy !== void 0) updateParams.networkPolicy = options.networkPolicy;
4841
+ if (options.resources !== void 0) updateParams.resources = options.resources;
4842
+ if (options.timeout !== void 0) updateParams.timeout = options.timeout;
4843
+ if (options.tags !== void 0) updateParams.tags = options.tags;
4844
+ if (Object.keys(updateParams).length > 0) {
4845
+ await sandbox.update(updateParams);
4846
+ }
4847
+ }
4848
+ return buildVercelSandbox(sandbox, sessionKey);
4849
+ },
4836
4850
  async captureState() {
4837
4851
  return {
4838
4852
  backendName: "vercel",
@@ -4844,6 +4858,39 @@ function createHandle2(sandbox, sessionKey) {
4844
4858
  }
4845
4859
  };
4846
4860
  }
4861
+ function buildVercelSandbox(sandbox, id) {
4862
+ const session = buildSandboxSession(createVercelSessionPrimitives(sandbox, id));
4863
+ return {
4864
+ ...session,
4865
+ get name() {
4866
+ return sandbox.name;
4867
+ },
4868
+ get persistent() {
4869
+ return sandbox.persistent;
4870
+ },
4871
+ get status() {
4872
+ return sandbox.status;
4873
+ },
4874
+ get networkPolicy() {
4875
+ return sandbox.networkPolicy;
4876
+ },
4877
+ get tags() {
4878
+ return sandbox.tags;
4879
+ },
4880
+ async update(params) {
4881
+ await sandbox.update(params);
4882
+ },
4883
+ async stop(opts) {
4884
+ await sandbox.stop(opts);
4885
+ },
4886
+ async snapshot(opts) {
4887
+ return await sandbox.snapshot(opts);
4888
+ },
4889
+ domain(port) {
4890
+ return sandbox.domain(port);
4891
+ }
4892
+ };
4893
+ }
4847
4894
  function createVercelSessionPrimitives(sandbox, id) {
4848
4895
  return {
4849
4896
  id,
@@ -4979,8 +5026,8 @@ var DEFAULT_SANDBOX_TIMEOUT_MS = 30 * 60 * 1e3;
4979
5026
  var VERCEL_SANDBOX_TAG_LIMIT = 5;
4980
5027
 
4981
5028
  // src/public/sandbox/backends/vercel.ts
4982
- function vercelBackend(options = {}) {
4983
- return createVercelSandboxBackend({ createOptions: options });
5029
+ function vercelBackend() {
5030
+ return createVercelSandboxBackend();
4984
5031
  }
4985
5032
 
4986
5033
  // src/public/sandbox/backends/default.ts
@@ -6723,19 +6770,30 @@ async function normalizeAuthoredModelReference(input) {
6723
6770
  `Expected the authored agent config export "${source.exportName ?? "default"}" from "${source.logicalPath}" to provide a valid AI SDK language model.`
6724
6771
  );
6725
6772
  }
6726
- return await withCompiledRuntimeModelLimits(
6727
- {
6728
- id: formatLanguageModelGatewayId(languageModel),
6729
- source: {
6730
- exportName: source.exportName,
6731
- sourceKind: "module",
6732
- logicalPath: source.logicalPath,
6733
- sourceId: source.sourceId
6734
- },
6735
- providerOptions: input.providerOptions === void 0 ? void 0 : parseJsonObject(input.providerOptions)
6773
+ const sourceBackedModel = {
6774
+ id: formatLanguageModelGatewayId(languageModel),
6775
+ source: {
6776
+ exportName: source.exportName,
6777
+ sourceKind: "module",
6778
+ logicalPath: source.logicalPath,
6779
+ sourceId: source.sourceId
6736
6780
  },
6737
- input
6738
- );
6781
+ providerOptions: input.providerOptions === void 0 ? void 0 : parseJsonObject(input.providerOptions)
6782
+ };
6783
+ if (input.contextWindowTokens === void 0) {
6784
+ const providerResult = await input.modelCatalog.getByProviderModelId(
6785
+ languageModel.provider,
6786
+ languageModel.modelId
6787
+ );
6788
+ if (providerResult) {
6789
+ return {
6790
+ ...sourceBackedModel,
6791
+ id: providerResult.slug,
6792
+ contextWindowTokens: providerResult.limits.contextWindowTokens
6793
+ };
6794
+ }
6795
+ }
6796
+ return await withCompiledRuntimeModelLimits(sourceBackedModel, input);
6739
6797
  }
6740
6798
  async function withCompiledRuntimeModelLimits(model, input) {
6741
6799
  if (input.contextWindowTokens !== void 0) {
@@ -8647,20 +8705,24 @@ import { join as join17, relative as relative4, resolve as resolve8 } from "node
8647
8705
  import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
8648
8706
  import { join as join14 } from "node:path";
8649
8707
  import { z as z7 } from "zod";
8650
- var AI_GATEWAY_MODELS_URL = "https://ai-gateway.vercel.sh/v1/models";
8651
- var COMPILED_RUNTIME_MODEL_CATALOG_CACHE_KIND = "ash-runtime-model-catalog-cache";
8652
- var COMPILED_RUNTIME_MODEL_CATALOG_CACHE_VERSION = 1;
8708
+ var AI_GATEWAY_MODELS_CATALOG_URL = "https://ai-gateway.vercel.sh/v1/models/catalog";
8709
+ var COMPILED_RUNTIME_MODEL_CATALOG_CACHE_KIND = "ash-model-catalog-cache";
8710
+ var COMPILED_RUNTIME_MODEL_CATALOG_CACHE_VERSION = 2;
8653
8711
  var COMPILED_RUNTIME_MODEL_CATALOG_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
8654
8712
  var THINKING_SUFFIX = "-thinking";
8655
- var aiGatewayModelCatalogResponseSchema = z7.object({
8656
- data: z7.array(
8657
- z7.object({
8658
- context_window: z7.number().int().nonnegative().optional(),
8659
- id: z7.string().min(1),
8660
- max_tokens: z7.number().int().nonnegative().optional()
8661
- }).passthrough()
8662
- ),
8663
- object: z7.literal("list")
8713
+ var catalogModelProviderSchema = z7.object({
8714
+ provider: z7.string().min(1),
8715
+ providerModelId: z7.string().min(1),
8716
+ contextWindowTokens: z7.number().int().nonnegative().optional(),
8717
+ maxOutputTokens: z7.number().int().nonnegative().optional()
8718
+ }).passthrough();
8719
+ var catalogModelSchema = z7.object({
8720
+ slug: z7.string().min(1),
8721
+ providers: z7.array(catalogModelProviderSchema).min(1)
8722
+ }).passthrough();
8723
+ var modelCatalogResponseSchema = z7.object({
8724
+ models: z7.array(catalogModelSchema),
8725
+ providerAliases: z7.record(z7.string(), z7.string())
8664
8726
  }).passthrough();
8665
8727
  var compiledRuntimeModelLimitsSchema = z7.object({
8666
8728
  contextWindowTokens: z7.number().int().positive(),
@@ -8669,8 +8731,8 @@ var compiledRuntimeModelLimitsSchema = z7.object({
8669
8731
  var compiledRuntimeModelCatalogCacheSchema = z7.object({
8670
8732
  fetchedAt: z7.string(),
8671
8733
  kind: z7.literal(COMPILED_RUNTIME_MODEL_CATALOG_CACHE_KIND),
8672
- models: z7.record(z7.string(), compiledRuntimeModelLimitsSchema),
8673
- source: z7.literal("gateway"),
8734
+ models: z7.array(catalogModelSchema),
8735
+ providerAliases: z7.record(z7.string(), z7.string()),
8674
8736
  version: z7.literal(COMPILED_RUNTIME_MODEL_CATALOG_CACHE_VERSION)
8675
8737
  }).strict();
8676
8738
  var builtInCompiledRuntimeModelLimitsById = /* @__PURE__ */ new Map([
@@ -8704,7 +8766,7 @@ function createCompiledRuntimeModelCatalogLoader(appRoot) {
8704
8766
  let fetchedCatalogError = null;
8705
8767
  let fetchedCatalogPromise = null;
8706
8768
  const getCachedCatalog = async () => {
8707
- cachedCatalogPromise ??= readCompiledRuntimeModelCatalogCache(appRoot);
8769
+ cachedCatalogPromise ??= readModelCatalogCache(appRoot);
8708
8770
  return await cachedCatalogPromise;
8709
8771
  };
8710
8772
  const getFetchedCatalog = async () => {
@@ -8714,7 +8776,7 @@ function createCompiledRuntimeModelCatalogLoader(appRoot) {
8714
8776
  if (fetchedCatalogPromise !== null) {
8715
8777
  return await fetchedCatalogPromise;
8716
8778
  }
8717
- fetchedCatalogPromise = fetchAndPersistCompiledRuntimeModelCatalog(appRoot).then((catalog) => {
8779
+ fetchedCatalogPromise = fetchAndPersistModelCatalog(appRoot).then((catalog) => {
8718
8780
  cachedCatalogPromise = Promise.resolve(catalog);
8719
8781
  return catalog;
8720
8782
  });
@@ -8725,77 +8787,87 @@ function createCompiledRuntimeModelCatalogLoader(appRoot) {
8725
8787
  throw error;
8726
8788
  }
8727
8789
  };
8790
+ const resolveModelsFromCacheOrFetch = async () => {
8791
+ const cachedCatalog = await getCachedCatalog();
8792
+ if (cachedCatalog !== null && isCacheFresh(cachedCatalog)) {
8793
+ return cachedCatalog;
8794
+ }
8795
+ try {
8796
+ return await getFetchedCatalog();
8797
+ } catch {
8798
+ if (cachedCatalog !== null) {
8799
+ return cachedCatalog;
8800
+ }
8801
+ return null;
8802
+ }
8803
+ };
8728
8804
  return {
8729
8805
  async getModelLimits(modelId) {
8730
- const normalizedModelId = normalizeModelIdForCatalogLookup(modelId);
8731
- const cachedCatalog = await getCachedCatalog();
8732
- const cachedLimits = getCompiledRuntimeModelLimitsFromCache(cachedCatalog, modelId);
8733
- if (cachedLimits !== null && isCompiledRuntimeModelCatalogCacheFresh(cachedCatalog)) {
8734
- return cachedLimits;
8735
- }
8736
- try {
8737
- const fetchedCatalog = await getFetchedCatalog();
8738
- const fetchedLimits = getCompiledRuntimeModelLimitsFromCache(fetchedCatalog, modelId);
8739
- if (fetchedLimits !== null) {
8740
- return fetchedLimits;
8741
- }
8742
- } catch (error) {
8743
- if (cachedLimits !== null) {
8744
- return cachedLimits;
8745
- }
8746
- const builtInLimits = builtInCompiledRuntimeModelLimitsById.get(normalizedModelId);
8747
- if (builtInLimits !== void 0) {
8748
- return builtInLimits;
8806
+ const normalizedId = normalizeModelId(modelId);
8807
+ const resolved = await resolveModelsFromCacheOrFetch();
8808
+ if (resolved !== null) {
8809
+ const model = findBySlug(resolved.models, normalizedId);
8810
+ if (model) {
8811
+ for (const p of model.providers) {
8812
+ const limits = limitsFromProvider(p);
8813
+ if (limits !== null) {
8814
+ return limits;
8815
+ }
8816
+ }
8749
8817
  }
8750
- throw error;
8751
8818
  }
8752
- if (cachedLimits !== null) {
8753
- return cachedLimits;
8819
+ return builtInCompiledRuntimeModelLimitsById.get(normalizedId) ?? null;
8820
+ },
8821
+ async getByProviderModelId(provider, providerModelId) {
8822
+ const resolved = await resolveModelsFromCacheOrFetch();
8823
+ if (resolved === null) {
8824
+ return null;
8754
8825
  }
8755
- return builtInCompiledRuntimeModelLimitsById.get(normalizedModelId) ?? null;
8826
+ const baseProvider = provider.split(".")[0];
8827
+ const resolvedProvider = resolved.providerAliases[baseProvider] ?? baseProvider;
8828
+ const normalizedModelId = normalizeModelId(providerModelId);
8829
+ for (const model of resolved.models) {
8830
+ for (const p of model.providers) {
8831
+ if (p.provider === resolvedProvider && normalizeModelId(p.providerModelId) === normalizedModelId) {
8832
+ const limits = limitsFromProvider(p);
8833
+ if (limits !== null) {
8834
+ return { slug: model.slug, limits };
8835
+ }
8836
+ }
8837
+ }
8838
+ }
8839
+ return null;
8756
8840
  }
8757
8841
  };
8758
8842
  }
8759
- async function fetchAndPersistCompiledRuntimeModelCatalog(appRoot) {
8760
- const response = await fetch(AI_GATEWAY_MODELS_URL);
8843
+ async function fetchAndPersistModelCatalog(appRoot) {
8844
+ const response = await fetch(AI_GATEWAY_MODELS_CATALOG_URL);
8761
8845
  if (!response.ok) {
8762
8846
  throw new Error(
8763
8847
  `AI Gateway model catalog request failed with HTTP ${response.status} ${response.statusText}.`
8764
8848
  );
8765
8849
  }
8766
- const parsed = aiGatewayModelCatalogResponseSchema.safeParse(await response.json());
8850
+ const parsed = modelCatalogResponseSchema.safeParse(await response.json());
8767
8851
  if (!parsed.success) {
8768
8852
  throw new Error("AI Gateway model catalog response did not match the expected schema.");
8769
8853
  }
8770
- const models = {};
8771
- for (const model of parsed.data.data) {
8772
- if (model.context_window === void 0 || model.context_window <= 0) {
8773
- continue;
8774
- }
8775
- const limits = {
8776
- contextWindowTokens: model.context_window
8777
- };
8778
- if (model.max_tokens !== void 0 && model.max_tokens > 0) {
8779
- limits.maxOutputTokens = model.max_tokens;
8780
- }
8781
- models[model.id] = limits;
8782
- }
8783
8854
  const cacheArtifact = {
8784
8855
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
8785
8856
  kind: COMPILED_RUNTIME_MODEL_CATALOG_CACHE_KIND,
8786
- models,
8787
- source: "gateway",
8857
+ models: parsed.data.models,
8858
+ providerAliases: parsed.data.providerAliases,
8788
8859
  version: COMPILED_RUNTIME_MODEL_CATALOG_CACHE_VERSION
8789
8860
  };
8790
- const cachePath = resolveCompiledRuntimeModelCatalogCachePath(appRoot);
8791
- await mkdir2(join14(appRoot, ".ash", "cache"), {
8792
- recursive: true
8793
- });
8794
- await writeFile2(cachePath, `${JSON.stringify(cacheArtifact, null, 2)}
8861
+ try {
8862
+ const cachePath = resolveCompiledRuntimeModelCatalogCachePath(appRoot);
8863
+ await mkdir2(join14(appRoot, ".ash", "cache"), { recursive: true });
8864
+ await writeFile2(cachePath, `${JSON.stringify(cacheArtifact, null, 2)}
8795
8865
  `, "utf8");
8866
+ } catch {
8867
+ }
8796
8868
  return cacheArtifact;
8797
8869
  }
8798
- async function readCompiledRuntimeModelCatalogCache(appRoot) {
8870
+ async function readModelCatalogCache(appRoot) {
8799
8871
  try {
8800
8872
  const cacheText = await readFile3(resolveCompiledRuntimeModelCatalogCachePath(appRoot), "utf8");
8801
8873
  const parsed = compiledRuntimeModelCatalogCacheSchema.safeParse(JSON.parse(cacheText));
@@ -8807,24 +8879,26 @@ async function readCompiledRuntimeModelCatalogCache(appRoot) {
8807
8879
  return null;
8808
8880
  }
8809
8881
  }
8810
- function getCompiledRuntimeModelLimitsFromCache(cache, modelId) {
8811
- if (cache === null) {
8882
+ function findBySlug(models, slug) {
8883
+ return models.find((m) => m.slug === slug);
8884
+ }
8885
+ function limitsFromProvider(provider) {
8886
+ if (provider.contextWindowTokens === void 0 || provider.contextWindowTokens <= 0) {
8812
8887
  return null;
8813
8888
  }
8814
- const cachedModelId = normalizeModelIdForCatalogLookup(modelId);
8815
- return cache.models[cachedModelId] ?? null;
8889
+ return {
8890
+ contextWindowTokens: provider.contextWindowTokens,
8891
+ ...provider.maxOutputTokens !== void 0 && provider.maxOutputTokens > 0 && { maxOutputTokens: provider.maxOutputTokens }
8892
+ };
8816
8893
  }
8817
- function isCompiledRuntimeModelCatalogCacheFresh(cache) {
8818
- if (cache === null) {
8819
- return false;
8820
- }
8894
+ function isCacheFresh(cache) {
8821
8895
  const fetchedAt = Date.parse(cache.fetchedAt);
8822
8896
  if (!Number.isFinite(fetchedAt)) {
8823
8897
  return false;
8824
8898
  }
8825
8899
  return Date.now() - fetchedAt <= COMPILED_RUNTIME_MODEL_CATALOG_CACHE_TTL_MS;
8826
8900
  }
8827
- function normalizeModelIdForCatalogLookup(modelId) {
8901
+ function normalizeModelId(modelId) {
8828
8902
  return modelId.endsWith(THINKING_SUFFIX) ? modelId.slice(0, -THINKING_SUFFIX.length) : modelId;
8829
8903
  }
8830
8904
 
@@ -9723,4 +9797,4 @@ export {
9723
9797
  resolveWorkflowBuildDirectory,
9724
9798
  getApplicationInfo
9725
9799
  };
9726
- //# sourceMappingURL=chunk-XQMYWUU7.js.map
9800
+ //# sourceMappingURL=chunk-IKHNAPQA.js.map