experimental-ash 0.40.0 → 0.41.0

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 (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/docs/public/tools.md +165 -0
  3. package/dist/src/compiled/.vendor-stamp.json +1 -1
  4. package/dist/src/compiled/@workflow/core/index.js +1 -1
  5. package/dist/src/compiled/@workflow/core/runtime.js +1 -1
  6. package/dist/src/compiled/_chunks/workflow/{resume-hook-D6dFbxMV.js → resume-hook-B2kqAsX6.js} +1 -1
  7. package/dist/src/compiler/manifest.d.ts +16 -2
  8. package/dist/src/compiler/manifest.js +1 -1
  9. package/dist/src/compiler/module-map.js +1 -1
  10. package/dist/src/compiler/normalize-agent-config.js +1 -1
  11. package/dist/src/compiler/normalize-manifest.js +1 -1
  12. package/dist/src/compiler/normalize-tool.d.ts +7 -3
  13. package/dist/src/compiler/normalize-tool.js +1 -1
  14. package/dist/src/context/dynamic-tool-lifecycle.d.ts +35 -0
  15. package/dist/src/context/dynamic-tool-lifecycle.js +1 -0
  16. package/dist/src/context/hook-lifecycle.js +1 -1
  17. package/dist/src/context/keys.d.ts +37 -0
  18. package/dist/src/context/keys.js +1 -1
  19. package/dist/src/context/providers/sandbox.js +1 -1
  20. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  21. package/dist/src/execution/node-step.js +1 -1
  22. package/dist/src/execution/workflow-steps.js +1 -1
  23. package/dist/src/harness/attachment-staging.js +1 -1
  24. package/dist/src/harness/code-mode.d.ts +1 -1
  25. package/dist/src/harness/code-mode.js +1 -1
  26. package/dist/src/harness/tool-loop.js +1 -1
  27. package/dist/src/harness/types.d.ts +7 -0
  28. package/dist/src/internal/application/package.js +1 -1
  29. package/dist/src/internal/authored-definition/core.js +1 -1
  30. package/dist/src/internal/authored-definition/schema-backed.d.ts +9 -3
  31. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  32. package/dist/src/internal/authored-module.d.ts +4 -0
  33. package/dist/src/internal/authored-module.js +1 -1
  34. package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
  35. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +39 -0
  36. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +4 -0
  37. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  38. package/dist/src/public/definitions/agent.d.ts +1 -1
  39. package/dist/src/public/definitions/defineChannel.js +1 -1
  40. package/dist/src/public/definitions/tool.d.ts +67 -0
  41. package/dist/src/public/definitions/tool.js +1 -1
  42. package/dist/src/public/index.d.ts +1 -1
  43. package/dist/src/public/tools/index.d.ts +2 -1
  44. package/dist/src/public/tools/index.js +1 -1
  45. package/dist/src/runtime/framework-tools/connection-search.js +1 -1
  46. package/dist/src/runtime/framework-tools/connection-tools.js +1 -1
  47. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  48. package/dist/src/runtime/resolve-agent.js +1 -1
  49. package/dist/src/runtime/resolve-dynamic-tool.d.ts +12 -0
  50. package/dist/src/runtime/resolve-dynamic-tool.js +1 -0
  51. package/dist/src/runtime/types.d.ts +12 -0
  52. package/dist/src/shared/agent-definition.d.ts +25 -0
  53. package/dist/src/shared/code-mode.d.ts +14 -1
  54. package/dist/src/shared/code-mode.js +1 -1
  55. package/dist/src/shared/dynamic-tool-definition.d.ts +132 -0
  56. package/dist/src/shared/dynamic-tool-definition.js +1 -0
  57. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.41.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d4dd396: Move code mode from the `CODE_MODE` environment variable to a per-agent
8
+ `experimental.codeMode` flag on `defineAgent({ experimental: { codeMode: true } })`.
9
+ Each agent and subagent can now opt in independently. For backwards
10
+ compatibility, agents that omit the flag fall back to the
11
+ `ASH_EXPERIMENTAL_CODE_MODE` environment variable (`"1"` enables it); the
12
+ previous `CODE_MODE` variable is no longer read.
13
+ - 09beab5: Add `defineTools` and `defineTool` dynamic overloads for runtime tool resolution.
14
+
15
+ Tools can now be resolved dynamically at runtime based on session context via `defineTools({ events: { "session.started": handler } })` and `defineTool({ events: { "session.started": handler } })`. Session-scoped resolvers run once and are cached; step-scoped resolvers re-resolve before every model call. Dynamic tools appear in the model's tool list identically to static tools.
16
+
17
+ ## 0.40.1
18
+
19
+ ### Patch Changes
20
+
21
+ - 35ca14b: Fix cross-deployment turn workflows so forwarded parent streams resolve the parent run's encryption key instead of the latest deployment's key.
22
+
3
23
  ## 0.40.0
4
24
 
5
25
  ### Minor Changes
@@ -368,6 +368,171 @@ full API.
368
368
  Note: `retentionPolicy` and `onCompact` operate on the projected output (what the model sees),
369
369
  not the full `execute` return.
370
370
 
371
+ ## Dynamic Tools
372
+
373
+ Dynamic tools resolve at runtime based on session context — tenant configuration, feature flags, user roles, or external data. Use `defineTools` with an `events` object to produce tools dynamically.
374
+
375
+ ### Single Dynamic Tool
376
+
377
+ `agent/tools/analytics.ts`
378
+
379
+ ```ts
380
+ import { defineTool } from "experimental-ash/tools";
381
+ import { z } from "zod";
382
+
383
+ export default defineTool({
384
+ events: {
385
+ "session.started": async (event, ctx) => {
386
+ const flags = await fetchFeatureFlags(ctx.session.id);
387
+ if (!flags.advancedAnalytics) return null;
388
+
389
+ return {
390
+ description: "Run an advanced analytics query.",
391
+ inputSchema: z.object({ query: z.string() }),
392
+ async execute(input) {
393
+ return runAnalytics(input.query);
394
+ },
395
+ };
396
+ },
397
+ },
398
+ });
399
+ ```
400
+
401
+ Return `null` to produce no tool. The tool name is the file slug (`analytics`), identical to a static `defineTool`.
402
+
403
+ ### Multiple Dynamic Tools
404
+
405
+ `agent/tools/tenant.ts`
406
+
407
+ ```ts
408
+ import { defineTools, tool } from "experimental-ash/tools";
409
+ import { z } from "zod";
410
+
411
+ export default defineTools({
412
+ events: {
413
+ "session.started": async (event, ctx) => {
414
+ const tenant = await fetchTenant(ctx.session.id);
415
+ if (!tenant) return null;
416
+
417
+ return {
418
+ export: tool({
419
+ description: `Export ${tenant.name} data`,
420
+ inputSchema: z.object({ format: z.enum(["csv", "json"]) }),
421
+ async execute(input) {
422
+ // input.format is typed as "csv" | "json"
423
+ return callTenantApi(tenant.apiUrl, "export", input.format);
424
+ },
425
+ }),
426
+ query: tool({
427
+ description: `Query ${tenant.name} data`,
428
+ inputSchema: z.object({ sql: z.string() }),
429
+ async execute(input) {
430
+ // input.sql is typed as string
431
+ return callTenantApi(tenant.apiUrl, "query", input.sql);
432
+ },
433
+ }),
434
+ };
435
+ },
436
+ },
437
+ });
438
+ ```
439
+
440
+ Each entry must be wrapped with `tool()` — it captures the schema type so `execute(input)` is inferred from `inputSchema`, matching the AI SDK's `tool()` pattern.
441
+
442
+ ### Tool Naming
443
+
444
+ Dynamic tool names are derived from the file path and the definition function:
445
+
446
+ | Definition | File | Entry keys | Tool name(s) |
447
+ | ------------- | -------------------------- | ----------------- | --------------------------------- |
448
+ | `defineTool` | `agent/tools/analytics.ts` | _(single entry)_ | `analytics` |
449
+ | `defineTools` | `agent/tools/tenant.ts` | `export`, `query` | `tenant__export`, `tenant__query` |
450
+ | `defineTools` | `agent/tools/search.ts` | `run` | `search__run` |
451
+
452
+ `defineTool` always produces one tool named after the file slug — identical to static tools. `defineTools` always uses `slug__key`, even when the resolver returns a single entry. This keeps names stable: adding a second entry to a `defineTools` file does not rename the first.
453
+
454
+ ### Event Scopes
455
+
456
+ The `events` object uses the same event names as hook stream events:
457
+
458
+ | Event | When the resolver runs | I/O behavior |
459
+ | ----------------- | ----------------------- | ----------------------------------- |
460
+ | `session.started` | Once per session | Runs once; results cached durably |
461
+ | `turn.started` | Once per turn | Runs once per turn; cached for turn |
462
+ | `step.started` | Before every model call | Runs every step; no caching |
463
+
464
+ Session-scoped resolvers can do I/O (fetch from a database, call an API). The framework's bundler transform ensures the I/O executes once per session — subsequent workflow steps reconstruct execute functions from stored closure variables without re-running the resolver. Resolvers run concurrently — if multiple dynamic tool files declare the same event, their I/O overlaps.
465
+
466
+ ### Multiple Events
467
+
468
+ A single file can declare handlers for multiple events. The result of the most recently fired handler owns the tool set for that file:
469
+
470
+ ```ts
471
+ import { defineTools, tool } from "experimental-ash/tools";
472
+
473
+ export default defineTools({
474
+ events: {
475
+ "session.started": async (event, ctx) => {
476
+ return { query: tool({ description: "Query", inputSchema: {}, execute: async () => {} }) };
477
+ },
478
+ "turn.started": async (event, ctx) => {
479
+ // Turn handler replaces session handler's tools for this file
480
+ return { search: tool({ description: "Search", inputSchema: {}, execute: async () => {} }) };
481
+ },
482
+ },
483
+ });
484
+ ```
485
+
486
+ ### Type Inference
487
+
488
+ `inputSchema` accepts Zod, Standard Schema, or plain JSON Schema — same as static `defineTool`. When using Zod, the `tool()` wrapper infers the `execute` input type automatically:
489
+
490
+ ```ts
491
+ return {
492
+ query: tool({
493
+ inputSchema: z.object({ city: z.string() }),
494
+ async execute(input) {
495
+ // input.city is typed as string
496
+ return fetchWeather(input.city);
497
+ },
498
+ }),
499
+ };
500
+ ```
501
+
502
+ The `execute` signature matches static tools: `execute(input: TInput, ctx: ToolContext)`.
503
+
504
+ ### Helper Functions
505
+
506
+ `execute` can live inside helper functions, `.map()` callbacks, or IIFEs — the bundler transform follows nested scopes and captures all referenced variables:
507
+
508
+ ```ts
509
+ import { defineTools, tool } from "experimental-ash/tools";
510
+ import { z } from "zod";
511
+
512
+ export default defineTools({
513
+ events: {
514
+ "session.started": async (event, ctx) => {
515
+ const tenant = await fetchTenant(ctx.session.id);
516
+
517
+ function buildTool(action: string) {
518
+ const endpoint = `${tenant.apiUrl}/${action}`;
519
+ return tool({
520
+ description: `${action} for ${tenant.name}`,
521
+ inputSchema: z.object({ query: z.string() }),
522
+ async execute(input) {
523
+ return callApi(endpoint, input.query);
524
+ },
525
+ });
526
+ }
527
+
528
+ return { query: buildTool("query"), export: buildTool("export") };
529
+ },
530
+ },
531
+ });
532
+ ```
533
+
534
+ **Limitation:** `execute` must be an inline function — a function expression, arrow function, or method shorthand written directly as the property value. Assigning a variable (`execute: myFn`) or a call result (`execute: makeFn()`) is not detected by the transform; the tool will work on the first workflow step but will not survive replay across step boundaries.
535
+
371
536
  ## What To Read Next
372
537
 
373
538
  - [Session Context](./session-context.md)
@@ -27,5 +27,5 @@
27
27
  "zod": "4.4.3",
28
28
  "zod-validation-error": "5.0.0"
29
29
  },
