@lunora/agent 1.0.0-alpha.1 → 1.0.0-alpha.100

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 (92) hide show
  1. package/README.md +2 -0
  2. package/dist/channels.d.mts +33 -21
  3. package/dist/channels.d.ts +33 -21
  4. package/dist/channels.mjs +1 -181
  5. package/dist/component.d.mts +61 -56
  6. package/dist/component.d.ts +61 -56
  7. package/dist/component.mjs +2 -407
  8. package/dist/inbound.d.mts +23 -16
  9. package/dist/inbound.d.ts +23 -16
  10. package/dist/inbound.mjs +1 -32
  11. package/dist/index.d.mts +542 -409
  12. package/dist/index.d.ts +542 -409
  13. package/dist/index.mjs +1 -18
  14. package/dist/naming.d.mts +23 -10
  15. package/dist/naming.d.ts +23 -10
  16. package/dist/naming.mjs +1 -8
  17. package/dist/packem_shared/AGENT_MODULE-M4D1EejI.mjs +1 -0
  18. package/dist/packem_shared/VoiceSessionDO-D238XrlM.mjs +1 -0
  19. package/dist/packem_shared/adaptMcpResult-CVlq-_TO.mjs +2 -0
  20. package/dist/packem_shared/agent-loop-CE_Q6bk9.mjs +3 -0
  21. package/dist/packem_shared/agentAsTool-CgZb6ycK.mjs +1 -0
  22. package/dist/packem_shared/base64-5eyBfWO3.mjs +1 -0
  23. package/dist/packem_shared/braintrustTelemetry-Byne8uPK.mjs +1 -0
  24. package/dist/packem_shared/branch-marker-boZ00zmk.mjs +1 -0
  25. package/dist/packem_shared/buildModelMessages-Y4tGo1T9.mjs +5 -0
  26. package/dist/packem_shared/codeTool-BQb-K3my.mjs +1 -0
  27. package/dist/packem_shared/collectAgenticMemoryTools-BeMWT2qt.mjs +1 -0
  28. package/dist/packem_shared/combineTelemetry-DgE9W9G8.mjs +1 -0
  29. package/dist/packem_shared/common-CJSjtsfv.mjs +1 -0
  30. package/dist/packem_shared/compileAgentWorkflow-DVHi7NYD.mjs +1 -0
  31. package/dist/packem_shared/component-shared-G8ngerkU.mjs +1 -0
  32. package/dist/packem_shared/consoleTelemetry-BZY3Y-Ly.mjs +1 -0
  33. package/dist/packem_shared/createAgentContext-BvU9LP43.mjs +1 -0
  34. package/dist/packem_shared/createAgentGenerate-BOILDUZj.mjs +3 -0
  35. package/dist/packem_shared/createDispatchRunner-BxtGxAQi-DXXP7SxZ.mjs +1 -0
  36. package/dist/packem_shared/defineAgent-KgH-gwYg.mjs +3 -0
  37. package/dist/packem_shared/defineSkill-DGGvDWNq.mjs +1 -0
  38. package/dist/packem_shared/fnv1a-BNN96GYb.mjs +1 -0
  39. package/dist/packem_shared/functionTool-D3CP4f8-.mjs +1 -0
  40. package/dist/packem_shared/in-flight-calls-GH1A_1P3.mjs +1 -0
  41. package/dist/packem_shared/normalizeEntityName-k3fjicAG.mjs +2 -0
  42. package/dist/packem_shared/otlpTelemetry-ByqrB-72.mjs +1 -0
  43. package/dist/packem_shared/positive-integer-ztHqqpBk.mjs +1 -0
  44. package/dist/packem_shared/runAgentLoop-Drn1NcCc.mjs +1 -0
  45. package/dist/packem_shared/runVoiceTurn-BtcmyaBo.mjs +1 -0
  46. package/dist/packem_shared/sandboxComponent-BtOkUYpY.mjs +5 -0
  47. package/dist/packem_shared/sentryTelemetry-BHeydiAB.mjs +1 -0
  48. package/dist/packem_shared/tool-output-Cmk3JWMG.mjs +1 -0
  49. package/dist/packem_shared/types.d-B8WZl1rU.d.mts +1347 -0
  50. package/dist/packem_shared/types.d-B8WZl1rU.d.ts +1347 -0
  51. package/dist/packem_shared/voice-turn-BRj_it54.mjs +1 -0
  52. package/dist/reply.d.mts +40 -0
  53. package/dist/reply.d.ts +40 -0
  54. package/dist/reply.mjs +1 -0
  55. package/dist/sandbox.d.mts +130 -110
  56. package/dist/sandbox.d.ts +130 -110
  57. package/dist/sandbox.mjs +1 -109
  58. package/dist/skill-markdown.d.mts +36 -0
  59. package/dist/skill-markdown.d.ts +36 -0
  60. package/dist/skill-markdown.mjs +1 -0
  61. package/dist/telemetry/index.d.mts +319 -96
  62. package/dist/telemetry/index.d.ts +319 -96
  63. package/dist/telemetry/index.mjs +1 -4
  64. package/package.json +18 -9
  65. package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +0 -24
  66. package/dist/packem_shared/VoiceSessionDO-DLoXsHGF.mjs +0 -297
  67. package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +0 -65
  68. package/dist/packem_shared/agentAsTool-Dt8NlU6k.mjs +0 -94
  69. package/dist/packem_shared/base64-BVwtgRJV.mjs +0 -18
  70. package/dist/packem_shared/braintrustTelemetry-wuGDErob.mjs +0 -47
  71. package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +0 -69
  72. package/dist/packem_shared/codeTool-CjgJOC9t.mjs +0 -122
  73. package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +0 -97
  74. package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +0 -43
  75. package/dist/packem_shared/common-DAeFCot5.mjs +0 -61
  76. package/dist/packem_shared/compileAgentWorkflow-BxJjHgtD.mjs +0 -55
  77. package/dist/packem_shared/consoleTelemetry-z2MiP1jt.mjs +0 -93
  78. package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +0 -50
  79. package/dist/packem_shared/createAgentGenerate-BQv9YJ01.mjs +0 -192
  80. package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +0 -69
  81. package/dist/packem_shared/defineAgent-D6maSbVc.mjs +0 -148
  82. package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +0 -22
  83. package/dist/packem_shared/functionTool-D6lCa2jB.mjs +0 -20
  84. package/dist/packem_shared/graph-component-aoUwO-f0.mjs +0 -216
  85. package/dist/packem_shared/memory-D4FPcBsX.mjs +0 -12
  86. package/dist/packem_shared/normalizeEntityName-CyEEWFkR.mjs +0 -3
  87. package/dist/packem_shared/runAgentLoop-Dhg4ZNvw.mjs +0 -493
  88. package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +0 -211
  89. package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +0 -194
  90. package/dist/packem_shared/sentryTelemetry-CgqFJyLO.mjs +0 -36
  91. package/dist/packem_shared/types.d-BWG0uUtX.d.mts +0 -1015
  92. package/dist/packem_shared/types.d-BWG0uUtX.d.ts +0 -1015
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as AgentDefinition, c as AgentCompact, d as AgentEpisodeExtract, e as AgentGraphExtract, f as AgentGenerate, g as AgentTokenSink, h as AgentRunInput, i as AgentFunctionPaths, j as AgentRunFunction, k as AgentStepLike, l as AgentStreamGenerate, m as AgentRunResult, n as AgentMessageRow, o as AgentConfig, S as SkillDefinition, p as AnyAgentTool, q as AgentAsToolOptions, r as AgentSubToolInput, b as AgentToolDefinition, s as AgentBindingSpec, t as AgentHandle, u as AgentToolConfig, v as AgentFunctionReference, w as AgentModelInput, x as SkillConfig } from "./packem_shared/types.d-BWG0uUtX.mjs";
2
- export type { y as AgentEmailMapper, z as AgentEmailRun, B as AgentGenerateOptions, C as AgentGenerateResult, D as AgentInstructionsContext, E as AgentLiveEvent, F as AgentMemoryOptions, G as AgentMemorySource, H as AgentMessageStatus, J as AgentOnStepFinish, K as AgentPrepareStep, L as AgentPrepareStepInput, M as AgentPrepareStepResult, N as AgentProgressEvent, O as AgentRunHandle, P as AgentStepFinishInfo, Q as AgentStepInfo, R as AgentThreadStatus, T as AgentTokenDelta, U as AgentToolCall, a as AgentToolContext, V as AgentUsage, W as AgentVoiceConfig, X as AgentWorkflowBindingLike, Y as AgentWorkflowInstanceLike } from "./packem_shared/types.d-BWG0uUtX.mjs";
1
+ import { A as AgentDefinition, e as AgentCompact, f as AgentEpisodeExtract, g as AgentGraphExtract, h as AgentGenerate, i as AgentTokenSink, j as AgentRunInput, k as AgentFunctionPaths, l as AgentRunFunction, m as AgentStepLike, n as AgentStreamGenerate, o as AgentRunResult, p as AgentMessageRow, q as AgentConfig, d as SkillDefinition, r as AnyAgentTool, s as AgentAsToolOptions, t as AgentSubToolInput, c as AgentToolDefinition, u as AgentBindingSpec, v as AgentHandle, w as AgentToolConfig, x as AgentFunctionReference, y as AgentModelInput, z as SkillConfig } from "./packem_shared/types.d-B8WZl1rU.mjs";
2
+ export type { B as AgentApprovalContext, C as AgentEmailMapper, D as AgentEmailRun, E as AgentGenerateOptions, F as AgentGenerateResult, G as AgentInstructionsContext, H as AgentLiveEvent, J as AgentMemoryOptions, K as AgentMemorySource, L as AgentMessageStatus, M as AgentOnReply, N as AgentOnStepFinish, O as AgentPrepareStep, P as AgentPrepareStepInput, Q as AgentPrepareStepResult, R as AgentProgressEvent, T as AgentReplyInfo, a as AgentReplyRef, U as AgentRunHandle, V as AgentStepFinishInfo, W as AgentStepInfo, X as AgentThreadStatus, Y as AgentTokenDelta, Z as AgentToolCall, b as AgentToolContext, _ as AgentUsage, $ as AgentVoiceConfig, a0 as AgentWorkflowBindingLike, a1 as AgentWorkflowInstanceLike, a2 as EnsureThreadOutcome } from "./packem_shared/types.d-B8WZl1rU.mjs";
3
3
  export { type AgentComponent, agentComponent, agentExtension } from "./component.mjs";
