experimental-ash 0.42.0 → 0.43.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 (155) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/docs/public/README.md +8 -8
  3. package/dist/docs/public/advanced/{auth-and-route-protection.md → auth-and-route-protection.mdx} +11 -0
  4. package/dist/docs/public/advanced/context-control.md +4 -4
  5. package/dist/docs/public/advanced/{evals.md → evals.mdx} +11 -1
  6. package/dist/docs/public/advanced/{hooks.md → hooks.mdx} +28 -40
  7. package/dist/docs/public/advanced/instrumentation.md +92 -3
  8. package/dist/docs/public/advanced/project-layout.md +5 -5
  9. package/dist/docs/public/advanced/runs-and-streaming.md +8 -2
  10. package/dist/docs/public/advanced/session-context.md +1 -1
  11. package/dist/docs/public/advanced/typescript-api.md +49 -6
  12. package/dist/docs/public/advanced/vercel-deployment.md +1 -1
  13. package/dist/docs/public/agent-ts.md +5 -5
  14. package/dist/docs/public/channels/{discord.md → discord.mdx} +11 -0
  15. package/dist/docs/public/channels/index.md +10 -10
  16. package/dist/docs/public/channels/{slack.md → slack.mdx} +11 -0
  17. package/dist/docs/public/channels/{teams.md → teams.mdx} +12 -0
  18. package/dist/docs/public/channels/{telegram.md → telegram.mdx} +11 -0
  19. package/dist/docs/public/channels/{twilio.md → twilio.mdx} +11 -0
  20. package/dist/docs/public/{connections.md → connections.mdx} +18 -6
  21. package/dist/docs/public/frontend/README.md +16 -0
  22. package/dist/docs/public/frontend/meta.json +3 -0
  23. package/dist/docs/public/frontend/nextjs.md +192 -0
  24. package/dist/docs/public/frontend/use-ash-agent.md +332 -0
  25. package/dist/docs/public/{getting-started.md → getting-started.mdx} +12 -1
  26. package/dist/docs/public/{human-in-the-loop.md → human-in-the-loop.mdx} +12 -1
  27. package/dist/docs/public/meta.json +1 -0
  28. package/dist/docs/public/sandbox.md +1 -1
  29. package/dist/docs/public/{schedules.md → schedules.mdx} +9 -0
  30. package/dist/docs/public/skills.md +2 -2
  31. package/dist/docs/public/{subagents.md → subagents.mdx} +10 -0
  32. package/dist/docs/public/{tools.md → tools.mdx} +41 -26
  33. package/dist/src/channel/adapter.d.ts +13 -0
  34. package/dist/src/channel/instrumentation.d.ts +10 -0
  35. package/dist/src/channel/instrumentation.js +1 -0
  36. package/dist/src/channel/send.js +1 -1
  37. package/dist/src/channel/types.d.ts +16 -0
  38. package/dist/src/cli/commands/channels.d.ts +2 -1
  39. package/dist/src/cli/commands/channels.js +1 -1
  40. package/dist/src/compiler/manifest.d.ts +13 -1
  41. package/dist/src/compiler/manifest.js +1 -1
  42. package/dist/src/compiler/module-map.js +1 -1
  43. package/dist/src/compiler/normalize-manifest.js +1 -1
  44. package/dist/src/compiler/normalize-skill.d.ts +15 -2
  45. package/dist/src/compiler/normalize-skill.js +1 -1
  46. package/dist/src/compiler/normalize-tool.js +1 -1
  47. package/dist/src/context/dynamic-skill-lifecycle.d.ts +23 -0
  48. package/dist/src/context/dynamic-skill-lifecycle.js +1 -0
  49. package/dist/src/context/dynamic-tool-lifecycle.d.ts +2 -0
  50. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  51. package/dist/src/context/hook-lifecycle.d.ts +4 -6
  52. package/dist/src/context/hook-lifecycle.js +1 -1
  53. package/dist/src/context/keys.d.ts +6 -4
  54. package/dist/src/context/keys.js +1 -1
  55. package/dist/src/context/providers/connection.d.ts +9 -0
  56. package/dist/src/context/providers/connection.js +1 -1
  57. package/dist/src/context/providers/sandbox.js +1 -1
  58. package/dist/src/execution/ash-workflow-attributes.d.ts +118 -0
  59. package/dist/src/execution/ash-workflow-attributes.js +1 -0
  60. package/dist/src/execution/channel-context.d.ts +5 -0
  61. package/dist/src/execution/channel-context.js +1 -0
  62. package/dist/src/execution/create-session-step.d.ts +28 -1
  63. package/dist/src/execution/create-session-step.js +1 -1
  64. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  65. package/dist/src/execution/durable-session-store.d.ts +7 -0
  66. package/dist/src/execution/runtime-context.js +1 -1
  67. package/dist/src/execution/sandbox/prewarm.js +1 -1
  68. package/dist/src/execution/session.d.ts +6 -0
  69. package/dist/src/execution/session.js +2 -2
  70. package/dist/src/execution/skills/instructions.d.ts +3 -2
  71. package/dist/src/execution/subagent-tool.js +1 -1
  72. package/dist/src/execution/workflow-entry.js +1 -1
  73. package/dist/src/execution/workflow-steps.js +1 -1
  74. package/dist/src/harness/attachment-staging.js +1 -1
  75. package/dist/src/harness/code-mode.d.ts +0 -5
  76. package/dist/src/harness/code-mode.js +1 -1
  77. package/dist/src/harness/emission.d.ts +1 -1
  78. package/dist/src/harness/emission.js +1 -1
  79. package/dist/src/harness/instrumentation-config.d.ts +1 -1
  80. package/dist/src/harness/instrumentation-metadata.d.ts +23 -0
  81. package/dist/src/harness/instrumentation-metadata.js +1 -0
  82. package/dist/src/harness/otel-integration.d.ts +2 -2
  83. package/dist/src/harness/otel-integration.js +1 -1
  84. package/dist/src/harness/step-hooks.js +1 -1
  85. package/dist/src/harness/tool-loop.js +1 -1
  86. package/dist/src/harness/turn-tag-state.d.ts +50 -0
  87. package/dist/src/harness/turn-tag-state.js +1 -0
  88. package/dist/src/harness/types.d.ts +11 -2
  89. package/dist/src/internal/application/package.js +1 -1
  90. package/dist/src/internal/authored-definition/schema-backed.d.ts +0 -1
  91. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  92. package/dist/src/internal/instrumentation.d.ts +39 -0
  93. package/dist/src/internal/instrumentation.js +1 -0
  94. package/dist/src/internal/workflow/builtins.d.ts +32 -0
  95. package/dist/src/internal/workflow/builtins.js +1 -1
  96. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.d.ts +1 -1
  97. package/dist/src/internal/workflow-bundle/dynamic-tool-transform.js +1 -1
  98. package/dist/src/internal/workflow-bundle/workflow-core-shim.d.ts +34 -0
  99. package/dist/src/internal/workflow-bundle/workflow-core-shim.js +1 -1
  100. package/dist/src/internal/workflow-bundle/workflow-transformer.js +1 -1
  101. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  102. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +2 -2
  103. package/dist/src/public/channels/slack/attachments.js +1 -1
  104. package/dist/src/public/channels/slack/index.d.ts +1 -1
  105. package/dist/src/public/channels/slack/slackChannel.d.ts +6 -0
  106. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  107. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  108. package/dist/src/public/channels/twilio/index.d.ts +1 -1
  109. package/dist/src/public/channels/twilio/twilioChannel.d.ts +6 -0
  110. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  111. package/dist/src/public/definitions/defineChannel.d.ts +12 -2
  112. package/dist/src/public/definitions/defineChannel.js +1 -1
  113. package/dist/src/public/definitions/hook.d.ts +3 -11
  114. package/dist/src/public/definitions/instrumentation.d.ts +88 -2
  115. package/dist/src/public/definitions/skill.d.ts +5 -0
  116. package/dist/src/public/definitions/tool.d.ts +25 -66
  117. package/dist/src/public/definitions/tool.js +1 -1
  118. package/dist/src/public/instrumentation/index.d.ts +1 -4
  119. package/dist/src/public/instrumentation/index.js +1 -1
  120. package/dist/src/public/skills/index.d.ts +2 -0
  121. package/dist/src/public/skills/index.js +1 -1
  122. package/dist/src/public/tools/index.d.ts +2 -2
  123. package/dist/src/public/tools/index.js +1 -1
  124. package/dist/src/runtime/agent/mock-model-adapter.js +4 -7
  125. package/dist/src/runtime/agent/mock-model-skill-selection.d.ts +9 -0
  126. package/dist/src/runtime/agent/mock-model-skill-selection.js +4 -0
  127. package/dist/src/runtime/attributes/emit.d.ts +73 -0
  128. package/dist/src/runtime/attributes/emit.js +1 -0
  129. package/dist/src/runtime/channels/registry.js +1 -1
  130. package/dist/src/runtime/connections/mcp-client.js +1 -1
  131. package/dist/src/runtime/framework-tools/code-mode-connection-auth.d.ts +2 -0
  132. package/dist/src/runtime/framework-tools/connection-search-dynamic.d.ts +34 -0
  133. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -0
  134. package/dist/src/runtime/framework-tools/index.d.ts +7 -5
  135. package/dist/src/runtime/framework-tools/index.js +1 -1
  136. package/dist/src/runtime/prompt/connections.js +1 -1
  137. package/dist/src/runtime/resolve-agent-graph.js +1 -1
  138. package/dist/src/runtime/resolve-agent.js +1 -1
  139. package/dist/src/runtime/resolve-dynamic-skill.d.ts +8 -0
  140. package/dist/src/runtime/resolve-dynamic-skill.js +1 -0
  141. package/dist/src/runtime/resolve-dynamic-tool.js +1 -1
  142. package/dist/src/runtime/sessions/compiled-agent-cache.js +1 -1
  143. package/dist/src/runtime/sessions/runtime-context-keys.js +1 -1
  144. package/dist/src/runtime/types.d.ts +13 -4
  145. package/dist/src/shared/dynamic-tool-definition.d.ts +51 -76
  146. package/dist/src/shared/dynamic-tool-definition.js +1 -1
  147. package/dist/src/shared/guards.d.ts +14 -0
  148. package/dist/src/shared/guards.js +1 -1
  149. package/dist/src/shared/skill-definition.d.ts +5 -4
  150. package/dist/src/shared/tool-definition.d.ts +12 -0
  151. package/package.json +2 -1
  152. package/dist/src/runtime/framework-tools/connection-search.d.ts +0 -57
  153. package/dist/src/runtime/framework-tools/connection-search.js +0 -1
  154. package/dist/src/runtime/framework-tools/connection-tools.d.ts +0 -55
  155. package/dist/src/runtime/framework-tools/connection-tools.js +0 -1