30
- "scriptHash": "6a2fa642d99a77617cbfb4e0dc7bbbd774c22d880ea5726a09069c812896bfdd"
30
+ "scriptHash": "27a236d633a4d9d7191418c0c9eb03158389a9de5ca9a6b54bd35b754147c7f6"
31
31
  }
@@ -1,2 +1,2 @@
1
- import{i as e,s as t}from"../../_chunks/workflow/dist-C4EHshZE.js";import{c as n,et as r,nt as i,s as a,tt as o}from"../../_chunks/workflow/symbols-DygIC1sj.js";import{Bt as s,C as c,I as l,R as u,Rt as d,Vt as f,m as p,n as m,x as h,zt as g}from"../../_chunks/workflow/resume-hook-D6dFbxMV.js";import{n as _,t as v}from"../../_chunks/workflow/sleep-kQ0UwHn0.js";function y(e){o(`createHook()`,`https://workflow-sdk.dev/docs/api-reference/workflow/create-hook`,y)}function b(e){o(`createWebhook()`,`https://workflow-sdk.dev/docs/api-reference/workflow/create-webhook`,b)}function x({schema:e}={}){function t(e){o(`defineHook().create()`,`https://workflow-sdk.dev/docs/api-reference/workflow/define-hook`,t)}return{create:t,async resume(t,n){if(!e?.[`~standard`])return await m(t,n);let r=e[`~standard`].validate(n);if(r instanceof Promise&&(r=await r),r.issues){let e=r.issues.map(e=>{let t=e.path?.map(e=>String(typeof e==`object`&&e?e.key:e)).join(`.`);return t?` at "${t}": ${e.message}`:` ${e.message}`});throw Error(`Hook payload did not match the defined schema:\n${e.join(`
1
+ import{i as e,s as t}from"../../_chunks/workflow/dist-C4EHshZE.js";import{c as n,et as r,nt as i,s as a,tt as o}from"../../_chunks/workflow/symbols-DygIC1sj.js";import{Bt as s,C as c,I as l,R as u,Rt as d,Vt as f,m as p,n as m,x as h,zt as g}from"../../_chunks/workflow/resume-hook-B2kqAsX6.js";import{n as _,t as v}from"../../_chunks/workflow/sleep-kQ0UwHn0.js";function y(e){o(`createHook()`,`https://workflow-sdk.dev/docs/api-reference/workflow/create-hook`,y)}function b(e){o(`createWebhook()`,`https://workflow-sdk.dev/docs/api-reference/workflow/create-webhook`,b)}function x({schema:e}={}){function t(e){o(`defineHook().create()`,`https://workflow-sdk.dev/docs/api-reference/workflow/define-hook`,t)}return{create:t,async resume(t,n){if(!e?.[`~standard`])return await m(t,n);let r=e[`~standard`].validate(n);if(r instanceof Promise&&(r=await r),r.issues){let e=r.issues.map(e=>{let t=e.path?.map(e=>String(typeof e==`object`&&e?e.key:e)).join(`.`);return t?` at "${t}": ${e.message}`:` ${e.message}`});throw Error(`Hook payload did not match the defined schema:\n${e.join(`
2
2
  `)}`)}return await m(t,r.value)}}}const S=Symbol.for(`@workflow/setAttributes//unsupportedWorldWarned`);async function C(t,n={}){let r=u.getStore()?.workflowMetadata?.workflowRunId;if(!r)throw new e(`experimental_setAttributes() must be called from a 'use workflow' or 'use step' function. Calling it from plain host code is not supported.`);let i=_(t,n);if(i.length===0)return;let a=await d();if(typeof a.runs.experimentalSetAttributes!=`function`){let e=globalThis;if(!e[S]){e[S]=!0;let t=`name`in a&&typeof a.name==`string`?a.name:``,n=t?` (${t})`:``;console.warn(`[workflow] setAttributes: the current world implementation${n} does not implement experimentalSetAttributes; this call (and any subsequent setAttributes calls in this process) is a no-op. Attributes will become available once the world adapter adds support.`)}return}await a.runs.experimentalSetAttributes(r,i,n.allowReservedAttributes===!0?{allowReservedAttributes:!0}:{})}function w(){let e=u.getStore();return e||r(`getStepMetadata()`,`https://workflow-sdk.dev/docs/api-reference/workflow/get-step-metadata`,w),e.stepMetadata}function T(){let e=u.getStore();return e||i(`getWorkflowMetadata()`,`https://workflow-sdk.dev/docs/api-reference/workflow/get-workflow-metadata`,T),e.workflowMetadata}function E(e={}){let t=u.getStore();t||i(`getWritable()`,`https://workflow-sdk.dev/docs/api-reference/workflow/get-writable`,E);let{namespace:r}=e,o=t.workflowMetadata.workflowRunId,d=l(o,r),m=t.writables??=new Map,_=m.get(d);if(_)return _.writable;let v=c(h(globalThis,t.ops,o,t.encryptionKey),t.encryptionKey),y=new p(o,d),b=g();return t.ops.push(b.promise),s(v.readable,y,b).catch(()=>{}),f(v.writable,b),Object.defineProperty(v.writable,a,{value:d,writable:!1}),Object.defineProperty(v.writable,n,{value:o,writable:!1}),m.set(d,{writable:v.writable,state:b}),v.writable}export{e as FatalError,t as RetryableError,y as createHook,b as createWebhook,x as defineHook,C as experimental_setAttributes,w as getStepMetadata,T as getWorkflowMetadata,E as getWritable,v as sleep};