4
4
  import { FlexibleSchema, LanguageModel, ModelMessage } from 'ai';
5
5
  export { agentBindingName, agentClassName, agentDefaultName, voiceBindingName, voiceClassName } from "./naming.mjs";
@@ -8,99 +8,114 @@ import { createAi } from '@lunora/ai';
8
8
  import { WorkflowDefinition } from '@lunora/workflow';
9
9
  import '@lunora/mail/inbound';
10
10
  import '@lunora/server';
11
+ /**
12
+ * `AgentLoopOptions` is part of the experimental `@lunora/agent` API and may change without a major version bump.
13
+ * @experimental
14
+ */
11
15
  interface AgentLoopOptions {
12
16
  agent: AgentDefinition;
13
17
  /**
14
- * The history-compaction seam — production wires AI SDK `generateText`.
15
- * Absent (the default) disables compaction, so an agent with no `compaction`
16
- * config, and every unit test that doesn't opt in, is byte-identical.
17
- */
18
+ * The history-compaction seam — production wires AI SDK `generateText`.
19
+ * Absent (the default) disables compaction, so an agent with no `compaction`
20
+ * config, and every unit test that doesn't opt in, is byte-identical.
21
+ */
18
22
  compact?: AgentCompact;
19
23
  env: Record<string, unknown>;
20
24
  /** The agent's `lunora/agents.ts` export name (thread attribution). */
21
25
  exportName: string;
22
26
  /**
23
- * The run-end episode-summary seam — production wires AI SDK `generateText`.
24
- * Absent (the default) disables episode recording, so an agent with no
25
- * episodic memory, and every unit test that doesn't opt in, is byte-identical.
26
- */
27
+ * The run-end episode-summary seam — production wires AI SDK `generateText`.
28
+ * Absent (the default) disables episode recording, so an agent with no
29
+ * episodic memory, and every unit test that doesn't opt in, is byte-identical.
30
+ */
27
31
  extractEpisode?: AgentEpisodeExtract;
28
32
  /**
29
- * The run-end graph-extraction seam — production wires AI SDK `generateText`
30
- * with a structured `Output.object`. Absent (the default) disables
31
- * extraction, so an agent with no graph memory, and every unit test that
32
- * doesn't opt in, is byte-identical.
33
- */
33
+ * The run-end graph-extraction seam — production wires AI SDK `generateText`
34
+ * with a structured `Output.object`. Absent (the default) disables
35
+ * extraction, so an agent with no graph memory, and every unit test that
36
+ * doesn't opt in, is byte-identical.
37
+ */
34
38
  extractGraph?: AgentGraphExtract;
35
39
  /** The LLM-turn seam — production wires AI SDK `generateText`. */
36
40
  generate: AgentGenerate;
37
41
  /** The workflow instance id — the deterministic per-run message-key prefix. */
38
42
  instanceId: string;
39
43
  /**
40
- * Live-only sink for streamed token deltas. Supplied by the runtime (tees to
41
- * the existing stream transport); when present together with
42
- * {@link AgentLoopOptions.streamGenerate} each turn streams its deltas here
43
- * instead of a single non-streaming `generate` call. Absent (the default)
44
- * keeps the byte-identical non-streaming path. Never fired on a replay — the
45
- * memoized turn skips the step body that would emit deltas.
46
- */
44
+ * Live-only sink for streamed token deltas. Supplied by the runtime (tees to
45
+ * the existing stream transport); when present together with
46
+ * {@link AgentLoopOptions.streamGenerate} each turn streams its deltas here
47
+ * instead of a single non-streaming `generate` call. Absent (the default)
48
+ * keeps the byte-identical non-streaming path. Never fired on a replay — the
49
+ * memoized turn skips the step body that would emit deltas.
50
+ */
47
51
  onTokenDelta?: AgentTokenSink;
48
52
  params: AgentRunInput;
49
53
  paths: AgentFunctionPaths;
50
54
  run: AgentRunFunction;
51
55
  step: AgentStepLike;
52
56
  /**
53
- * The streaming LLM-turn seam — production wires AI SDK `streamText`. Used
54
- * only when {@link AgentLoopOptions.onTokenDelta} is also present; otherwise
55
- * the loop falls back to {@link AgentLoopOptions.generate} unchanged.
56
- */
57
+ * The streaming LLM-turn seam — production wires AI SDK `streamText`. Used
58
+ * only when {@link AgentLoopOptions.onTokenDelta} is also present; otherwise
59
+ * the loop falls back to {@link AgentLoopOptions.generate} unchanged.
60
+ */
57
61
  streamGenerate?: AgentStreamGenerate;
58
62
  }
59
63
  /**
60
- * Decide the compaction split for a turn: when `compaction` is configured and the
61
- * history exceeds `maxMessages`, return the `older` messages to summarize and the
62
- * `recent` tail to keep verbatim (the most recent `keepRecent`, default
63
- * `ceil(maxMessages / 2)`). Returns `undefined` when compaction is unset, the
64
- * history is short enough, or the tail would be the whole history — a PURE
65
- * decision (no I/O), unit-testable in isolation.
66
- */
64
+ * Decide the compaction split for a turn: when `compaction` is configured and the
65
+ * history exceeds `maxMessages`, return the `older` messages to summarize and the
66
+ * `recent` tail to keep verbatim (the most recent `keepRecent`, default
67
+ * `ceil(maxMessages / 2)`). Returns `undefined` when compaction is unset, the
68
+ * history is short enough, or the tail would be the whole history — a PURE
69
+ * decision (no I/O), unit-testable in isolation.
70
+ * @experimental
71
+ */
67
72
  declare const splitForCompaction: (history: ReadonlyArray<AgentMessageRow>, compaction: AgentConfig["compaction"]) => {
68
73
  older: AgentMessageRow[];
69
74
  recent: AgentMessageRow[];
70
75
  } | undefined;
71
76
  /**
72
- * The durable tool-loop. Correctness rests on two invariants:
73
- *
74
- * 1. **Deterministic step names.** Each LLM turn is `llm:turn:N` (`N` = the
75
- * loop index, replay-stable) and each tool call is `tool:NAME:CALL_ID`
76
- * (`CALL_ID` = the provider's stable id, itself replayed from the memoized LLM
77
- * step). Native `step.do` memoization then guarantees a COMPLETED step is
78
- * never re-run on replay — a resumed run cannot double-charge a card. A step
79
- * that FAILS mid-body is retried at-least-once; the tool receives its step
80
- * name as `idempotencyKey` to dedupe on.
81
- *
82
- * 2. **Idempotent persistence.** Every message write is keyed
83
- * `INSTANCE:ROLE:POSITION` and the append mutation dedupes on it, so a
84
- * replayed persist is a no-op — the thread never duplicates.
85
- *
86
- * Loop control derives ONLY from persisted step outputs (never fresh
87
- * `Date.now()`/`Math.random()` at the top level), so replays walk the same
88
- * step-name sequence.
89
- */
77
+ * The durable tool-loop. Correctness rests on two invariants:
78
+ *
79
+ * 1. **Deterministic step names.** Each LLM turn is `llm:turn:N` (`N` = the
80
+ * loop index, replay-stable) and each tool call is `tool:NAME:CALL_ID`
81
+ * (`CALL_ID` = the provider's stable id, itself replayed from the memoized LLM
82
+ * step). Native `step.do` memoization then guarantees a COMPLETED step is
83
+ * never re-run on replay — a resumed run cannot double-charge a card. A step
84
+ * that FAILS mid-body is retried at-least-once; the tool receives its step
85
+ * name as `idempotencyKey` to dedupe on.
86
+ *
87
+ * 2. **Idempotent persistence.** Every message write is keyed
88
+ * `INSTANCE:ROLE:POSITION` and the append mutation dedupes on it, so a
89
+ * replayed persist is a no-op — the thread never duplicates.
90
+ *
91
+ * Loop control derives ONLY from persisted step outputs (never fresh
92
+ * `Date.now()`/`Math.random()` at the top level), so replays walk the same
93
+ * step-name sequence.
94
+ * @experimental
95
+ */
90
96
  declare const runAgentLoop: (options: AgentLoopOptions) => Promise<AgentRunResult>;
91
- /** Input the model provides to a minted `searchMemory` tool. */
97
+ /**
98
+ * Input the model provides to a minted `searchMemory` tool.
99
+ * @experimental
100
+ */
92
101
  interface AgentMemorySearchInput {
93
102
  /** The natural-language query. */
94
103
  query: string;
95
104
  /** Per-call retrieval depth (overrides the source's configured `topK`). */
96
105
  topK?: number;
97
106
  }