@@ -53,7 +53,7 @@ export interface ReceiveInput<TReceiveArgs = Record<string, unknown>> {
53
53
  readonly args: Readonly<TReceiveArgs>;
54
54
  readonly auth: SessionAuthContext | null;
55
55
  }
56
- export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>> {
56
+ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> {
57
57
  readonly state?: TState;
58
58
  /**
59
59
  * Builds the per-step channel context handed to `events` and
@@ -84,6 +84,16 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
84
84
  * stage the file to the sandbox.
85
85
  */
86
86
  readonly fetchFile?: (url: string) => Promise<Buffer | FetchFileResult | null>;
87
+ /**
88
+ * Channel-owned metadata exposed to instrumentation callbacks.
89
+ *
90
+ * Keep this projection intentionally small. It is the channel's public
91
+ * observability surface, not a dump of durable adapter state. Return an
92
+ * object composed of JSON primitives, arrays, and plain objects. Ash omits
93
+ * `undefined` object properties and drops projections containing values such
94
+ * as `Date` or `Map`.
95
+ */
96
+ readonly metadata?: (state: NonNullable<TState>) => TMetadata;
87
97
  /**
88
98
  * Identifier of the adapter family this channel belongs to. Set by
89
99
  * higher-level wrappers (e.g. `slackChannel` passes `"slack"`) so
@@ -107,4 +117,4 @@ export interface Channel<TState = undefined, TReceiveArgs = Record<string, unkno
107
117
  send: SendFn<TState>;
108
118
  }) => Promise<Session>;
109
119
  }
110
- export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs>): Channel<TState, TReceiveArgs>;
120
+ export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs, TMetadata>): Channel<TState, TReceiveArgs>;
@@ -1 +1 @@
1
- import{CHANNEL_SENTINEL}from"#channel/compiled-channel.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{defaultDeliverResult}from"#channel/adapter.js";import{HTTP_ADAPTER_KIND}from"#channel/http.js";import{DELETE,GET,PATCH,POST,PUT}from"#channel/routes.js";function defineChannel(t){let n=buildAdapter(t);return{__kind:CHANNEL_SENTINEL,routes:t.routes,adapter:n,receive:t.receive}}function buildAdapter(e){let i=e.state!=null,a=e.context!=null,o=e.fetchFile!==void 0,s=i||a,c={},l=!1,u=[`turn.started`,`actions.requested`,`action.result`,`message.completed`,`message.appended`,`input.requested`,`turn.failed`,`turn.completed`,`session.failed`,`session.completed`,`session.waiting`,`authorization.required`,`authorization.completed`],d=e.events;for(let e of u){let n=d?.[e];n&&(l=!0,c[e]=(r,i)=>{let a={...i,continuationToken:i.session?.continuationToken??``,setContinuationToken:e=>i.session?.setContinuationToken(e)};return e===`session.failed`?n(r,a):n(r,a,buildCallbackContext())})}return!s&&!l&&!o?{kind:e.kindHint??HTTP_ADAPTER_KIND}:{kind:e.kindHint??`defineChannel`,state:i?{...e.state}:{},fetchFile:e.fetchFile,createAdapterContext(t){let n=t.state,r=t.session;return{...a?e.context(n,r):{},state:n,ctx:t.ctx,session:r}},deliver(e){return defaultDeliverResult(e)},...c}}export{DELETE,GET,PATCH,POST,PUT,defineChannel};
1
+ import{CHANNEL_SENTINEL}from"#channel/compiled-channel.js";import{defaultDeliverResult}from"#channel/adapter.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{HTTP_ADAPTER_KIND}from"#channel/http.js";import{DELETE,GET,PATCH,POST,PUT}from"#channel/routes.js";function defineChannel(t){let n=buildAdapter(t);return{__kind:CHANNEL_SENTINEL,routes:t.routes,adapter:n,receive:t.receive}}function buildAdapter(e){let i=e.state!=null,a=e.context!=null,o=e.fetchFile!==void 0,s=e.metadata,c=i||a||s!==void 0,l={},u=!1,d=[`turn.started`,`actions.requested`,`action.result`,`message.completed`,`message.appended`,`input.requested`,`turn.failed`,`turn.completed`,`session.failed`,`session.completed`,`session.waiting`,`authorization.required`,`authorization.completed`],f=e.events;for(let e of d){let t=f?.[e];t&&(u=!0,l[e]=(r,i)=>{let a={...i,continuationToken:i.session?.continuationToken??``,setContinuationToken:e=>i.session?.setContinuationToken(e)};return e===`session.failed`?t(r,a):t(r,a,buildCallbackContext())})}return!c&&!u&&!o?{kind:e.kindHint??HTTP_ADAPTER_KIND}:{kind:e.kindHint??`defineChannel`,state:i?{...e.state}:{},fetchFile:e.fetchFile,instrumentation:s===void 0?void 0:{metadata(e){return s(e)}},createAdapterContext(t){let n=t.state,r=t.session;return{...a?e.context(n,r):{},state:n,ctx:t.ctx,session:r}},deliver(e){return defaultDeliverResult(e)},...l}}export{DELETE,GET,PATCH,POST,PUT,defineChannel};
@@ -2,7 +2,6 @@ import type { ModelMessage } from "ai";
2
2
  import type { HandleMessageStreamEvent } from "../../protocol/message.js";
3
3
  import type { SessionContext } from "./callback-context.js";
4
4
  import type { ExactDefinition } from "./exact.js";
5
- import type { NamedSkillDefinition } from "./skill.js";
6
5
  /**
7
6
  * Context passed to every hook handler.
8
7
  *
@@ -24,22 +23,15 @@ export interface HookContext extends SessionContext {
24
23
  * `lifecycle.turn`).
25
24
  *
26
25
  * `modelContext` messages are appended to the next model call's message
27
- * list and never written to durable session history. `skills` are
28
- * materialized into the live sandbox and announced through durable
29
- * system history. Each lifecycle key runs in registry order;
30
- * contributions are concatenated session-then-turn before the harness's
31
- * first model call.
26
+ * list and never written to durable session history. Each lifecycle key
27
+ * runs in registry order; contributions are concatenated session-then-turn
28
+ * before the harness's first model call.
32
29
  *
33
30
  * Returning `void` (or `undefined`) means "no opinion" — the turn
34
31
  * proceeds with no additional model context from this hook.
35
32
  */
36
33
  export interface LifecycleHookResult {
37
34
  readonly modelContext?: readonly ModelMessage[];
38
- /**
39
- * Skill packages to materialize into the live durable sandbox before the
40
- * next model call. Authored skill names cannot be overwritten.
41
- */
42
- readonly skills?: readonly NamedSkillDefinition[];
43
35
  }
44
36
  /**
45
37
  * Lifecycle hook signature shared by `lifecycle.session` and
@@ -1,4 +1,9 @@
1
1
  import type { ExactDefinition } from "#public/definitions/exact.js";
2
+ /**
3
+ * Instrumentation authoring helpers for `agent/instrumentation.ts`.
4
+ */
5
+ import type { ModelMessage, SystemModelMessage } from "ai";
6
+ import type { SessionAuthContext, SessionParent } from "#channel/types.js";
2
7
  /**
3
8
  * Context passed to the {@link InstrumentationDefinition.setup} callback.
4
9
  */
@@ -11,6 +16,87 @@ export interface InstrumentationSetupContext {
11
16
  */
12
17
  readonly agentName: string;
13
18
  }
19
+ /**
20
+ * User-authored metadata attached to AI SDK telemetry spans.
21
+ *
22
+ * Keys beginning with `ash.` are reserved for framework-owned metadata
23
+ * and are ignored when returned from authored instrumentation.
24
+ */
25
+ export type InstrumentationMetadata = Readonly<Record<string, string>>;
26
+ /**
27
+ * Base channel metadata shape used by framework channel kinds.
28
+ */
29
+ export type InstrumentationChannelMetadata = Readonly<Record<string, unknown>>;
30
+ /**
31
+ * Channel metadata projections keyed by instrumentation channel kind.
32
+ *
33
+ * Channel packages and user-authored channels declaration-merge additional
34
+ * `channel:<registered-name>` entries into this map so `input.channel.metadata`
35
+ * narrows after checking `input.channel.kind`.
36
+ */
37
+ export interface ChannelMetadataMap {
38
+ readonly http: InstrumentationChannelMetadata;
39
+ readonly schedule: InstrumentationChannelMetadata;
40
+ readonly subagent: InstrumentationChannelMetadata;
41
+ readonly unknown: InstrumentationChannelMetadata;
42
+ }
43
+ export type InstrumentationChannelKind = keyof ChannelMetadataMap;
44
+ /**
45
+ * Channel projection exposed to instrumentation callbacks.
46
+ *
47
+ * `kind` is the channel identity Ash has for the current turn (`"http"`,
48
+ * `"schedule"`, `"subagent"`, or `channel:<name>` for authored channels,
49
+ * where `<name>` is the channel's filename under `agent/channels/`).
50
+ */
51
+ export interface InstrumentationChannelForKind<K extends InstrumentationChannelKind> {
52
+ readonly kind: K;
53
+ readonly metadata: ChannelMetadataMap[K];
54
+ }
55
+ export type InstrumentationChannel = {
56
+ readonly [K in InstrumentationChannelKind]: InstrumentationChannelForKind<K>;
57
+ }[InstrumentationChannelKind];
58
+ export interface InstrumentationSession {
59
+ readonly auth: {
60
+ readonly current: SessionAuthContext | null;
61
+ readonly initiator: SessionAuthContext | null;
62
+ };
63
+ readonly id: string;
64
+ readonly parent?: SessionParent;
65
+ }
66
+ export interface InstrumentationTurn {
67
+ readonly id: string;
68
+ readonly sequence: number;
69
+ }
70
+ export interface InstrumentationStep {
71
+ readonly index: number;
72
+ }
73
+ export interface InstrumentationModelInput {
74
+ readonly instructions: string | readonly SystemModelMessage[] | undefined;
75
+ readonly messages: readonly ModelMessage[];
76
+ }
77
+ /**
78
+ * Input passed to `metadata["step.started"]`.
79
+ *
80
+ * The callback runs after Ash has built the final model input for this
81
+ * model-call attempt and before the AI SDK model call is constructed.
82
+ */
83
+ export interface InstrumentationStepStartedMetadataInput {
84
+ readonly channel: InstrumentationChannel;
85
+ readonly modelInput: InstrumentationModelInput;
86
+ readonly session: InstrumentationSession;
87
+ readonly step: InstrumentationStep;
88
+ readonly turn: InstrumentationTurn;
89
+ }
90
+ /**
91
+ * Metadata hooks accepted by {@link defineInstrumentation}.
92
+ */
93
+ export interface InstrumentationMetadataConfig {
94
+ /**
95
+ * Per-attempt metadata resolved before the model call so child spans
96
+ * created by the AI SDK inherit the returned values.
97
+ */
98
+ readonly "step.started"?: (input: InstrumentationStepStartedMetadataInput) => InstrumentationMetadata;
99
+ }
14
100
  /**
15
101
  * Authored instrumentation settings accepted by `defineInstrumentation`.
16
102
  *
@@ -25,9 +111,9 @@ export interface InstrumentationDefinition {
25
111
  */
26
112
  readonly functionId?: string;
27
113
  /**
28
- * Additional metadata merged into every AI SDK telemetry span.
114
+ * Additional metadata merged into AI SDK telemetry spans.
29
115
  */
30
- readonly metadata?: Record<string, string>;
116
+ readonly metadata?: InstrumentationMetadataConfig;
31
117
  /**
32
118
  * Whether to record full model inputs in telemetry spans.
33
119
  *
@@ -12,5 +12,10 @@ export type SkillDefinition = SkillPackageDefinition;
12
12
  /**
13
13
  * Defines a skill in TypeScript using the same shape discovery produces from
14
14
  * markdown, with optional package-relative sibling files.
15
+ *
16
+ * When used as the default export of a file in `agent/skills/`, this
17
+ * produces a static skill. When used inside a `defineDynamic` handler,
18
+ * the brand stamp lets the lifecycle code detect single-entry vs
19
+ * map-of-entries return shapes.
15
20
  */
16
21
  export declare function defineSkill<TSkill extends SkillDefinition>(definition: ExactDefinition<TSkill, SkillDefinition>): TSkill;
@@ -1,9 +1,9 @@
1
1
  import type { StandardJSONSchemaV1 } from "#compiled/@standard-schema/spec/index.js";
2
2
  import type { ModelMessage } from "ai";
3
- import type { PublicToolDefinition } from "#shared/tool-definition.js";
3
+ import type { PublicToolDefinition, ToolModelOutput } from "#shared/tool-definition.js";
4
4
  import type { SessionContext } from "#public/definitions/callback-context.js";
5
5
  import type { JsonObject } from "#shared/json.js";
6
- import { type DynamicSingleToolEvents, type DynamicToolEntry, type DynamicToolEvents, type DynamicToolSentinel, type DynamicToolsSentinel } from "#shared/dynamic-tool-definition.js";
6
+ import { type DynamicEvents, type DynamicSentinel } from "#shared/dynamic-tool-definition.js";
7
7
  /**
8
8
  * Domain input passed to a tool's {@link ToolDefinition.onCompact} hook.
9
9
  */
@@ -39,19 +39,7 @@ export interface NeedsApprovalContext<TInput = Record<string, unknown>> {
39
39
  readonly toolInput?: ApprovalToolInput<TInput>;
40
40
  readonly toolName: string;
41
41
  }
42
- /**
43
- * Ash-owned shape for the model-facing tool result produced by
44
- * {@link ToolDefinition.toModelOutput}. Structurally compatible with the
45
- * AI SDK's `ToolResultOutput` so the harness can forward it without
46
- * conversion.
47
- */
48
- export type ToolModelOutput = {
49
- readonly type: "text";
50
- readonly value: string;
51
- } | {
52
- readonly type: "json";
53
- readonly value: unknown;
54
- };
42
+ export type { ToolModelOutput } from "#shared/tool-definition.js";
55
43
  /**
56
44
  * Result returned from a {@link ToolRetentionPolicy} function.
57
45
  *
@@ -140,6 +128,13 @@ export type ToolDefinition<TInput = unknown, TOutput = unknown> = PublicToolDefi
140
128
  };
141
129
  /**
142
130
  * Defines a tool configuration.
131
+ *
132
+ * Used for both static tools (default export from `agent/tools/*.ts`)
133
+ * and as the entry wrapper inside `defineDynamic` resolvers.
134
+ *
135
+ * For static tools, the runtime tool name is the filename slug.
136
+ * For dynamic entries, `defineTool` stamps a brand that the
137
+ * lifecycle code validates — raw object literals are rejected.
143
138
  */
144
139
  export declare function defineTool<TSchema extends StandardJSONSchemaV1<unknown, unknown>, TOutput>(definition: {
145
140
  description: ToolDefinition<unknown, unknown>["description"];
@@ -160,72 +155,37 @@ export declare function defineTool<TOutput>(definition: {
160
155
  onCompact?: ToolDefinition<unknown, unknown>["onCompact"];
161
156
  }): ToolDefinition<Record<string, unknown>, TOutput>;
162
157
  export declare function defineTool<TInput = unknown, TOutput = unknown>(definition: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
163
- export declare function defineTool<TInput = unknown, TOutput = unknown>(definition: ToolDefinition<TInput, TOutput>): ToolDefinition<TInput, TOutput>;
164
- export declare function defineTool(definition: {
165
- readonly events: DynamicSingleToolEvents;
166
- }): DynamicToolSentinel;
167
158
  /**
168
159
  * Defines a dynamic tool set resolved at runtime. The file's
169
- * path-derived slug becomes the tool name prefix; entry keys become
170
- * suffixes joined by `__`. When the resolver returns exactly one
171
- * entry, the tool takes the file's name directly.
172
- *
173
- * Uses the same `events` object shape as {@link defineHook}:
160
+ * path-derived slug becomes the tool name; the handler's return
161
+ * shape determines naming:
174
162
  *
175
- * ```ts
176
- * export default defineTools({
177
- * events: {
178
- * "session.started": async (event, ctx) => ({
179
- * export: { description: "...", inputSchema: {...}, execute: ... },
180
- * query: { description: "...", inputSchema: {...}, execute: ... },
181
- * }),
182
- * },
183
- * });
184
- * ```
185
- */
186
- export declare function defineTools(definition: {
187
- readonly events: DynamicToolEvents;
188
- }): DynamicToolsSentinel;
189
- /**
190
- * Typed wrapper for a single entry inside a `defineTools` resolver.
191
- * Mirrors the AI SDK's `tool()` pattern — an identity function at
192
- * runtime that captures `TSchema` at the type level so `execute(input)`
193
- * is inferred from the schema.
163
+ * - Return a single `defineTool(...)` → tool named after the file slug.
164
+ * - Return a `Record<string, defineTool(...)>` → tools named `slug__key`.
165
+ * - Return `null` → no tools for this event.
194
166
  *
195
167
  * ```ts
196
- * import { defineTools, tool } from "experimental-ash/tools";
168
+ * import { defineDynamic, defineTool } from "experimental-ash/tools";
197
169
  * import { z } from "zod";
198
170
  *
199
- * export default defineTools({
171
+ * export default defineDynamic({
200
172
  * events: {
201
- * "session.started": async () => ({
202
- * weather: tool({
203
- * description: "Get weather",
204
- * inputSchema: z.object({ city: z.string() }),
173
+ * "session.started": async (event, ctx) => ({
174
+ * export: defineTool({
175
+ * description: "Export data",
176
+ * inputSchema: z.object({ format: z.string() }),
205
177
  * async execute(input) {
206
- * // input.city is typed as string
207
- * return fetchWeather(input.city);
178
+ * return doExport(input.format);
208
179
  * },
209
180
  * }),
210
181
  * }),
211
182
  * },
212
183
  * });
213
184
  * ```
214
- *
215
- * Without `tool()`, `execute(input)` defaults to
216
- * `Record<string, unknown>`. Use `tool()` when you want schema-
217
- * inferred types.
218
185
  */
219
- export declare function tool<TSchema extends StandardJSONSchemaV1<unknown, unknown>, TOutput>(entry: {
220
- description: string;
221
- inputSchema: TSchema;
222
- execute(input: StandardJSONSchemaV1.InferOutput<TSchema>, ctx: ToolContext): TOutput | Promise<TOutput>;
223
- }): DynamicToolEntry<StandardJSONSchemaV1.InferOutput<TSchema>, TOutput>;
224
- export declare function tool<TOutput>(entry: {
225
- description: string;
226
- inputSchema: JsonObject;
227
- execute(input: Record<string, unknown>, ctx: ToolContext): TOutput | Promise<TOutput>;
228
- }): DynamicToolEntry<Record<string, unknown>, TOutput>;
186
+ export declare function defineDynamic(definition: {
187
+ readonly events: DynamicEvents;
188
+ }): DynamicSentinel;
229
189
  /**
230
190
  * Marker discriminator written into every {@link DisabledToolSentinel}.
231
191
  */
@@ -248,4 +208,3 @@ export declare function disableTool(): DisabledToolSentinel;
248
208
  * produced by {@link disableTool}.
249
209
  */
250
210
  export declare function isDisabledToolSentinel(value: unknown): value is DisabledToolSentinel;
251
- export {};
@@ -1 +1 @@
1
- import{DYNAMIC_TOOLS_SENTINEL_KIND,DYNAMIC_TOOL_SENTINEL_KIND}from"#shared/dynamic-tool-definition.js";import{stampDefinitionKey}from"#public/tool-result-narrowing.js";function defineTool(e){if(`events`in e){let n={kind:DYNAMIC_TOOL_SENTINEL_KIND,events:e.events};return stampDefinitionKey(n,`dynamic-tool:${Object.keys(e.events).join(`,`)}`),n}return stampDefinitionKey(e,`tool:${e.description}`),e}function defineTools(t){let n={kind:DYNAMIC_TOOLS_SENTINEL_KIND,events:t.events};return stampDefinitionKey(n,`dynamic-tools:${Object.keys(t.events).join(`,`)}`),n}function tool(e){return Object.assign(e,{__brand:`ash:tool`})}const DISABLED_TOOL_SENTINEL_KIND=`ash:disabled-tool`;function disableTool(){return{kind:DISABLED_TOOL_SENTINEL_KIND}}function isDisabledToolSentinel(e){return typeof e==`object`&&!!e&&e.kind===DISABLED_TOOL_SENTINEL_KIND}export{defineTool,defineTools,disableTool,isDisabledToolSentinel,tool};
1
+ import{DYNAMIC_SENTINEL_KIND,TOOL_BRAND}from"#shared/dynamic-tool-definition.js";import{stampDefinitionKey}from"#public/tool-result-narrowing.js";function defineTool(e){return Object.assign(e,{[TOOL_BRAND]:!0}),stampDefinitionKey(e,`tool:${e.description}`),e}function defineDynamic(t){let n={kind:DYNAMIC_SENTINEL_KIND,events:t.events};return stampDefinitionKey(n,`dynamic:${Object.keys(t.events).join(`,`)}`),n}const DISABLED_TOOL_SENTINEL_KIND=`ash:disabled-tool`;function disableTool(){return{kind:DISABLED_TOOL_SENTINEL_KIND}}function isDisabledToolSentinel(e){return typeof e==`object`&&!!e&&e.kind===DISABLED_TOOL_SENTINEL_KIND}export{defineDynamic,defineTool,disableTool,isDisabledToolSentinel};
@@ -1,4 +1 @@
1
- /**
2
- * Instrumentation authoring helpers for `agent/instrumentation.ts`.
3
- */
4
- export { defineInstrumentation, type InstrumentationDefinition, type InstrumentationSetupContext, } from "#public/definitions/instrumentation.js";
1
+ export * from "#public/definitions/instrumentation.js";
@@ -1 +1 @@
1
- import{defineInstrumentation}from"#public/definitions/instrumentation.js";export{defineInstrumentation};
1
+ export*from"#public/definitions/instrumentation.js";export{};
@@ -3,3 +3,5 @@
3
3
  */
4
4
  export type { SkillFile, SkillHandle } from "#execution/skills/types.js";
5
5
  export { defineSkill, type NamedSkillDefinition, type SkillDefinition, type SkillFileContent, type SkillPackageDefinition, } from "#public/definitions/skill.js";
6
+ export { defineDynamic } from "#public/definitions/tool.js";
7
+ export type { DynamicResolveContext, DynamicSentinel } from "#shared/dynamic-tool-definition.js";
@@ -1 +1 @@
1
- import{defineSkill}from"#public/definitions/skill.js";export{defineSkill};
1
+ import{defineDynamic}from"#public/definitions/tool.js";import{defineSkill}from"#public/definitions/skill.js";export{defineDynamic,defineSkill};
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Tool authoring helpers for `agent/tools/*.ts` files.
3
3
  */
4
- export { type CompactionInput, type CompactionHookResult, type DisabledToolSentinel, defineTool, defineTools, disableTool, isDisabledToolSentinel, type NeedsApprovalContext, type RetentionSummary, tool, type ToolDefinition, type ToolContext, type ToolModelOutput, type ToolRetentionPolicy, } from "#public/definitions/tool.js";
5
- export type { DynamicToolEntry, DynamicToolEvents, DynamicToolResolveContext, DynamicToolSentinel, DynamicToolSet, DynamicToolsSentinel, DynamicSingleToolEvents, } from "#shared/dynamic-tool-definition.js";
4
+ export { type CompactionInput, type CompactionHookResult, type DisabledToolSentinel, defineDynamic, defineTool, disableTool, isDisabledToolSentinel, type NeedsApprovalContext, type RetentionSummary, type ToolDefinition, type ToolContext, type ToolModelOutput, type ToolRetentionPolicy, } from "#public/definitions/tool.js";
5
+ export type { DynamicToolEntry, DynamicEvents, DynamicToolEvents, DynamicResolveContext, DynamicSentinel, DynamicToolSet, DynamicToolResult, } from "#shared/dynamic-tool-definition.js";
6
6
  export { type SessionContext } from "#public/definitions/callback-context.js";
7
7
  export { toolResultFrom, type MatchedConnectionResult, type MatchedToolResult, type ToolResultFromFn, } from "#public/tool-result-narrowing.js";
8
8
  export { type DefineBashToolInput, defineBashTool } from "#public/tools/define-bash-tool.js";
@@ -1 +1 @@
1
- import{defineTool,defineTools,disableTool,isDisabledToolSentinel,tool}from"#public/definitions/tool.js";import{toolResultFrom}from"#public/tool-result-narrowing.js";import{defineBashTool}from"#public/tools/define-bash-tool.js";import{defineGlobTool}from"#public/tools/define-glob-tool.js";import{defineGrepTool}from"#public/tools/define-grep-tool.js";import{defineReadFileTool}from"#public/tools/define-read-file-tool.js";import{defineWriteFileTool}from"#public/tools/define-write-file-tool.js";export{defineBashTool,defineGlobTool,defineGrepTool,defineReadFileTool,defineTool,defineTools,defineWriteFileTool,disableTool,isDisabledToolSentinel,tool,toolResultFrom};
1
+ import{defineDynamic,defineTool,disableTool,isDisabledToolSentinel}from"#public/definitions/tool.js";import{toolResultFrom}from"#public/tool-result-narrowing.js";import{defineBashTool}from"#public/tools/define-bash-tool.js";import{defineGlobTool}from"#public/tools/define-glob-tool.js";import{defineGrepTool}from"#public/tools/define-grep-tool.js";import{defineReadFileTool}from"#public/tools/define-read-file-tool.js";import{defineWriteFileTool}from"#public/tools/define-write-file-tool.js";export{defineBashTool,defineDynamic,defineGlobTool,defineGrepTool,defineReadFileTool,defineTool,defineWriteFileTool,disableTool,isDisabledToolSentinel,toolResultFrom};
@@ -1,8 +1,5 @@
1
- import{z}from"#compiled/zod/index.js";import{CODE_MODE_TOOL_NAME}from"#shared/code-mode.js";import{MockLanguageModelV3}from"ai/test";import{BOOTSTRAP_RUNTIME_MODEL_ID,BOOTSTRAP_RUNTIME_SYSTEM_PROMPT}from"#runtime/agent/bootstrap.js";import{createBootstrapGenerateResult,createBootstrapStreamResult,estimateTokenCount,getLastUserPromptText,getPromptContentText,getPromptText}from"#runtime/agent/bootstrap-model-utils.js";import{LOAD_SKILL_TOOL_NAME}from"#runtime/skills/fragment-context.js";const authoredRuntimeModelMocks=new Map,bootstrapWeatherPayloadSchema=z.object({city:z.string(),condition:z.string(),summary:z.string(),temperatureF:z.number().finite()}).strict(),ASH_MOCK_AUTHORED_MODELS_ENV=`ASH_MOCK_AUTHORED_MODELS`;function shouldMockAuthoredRuntimeModels(){return process.env.ASH_MOCK_AUTHORED_MODELS===`1`||process.env.NODE_ENV===`test`}function createMockAuthoredRuntimeModel(e){let t=authoredRuntimeModelMocks.get(e.id);if(t!==void 0)return t;let r=new MockLanguageModelV3({modelId:e.id,provider:`ash-runtime-mock`,doGenerate:async t=>createMockModelResult(t,e.id),doStream:async t=>createBootstrapStreamResult(createMockModelResult(t,e.id))});return authoredRuntimeModelMocks.set(e.id,r),r}function createMockModelResult(e,t){let n=getLastAuthoredToolResult(e.prompt);if(n!==null){let r=formatToolResultReply(n,e.prompt);return createBootstrapGenerateResult({inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:t,outputTokens:estimateTokenCount(r),text:r})}let r=createSkillLoadResult(e.prompt,t);if(r!==null)return r;let i=createAuthoredToolCallResult(e,t);if(i!==null)return i;let o=createAssistantMessage(e.prompt);return createBootstrapGenerateResult({inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:t,outputTokens:estimateTokenCount(o),text:o})}function resolveMockAuthoredRuntimeModel(e){return!shouldMockAuthoredRuntimeModels()||e.id===BOOTSTRAP_RUNTIME_MODEL_ID?null:createMockAuthoredRuntimeModel(e)}function createSkillLoadResult(e,t){let n=getLastUserPromptText(e);if(n===null||getActivatedSkillIds(e).length>0)return null;let r=findRelevantSkill(getAvailableSkills(e),n);return r===null?null:createToolCallGenerateResult({input:{skill:r.name},inputTokens:estimateTokenCount(getPromptText(e)),modelId:t,outputTokens:estimateTokenCount(r.name),toolCallId:`call_load_skill`,toolName:LOAD_SKILL_TOOL_NAME})}function createAuthoredToolCallResult(e,n){let r=getLastUserPromptText(e.prompt);if(r===null)return null;let i=findRelevantTool(getAvailableTools(e),r);if(i===null)return null;let a=resolveWeatherCity(r);if(i.name===CODE_MODE_TOOL_NAME){let t=findRelevantCodeModeHostTool(i.description,r);if(t===null)return null;let o=`return await tools${formatCodeModeToolAccess(t)}({ city: ${JSON.stringify(a)} });`;return createToolCallGenerateResult({input:{js:o},inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:n,outputTokens:estimateTokenCount(o),toolCallId:`call_code_mode`,toolName:i.name})}return createToolCallGenerateResult({input:{city:a},inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:n,outputTokens:estimateTokenCount(a),toolCallId:`call_get_weather`,toolName:i.name})}function createAssistantMessage(e){let t=getLastUserPromptText(e)??`Hello from Ash`,n=getSystemPromptLabels(e),r=resolveSystemProbe(e);return n.length>0?r===null?`Bootstrap reply [${n.join(`, `)}]: ${t}`:`Bootstrap reply [${n.join(`, `)}; probe=${r}]: ${t}`:r===null?`Bootstrap reply: ${t}`:`Bootstrap reply [probe=${r}]: ${t}`}function formatToolResultReply(e,t){if(e.isError)return`Local weather tool failed: ${formatToolOutput(e.output)}`;if(isWeatherPayload(e.output))return`Used local weather tool for ${e.output.city}: ${e.output.condition}, ${e.output.temperatureF}F. ${e.output.summary}`;let n=getLastUserPromptText(t)??`Hello from Ash`;return`Used ${e.toolName} for "${n}": ${formatToolOutput(e.output)}`}function createToolCallGenerateResult(e){return{content:[{input:JSON.stringify(e.input),toolCallId:e.toolCallId,toolName:e.toolName,type:`tool-call`}],finishReason:`tool-calls`,response:{id:`bootstrap-response`,modelId:e.modelId,timestamp:new Date(`2026-03-16T00:00:00.000Z`)},usage:{inputTokens:{cacheRead:0,cacheWrite:0,noCache:e.inputTokens,total:e.inputTokens},outputTokens:{reasoning:0,text:e.outputTokens,total:e.outputTokens}},warnings:[]}}function getAvailableTools(e){return(e.tools??[]).flatMap(e=>e.type===`function`?[{description:e.description,name:e.name}]:[])}function getLastAuthoredToolResult(e){for(let t of[...e].reverse())if(!(t.role!==`tool`&&t.role!==`assistant`)){for(let e of[...t.content].reverse())if(!(typeof e==`string`||e.type!==`tool-result`)&&e.toolName!==LOAD_SKILL_TOOL_NAME)return{isError:e.output.type===`error-json`||e.output.type===`error-text`||e.output.type===`execution-denied`,output:e.output.type===`execution-denied`?{reason:e.output.reason??null,type:e.output.type}:e.output.value,toolCallId:e.toolCallId,toolName:e.toolName}}return null}function getSystemPromptLabels(e){let t=e.filter(e=>e.role===`system`);if(t.length===0)return[];let n=t.flatMap(e=>{let t=getPromptContentText(e.content);if(t.startsWith(`Available skills
1
+ import{z}from"#compiled/zod/index.js";import{CODE_MODE_TOOL_NAME}from"#shared/code-mode.js";import{MockLanguageModelV3}from"ai/test";import{BOOTSTRAP_RUNTIME_MODEL_ID,BOOTSTRAP_RUNTIME_SYSTEM_PROMPT}from"#runtime/agent/bootstrap.js";import{createBootstrapGenerateResult,createBootstrapStreamResult,estimateTokenCount,getLastUserPromptText,getPromptContentText,getPromptText}from"#runtime/agent/bootstrap-model-utils.js";import{findRelevantSkill,getActivatedSkillIds,getAvailableSkills}from"#runtime/agent/mock-model-skill-selection.js";import{LOAD_SKILL_TOOL_NAME}from"#runtime/skills/fragment-context.js";const authoredRuntimeModelMocks=new Map,bootstrapWeatherPayloadSchema=z.object({city:z.string(),condition:z.string(),summary:z.string(),temperatureF:z.number().finite()}).strict(),ASH_MOCK_AUTHORED_MODELS_ENV=`ASH_MOCK_AUTHORED_MODELS`;function shouldMockAuthoredRuntimeModels(){return process.env.ASH_MOCK_AUTHORED_MODELS===`1`||process.env.NODE_ENV===`test`}function createMockAuthoredRuntimeModel(e){let t=authoredRuntimeModelMocks.get(e.id);if(t!==void 0)return t;let r=new MockLanguageModelV3({modelId:e.id,provider:`ash-runtime-mock`,doGenerate:async t=>createMockModelResult(t,e.id),doStream:async t=>createBootstrapStreamResult(createMockModelResult(t,e.id))});return authoredRuntimeModelMocks.set(e.id,r),r}function createMockModelResult(e,t){let n=getLastAuthoredToolResult(e.prompt);if(n!==null){let r=createFollowUpToolCallResult({modelId:t,options:e,result:n});if(r!==null)return r;let i=formatToolResultReply(n,e.prompt);return createBootstrapGenerateResult({inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:t,outputTokens:estimateTokenCount(i),text:i})}let r=createSkillLoadResult(e.prompt,t);if(r!==null)return r;let i=createAuthoredToolCallResult(e,t);if(i!==null)return i;let o=createAssistantMessage(e.prompt);return createBootstrapGenerateResult({inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:t,outputTokens:estimateTokenCount(o),text:o})}function resolveMockAuthoredRuntimeModel(e){return!shouldMockAuthoredRuntimeModels()||e.id===BOOTSTRAP_RUNTIME_MODEL_ID?null:createMockAuthoredRuntimeModel(e)}function createSkillLoadResult(e,t){let n=getLastUserPromptText(e);if(n===null||getActivatedSkillIds(e).length>0)return null;let r=findRelevantSkill(getAvailableSkills(e),n);return r===null?null:createToolCallGenerateResult({input:{skill:r.name},inputTokens:estimateTokenCount(getPromptText(e)),modelId:t,outputTokens:estimateTokenCount(r.name),toolCallId:`call_load_skill`,toolName:LOAD_SKILL_TOOL_NAME})}function createAuthoredToolCallResult(e,n){let r=getLastUserPromptText(e.prompt);if(r===null)return null;let i=findRelevantTool(getAvailableTools(e),r);if(i===null)return null;let a=resolveWeatherCity(r),o=createAuthoredToolInput(i,r,a);if(i.name===CODE_MODE_TOOL_NAME){let t=findRelevantCodeModeHostTool(i.description,r);if(t===null)return null;let o=`return await tools${formatCodeModeToolAccess(t)}({ city: ${JSON.stringify(a)} });`;return createToolCallGenerateResult({input:{js:o},inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:n,outputTokens:estimateTokenCount(o),toolCallId:createToolCallId(i.name),toolName:i.name})}return createToolCallGenerateResult({input:o,inputTokens:estimateTokenCount(getPromptText(e.prompt)),modelId:n,outputTokens:estimateTokenCount(Object.values(o).join(` `)),toolCallId:createToolCallId(i.name),toolName:i.name})}function createFollowUpToolCallResult(e){let t=findNextExplicitToolAfterResult({previousToolName:e.result.toolName,prompt:e.options.prompt,tools:getAvailableTools(e.options)});if(t===null)return null;let n=createFollowUpToolInput(e.result.output);return n===null?null:createToolCallGenerateResult({input:n,inputTokens:estimateTokenCount(getPromptText(e.options.prompt)),modelId:e.modelId,outputTokens:estimateTokenCount(Object.values(n).join(` `)),toolCallId:createToolCallId(t.name),toolName:t.name})}function createAuthoredToolInput(e,t,n){let r=getToolInputPropertyNames(e.inputSchema);return r.includes(`topic`)||/\btopic\b/u.test(normalizeText(t))?{topic:resolveLookupTopic(t)}:r.length===1&&r[0]===`message`?{message:t}:{city:n}}function createAssistantMessage(e){let t=getLastUserPromptText(e)??`Hello from Ash`,n=getSystemPromptLabels(e),r=resolveSystemProbe(e);return n.length>0?r===null?`Bootstrap reply [${n.join(`, `)}]: ${t}`:`Bootstrap reply [${n.join(`, `)}; probe=${r}]: ${t}`:r===null?`Bootstrap reply: ${t}`:`Bootstrap reply [probe=${r}]: ${t}`}function formatToolResultReply(e,t){if(e.isError)return`Local weather tool failed: ${formatToolOutput(e.output)}`;if(isWeatherPayload(e.output))return`Used local weather tool for ${e.output.city}: ${e.output.condition}, ${e.output.temperatureF}F. ${e.output.summary}`;let n=getLastUserPromptText(t)??`Hello from Ash`;return`Used ${e.toolName} for "${n}": ${formatToolOutput(e.output)}`}function createToolCallGenerateResult(e){return{content:[{input:JSON.stringify(e.input),toolCallId:e.toolCallId,toolName:e.toolName,type:`tool-call`}],finishReason:`tool-calls`,response:{id:`bootstrap-response`,modelId:e.modelId,timestamp:new Date(`2026-03-16T00:00:00.000Z`)},usage:{inputTokens:{cacheRead:0,cacheWrite:0,noCache:e.inputTokens,total:e.inputTokens},outputTokens:{reasoning:0,text:e.outputTokens,total:e.outputTokens}},warnings:[]}}function getAvailableTools(e){return(e.tools??[]).flatMap(e=>e.type===`function`?[{description:e.description,inputSchema:`inputSchema`in e?e.inputSchema:void 0,name:e.name}]:[])}function getLastAuthoredToolResult(e){for(let t of[...e].reverse()){if(t.role===`user`)return null;if(!(t.role!==`tool`&&t.role!==`assistant`)){for(let e of[...t.content].reverse())if(!(typeof e==`string`||e.type!==`tool-result`)&&e.toolName!==LOAD_SKILL_TOOL_NAME)return{isError:e.output.type===`error-json`||e.output.type===`error-text`||e.output.type===`execution-denied`,output:e.output.type===`execution-denied`?{reason:e.output.reason??null,type:e.output.type}:e.output.value,toolCallId:e.toolCallId,toolName:e.toolName}}}return null}function findNextExplicitToolAfterResult(e){let t=getLastUserPromptText(e.prompt);if(t===null)return null;let n=normalizeText(t),r=n.indexOf(normalizeText(e.previousToolName));return r<0?null:e.tools.filter(t=>t.name!==e.previousToolName).flatMap(e=>{let t=n.indexOf(normalizeText(e.name),r+1);return t<0?[]:[{index:t,tool:e}]}).sort((e,t)=>e.index-t.index)[0]?.tool??null}function createFollowUpToolInput(e){return isRecord(e)&&typeof e.stepKey==`string`?{stepKey:e.stepKey}:null}function getSystemPromptLabels(e){let t=e.filter(e=>e.role===`system`);if(t.length===0)return[];let n=t.flatMap(e=>{let t=getPromptContentText(e.content);if(t.startsWith(`Available skills
2
2
  `))return[];let n=t.split(`
3
- `).map(e=>e.trim()).filter(e=>e.length>0),r=[];for(let e of n){if(e===BOOTSTRAP_RUNTIME_SYSTEM_PROMPT||e===`Available skills`)continue;let t=/^System \((.+)\)$/.exec(e);if(t?.[1]){r.push(t[1]);continue}let n=/^Skill \((.+)\)$/.exec(e);n?.[1]&&r.push(n[1])}if(r.length>0)return r;let a=n.find(e=>e!==BOOTSTRAP_RUNTIME_SYSTEM_PROMPT&&e!==`Available skills`);return a===void 0?[]:[a]});return[...new Set(n)]}function getActivatedSkillIds(e){return e.filter(e=>e.role===`system`).flatMap(e=>getPromptContentText(e.content).split(`
4
- `).map(e=>e.trim()).flatMap(e=>{let t=/^Skill \((.+)\)$/.exec(e);return t?.[1]===void 0?[]:[t[1]]}))}function getAvailableSkills(e){let t=new Map;for(let n of e){if(n.role!==`system`)continue;let e=getPromptContentText(n.content);if(e.startsWith(`Available skills
5
- `))for(let n of e.split(`
6
- `).slice(1)){let e=parseAvailableSkill(n);e!==null&&t.set(e.name,e)}}return[...t.values()]}function parseAvailableSkill(e){let t=/^- (.+?): (.+?)(?: \(path: (.+)\))?$/.exec(e.trim());return t?.[1]&&t[2]?{description:t[2],name:t[1]}:null}function findRelevantTool(e,t){let n=normalizeText(t);return/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(n)?e.find(e=>/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(normalizeText(`${e.name} ${e.description??``}`)))??null:null}function findRelevantCodeModeHostTool(e,t){return e===void 0?null:findRelevantTool(parseCodeModeHostTools(e),t)?.name??null}function parseCodeModeHostTools(e){let t=[],n;for(let r of e.split(`
7
- `)){let e=/^\s*\/\*\*\s*(.*?)\s*\*\/\s*$/u.exec(r);if(e?.[1]!==void 0){n=e[1];continue}let i=/^\s*(?:([$A-Z_a-z][$\w]*)|(["'])(.*?)\2)\s*:\s*\(input:/u.exec(r),a=i?.[1]??i?.[3];a!==void 0&&(t.push({description:n,name:a}),n=void 0)}return t}function formatCodeModeToolAccess(e){return/^[$A-Z_a-z][$\w]*$/u.test(e)?`.${e}`:`[${JSON.stringify(e)}]`}function findRelevantSkill(e,t){let n=normalizeText(t);for(let t of e)if(n.includes(normalizeText(t.name)))return t;return/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(n)?e.find(e=>/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(normalizeText(`${e.name} ${e.description}`)))??null:null}function normalizeText(e){return e.toLowerCase().replace(/[^a-z0-9]+/gu,` `).trim()}function resolveSystemProbe(e){let t=e.filter(e=>e.role===`system`).map(e=>getPromptContentText(e.content)).join(`
8
- `);return/hmr-probe:\s*([^\n]+)/iu.exec(t)?.[1]?.trim()||null}function resolveWeatherCity(e){let t=/"city"\s*:\s*"([^"]+)"/u.exec(e);return t?.[1]?t[1].trim():(/\b(?:in|for)\s+([A-Za-z][A-Za-z\s.-]*?)(?:[?.!,]|$)/u.exec(e)??/\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b/u.exec(e))?.[1]?.trim()||`Brooklyn`}function isWeatherPayload(e){return bootstrapWeatherPayloadSchema.safeParse(e).success}function formatToolOutput(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return String(e)}}export{ASH_MOCK_AUTHORED_MODELS_ENV,createMockAuthoredRuntimeModel,resolveMockAuthoredRuntimeModel,shouldMockAuthoredRuntimeModels};
3
+ `).map(e=>e.trim()).filter(e=>e.length>0),r=[];for(let e of n){if(e===BOOTSTRAP_RUNTIME_SYSTEM_PROMPT||e===`Available skills`)continue;let t=/^System \((.+)\)$/.exec(e);if(t?.[1]){r.push(t[1]);continue}let n=/^Skill \((.+)\)$/.exec(e);n?.[1]&&r.push(n[1])}if(r.length>0)return r;let a=n.find(e=>e!==BOOTSTRAP_RUNTIME_SYSTEM_PROMPT&&e!==`Available skills`);return a===void 0?[]:[a]});return[...new Set(n)]}function getToolInputPropertyNames(e){return!isRecord(e)||!isRecord(e.properties)?[]:Object.keys(e.properties)}function findRelevantTool(e,t){let n=normalizeText(t),r=e.find(e=>n.includes(normalizeText(e.name)));return r===void 0?/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(n)?e.find(e=>/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(normalizeText(`${e.name} ${e.description??``}`)))??null:null:r}function findRelevantCodeModeHostTool(e,t){return e===void 0?null:findRelevantTool(parseCodeModeHostTools(e),t)?.name??null}function parseCodeModeHostTools(e){let t=[],n;for(let r of e.split(`
4
+ `)){let e=/^\s*\/\*\*\s*(.*?)\s*\*\/\s*$/u.exec(r);if(e?.[1]!==void 0){n=e[1];continue}let i=/^\s*(?:([$A-Z_a-z][$\w]*)|(["'])(.*?)\2)\s*:\s*\(input:/u.exec(r),a=i?.[1]??i?.[3];a!==void 0&&(t.push({description:n,name:a}),n=void 0)}return t}function formatCodeModeToolAccess(e){return/^[$A-Z_a-z][$\w]*$/u.test(e)?`.${e}`:`[${JSON.stringify(e)}]`}function normalizeText(e){return e.toLowerCase().replace(/[^a-z0-9]+/gu,` `).trim()}function createToolCallId(e){return`call_${e.toLowerCase().replace(/[^a-z0-9]+/gu,`_`).replace(/^_+|_+$/gu,``)||`tool`}`}function resolveSystemProbe(e){let t=e.filter(e=>e.role===`system`).map(e=>getPromptContentText(e.content)).join(`
5
+ `);return/hmr-probe:\s*([^\n]+)/iu.exec(t)?.[1]?.trim()||null}function resolveWeatherCity(e){let t=/"city"\s*:\s*"([^"]+)"/u.exec(e);return t?.[1]?t[1].trim():(/\b(?:in|for)\s+([A-Za-z][A-Za-z\s.-]*?)(?:[?.!,]|$)/u.exec(e)??/\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)\b/u.exec(e))?.[1]?.trim()||`Brooklyn`}function resolveLookupTopic(e){return/\btopic\s+['"]?([A-Za-z0-9_.-]+)['"]?/u.exec(e)?.[1]??`demo`}function isWeatherPayload(e){return bootstrapWeatherPayloadSchema.safeParse(e).success}function isRecord(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function formatToolOutput(e){if(typeof e==`string`)return e;try{return JSON.stringify(e)}catch{return String(e)}}export{ASH_MOCK_AUTHORED_MODELS_ENV,createMockAuthoredRuntimeModel,resolveMockAuthoredRuntimeModel,shouldMockAuthoredRuntimeModels};
@@ -0,0 +1,9 @@
1
+ import type { BootstrapPrompt } from "#runtime/agent/bootstrap-model-utils.js";
2
+ interface AvailableBootstrapSkill {
3
+ readonly description: string;
4
+ readonly name: string;
5
+ }
6
+ export declare function getAvailableSkills(prompt: BootstrapPrompt): AvailableBootstrapSkill[];
7
+ export declare function findRelevantSkill(skills: readonly AvailableBootstrapSkill[], message: string): AvailableBootstrapSkill | null;
8
+ export declare function getActivatedSkillIds(prompt: BootstrapPrompt): string[];
9
+ export {};
@@ -0,0 +1,4 @@
1
+ import{getPromptContentText}from"#runtime/agent/bootstrap-model-utils.js";function getAvailableSkills(t){let n=new Map;for(let r of t){if(r.role!==`system`)continue;let t=getPromptContentText(r.content);if(t.startsWith(`Available skills
2
+ `))for(let e of t.split(`
3
+ `).slice(1)){let t=parseAvailableSkill(e);t!==null&&n.set(t.name,t)}}return[...n.values()]}function findRelevantSkill(e,t){let n=normalizeText(t);for(let t of e)if(n.includes(normalizeText(t.name)))return t;return/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(n)?e.find(e=>/\b(forecast|temperature|weather|wind|rain|snow)\b/u.test(normalizeText(`${e.name} ${e.description}`)))??null:null}function getActivatedSkillIds(t){return t.filter(e=>e.role===`system`).flatMap(t=>getPromptContentText(t.content).split(`
4
+ `).map(e=>e.trim()).flatMap(e=>{let t=/^Skill \((.+)\)$/.exec(e);return t?.[1]===void 0?[]:[t[1]]}))}function parseAvailableSkill(e){let t=/^- (.+?): (.+?)(?: \(path: (.+)\))?$/.exec(e.trim());return t?.[1]&&t[2]?{description:t[2],name:t[1]}:null}function normalizeText(e){return e.toLowerCase().replace(/[^a-z0-9]+/gu,` `).trim()}export{findRelevantSkill,getActivatedSkillIds,getAvailableSkills};
@@ -0,0 +1,73 @@
1
+ import "#internal/workflow/builtins.js";
2
+ /**
3
+ * Maximum byte size for an attribute value on a workflow run.
4
+ *
5
+ * Local mirror of `ATTRIBUTE_VALUE_MAX_BYTES` from `@workflow/world`
6
+ * (source of truth: `packages/world/src/attributes.ts` in the workflow
7
+ * repo). The value is duplicated rather than imported because
8
+ * `@workflow/core` — the only workflow surface bundled into the
9
+ * workflow body — does not re-export it, and pulling `@workflow/world`
10
+ * into the body bundle would drag in the full zod attribute validator
11
+ * (the same reason `workflow-core-shim.ts` skips runtime validation in
12
+ * its `experimental_setAttributes`).
13
+ *
14
+ * Strings emitted through {@link setAshAttributes} are truncated to this
15
+ * byte count before they reach the runtime so the validator never
16
+ * rejects a tag for length alone.
17
+ *
18
+ * Drift is conservative-by-construction: if workflow LOWERS the limit,
19
+ * over-long values are rejected and `setAshAttributes` swallows the
20
+ * failure (warn-once-per-process) — dashboards see a missing tag, never
21
+ * a broken agent; if workflow RAISES it, titles are merely shorter than
22
+ * necessary. `emit.drift.test.ts` asserts equality against the real
23
+ * `@workflow/world` export (a devDependency) so CI fails loudly the day
24
+ * the constants diverge.
25
+ */
26
+ export declare const ASH_ATTRIBUTE_VALUE_MAX_BYTES = 256;
27
+ /**
28
+ * Attribute value the caller wants to write. `undefined` values are
29
+ * stripped before the runtime call; numbers are stringified; strings
30
+ * are truncated to {@link ASH_ATTRIBUTE_VALUE_MAX_BYTES}.
31
+ */
32
+ export type AshAttributeValue = string | number | undefined;
33
+ /**
34
+ * Truncates a string so its UTF-8 byte length is at most `maxBytes`
35
+ * without splitting a multi-byte character.
36
+ *
37
+ * The workflow runtime measures attribute values in UTF-8 bytes, not
38
+ * code units, so `value.slice(0, maxBytes)` is not safe — a JS string
39
+ * with two-byte characters (e.g. emoji surrogate pairs) can serialize
40
+ * to twice as many bytes as code units. We re-encode the truncated
41
+ * candidate after each drop and shrink one code unit at a time when
42
+ * the candidate's last character straddles the byte budget.
43
+ */
44
+ export declare function truncateForTag(value: string, maxBytes?: number): string;
45
+ /**
46
+ * Writes a batch of Ash-owned attributes to the active workflow run.
47
+ *
48
+ * Reserved-namespace contract:
49
+ * - All keys must use the `$ash.` prefix (the workflow runtime would
50
+ * otherwise reject them as user-space writes into the reserved `$`
51
+ * namespace).
52
+ * - The call always opts in via `{ allowReservedAttributes: true }`
53
+ * on behalf of the framework — authored code never calls this helper
54
+ * directly.
55
+ *
56
+ * Value normalization:
57
+ * - `undefined` entries are dropped so callers can build attribute
58
+ * maps with optional fields (`$ash.subagent` is only present on
59
+ * subagent roots, for example).
60
+ * - Numbers are stringified (the runtime stores all values as strings).
61
+ * - Strings are truncated to {@link ASH_ATTRIBUTE_VALUE_MAX_BYTES} via
62
+ * {@link truncateForTag} so a long free-form value (e.g. `$ash.title`)
63
+ * can never trip the runtime's per-value byte budget.
64
+ *
65
+ * Failure policy: tag writes are observability metadata, not load-bearing
66
+ * state. A failure inside the runtime (transient network, schema bug,
67
+ * missing world adapter) is logged once per process and then swallowed
68
+ * so the Ash session it tagged is unaffected.
69
+ *
70
+ * Must be called from inside a `"use workflow"` or `"use step"` body —
71
+ * the runtime throws a `FatalError` outside those contexts.
72
+ */
73
+ export declare function setAshAttributes(attrs: Record<string, AshAttributeValue>): Promise<void>;
@@ -0,0 +1 @@
1
+ import"#internal/workflow/builtins.js";const ASH_ATTRIBUTE_VALUE_MAX_BYTES=256;let WARNED_ABOUT_TAG_FAILURE=!1;function truncateForTag(e,t=256){if(t<=0)return``;let n=new TextEncoder;if(n.encode(e).length<=t)return e;let r=e.length;for(;r>0;){let i=e.charCodeAt(r-1);if(i>=55296&&i<=56319){--r;continue}let a=e.slice(0,r);if(n.encode(a).length<=t)return a;--r}return``}async function setAshAttributes(e){let n={};for(let[t,r]of Object.entries(e))r!==void 0&&(n[t]=truncateForTag(typeof r==`number`?String(r):r));if(Object.keys(n).length!==0)try{let{experimental_setAttributes:e}=await import(`#compiled/@workflow/core/index.js`);await e(n,{allowReservedAttributes:!0})}catch(e){WARNED_ABOUT_TAG_FAILURE||(WARNED_ABOUT_TAG_FAILURE=!0,console.warn(`[ash] setAshAttributes failed; suppressing further warnings this process.`,{keys:Object.keys(n),error:e.message}))}}export{ASH_ATTRIBUTE_VALUE_MAX_BYTES,setAshAttributes,truncateForTag};
@@ -1 +1 @@
1
- import{getAdapterKind}from"#channel/adapter.js";import{SUBAGENT_ADAPTER}from"#execution/subagent-adapter.js";import{SCHEDULE_ADAPTER}from"#channel/schedule.js";import{HTTP_ADAPTER}from"#channel/http.js";import{RuntimeRegistryError}from"#internal/runtime-registry.js";const FRAMEWORK_ADAPTERS=[HTTP_ADAPTER,SUBAGENT_ADAPTER,SCHEDULE_ADAPTER],ADAPTER_NON_EVENT_FIELDS=new Set([`kind`,`state`,`deliver`,`createAdapterContext`,`fetchFile`]);function createRuntimeAdapterRegistry(e){let t=new Map,n=new Set;for(let e of FRAMEWORK_ADAPTERS){let r=requireAdapterKind(e);n.add(r),t.set(r,e)}for(let r of e.channels){if(r.adapter===void 0)continue;let e={logicalPath:r.logicalPath,sourceId:r.sourceId},i=r.adapter,a=requireAdapterKind(i,e);if(n.has(a)){if(carriesAdapterBehavior(i))throw new RuntimeRegistryError(`adapter`,`Channel adapter kind "${a}" is reserved by the framework. A route-declared adapter may share a framework kind only as a pass-through with no \`deliver\` hook, event handlers, \`attachments\` resolver, or \`createAdapterContext\` factory. Use a custom \`kind\` to add channel-specific behavior.`,{...e,entryName:a});continue}t.set(a,i)}return{adaptersByKind:t}}function deserializeRuntimeAdapter(e,t){let n=t,r=e.adaptersByKind.get(n.kind);if(r===void 0)throw Error(`Unknown adapter kind: "${n.kind}". Declare the adapter on the route that starts this session so the runtime can rehydrate it.`);return{...r,state:n.state}}function requireAdapterKind(t,n){let r=getAdapterKind(t);if(typeof r!=`string`||r.length===0)throw new RuntimeRegistryError(`adapter`,"Adapters must declare a non-empty `kind` field.",{entryName:`unknown`,logicalPath:n?.logicalPath,sourceId:n?.sourceId});return r}function carriesAdapterBehavior(e){if(e.deliver!==void 0||e.fetchFile!==void 0||e.createAdapterContext!==void 0)return!0;for(let[t,n]of Object.entries(e))if(!ADAPTER_NON_EVENT_FIELDS.has(t)&&typeof n==`function`)return!0;return!1}export{createRuntimeAdapterRegistry,deserializeRuntimeAdapter};
1
+ import{getAdapterKind}from"#channel/adapter.js";import{SUBAGENT_ADAPTER}from"#execution/subagent-adapter.js";import{SCHEDULE_ADAPTER}from"#channel/schedule.js";import{HTTP_ADAPTER}from"#channel/http.js";import{RuntimeRegistryError}from"#internal/runtime-registry.js";const FRAMEWORK_ADAPTERS=[HTTP_ADAPTER,SUBAGENT_ADAPTER,SCHEDULE_ADAPTER],ADAPTER_NON_EVENT_FIELDS=new Set([`kind`,`state`,`deliver`,`createAdapterContext`,`fetchFile`,`instrumentation`]);function createRuntimeAdapterRegistry(e){let t=new Map,n=new Set;for(let e of FRAMEWORK_ADAPTERS){let r=requireAdapterKind(e);n.add(r),t.set(r,e)}for(let r of e.channels){if(r.adapter===void 0)continue;let e={logicalPath:r.logicalPath,sourceId:r.sourceId},i=r.adapter,a=requireAdapterKind(i,e);if(n.has(a)){if(carriesAdapterBehavior(i))throw new RuntimeRegistryError(`adapter`,`Channel adapter kind "${a}" is reserved by the framework. A route-declared adapter may share a framework kind only as a pass-through with no \`deliver\` hook, event handlers, \`attachments\` resolver, or \`createAdapterContext\` factory. Use a custom \`kind\` to add channel-specific behavior.`,{...e,entryName:a});continue}t.set(a,i)}return{adaptersByKind:t}}function deserializeRuntimeAdapter(e,t){let n=t,r=e.adaptersByKind.get(n.kind);if(r===void 0)throw Error(`Unknown adapter kind: "${n.kind}". Declare the adapter on the route that starts this session so the runtime can rehydrate it.`);return{...r,state:n.state}}function requireAdapterKind(t,n){let r=getAdapterKind(t);if(typeof r!=`string`||r.length===0)throw new RuntimeRegistryError(`adapter`,"Adapters must declare a non-empty `kind` field.",{entryName:`unknown`,logicalPath:n?.logicalPath,sourceId:n?.sourceId});return r}function carriesAdapterBehavior(e){if(e.deliver!==void 0||e.fetchFile!==void 0||e.createAdapterContext!==void 0)return!0;for(let[t,n]of Object.entries(e))if(!ADAPTER_NON_EVENT_FIELDS.has(t)&&typeof n==`function`)return!0;return!1}export{createRuntimeAdapterRegistry,deserializeRuntimeAdapter};
@@ -1 +1 @@
1
- import{isObject}from"#shared/guards.js";import{contextStorage}from"#context/container.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";import{createMCPClient}from"#compiled/@ai-sdk/mcp/index.js";import{readCachedToken,writeCachedToken}from"#runtime/connections/authorization-tokens.js";var McpConnectionClient=class{#e;#t;#n;#r;#i;constructor(e){this.#i=e}async connect(){if(this.#t!==void 0)return this.#t;if(this.#e!==void 0)return this.#e;this.#e=this.#a();try{return this.#t=await this.#e,this.#t}catch(e){throw this.#e=void 0,e}}async#a(){let e=await resolveHeaders(this.#i),t=this.#i.url;try{return await createMCPClient({transport:{type:`http`,url:t,headers:e}})}catch(n){if(!isMcpHttpFallbackRetryableError(n))throw n;return await createMCPClient({transport:{type:`sse`,url:t,headers:e}})}}async getToolMetadata(){return(await this.#o()).metadata}async getTools(){return(await this.#o()).tools}async executeTool(e,t){let{tools:n}=await this.#o(),r=n[e];if(r?.execute===void 0)throw Error(`Tool "${e}" not found in connection "${this.#i.connectionName}".`);return r.execute(t,{})}async#o(){if(this.#r!==void 0)return this.#r;if(this.#n!==void 0)return this.#n;this.#n=this.#s();try{return this.#r=await this.#n,this.#r}catch(e){throw this.#n=void 0,e}}async#s(){let e=await this.connect(),t=await e.listTools(),n=this.#i.tools,r=n===void 0?t.tools:t.tools.filter(e=>passesToolFilter(e.name,n)),i=e.toolsFromDefinitions({tools:r});return{metadata:r.map(e=>({annotations:e.annotations,description:e.description??``,inputSchema:e.inputSchema??{},name:e.name})),tools:i}}async close(){this.#t!==void 0&&(await this.#t.close(),this.#t=void 0),this.#e=void 0,this.#n=void 0,this.#r=void 0}};function isMcpHttpFallbackRetryableError(e){let t=readHttpStatus(e);return t===400||t===404||t===405}function readHttpStatus(t){for(let n of walkErrorChain(t)){if(!isObject(n))continue;let t=readStatusField(n);if(t!==void 0)return t;let r=n.response;if(isObject(r)){let e=readStatusField(r);if(e!==void 0)return e}if(typeof n.message==`string`){let e=/\bHTTP\s+(\d{3})\b/u.exec(n.message);if(e?.[1]!==void 0)return Number(e[1])}}}function readStatusField(e){if(typeof e.status==`number`)return e.status;if(typeof e.statusCode==`number`)return e.statusCode}function*walkErrorChain(t){let n=t,r=new Set;for(;n!=null&&!r.has(n);){if(r.add(n),yield n,!isObject(n)||!(`cause`in n))return;n=n.cause}}function passesToolFilter(e,t){return t===void 0?!0:`allow`in t?t.allow.includes(e):!t.block.includes(e)}async function resolveHeaders(e){let t={};if(e.authorization!==void 0&&(t.Authorization=`Bearer ${(await resolveToken(e)).token}`),e.headers!==void 0){let n=await resolveHeadersDefinition(e.headers);for(let[r,i]of Object.entries(n)){if(e.authorization!==void 0&&r.toLowerCase()===`authorization`)throw Error(`Connection "${e.connectionName}" headers must not include an "Authorization" key when "authorization" is also provided.`);t[r]=i}}return t}async function resolveToken(e){if(e.authorization===void 0)throw Error(`Connection "${e.connectionName}" does not define authorization.`);let i=contextStorage.getStore(),a=resolveConnectionPrincipal(e.connectionName,e.authorization,i),o={url:e.url};if(i===void 0)return await e.authorization.getToken({principal:a,connection:o});let s=principalKey(a),c=readCachedToken(i,e.connectionName,s);if(c!==void 0)return c;let l=await e.authorization.getToken({principal:a,connection:o});return writeCachedToken(i,e.connectionName,s,l),l}async function resolveHeadersDefinition(e){if(typeof e==`function`)return await e();let t={},n=Object.entries(e);for(let[e,r]of n)t[e]=await resolveHeaderValue(r);return t}async function resolveHeaderValue(e){return typeof e==`function`?await e():await e}export{McpConnectionClient,passesToolFilter,resolveHeaders};
1
+ import{isObject}from"#shared/guards.js";import{contextStorage}from"#context/container.js";import{createMCPClient}from"#compiled/@ai-sdk/mcp/index.js";import{readCachedToken,writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";var McpConnectionClient=class{#e;#t;#n;#r;#i;constructor(e){this.#i=e}async connect(){if(this.#t!==void 0)return this.#t;if(this.#e!==void 0)return this.#e;this.#e=this.#a();try{return this.#t=await this.#e,this.#t}catch(e){throw this.#e=void 0,e}}async#a(){let e=await resolveHeaders(this.#i),t=this.#i.url;try{return await createMCPClient({transport:{type:`http`,url:t,headers:e}})}catch(r){if(!isMcpHttpFallbackRetryableError(r))throw r;return await createMCPClient({transport:{type:`sse`,url:t,headers:e}})}}async getToolMetadata(){return(await this.#o()).metadata}async getTools(){return(await this.#o()).tools}async executeTool(e,t){let{tools:n}=await this.#o(),r=n[e];if(r?.execute===void 0)throw Error(`Tool "${e}" not found in connection "${this.#i.connectionName}".`);return r.execute(t,{})}async#o(){if(this.#r!==void 0)return this.#r;if(this.#n!==void 0)return this.#n;this.#n=this.#s();try{return this.#r=await this.#n,this.#r}catch(e){throw this.#n=void 0,e}}async#s(){let e=await this.connect(),t=await e.listTools(),n=this.#i.tools,r=n===void 0?t.tools:t.tools.filter(e=>passesToolFilter(e.name,n)),i=e.toolsFromDefinitions({tools:r});return{metadata:r.map(e=>({annotations:e.annotations,description:e.description??``,inputSchema:e.inputSchema??{},name:e.name})),tools:i}}async close(){this.#t!==void 0&&(await this.#t.close(),this.#t=void 0),this.#e=void 0,this.#n=void 0,this.#r=void 0}};function isMcpHttpFallbackRetryableError(e){let t=readHttpStatus(e);return t===400||t===404||t===405}function readHttpStatus(t){for(let n of walkErrorChain(t)){if(!isObject(n))continue;let t=readStatusField(n);if(t!==void 0)return t;let r=n.response;if(isObject(r)){let e=readStatusField(r);if(e!==void 0)return e}if(typeof n.message==`string`){let e=/\bHTTP\s+(\d{3})\b/u.exec(n.message);if(e?.[1]!==void 0)return Number(e[1])}}}function readStatusField(e){if(typeof e.status==`number`)return e.status;if(typeof e.statusCode==`number`)return e.statusCode}function*walkErrorChain(t){let n=t,r=new Set;for(;n!=null&&!r.has(n);){if(r.add(n),yield n,!isObject(n)||!(`cause`in n))return;n=n.cause}}function passesToolFilter(e,t){return t===void 0?!0:`allow`in t?t.allow.includes(e):!t.block.includes(e)}async function resolveHeaders(e){let t={};if(e.authorization!==void 0&&(t.Authorization=`Bearer ${(await resolveToken(e)).token}`),e.headers!==void 0){let n=await resolveHeadersDefinition(e.headers);for(let[r,i]of Object.entries(n)){if(e.authorization!==void 0&&r.toLowerCase()===`authorization`)throw Error(`Connection "${e.connectionName}" headers must not include an "Authorization" key when "authorization" is also provided.`);t[r]=i}}return t}async function resolveToken(e){if(e.authorization===void 0)throw Error(`Connection "${e.connectionName}" does not define authorization.`);let n=contextStorage.getStore(),a=resolveConnectionPrincipal(e.connectionName,e.authorization,n),o={url:e.url};if(n===void 0)return await e.authorization.getToken({principal:a,connection:o});let s=principalKey(a),c=readCachedToken(n,e.connectionName,s);if(c!==void 0)return c;let l=await e.authorization.getToken({principal:a,connection:o});return writeCachedToken(n,e.connectionName,s,l),l}async function resolveHeadersDefinition(e){if(typeof e==`function`)return await e();let t={},n=Object.entries(e);for(let[e,r]of n)t[e]=await resolveHeaderValue(r);return t}async function resolveHeaderValue(e){return typeof e==`function`?await e():await e}export{McpConnectionClient,passesToolFilter,resolveHeaders};
@@ -1,9 +1,11 @@
1
1
  import type { CodeModeInterrupt, CodeModeInterruptExecutionContext, CodeModeInterruptPayload } from "#shared/code-mode.js";
2
+ import type { AuthorizationChallenge } from "#harness/authorization.js";
2
3
  import type { JsonValue } from "#public/types/json.js";
3
4
  export declare const CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND = "ash.connection-auth";
4
5
  export interface CodeModeConnectionAuthPayload extends CodeModeInterruptPayload {
5
6
  readonly kind: typeof CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND;
6
7
  readonly args: JsonValue;
8
+ readonly challenges?: readonly AuthorizationChallenge[];
7
9
  readonly connectionName: string;
8
10
  readonly toolName: string;
9
11
  }
@@ -0,0 +1,34 @@
1
+ import type { ResolvedDynamicToolResolver } from "#runtime/types.js";
2
+ import type { DynamicToolEvents } from "#shared/dynamic-tool-definition.js";
3
+ import type { ModelMessage } from "ai";
4
+ interface ConnectionSearchResultItem {
5
+ readonly connection: string;
6
+ readonly description: string;
7
+ readonly error?: string;
8
+ readonly inputSchema?: Record<string, unknown>;
9
+ readonly needsAuthorization?: boolean;
10
+ readonly tool?: string;
11
+ readonly qualifiedName?: string;
12
+ }
13
+ /**
14
+ * Extracts connection search results from conversation history.
15
+ * Scans tool-result messages for `connection_search` results and
16
+ * returns deduplicated tool metadata (latest result wins per qualifiedName).
17
+ */
18
+ export declare function extractDiscoveredTools(messages: readonly ModelMessage[]): ConnectionSearchResultItem[];
19
+ /**
20
+ * Creates the connection search dynamic tool resolver events.
21
+ *
22
+ * The resolver subscribes to `step.started` so it re-derives the tool
23
+ * set from conversation history on every step. After compaction, old
24
+ * `connection_search` results disappear from messages and discovered
25
+ * tools naturally drop from the toolset — no `onCompact` hook needed.
26
+ */
27
+ export declare function createConnectionSearchEvents(): DynamicToolEvents;
28
+ /**
29
+ * Creates a `ResolvedDynamicToolResolver` for the framework connection
30
+ * search tool. Used by graph resolution to register alongside authored
31
+ * dynamic tool resolvers.
32
+ */
33
+ export declare function createConnectionSearchResolver(): ResolvedDynamicToolResolver;
34
+ export {};