98
- /** Input the model provides to a minted `readMemory` tool. */
107
+ /**
108
+ * Input the model provides to a minted `readMemory` tool.
109
+ * @experimental
110
+ */
99
111
  interface AgentMemoryReadInput {
100
112
  /** The chunk/document id from a `searchMemory` hit. */
101
113
  id: string;
102
114
  }
103
- /** One ranked hit in a {@link AgentMemorySearchResult}. */
115
+ /**
116
+ * One ranked hit in a {@link AgentMemorySearchResult}.
117
+ * @experimental
118
+ */
104
119
  interface AgentMemorySearchHit {
105
120
  id: string;
106
121
  score: number;
@@ -109,68 +124,81 @@ interface AgentMemorySearchHit {
109
124
  sourceId: string;
110
125
  }
111
126
  /**
112
- * The compact shape a minted `searchMemory` tool returns to the model: ranked
113
- * hits plus deduped source refs, with the giant joined `.context` string
114
- * DROPPED — the model reads snippets and decides what (if anything) to pull with
115
- * `readMemory`, which is the whole point of agentic (vs inject) retrieval.
116
- */
127
+ * The compact shape a minted `searchMemory` tool returns to the model: ranked
128
+ * hits plus deduped source refs, with the giant joined `.context` string
129
+ * DROPPED — the model reads snippets and decides what (if anything) to pull with
130
+ * `readMemory`, which is the whole point of agentic (vs inject) retrieval.
131
+ * @experimental
132
+ */
117
133
  interface AgentMemorySearchResult {
118
134
  results: AgentMemorySearchHit[];
119
135
  sources: ReadonlyArray<unknown>;
120
136
  }
121
137
  /**
122
- * Project a `@lunora/ai/rag` `RetrieveResult` into the compact
123
- * {@link AgentMemorySearchResult} a minted `searchMemory` tool returns. Defensive
124
- * against a source action that returns a differently-shaped value (the dispatch
125
- * boundary is untyped): a missing `chunks`/`sources` degrades to empty.
126
- */
138
+ * Project a `@lunora/ai/rag` `RetrieveResult` into the compact
139
+ * {@link AgentMemorySearchResult} a minted `searchMemory` tool returns. Defensive
140
+ * against a source action that returns a differently-shaped value (the dispatch
141
+ * boundary is untyped): a missing `chunks`/`sources` degrades to empty.
142
+ * @experimental
143
+ */
127
144
  declare const toSearchResults: (retrieved: unknown, snippetChars: number) => AgentMemorySearchResult;
128
145
  /**
129
- * Mint the model-facing tools for every `mode: "agentic"` memory source — the
130
- * agent's own `memory` (keyed `"default"` → `searchMemory`/`readMemory`) and
131
- * each skill's `knowledge` (keyed by the skill name → `search_KEY`/`read_KEY`).
132
- * An `"inject"`-mode source contributes NO tool (it stays on the auto-injection
133
- * path). The returned map is folded into the agent's flat tool namespace by
134
- * `defineAgent`, where a collision with a real tool throws.
135
- *
136
- * Walks the same two source origins as `collectMemorySources` so the two stay in
137
- * lockstep; the split is deliberate — inject sources feed retrieval, agentic
138
- * sources feed tools, and no source is ever on both paths.
139
- */
146
+ * Mint the model-facing tools for every `mode: "agentic"` memory source — the
147
+ * agent's own `memory` (keyed `"default"` → `searchMemory`/`readMemory`) and
148
+ * each skill's `knowledge` (keyed by the skill name → `search_KEY`/`read_KEY`).
149
+ * An `"inject"`-mode source contributes NO tool (it stays on the auto-injection
150
+ * path). The returned map is folded into the agent's flat tool namespace by
151
+ * `defineAgent`, where a collision with a real tool throws.
152
+ *
153
+ * Walks the same two source origins as `collectMemorySources` so the two stay in
154
+ * lockstep; the split is deliberate — inject sources feed retrieval, agentic
155
+ * sources feed tools, and no source is ever on both paths.
156
+ * @experimental
157
+ */
140
158
  declare const collectAgenticMemoryTools: (config: AgentConfig, skills: ReadonlyArray<SkillDefinition>) => Record<string, AnyAgentTool>;
141
159
  /**
142
- * Adapt a declared agent into a tool the parent's model can call: `execute`
143
- * starts a CHILD run on the child agent's Workflow binding, waits for it to
144
- * finish, and returns its final answer — so a supervisor agent can delegate to
145
- * specialists.
146
- *
147
- * Replay-safety: the whole `execute` runs inside the loop's `tool:NAME:CALL_ID`
148
- * durable step, so a completed sub-run is memoized and never re-run. The child
149
- * `threadKey` and Workflow instance id both DERIVE from the parent's
150
- * (replay-stable) `threadKey` + `toolCallId`, so a retried step reuses the same
151
- * child run rather than forking a new one (idempotent create-or-get). No
152
- * `Date.now()`/`Math.random()`.
153
- *
154
- * The child's completion is observed by POLLING `instance.status()` (Cloudflare
155
- * Workflows has no in-step await-instance primitive), and the answer is read
156
- * back from the child's persisted thread — the single source of truth — via the
157
- * shared `agents:agentMessages` runtime function.
158
- */
160
+ * Adapt a declared agent into a tool the parent's model can call: `execute`
161
+ * starts a CHILD run on the child agent's Workflow binding, waits for it to
162
+ * finish, and returns its final answer — so a supervisor agent can delegate to
163
+ * specialists.
164
+ *
165
+ * Replay-safety: the whole `execute` runs inside the loop's `tool:NAME:CALL_ID`
166
+ * durable step, so a completed sub-run is memoized and never re-run. The child
167
+ * `threadKey` and Workflow instance id both DERIVE from the parent's
168
+ * (replay-stable) `threadKey` + `toolCallId`, so a retried step reuses the same
169
+ * child run rather than forking a new one (idempotent create-or-get). No
170
+ * `Date.now()`/`Math.random()`.
171
+ *
172
+ * The child's completion is observed by POLLING `instance.status()` (Cloudflare
173
+ * Workflows has no in-step await-instance primitive), and the answer is read
174
+ * back from the child's persisted thread — the single source of truth — via the
175
+ * shared `agents:agentMessages` runtime function.
176
+ * @experimental
177
+ */
159
178
  declare const agentAsTool: (options: AgentAsToolOptions) => AgentToolDefinition<AgentSubToolInput, string>;
160
- /** One step of a tool-composition script — call `tool` with `input`, bind the result to `id`. */
179
+ /**
180
+ * One step of a tool-composition script — call `tool` with `input`, bind the result to `id`.
181
+ * @experimental
182
+ */
161
183
  interface ToolScriptStep {
162
184
  /** A stable name later steps reference the output by. */
163
185
  id: string;
164
- /** The tool's input; values may embed `{ "$from": "&lt;stepId>", "$path": "a.b" }` refs. */
186
+ /** The tool's input; values may embed `{ "$from": "<stepId>", "$path": "a.b" }` refs. */
165
187
  input?: Record<string, unknown>;
166
188
  /** The tool to call — one of the tools handed to {@link codeTool}. */
167
189
  tool: string;
168
190
  }
169
- /** The model-provided input to a {@link codeTool} call. */
191
+ /**
192
+ * The model-provided input to a {@link codeTool} call.
193
+ * @experimental
194
+ */
170
195
  interface ToolScript {
171
196
  steps: ToolScriptStep[];
172
197
  }
173
- /** The result of running a {@link ToolScript}: each step's output plus the last one. */
198
+ /**
199
+ * The result of running a {@link ToolScript}: each step's output plus the last one.
200
+ * @experimental
201
+ */
174
202
  interface ToolScriptResult {
175
203
  final: unknown;
176
204
  results: ReadonlyArray<{
@@ -178,109 +206,125 @@ interface ToolScriptResult {
178
206
  output: unknown;
179
207
  }>;
180
208
  }
181
- /** Author-supplied config for {@link codeTool}. */
209
+ /**
210
+ * Author-supplied config for {@link codeTool}.
211
+ * @experimental
212
+ */
182
213
  interface CodeToolOptions {
183
214
  /** Override the model-facing description (the default lists the available tools). */
184
215
  description?: string;
185
216
  /** Max steps per script (default 16). */
186
217
  maxSteps?: number;
187
218
  /**
188
- * Gate a whole script behind a human approval. Default: unattended. Note the
189
- * COMPOSED tools cannot carry their own approval gate — `codeTool` rejects any
190
- * tool with a `needsApproval`, because a code-mode script runs its steps in one
191
- * shot and can't pause mid-script to hibernate for HITL. Gate the whole script
192
- * here instead. Evaluated from replay-stable input, so keep it deterministic.
193
- */
219
+ * Gate a whole script behind a human approval. Default: unattended. Note the
220
+ * COMPOSED tools cannot carry their own approval gate — `codeTool` rejects any
221
+ * tool with a `needsApproval`, because a code-mode script runs its steps in one
222
+ * shot and can't pause mid-script to hibernate for HITL. Gate the whole script
223
+ * here instead. Evaluated from replay-stable input, so keep it deterministic.
224
+ */
194
225
  needsApproval?: ((input: ToolScript) => boolean) | boolean;
195
226
  }
196
227
  /**
197
- * Resolve `{ "$from": "&lt;stepId>", "$path"?: "a.b" }` references in a step's input
198
- * against earlier step results, recursing through nested objects and arrays. An
199
- * unknown `$from` throws — a forward/typo reference is a hard error, not a silent
200
- * `undefined`. Pure and deterministic (no I/O), so it's unit-testable alone.
201
- */
202
-
203
- /**
204
- * A "code mode" tool: instead of the model calling one tool per turn, it writes a
205
- * SCRIPT that composes several of the tools you hand `codeTool` — chaining a
206
- * later call's input to an earlier call's output — and the whole thing runs in a
207
- * single turn. This is a SAFE interpreted data-flow between whitelisted tools
208
- * (no `eval`, no isolate), so it runs natively in workerd; arbitrary-code
209
- * execution (the Cloudflare Worker Loader path) is a separate future mode.
210
- *
211
- * Each composed tool dispatches through the same durable context a normal call
212
- * gets (inheriting RLS), with a per-step idempotency key. A code-mode script runs
213
- * its steps in one shot and CANNOT pause mid-script for a human approval, so
214
- * `codeTool` REJECTS at construction any tool carrying a `needsApproval` gate —
215
- * keep approval-gated tools as normal top-level tools. Gate the whole script via
216
- * `opts.needsApproval` instead.
217
- *
218
- * ```ts
219
- * import { codeTool, defineAgent, functionTool } from "@lunora/agent";
220
- *
221
- * export const analyst = defineAgent({
222
- * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
223
- * tools: {
224
- * run: codeTool({
225
- * findUser: functionTool("users:byEmail", { description: "Look up a user by email.", inputSchema }),
226
- * recentOrders: functionTool("orders:recent", { description: "List a user's recent orders.", inputSchema }),
227
- * }),
228
- * },
229
- * });
230
- * // The model can now, in one turn, `findUser` then feed `{ "$from": "u", "$path": "id" }` into `recentOrders`.
231
- * ```
232
- */
228
+ * A "code mode" tool: instead of the model calling one tool per turn, it writes a
229
+ * SCRIPT that composes several of the tools you hand `codeTool` — chaining a
230
+ * later call's input to an earlier call's output — and the whole thing runs in a
231
+ * single turn. This is a SAFE interpreted data-flow between whitelisted tools
232
+ * (no `eval`, no isolate), so it runs natively in workerd; arbitrary-code
233
+ * execution (the Cloudflare Worker Loader path) is a separate future mode.
234
+ *
235
+ * Each composed tool dispatches through the same durable context a normal call
236
+ * gets (inheriting RLS), with a per-step idempotency key. A code-mode script runs
237
+ * its steps in one shot and CANNOT pause mid-script for a human approval, so
238
+ * `codeTool` REJECTS at construction any tool carrying a `needsApproval` gate —
239
+ * keep approval-gated tools as normal top-level tools. Gate the whole script via
240
+ * `opts.needsApproval` instead.
241
+ *
242
+ * STEP-OUTPUT CONTRACT — **every step's output must be JSON-serializable.** Each
243
+ * step runs inside its own durable `step.do`, so the workflow host serializes
244
+ * the returned value before the next step (or the tool result) ever sees it. A
245
+ * `Date` comes back as an ISO string, a `Map`/`Set` as `{}`, `undefined` as
246
+ * `null` or a dropped key, and a `bigint` throws outright. That applies to the
247
+ * value a later step reads through `{ "$from": … }` just as much as to the
248
+ * returned `results` — the hand-off is durable state, not an in-memory one. A
249
+ * composed tool that wants to pass a rich value should return its JSON form
250
+ * (`date.toISOString()`, `[...map]`) and let the consuming step rebuild it.
251
+ *
252
+ * ```ts
253
+ * import { codeTool, defineAgent, functionTool } from "@lunora/agent";
254
+ *
255
+ * export const analyst = defineAgent({
256
+ * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
257
+ * tools: {
258
+ * run: codeTool({
259
+ * findUser: functionTool("users:byEmail", { description: "Look up a user by email.", inputSchema }),
260
+ * recentOrders: functionTool("orders:recent", { description: "List a user's recent orders.", inputSchema }),
261
+ * }),
262
+ * },
263
+ * });
264
+ * // The model can now, in one turn, `findUser` then feed `{ "$from": "u", "$path": "id" }` into `recentOrders`.
265
+ * ```
266
+ * @experimental
267
+ */
233
268
  declare const codeTool: (tools: Record<string, AnyAgentTool>, options?: CodeToolOptions) => AgentToolDefinition<ToolScript, ToolScriptResult>;
234
269
  /**
235
- * Build the `ctx.agents` producer surface from the codegen-emitted spec list
236
- * (`LUNORA_AGENTS`), mirroring the `ctx.queues` property-access pattern: each
237
- * declared agent resolves its `AGENT_*` Workflow binding off `env` lazily, so
238
- * a missing binding only errors when that agent is actually started.
239
- *
240
- * `cancel` also needs to write the thread's status, so it dispatches the agent
241
- * runtime's `agentPatchThread` mutation. Production leaves `dispatch` undefined
242
- * and the env-backed `createDispatchRunner` (the same runner the workflow body
243
- * uses — it POSTs to `/_lunora/scheduler/dispatch`) is built on demand; tests
244
- * inject a `dispatch` double.
245
- */
270
+ * Build the `ctx.agents` producer surface from the codegen-emitted spec list
271
+ * (`LUNORA_AGENTS`), mirroring the `ctx.queues` property-access pattern: each
272
+ * declared agent resolves its `AGENT_*` Workflow binding off `env` lazily, so
273
+ * a missing binding only errors when that agent is actually started.
274
+ *
275
+ * `cancel` also needs to write the thread's status, so it dispatches the agent
276
+ * runtime's `agentPatchThread` mutation. Production leaves `dispatch` undefined
277
+ * and the env-backed `createDispatchRunner` (the same runner the workflow body
278
+ * uses — it POSTs to `/_lunora/scheduler/dispatch`) is built on demand; tests
279
+ * inject a `dispatch` double.
280
+ * @experimental
281
+ */
246
282
  declare const createAgentContext: (env: Record<string, unknown>, specs: ReadonlyArray<AgentBindingSpec>, dispatch?: AgentRunFunction) => Record<string, AgentHandle>;
247
283
  /**
248
- * Declare a durable agent. The definition compiles onto a Cloudflare Workflow
249
- * (each LLM turn and each tool call a named durable step; thread messages
250
- * persisted idempotently in DO SQLite), invoked from mutations/actions via
251
- * `ctx.agents.NAME.run(...)` and observed live by subscribing to the
252
- * `agents:agentMessages` query.
253
- *
254
- * ```ts
255
- * // lunora/agents.ts
256
- * import { defineAgent, defineAgentTool } from "@lunora/agent";
257
- *
258
- * export const support = defineAgent({
259
- * instructions: "You are a helpful support agent.",
260
- * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
261
- * tools: {
262
- * getWeather: defineAgentTool({
263
- * description: "Look up the current weather for a city.",
264
- * execute: async ({ city }, { run }) => run(api.weather.lookup, { city }),
265
- * inputSchema: jsonSchema({ properties: { city: { type: "string" } }, required: ["city"], type: "object" }),
266
- * }),
267
- * },
268
- * });
269
- * ```
270
- *
271
- * Declaring an agent is enough — codegen auto-registers the `agents:*` runtime
272
- * functions (from `agentComponent()`) and the `ctx.agents` producer surface.
273
- */
284
+ * Declare a durable agent. The definition compiles onto a Cloudflare Workflow
285
+ * (each LLM turn and each tool call a named durable step; thread messages
286
+ * persisted idempotently in DO SQLite), invoked from mutations/actions via
287
+ * `ctx.agents.NAME.run(...)` and observed live by subscribing to the
288
+ * `agents:agentMessages` query.
289
+ *
290
+ * ```ts
291
+ * // lunora/agents.ts
292
+ * import { defineAgent, defineAgentTool } from "@lunora/agent";
293
+ *
294
+ * export const support = defineAgent({
295
+ * instructions: "You are a helpful support agent.",
296
+ * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
297
+ * tools: {
298
+ * getWeather: defineAgentTool({
299
+ * description: "Look up the current weather for a city.",
300
+ * execute: async ({ city }, { run }) => run(api.weather.lookup, { city }),
301
+ * inputSchema: jsonSchema({ properties: { city: { type: "string" } }, required: ["city"], type: "object" }),
302
+ * }),
303
+ * },
304
+ * });
305
+ * ```
306
+ *
307
+ * Declaring an agent is enough — codegen auto-registers the `agents:*` runtime
308
+ * functions (from `agentComponent()`) and the `ctx.agents` producer surface.
309
+ * @experimental
310
+ */
274
311
  declare const defineAgent: (config: AgentConfig) => AgentDefinition;
275
- /** Runtime brand check for a {@link AgentDefinition}. */
312
+ /**
313
+ * Runtime brand check for a {@link AgentDefinition}.
314
+ * @experimental
315
+ */
276
316
  declare const isAgentDefinition: (value: unknown) => value is AgentDefinition;
277
317
  /**
278
- * Declare an agent tool — see `AgentToolDefinition` for why `execute` runs in
279
- * the loop's durable step (with an `AgentToolContext`) rather than inside the
280
- * model call.
281
- */
318
+ * Declare an agent tool — see `AgentToolDefinition` for why `execute` runs in
319
+ * the loop's durable step (with an `AgentToolContext`) rather than inside the
320
+ * model call.
321
+ * @experimental
322
+ */
282
323
  declare const defineAgentTool: <Input, Output>(config: AgentToolConfig<Input, Output>) => AgentToolDefinition<Input, Output>;
283
- /** Author-supplied config for {@link functionTool}. */
324
+ /**
325
+ * Author-supplied config for {@link functionTool}.
326
+ * @experimental
327
+ */
284
328
  interface FunctionToolOptions<Input> {
285
329
  /** What the tool does — shown to the model (the model decides from it). */
286
330
  description: string;
@@ -288,99 +332,116 @@ interface FunctionToolOptions<Input> {
288
332
  inputSchema: FlexibleSchema<Input>;
289
333
  }
290
334
  /**
291
- * Expose any Lunora query/mutation/action as an agent tool. The returned
292
- * definition's `execute` dispatches the referenced function through the loop's
293
- * existing `run` seam (the workflow `ctx.run`) — so there is no bespoke wrapper
294
- * per tool, and the call runs inside the loop's named durable step exactly like
295
- * any other tool.
296
- *
297
- * ```ts
298
- * import { functionTool } from "@lunora/agent";
299
- * import { api } from "./_generated/api";
300
- *
301
- * export const support = defineAgent({
302
- * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
303
- * tools: {
304
- * lookupOrder: functionTool(api.orders.byId, {
305
- * description: "Look up an order by its id.",
306
- * inputSchema: jsonSchema({ properties: { id: { type: "string" } }, required: ["id"], type: "object" }),
307
- * }),
308
- * },
309
- * });
310
- * ```
311
- *
312
- * The function reference may be a typed `api.*` reference or a `"module:name"`
313
- * path. The model-provided input is passed verbatim as the function's args, so
314
- * `inputSchema` should mirror the referenced function's argument validator.
315
- */
335
+ * Expose any Lunora query/mutation/action as an agent tool. The returned
336
+ * definition's `execute` dispatches the referenced function through the loop's
337
+ * existing `run` seam (the workflow `ctx.run`) — so there is no bespoke wrapper
338
+ * per tool, and the call runs inside the loop's named durable step exactly like
339
+ * any other tool.
340
+ *
341
+ * ```ts
342
+ * import { functionTool } from "@lunora/agent";
343
+ * import { api } from "./_generated/api";
344
+ *
345
+ * export const support = defineAgent({
346
+ * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
347
+ * tools: {
348
+ * lookupOrder: functionTool(api.orders.byId, {
349
+ * description: "Look up an order by its id.",
350
+ * inputSchema: jsonSchema({ properties: { id: { type: "string" } }, required: ["id"], type: "object" }),
351
+ * }),
352
+ * },
353
+ * });
354
+ * ```
355
+ *
356
+ * The function reference may be a typed `api.*` reference or a `"module:name"`
357
+ * path. The model-provided input is passed as the function's args, so
358
+ * `inputSchema` should mirror the referenced function's argument validator —
359
+ * PLUS the call's `idempotencyKey`, pinned onto the args after the model
360
+ * input (so out-of-schema model input can never override it). A function
361
+ * that wants to dedupe a side-effecting call across an at-least-once step
362
+ * retry declares `idempotencyKey: v.optional(v.string())` in its own args
363
+ * and checks it; a function that ignores it is unaffected — an args field
364
+ * the function's own validator doesn't declare is dropped, not rejected.
365
+ * @experimental
366
+ */
316
367
  declare const functionTool: <Input extends Record<string, unknown> = Record<string, unknown>, Output = unknown>(reference: AgentFunctionReference | string, options: FunctionToolOptions<Input>) => AgentToolDefinition<Input, Output>;
317
- /** Resolve the configured model against the Worker env (see `AgentModelInput`). */
368
+ /**
369
+ * Resolve the configured model against the Worker env (see `AgentModelInput`).
370
+ * @experimental
371
+ */
318
372
  declare const resolveAgentModel: (model: AgentModelInput, env: Record<string, unknown>) => LanguageModel;
319
373
  /**
320
- * Build the production LLM-turn seam over AI SDK `generateText`. The agent's
321
- * tools are exposed to the model schema-only (NO `execute`) — the model can
322
- * decide to call them, but execution happens back in the loop as named
323
- * durable steps, never inside the model call.
324
- *
325
- * The static generation settings (`temperature`, `maxOutputTokens`,
326
- * `toolChoice`, `output`, `telemetry`) come off the agent config; the per-turn
327
- * `activeTools` / `toolChoice` / `model` overrides (from `prepareStep`) arrive
328
- * on each call. When `output` is set the model runs with `Output.object` and
329
- * the parsed answer is returned alongside the text.
330
- */
374
+ * Build the production LLM-turn seam over AI SDK `generateText`. The agent's
375
+ * tools are exposed to the model schema-only (NO `execute`) — the model can
376
+ * decide to call them, but execution happens back in the loop as named
377
+ * durable steps, never inside the model call.
378
+ *
379
+ * The static generation settings (`temperature`, `maxOutputTokens`,
380
+ * `toolChoice`, `output`, `telemetry`) come off the agent config; the per-turn
381
+ * `activeTools` / `toolChoice` / `model` overrides (from `prepareStep`) arrive
382
+ * on each call. When `output` is set the model runs with `Output.object` and
383
+ * the parsed answer is returned alongside the text.
384
+ * @experimental
385
+ */
331
386
  declare const createAgentGenerate: (agent: AgentDefinition, env: Record<string, unknown>) => AgentGenerate;
332
387
  /**
333
- * The streaming counterpart of {@link createAgentGenerate} over AI SDK
334
- * `streamText`. It tees each text delta to `onDelta` as the model produces it,
335
- * then resolves the SAME {@link AgentGenerate} result the non-streaming seam
336
- * returns — identical `{ text, toolCalls, usage, output }` — so the value the
337
- * durable `llm:turn:N` step memoizes (and persists) is byte-for-byte unchanged
338
- * whether the turn streamed or not.
339
- *
340
- * Deltas are live-only: the seam runs inside the turn's durable step, so a
341
- * workflow replay serves the memoized final value without re-invoking it — no
342
- * delta is ever re-emitted. The persisted assistant message stays the single
343
- * source of truth.
344
- */
388
+ * The streaming counterpart of {@link createAgentGenerate} over AI SDK
389
+ * `streamText`. It tees each text delta to `onDelta` as the model produces it,
390
+ * then resolves the SAME {@link AgentGenerate} result the non-streaming seam
391
+ * returns — identical `{ text, toolCalls, usage, output }` — so the value the
392
+ * durable `llm:turn:N` step memoizes (and persists) is byte-for-byte unchanged
393
+ * whether the turn streamed or not.
394
+ *
395
+ * Deltas are live-only: the seam runs inside the turn's durable step, so a
396
+ * workflow replay serves the memoized final value without re-invoking it — no
397
+ * delta is ever re-emitted. The persisted assistant message stays the single
398
+ * source of truth.
399
+ * @experimental
400
+ */
345
401
  declare const createStreamGenerate: (agent: AgentDefinition, env: Record<string, unknown>) => AgentStreamGenerate;
346
402
  /**
347
- * Build the production run-end graph-extraction seam over AI SDK `generateText`
348
- * with an `Output.object` setting (the non-deprecated replacement for
349
- * `generateObject`, and the same structured-output path the turn seams use). It
350
- * resolves the (optionally cheaper) extraction model against the env, runs the
351
- * model over {@link GRAPH_EXTRACTION_SCHEMA}, and returns the parsed
352
- * `{ entities, relations }`. Wired by `compileAgentWorkflow` and called inside
353
- * the loop's memoized `memory:extract` step.
354
- */
403
+ * Build the production run-end graph-extraction seam over AI SDK `generateText`
404
+ * with an `Output.object` setting (the non-deprecated replacement for
405
+ * `generateObject`, and the same structured-output path the turn seams use). It
406
+ * resolves the (optionally cheaper) extraction model against the env, runs the
407
+ * model over {@link GRAPH_EXTRACTION_SCHEMA}, and returns the parsed
408
+ * `{ entities, relations }`. Wired by `compileAgentWorkflow` and called inside
409
+ * the loop's memoized `memory:extract` step.
410
+ * @experimental
411
+ */
355
412
  declare const createGraphExtract: () => AgentGraphExtract;
356
413
  /**
357
- * Build the production run-end episode-extraction seam over AI SDK `generateText`.
358
- * Unlike the graph extractor this needs no structured schema — an episode is a
359
- * single natural-language summary — so it returns the trimmed model text. Wired
360
- * by `compileAgentWorkflow` and called inside the loop's memoized
361
- * `memory:episode` step.
362
- */
414
+ * Build the production run-end episode-extraction seam over AI SDK `generateText`.
415
+ * Unlike the graph extractor this needs no structured schema — an episode is a
416
+ * single natural-language summary — so it returns the trimmed model text. Wired
417
+ * by `compileAgentWorkflow` and called inside the loop's memoized
418
+ * `memory:episode` step.
419
+ * @experimental
420
+ */
363
421
  declare const createEpisodeExtract: () => AgentEpisodeExtract;
364
422
  /**
365
- * Build the production history-compaction seam over AI SDK `generateText`: it
366
- * summarizes the older conversation messages under {@link COMPACTION_SYSTEM} and
367
- * returns the brief. Wired by `compileAgentWorkflow` and called inside the loop's
368
- * memoized `llm:turn:N` step so the summarization is replay-safe.
369
- */
370
- /** One text/image/… content part of an MCP `CallToolResult`. */
423
+ * One text/image/… content part of an MCP `CallToolResult`.
424
+ * @experimental
425
+ */
371
426
  interface McpContentPart {
372
427
  [key: string]: unknown;
373
428
  text?: string;
374
429
  type: string;
375
430
  }
376
- /** The result of an MCP `tools/call` — the structural subset the adapter reads. */
431
+ /**
432
+ * The result of an MCP `tools/call` — the structural subset the adapter reads.
433
+ * @experimental
434
+ */
377
435
  interface McpCallResult {
378
436
  [key: string]: unknown;
379
437
  content?: ReadonlyArray<McpContentPart>;
380
438
  isError?: boolean;
381
439
  structuredContent?: unknown;
382
440
  }
383
- /** One entry of an MCP `tools/list` — the structural subset the adapter reads. */
441
+ /**
442
+ * One entry of an MCP `tools/list` — the structural subset the adapter reads.
443
+ * @experimental
444
+ */
384
445
  interface McpToolInfo {
385
446
  description?: string;
386
447
  inputSchema: {
@@ -392,10 +453,11 @@ interface McpToolInfo {
392
453
  name: string;
393
454
  }
394
455
  /**
395
- * Structural subset of `@modelcontextprotocol/sdk`'s `Client` the adapter uses.
396
- * Declaring it locally lets tests inject a mock without the real SDK (and its
397
- * transport) and keeps the heavy dependency behind a lazy dynamic import.
398
- */
456
+ * Structural subset of `@modelcontextprotocol/sdk`'s `Client` the adapter uses.
457
+ * Declaring it locally lets tests inject a mock without the real SDK (and its
458
+ * transport) and keeps the heavy dependency behind a lazy dynamic import.
459
+ * @experimental
460
+ */
399
461
  interface McpClientLike {
400
462
  callTool: (params: {
401
463
  arguments?: Record<string, unknown>;
@@ -407,21 +469,35 @@ interface McpClientLike {
407
469
  tools: ReadonlyArray<McpToolInfo>;
408
470
  }>;
409
471
  }
410
- /** Options for {@link mcpTools}. */
472
+ /**
473
+ * Options for {@link mcpTools}.
474
+ * @experimental
475
+ */
411
476
  interface McpToolsOptions {
412
477
  /**
413
- * A pre-built (already-connected) MCP client. Takes precedence over `url` —
414
- * the seam tests inject, and the escape hatch for a custom transport.
415
- */
478
+ * A pre-built (already-connected) MCP client. Takes precedence over `url` —
479
+ * the seam tests inject, and the escape hatch for a custom transport.
480
+ */
416
481
  client?: McpClientLike;
417
482
  /**
418
- * A stdio command. NOT supported in the Workers runtime (stdio transports
419
- * cannot run in workerd) — passing it throws unless a `client` is injected.
420
- * Present so the intent is explicit rather than silently unsupported.
421
- */
483
+ * A stdio command. NOT supported in the Workers runtime (stdio transports
484
+ * cannot run in workerd) — passing it throws unless a `client` is injected.
485
+ * Present so the intent is explicit rather than silently unsupported.
486
+ */
422
487
  command?: string;
423
488
  /** Identify this client to the server on connect. Default `"lunora-agent"`. */
424
489
  name?: string;
490
+ /**
491
+ * Gate an adapted tool's call behind a human approval. Defaults to
492
+ * **unattended** (no gate) — matching today's behavior. An MCP tool's
493
+ * result feeds back into the model's context (`adaptMcpResult`), so a
494
+ * hostile downstream server can pair indirect prompt injection with an
495
+ * unattended mutating tool call; pass a boolean to gate every adapted
496
+ * tool the same way, or a predicate `(toolName, input) => boolean` to
497
+ * gate per tool (evaluated from replay-stable input, so keep it
498
+ * deterministic).
499
+ */
500
+ needsApproval?: ((toolName: string, input: Record<string, unknown>) => boolean) | boolean;
425
501
  /** Only adapt these tool names (default: every tool the server lists). */
426
502
  only?: ReadonlyArray<string>;
427
503
  /** Prefix added to each adapted tool's key (disambiguate multiple servers). */
@@ -434,40 +510,46 @@ interface McpToolsOptions {
434
510
  version?: string;
435
511
  }
436
512
  /**
437
- * Reduce an MCP tool result to the value persisted as the tool message. A
438
- * server's `structuredContent` is returned as-is; otherwise the text parts are
439
- * joined. An `isError` result is returned as an error STRING (not thrown) so
440
- * the next LLM turn can recover, consistent with the loop's unknown-tool path.
441
- */
513
+ * Reduce an MCP tool result to the value persisted as the tool message. A
514
+ * server's `structuredContent` is returned as-is; otherwise the text parts are
515
+ * joined. An `isError` result is returned as an error STRING (not thrown) so
516
+ * the next LLM turn can recover, consistent with the loop's unknown-tool path.
517
+ * @experimental
518
+ */
442
519
  declare const adaptMcpResult: (result: McpCallResult) => unknown;
443
520
  /**
444
- * Connect to an external MCP server, list its tools, and adapt each into an
445
- * {@link AgentToolDefinition} — a record you spread into an agent's `tools`.
446
- * Each adapted tool's `execute` calls the MCP tool through the client, and runs
447
- * inside the loop's named durable step like any other tool (so a completed call
448
- * is never re-run on a workflow replay).
449
- *
450
- * ```ts
451
- * export const support = defineAgent({
452
- * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
453
- * tools: {
454
- * ...(await mcpTools({ url: "https://mcp.example.com/mcp" })),
455
- * },
456
- * });
457
- * ```
458
- *
459
- * The connection is made at build time (listing tools needs it), then reused by
460
- * every call. In the Workers runtime only the HTTP/SSE transports run (stdio
461
- * does not) — pass `url`, or inject an already-connected `client` for a custom
462
- * transport (also the test seam).
463
- */
521
+ * Connect to an external MCP server, list its tools, and adapt each into an
522
+ * {@link AgentToolDefinition} — a record you spread into an agent's `tools`.
523
+ * Each adapted tool's `execute` calls the MCP tool through the client, and runs
524
+ * inside the loop's named durable step like any other tool (so a completed call
525
+ * is never re-run on a workflow replay).
526
+ *
527
+ * ```ts
528
+ * export const support = defineAgent({
529
+ * model: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
530
+ * tools: {
531
+ * ...(await mcpTools({ url: "https://mcp.example.com/mcp" })),
532
+ * },
533
+ * });
534
+ * ```
535
+ *
536
+ * The connection is made at build time (listing tools needs it), then reused by
537
+ * every call. In the Workers runtime only the HTTP/SSE transports run (stdio
538
+ * does not) — pass `url`, or inject an already-connected `client` for a custom
539
+ * transport (also the test seam).
540
+ *
541
+ * Every adapted tool runs UNATTENDED by default (no approval gate) — pass
542
+ * `opts.needsApproval` to change that; see {@link McpToolsOptions.needsApproval}.
543
+ * @experimental
544
+ */
464
545
  declare const mcpTools: (options: McpToolsOptions) => Promise<Record<string, AgentToolDefinition<Record<string, unknown>>>>;
465
546
  /**
466
- * Assemble the AI SDK conversation for one LLM turn: instructions, then the
467
- * retrieved memory context (when any), then the persisted thread history with
468
- * tool calls/results correlated the way providers expect (assistant tool-call
469
- * parts answered by tool-result parts sharing the `toolCallId`).
470
- */
547
+ * Assemble the AI SDK conversation for one LLM turn: instructions, then the
548
+ * retrieved memory context (when any), then the persisted thread history with
549
+ * tool calls/results correlated the way providers expect (assistant tool-call
550
+ * parts answered by tool-result parts sharing the `toolCallId`).
551
+ * @experimental
552
+ */
471
553
  declare const buildModelMessages: (options: {
472
554
  history: ReadonlyArray<AgentMessageRow>;
473
555
  instructions?: string;
@@ -475,63 +557,79 @@ declare const buildModelMessages: (options: {
475
557
  summary?: string;
476
558
  }) => ModelMessage[];
477
559
  /**
478
- * The namespace the agent runtime functions register under. Codegen
479
- * auto-registers `agentComponent().functions` here whenever `lunora/agents.ts`
480
- * declares an agent, so the loop's dispatch paths below hold by construction.
481
- */
560
+ * The namespace the agent runtime functions register under. Codegen
561
+ * auto-registers `agentComponent().functions` here whenever `lunora/agents.ts`
562
+ * declares an agent, so the loop's dispatch paths below hold by construction.
563
+ * @experimental
564
+ */
482
565
  declare const AGENT_MODULE = "agents";
483
566
  /**
484
- * The namespace + name the batteries-included sandbox dispatcher registers
485
- * under. Codegen auto-registers `sandboxComponent().invoke` here whenever
486
- * `lunora/` imports a sandbox tool (`browserTool`/`containerTool`), so the
487
- * tools' `execute` can dispatch to it through the loop's `run` seam.
488
- */
567
+ * The namespace + name the batteries-included sandbox dispatcher registers
568
+ * under. Codegen auto-registers `sandboxComponent().invoke` here whenever
569
+ * `lunora/` imports a sandbox tool (`browserTool`/`containerTool`), so the
570
+ * tools' `execute` can dispatch to it through the loop's `run` seam.
571
+ * @experimental
572
+ */
489
573
  declare const SANDBOX_MODULE = "sandbox";
490
- /** The dispatch path of the sandbox runtime action (an internal action). */
574
+ /**
575
+ * The dispatch path of the sandbox runtime action (an internal action).
576
+ * @experimental
577
+ */
491
578
  declare const SANDBOX_INVOKE_PATH: "sandbox:invoke";
492
- /** Default dispatch paths of the agent runtime functions. */
579
+ /**
580
+ * Default dispatch paths of the agent runtime functions.
581
+ * @experimental
582
+ */
493
583
  declare const DEFAULT_AGENT_FUNCTION_PATHS: AgentFunctionPaths;
494
- /** Mint a dispatchable function reference from a path (or pass one through). */
584
+ /**
585
+ * Mint a dispatchable function reference from a path (or pass one through).
586
+ * @experimental
587
+ */
495
588
  declare const toFunctionReference: (source: AgentFunctionReference | string) => AgentFunctionReference;
496
589
  /**
497
- * Declare a reusable skill: a bundle of expertise — an instruction fragment,
498
- * tools, and retrieval `knowledge` — an agent composes in via
499
- * `defineAgent({ skills: [...] })`. Reuse-first: a skill's `tools` carry the
500
- * SAME `AnyAgentTool` shape agents already use (`functionTool` / `mcpTools` /
501
- * `agentAsTool`), and `knowledge` reuses `memory`'s `AgentMemoryOptions`
502
- * retrieval verbatim.
503
- *
504
- * ```ts
505
- * import { defineSkill, functionTool } from "@lunora/agent";
506
- *
507
- * export const billing = defineSkill({
508
- * name: "billing",
509
- * instructions: "When asked about invoices, cite the invoice id.",
510
- * knowledge: { source: "rag:searchBillingDocs", topK: 4 },
511
- * tools: {
512
- * lookupInvoice: functionTool(api.billing.invoiceById, {
513
- * description: "Look up an invoice by id.",
514
- * inputSchema: jsonSchema({ properties: { id: { type: "string" } }, required: ["id"], type: "object" }),
515
- * }),
516
- * },
517
- * });
518
- * ```
519
- *
520
- * The merge into the agent's flat namespace (tool-name collisions, instruction
521
- * ordering, per-skill knowledge retrieval) happens in `defineAgent` — a skill
522
- * only validates its own `name`; tool-name validation is deferred to the merge,
523
- * which owns the flat model-facing namespace.
524
- */
590
+ * Declare a reusable skill: a bundle of expertise — an instruction fragment,
591
+ * tools, and retrieval `knowledge` — an agent composes in via
592
+ * `defineAgent({ skills: [...] })`. Reuse-first: a skill's `tools` carry the
593
+ * SAME `AnyAgentTool` shape agents already use (`functionTool` / `mcpTools` /
594
+ * `agentAsTool`), and `knowledge` reuses `memory`'s `AgentMemoryOptions`
595
+ * retrieval verbatim.
596
+ *
597
+ * ```ts
598
+ * import { defineSkill, functionTool } from "@lunora/agent";
599
+ *
600
+ * export const billing = defineSkill({
601
+ * name: "billing",
602
+ * instructions: "When asked about invoices, cite the invoice id.",
603
+ * knowledge: { source: "rag:searchBillingDocs", topK: 4 },
604
+ * tools: {
605
+ * lookupInvoice: functionTool(api.billing.invoiceById, {
606
+ * description: "Look up an invoice by id.",
607
+ * inputSchema: jsonSchema({ properties: { id: { type: "string" } }, required: ["id"], type: "object" }),
608
+ * }),
609
+ * },
610
+ * });
611
+ * ```
612
+ *
613
+ * The merge into the agent's flat namespace (tool-name collisions, instruction
614
+ * ordering, per-skill knowledge retrieval) happens in `defineAgent` — a skill
615
+ * only validates its own `name`; tool-name validation is deferred to the merge,
616
+ * which owns the flat model-facing namespace.
617
+ * @experimental
618
+ */
525
619
  declare const defineSkill: (config: SkillConfig) => SkillDefinition;
526
- /** Runtime brand check for a {@link SkillDefinition}. */
620
+ /**
621
+ * Runtime brand check for a {@link SkillDefinition}.
622
+ * @experimental
623
+ */
527
624
  declare const isSkillDefinition: (value: unknown) => value is SkillDefinition;
528
625
  /**
529
- * A control frame the server sends the client as a JSON text message (audio
530
- * rides separate binary frames). `ready` announces the negotiated codec on
531
- * connect; `user_transcript` carries the STT result; `assistant_delta` streams
532
- * the live LLM text; `assistant_done` is the final turn text; `interrupted`
533
- * acks a barge-in; `error` reports a non-fatal turn failure.
534
- */
626
+ * A control frame the server sends the client as a JSON text message (audio
627
+ * rides separate binary frames). `ready` announces the negotiated codec on
628
+ * connect; `user_transcript` carries the STT result; `assistant_delta` streams
629
+ * the live LLM text; `assistant_done` is the final turn text; `interrupted`
630
+ * acks a barge-in; `error` reports a non-fatal turn failure.
631
+ * @experimental
632
+ */
535
633
  type VoiceServerFrame = {
536
634
  audioFormat: "mp3" | "wav";
537
635
  type: "ready";
@@ -550,7 +648,10 @@ type VoiceServerFrame = {
550
648
  } | {
551
649
  type: "interrupted";
552
650
  };
553
- /** A control frame the client sends the server (audio rides separate binary frames). */
651
+ /**
652
+ * A control frame the client sends the server (audio rides separate binary frames).
653
+ * @experimental
654
+ */
554
655
  type VoiceClientFrame = {
555
656
  text: string;
556
657
  type: "text";
@@ -559,17 +660,35 @@ type VoiceClientFrame = {
559
660
  } | {
560
661
  type: "interrupt";
561
662
  };
562
- /** A synthesized-audio source the TTS seam yields — normalized to bytes by {@link toByteIterable}. */
663
+ /**
664
+ * A synthesized-audio source the TTS seam yields — normalized to bytes by {@link toByteIterable}.
665
+ * @experimental
666
+ */
563
667
  type VoiceAudioSource = AsyncIterable<Uint8Array> | ReadableStream<Uint8Array> | Uint8Array;
564
- /** Transcribe one buffered utterance (16kHz mono 16-bit PCM) to text. */
668
+ /**
669
+ * Transcribe one buffered utterance (16kHz mono 16-bit PCM) to text.
670
+ * @experimental
671
+ */
565
672
  type VoiceTranscribe = (pcm: Uint8Array) => Promise<string>;
566
- /** Synthesize one sentence to an audio byte stream; honors `signal` for barge-in. */
673
+ /**
674
+ * Synthesize one sentence to an audio byte stream; honors `signal` for barge-in.
675
+ * @experimental
676
+ */
567
677
  type VoiceSynthesize = (text: string, signal: AbortSignal) => Promise<VoiceAudioSource>;
568
- /** Send a JSON control frame to the client. */
678
+ /**
679
+ * Send a JSON control frame to the client.
680
+ * @experimental
681
+ */
569
682
  type VoiceSend = (frame: VoiceServerFrame) => void;
570
- /** Send a binary audio frame to the client. */
683
+ /**
684
+ * Send a binary audio frame to the client.
685
+ * @experimental
686
+ */
571
687
  type VoiceSendAudio = (bytes: Uint8Array) => void;
572
- /** The outcome of one voice turn. */
688
+ /**
689
+ * The outcome of one voice turn.
690
+ * @experimental
691
+ */
573
692
  interface VoiceTurnResult {
574
693
  /** The final assistant text (may be partial if `interrupted`). */
575
694
  assistantText: string;
@@ -578,10 +697,20 @@ interface VoiceTurnResult {
578
697
  /** The transcribed (or typed) user text — empty when the utterance was silence. */
579
698
  userText: string;
580
699
  }
581
- /** Options for one {@link runVoiceTurn}. */
700
+ /**
701
+ * Options for one {@link runVoiceTurn}.
702
+ * @experimental
703
+ */
582
704
  interface RunVoiceTurnOptions {
583
705
  /** The agent whose thread + models back this session. */
584
706
  agent: AgentDefinition;
707
+ /**
708
+ * History-compaction seam — the SAME one the durable loop uses. Voice and
709
+ * text turns share one thread, so an agent that declares `compaction` must
710
+ * get it here too; absent (or with no `compaction` config) the turn takes
711
+ * the byte-identical uncompacted path.
712
+ */
713
+ compact?: AgentCompact;
585
714
  /** Stable per-socket id — the message-key prefix that keeps persisted rows idempotent. */
586
715
  connectionId: string;
587
716
  /** The Worker env (resolves a dynamic `instructions` thunk). */
@@ -617,23 +746,21 @@ interface RunVoiceTurnOptions {
617
746
  /** Optional outbound-audio backpressure: awaited before each audio frame so a slow client can't balloon DO memory. Never throws. */
618
747
  waitForDrain?: () => Promise<void>;
619
748
  }
620
- /** Normalize a {@link VoiceAudioSource} to an async iterable of byte chunks. */
621
-
622
- /**
623
- * Run one conversational voice turn IN-DO: transcribe the utterance (or take the
624
- * typed text), persist the user turn onto the SHARED agent thread, stream the
625
- * assistant reply, and synthesize it sentence-by-sentence back to the client —
626
- * TTS overlapping generation, both cancellable by a barge-in `signal`.
627
- *
628
- * This is NOT the replay-durable Workflow tool-loop: a voice turn is
629
- * conversational only (no tools in v1) and not replay-safe. Persistence stays
630
- * idempotent by `voice:{connectionId}:{turn}:{role}` message keys so a resent
631
- * frame never duplicates a row; the thread tables are the same ones the durable
632
- * agent loop and `useAgentChat` read, so a voice session and a text session on
633
- * the same `threadKey` share one history.
634
- */
749
+ /**
750
+ * Run one conversational voice turn IN-DO: transcribe the utterance (or take the
751
+ * typed text), persist the user turn onto the SHARED agent thread, stream the
752
+ * assistant reply, and synthesize it sentence-by-sentence back to the client —
753
+ * TTS overlapping generation, both cancellable by a barge-in `signal`.
754
+ *
755
+ * This is NOT the replay-durable Workflow tool-loop: a voice turn is
756
+ * conversational only (no tools in v1) and not replay-safe. Persistence stays
757
+ * idempotent by `voice:{connectionId}:{turn}:{role}` message keys so a resent
758
+ * frame never duplicates a row; the thread tables are the same ones the durable
759
+ * agent loop and `useAgentChat` read, so a voice session and a text session on
760
+ * the same `threadKey` share one history.
761
+ * @experimental
762
+ */
635
763
  declare const runVoiceTurn: (options: RunVoiceTurnOptions) => Promise<VoiceTurnResult>;
636
- /** Parse the JSON identity envelope forwarded on the `x-lunora-identity` upgrade header. */
637
764
  /** Structural subset of `DurableObjectState` the voice DO needs (typed locally for unit doubles). */
638
765
  interface VoiceSessionState {
639
766
  acceptWebSocket: (ws: WebSocket, tags?: string[]) => void;
@@ -641,20 +768,21 @@ interface VoiceSessionState {
641
768
  waitUntil?: (promise: Promise<unknown>) => void;
642
769
  }
643
770
  /**
644
- * A hibernatable-WebSocket Durable Object that runs an agent's real-time VOICE
645
- * session. One instance per `threadKey`: the client opens a WebSocket, streams
646
- * 16kHz mono PCM as binary frames, and marks utterance boundaries with a JSON
647
- * `{ type: "commit" }` control frame; the DO transcribes the utterance, streams
648
- * the agent's reply through the LLM, and synthesizes it back as MP3 binary
649
- * frames — all IN-DO. It SHARES the agent's `agent_threads`/`agent_messages`
650
- * tables through the same runtime dispatch seam the durable loop uses, so voice
651
- * and text turns interleave on one history.
652
- *
653
- * Codegen emits a thin subclass per voice-enabled agent (e.g.
654
- * `SupportVoiceDO extends VoiceSessionDO`, constructed with the agent
655
- * definition + its export name) bound under the agent's `VOICE_...` Durable
656
- * Object binding.
657
- */
771
+ * A hibernatable-WebSocket Durable Object that runs an agent's real-time VOICE
772
+ * session. One instance per `threadKey`: the client opens a WebSocket, streams
773
+ * 16kHz mono PCM as binary frames, and marks utterance boundaries with a JSON
774
+ * `{ type: "commit" }` control frame; the DO transcribes the utterance, streams
775
+ * the agent's reply through the LLM, and synthesizes it back as MP3 binary
776
+ * frames — all IN-DO. It SHARES the agent's `agent_threads`/`agent_messages`
777
+ * tables through the same runtime dispatch seam the durable loop uses, so voice
778
+ * and text turns interleave on one history.
779
+ *
780
+ * Codegen emits a thin subclass per voice-enabled agent (e.g.
781
+ * `SupportVoiceDO extends VoiceSessionDO`, constructed with the agent
782
+ * definition + its export name) bound under the agent's `VOICE_...` Durable
783
+ * Object binding.
784
+ * @experimental
785
+ */
658
786
  declare class VoiceSessionDO {
659
787
  protected readonly agent: AgentDefinition;
660
788
  protected readonly ai: ReturnType<typeof createAi>;
@@ -664,6 +792,8 @@ declare class VoiceSessionDO {
664
792
  protected readonly streamGenerate: AgentStreamGenerate;
665
793
  protected readonly sttModel: string;
666
794
  protected readonly ttsModel: string;
795
+ /** Turn budget for one socket — see {@link DEFAULT_MAX_SESSION_TURNS}. */
796
+ private readonly maxSessionTurns;
667
797
  private readonly audioBuffers;
668
798
  private readonly bufferedBytes;
669
799
  private readonly controllers;
@@ -678,11 +808,11 @@ declare class VoiceSessionDO {
678
808
  /** Abort any in-flight turn + free the socket's buffers on error. Never throws. */
679
809
  webSocketError(ws: WebSocket): void;
680
810
  /**
681
- * The runtime dispatch seam reaching the shared agent thread functions. When
682
- * the socket carries a verified identity it is forwarded so the `agents:*`
683
- * thread writes are attributed to the caller (RLS / row ownership) rather
684
- * than the anonymous system dispatch.
685
- */
811
+ * The runtime dispatch seam reaching the shared agent thread functions. When
812
+ * the socket carries a verified identity it is forwarded so the `agents:*`
813
+ * thread writes are attributed to the caller (RLS / row ownership) rather
814
+ * than the anonymous system dispatch.
815
+ */
686
816
  protected resolveRun(userId?: string, claims?: Record<string, unknown>): AgentRunFunction;
687
817
  /** Production STT seam: WAV-wrap the utterance and run the batch transcription model. */
688
818
  protected transcribe(pcm: Uint8Array): Promise<string>;
@@ -702,38 +832,41 @@ declare class VoiceSessionDO {
702
832
  private synthesizeWithSignal;
703
833
  /** Abort an in-flight turn and free a socket's transient buffers. */
704
834
  private cleanupSocket;
835
+ /** Close a socket that broke a session bound, swallowing an already-closed error (never throw from a handler). */
836
+ private closeSocket;
705
837
  /** Send a JSON control frame, swallowing a closed-socket error (never throw from a handler). */
706
838
  private send;
707
839
  /** Send a binary audio frame, swallowing a closed-socket error. */
708
840
  private sendAudio;
709
841
  /**
710
- * Outbound backpressure: if the socket exposes `bufferedAmount`, yield in
711
- * short polls until the send buffer drains below the cap so a slow client
712
- * can't balloon DO memory. Bounded by {@link MAX_DRAIN_WAIT_MS} so a stuck
713
- * socket never blocks a turn forever, and never throws (a socket without
714
- * `bufferedAmount` resolves immediately).
715
- */
842
+ * Outbound backpressure: if the socket exposes `bufferedAmount`, yield in
843
+ * short polls until the send buffer drains below the cap so a slow client
844
+ * can't balloon DO memory. Bounded by {@link MAX_DRAIN_WAIT_MS} so a stuck
845
+ * socket never blocks a turn forever, and never throws (a socket without
846
+ * `bufferedAmount` resolves immediately).
847
+ */
716
848
  private waitForSocketDrain;
717
849
  }
718
850
  /**
719
- * Compile a `defineAgent` definition into the workflow the generated
720
- * `&lt;Name>AgentWorkflow` entrypoint class runs. Codegen emits, per agent:
721
- *
722
- * ```ts
723
- * import LunoraWorkflow from "@lunora/workflow/do";
724
- * import { compileAgentWorkflow } from "@lunora/agent";
725
- * import { support } from "../agents.js";
726
- *
727
- * export class SupportAgentWorkflow extends LunoraWorkflow&lt;AgentRunInput, AgentRunResult> {
728
- * public constructor(ctx: ConstructorParameters&lt;typeof LunoraWorkflow>[0], env: Record&lt;string, unknown>) {
729
- * super(ctx, env, compileAgentWorkflow(support, "support"), "support");
730
- * }
731
- * }
732
- * ```
733
- *
734
- * The workflow ctx supplies durability (`step.do`) and the Lunora dispatcher
735
- * (`run`); the loop supplies determinism (step naming + idempotent persists).
736
- */
851
+ * Compile a `defineAgent` definition into the workflow the generated
852
+ * `<Name>AgentWorkflow` entrypoint class runs. Codegen emits, per agent:
853
+ *
854
+ * ```ts
855
+ * import LunoraWorkflow from "@lunora/workflow/do";
856
+ * import { compileAgentWorkflow } from "@lunora/agent";
857
+ * import { support } from "../agents.js";
858
+ *
859
+ * export class SupportAgentWorkflow extends LunoraWorkflow<AgentRunInput, AgentRunResult> {
860
+ * public constructor(ctx: ConstructorParameters<typeof LunoraWorkflow>[0], env: Record<string, unknown>) {
861
+ * super(ctx, env, compileAgentWorkflow(support, "support"), "support");
862
+ * }
863
+ * }
864
+ * ```
865
+ *
866
+ * The workflow ctx supplies durability (`step.do`) and the Lunora dispatcher
867
+ * (`run`); the loop supplies determinism (step naming + idempotent persists).
868
+ * @experimental
869
+ */
737
870
  declare const compileAgentWorkflow: (agent: AgentDefinition, exportName: string, options?: {
738
871
  paths?: AgentFunctionPaths;
739
872
  }) => WorkflowDefinition<AgentRunInput, AgentRunResult>;