@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.
- package/README.md +2 -0
- package/dist/channels.d.mts +33 -21
- package/dist/channels.d.ts +33 -21
- package/dist/channels.mjs +1 -181
- package/dist/component.d.mts +61 -56
- package/dist/component.d.ts +61 -56
- package/dist/component.mjs +2 -407
- package/dist/inbound.d.mts +23 -16
- package/dist/inbound.d.ts +23 -16
- package/dist/inbound.mjs +1 -32
- package/dist/index.d.mts +542 -409
- package/dist/index.d.ts +542 -409
- package/dist/index.mjs +1 -18
- package/dist/naming.d.mts +23 -10
- package/dist/naming.d.ts +23 -10
- package/dist/naming.mjs +1 -8
- package/dist/packem_shared/AGENT_MODULE-M4D1EejI.mjs +1 -0
- package/dist/packem_shared/VoiceSessionDO-D238XrlM.mjs +1 -0
- package/dist/packem_shared/adaptMcpResult-CVlq-_TO.mjs +2 -0
- package/dist/packem_shared/agent-loop-CE_Q6bk9.mjs +3 -0
- package/dist/packem_shared/agentAsTool-CgZb6ycK.mjs +1 -0
- package/dist/packem_shared/base64-5eyBfWO3.mjs +1 -0
- package/dist/packem_shared/braintrustTelemetry-Byne8uPK.mjs +1 -0
- package/dist/packem_shared/branch-marker-boZ00zmk.mjs +1 -0
- package/dist/packem_shared/buildModelMessages-Y4tGo1T9.mjs +5 -0
- package/dist/packem_shared/codeTool-BQb-K3my.mjs +1 -0
- package/dist/packem_shared/collectAgenticMemoryTools-BeMWT2qt.mjs +1 -0
- package/dist/packem_shared/combineTelemetry-DgE9W9G8.mjs +1 -0
- package/dist/packem_shared/common-CJSjtsfv.mjs +1 -0
- package/dist/packem_shared/compileAgentWorkflow-DVHi7NYD.mjs +1 -0
- package/dist/packem_shared/component-shared-G8ngerkU.mjs +1 -0
- package/dist/packem_shared/consoleTelemetry-BZY3Y-Ly.mjs +1 -0
- package/dist/packem_shared/createAgentContext-BvU9LP43.mjs +1 -0
- package/dist/packem_shared/createAgentGenerate-BOILDUZj.mjs +3 -0
- package/dist/packem_shared/createDispatchRunner-BxtGxAQi-DXXP7SxZ.mjs +1 -0
- package/dist/packem_shared/defineAgent-KgH-gwYg.mjs +3 -0
- package/dist/packem_shared/defineSkill-DGGvDWNq.mjs +1 -0
- package/dist/packem_shared/fnv1a-BNN96GYb.mjs +1 -0
- package/dist/packem_shared/functionTool-D3CP4f8-.mjs +1 -0
- package/dist/packem_shared/in-flight-calls-GH1A_1P3.mjs +1 -0
- package/dist/packem_shared/normalizeEntityName-k3fjicAG.mjs +2 -0
- package/dist/packem_shared/otlpTelemetry-ByqrB-72.mjs +1 -0
- package/dist/packem_shared/positive-integer-ztHqqpBk.mjs +1 -0
- package/dist/packem_shared/runAgentLoop-Drn1NcCc.mjs +1 -0
- package/dist/packem_shared/runVoiceTurn-BtcmyaBo.mjs +1 -0
- package/dist/packem_shared/sandboxComponent-BtOkUYpY.mjs +5 -0
- package/dist/packem_shared/sentryTelemetry-BHeydiAB.mjs +1 -0
- package/dist/packem_shared/tool-output-Cmk3JWMG.mjs +1 -0
- package/dist/packem_shared/types.d-B8WZl1rU.d.mts +1347 -0
- package/dist/packem_shared/types.d-B8WZl1rU.d.ts +1347 -0
- package/dist/packem_shared/voice-turn-BRj_it54.mjs +1 -0
- package/dist/reply.d.mts +40 -0
- package/dist/reply.d.ts +40 -0
- package/dist/reply.mjs +1 -0
- package/dist/sandbox.d.mts +130 -110
- package/dist/sandbox.d.ts +130 -110
- package/dist/sandbox.mjs +1 -109
- package/dist/skill-markdown.d.mts +36 -0
- package/dist/skill-markdown.d.ts +36 -0
- package/dist/skill-markdown.mjs +1 -0
- package/dist/telemetry/index.d.mts +319 -96
- package/dist/telemetry/index.d.ts +319 -96
- package/dist/telemetry/index.mjs +1 -4
- package/package.json +18 -9
- package/dist/packem_shared/AGENT_MODULE-Dnt_-AAT.mjs +0 -24
- package/dist/packem_shared/VoiceSessionDO-DLoXsHGF.mjs +0 -297
- package/dist/packem_shared/adaptMcpResult-wtNMvLoP.mjs +0 -65
- package/dist/packem_shared/agentAsTool-Dt8NlU6k.mjs +0 -94
- package/dist/packem_shared/base64-BVwtgRJV.mjs +0 -18
- package/dist/packem_shared/braintrustTelemetry-wuGDErob.mjs +0 -47
- package/dist/packem_shared/buildModelMessages-BWFigaoo.mjs +0 -69
- package/dist/packem_shared/codeTool-CjgJOC9t.mjs +0 -122
- package/dist/packem_shared/collectAgenticMemoryTools-QrzpV-WX.mjs +0 -97
- package/dist/packem_shared/combineTelemetry-DCyaaWAI.mjs +0 -43
- package/dist/packem_shared/common-DAeFCot5.mjs +0 -61
- package/dist/packem_shared/compileAgentWorkflow-BxJjHgtD.mjs +0 -55
- package/dist/packem_shared/consoleTelemetry-z2MiP1jt.mjs +0 -93
- package/dist/packem_shared/createAgentContext-4xJGXNR4.mjs +0 -50
- package/dist/packem_shared/createAgentGenerate-BQv9YJ01.mjs +0 -192
- package/dist/packem_shared/createDispatchRunner-DSbp_dph-ZHTtxy3f.mjs +0 -69
- package/dist/packem_shared/defineAgent-D6maSbVc.mjs +0 -148
- package/dist/packem_shared/defineSkill-Ctf_S-rz.mjs +0 -22
- package/dist/packem_shared/functionTool-D6lCa2jB.mjs +0 -20
- package/dist/packem_shared/graph-component-aoUwO-f0.mjs +0 -216
- package/dist/packem_shared/memory-D4FPcBsX.mjs +0 -12
- package/dist/packem_shared/normalizeEntityName-CyEEWFkR.mjs +0 -3
- package/dist/packem_shared/runAgentLoop-Dhg4ZNvw.mjs +0 -493
- package/dist/packem_shared/runVoiceTurn-LnqLvCRR.mjs +0 -211
- package/dist/packem_shared/sandboxComponent-DR3pTwBL.mjs +0 -194
- package/dist/packem_shared/sentryTelemetry-CgqFJyLO.mjs +0 -36
- package/dist/packem_shared/types.d-BWG0uUtX.d.mts +0 -1015
- package/dist/packem_shared/types.d-BWG0uUtX.d.ts +0 -1015
|
@@ -0,0 +1,1347 @@
|
|
|
1
|
+
import { InboundEmail } from '@lunora/mail/inbound';
|
|
2
|
+
import { FlexibleSchema, LanguageModel, ModelMessage, ToolSet, ToolChoice, ToolCallRepairFunction, StopCondition, TelemetryOptions } from 'ai';
|
|
3
|
+
/**
|
|
4
|
+
* Structural mirror of the Lunora function reference (`{ __lunoraRef }`).
|
|
5
|
+
* Declared locally so the loop can mint references to the agent runtime
|
|
6
|
+
* functions by path without importing `@lunora/dispatch`.
|
|
7
|
+
* @experimental
|
|
8
|
+
*/
|
|
9
|
+
interface AgentFunctionReference {
|
|
10
|
+
__lunoraRef: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `ctx.run`-shaped dispatcher the loop uses to call Lunora functions.
|
|
14
|
+
* @experimental
|
|
15
|
+
*/
|
|
16
|
+
type AgentRunFunction = (reference: AgentFunctionReference, args?: Record<string, unknown>) => Promise<unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Structural subset of the Cloudflare Workflows durable-step API the loop needs.
|
|
19
|
+
* @experimental
|
|
20
|
+
*/
|
|
21
|
+
interface AgentStepLike {
|
|
22
|
+
do: <T>(name: string, callback: () => Promise<T>) => Promise<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Durably hibernate until an external event of `type` arrives, then return
|
|
25
|
+
* its payload. Used for human-in-the-loop approvals: a run pauses on
|
|
26
|
+
* `approval:<toolCallId>` until a client resolves it. Like `do`, a resolved
|
|
27
|
+
* wait is memoized — a replay returns the recorded decision without pausing
|
|
28
|
+
* again. Signature mirrors `@lunora/workflow`'s `WorkflowStepLike`.
|
|
29
|
+
*/
|
|
30
|
+
waitForEvent: <T = unknown>(name: string, options: {
|
|
31
|
+
timeout?: number | string;
|
|
32
|
+
type: string;
|
|
33
|
+
}) => Promise<{
|
|
34
|
+
payload: Readonly<T>;
|
|
35
|
+
type: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The context handed to an agent tool's `execute`. Beyond the model-provided
|
|
40
|
+
* input, a tool sees who it runs for (`threadKey`), how to call back into the
|
|
41
|
+
* app (`run` — dispatches Lunora queries/mutations/actions), and its
|
|
42
|
+
* `idempotencyKey`.
|
|
43
|
+
*
|
|
44
|
+
* The `idempotencyKey` is the deterministic durable-step name
|
|
45
|
+
* (`tool:<name>:<toolCallId>`, further suffixed per script step for a
|
|
46
|
+
* `codeTool`). A COMPLETED tool step is never re-run on a workflow replay
|
|
47
|
+
* (native step memoization) — but a step that FAILS mid-body is retried
|
|
48
|
+
* at-least-once, so a side-effecting tool (charge a card, send a mail) must
|
|
49
|
+
* dedupe on this key itself. `functionTool` forwards it to the dispatched
|
|
50
|
+
* function as `args.idempotencyKey` (pinned after the model input, so it
|
|
51
|
+
* can't be overridden) — a function that wants to dedupe declares
|
|
52
|
+
* `idempotencyKey: v.optional(v.string())` in its own args and checks it; a
|
|
53
|
+
* function that ignores it is unaffected (an undeclared arg field is dropped,
|
|
54
|
+
* not rejected).
|
|
55
|
+
* @experimental
|
|
56
|
+
*/
|
|
57
|
+
interface AgentToolContext {
|
|
58
|
+
/**
|
|
59
|
+
* How many sub-agent delegations deep this run already is — 0 for a run a
|
|
60
|
+
* user started, one more per `agent.asTool` hop. The loop copies it off
|
|
61
|
+
* {@link AgentRunInput.depth}; `agent.asTool` reads it to refuse spawning a
|
|
62
|
+
* child past the delegation-depth bound (see `as-tool.ts`). Absent is 0.
|
|
63
|
+
*/
|
|
64
|
+
depth?: number;
|
|
65
|
+
/** The Worker environment bindings. */
|
|
66
|
+
env: Record<string, unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Read the thread's synced state — dispatches the public owner-gated
|
|
69
|
+
* `agents:agentState` query through {@link AgentToolContext.run} (the same
|
|
70
|
+
* admin-dispatch path the loop reads history with), so it inherits the
|
|
71
|
+
* thread's identity/owner gate. Returns `undefined` before any state was
|
|
72
|
+
* seeded (`defineAgent({ initialState })`) or written.
|
|
73
|
+
*
|
|
74
|
+
* NOT replay-stable: on an at-least-once step retry this reflects whatever a
|
|
75
|
+
* prior attempt already wrote via {@link AgentToolContext.setState} (a real
|
|
76
|
+
* committed write, not rolled back), so a value *derived* from it is not
|
|
77
|
+
* safe to pass straight back to `setState` — see its doc for the
|
|
78
|
+
* read-modify-write hazard and the `idempotencyKey` dedupe fix.
|
|
79
|
+
*/
|
|
80
|
+
getState: () => Promise<Record<string, unknown> | undefined>;
|
|
81
|
+
/** Deterministic idempotency key — the tool's durable-step name. */
|
|
82
|
+
idempotencyKey: string;
|
|
83
|
+
/**
|
|
84
|
+
* Verified owner of the thread this run belongs to (the loop copies it off
|
|
85
|
+
* {@link AgentRunInput.owner}); `undefined` on an anonymous/single-tenant
|
|
86
|
+
* thread. `agent.asTool` forwards it to the CHILD run, so a sub-agent thread
|
|
87
|
+
* under an owned parent inherits the same RLS scope instead of being created
|
|
88
|
+
* ownerless — which left it readable by anyone who knew its key.
|
|
89
|
+
*/
|
|
90
|
+
owner?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Emit an EPHEMERAL progress event for this tool call on the agent's live
|
|
93
|
+
* channel — the same live-only sink the streamed token deltas ride. NOT
|
|
94
|
+
* persisted and NEVER replayed: it fires only while `execute` runs inside the
|
|
95
|
+
* tool's memoized durable step, so a COMPLETED step is served from the memo on
|
|
96
|
+
* replay without re-emitting. A safe no-op when the runtime wired no live sink
|
|
97
|
+
* (the durable path's default), exactly as `streamingText` stays empty then.
|
|
98
|
+
* Surfaced on the client by `useAgentToolEvents`, correlated to this call by
|
|
99
|
+
* {@link AgentToolContext.toolCallId}. `data` must be JSON-serializable.
|
|
100
|
+
*/
|
|
101
|
+
reportProgress: (data: unknown) => void;
|
|
102
|
+
/** Dispatch a Lunora function (the workflow `ctx.run`). */
|
|
103
|
+
run: AgentRunFunction;
|
|
104
|
+
/**
|
|
105
|
+
* Replace the thread's synced state — dispatches the internal
|
|
106
|
+
* `agents:agentSetState` mutation (absolute REPLACE of the whole object) and
|
|
107
|
+
* broadcasts to every `useAgentState` subscriber over the existing reactive
|
|
108
|
+
* channel. Runs inside the tool's memoized durable step, so a COMPLETED step
|
|
109
|
+
* is served from the memo on replay without re-dispatching.
|
|
110
|
+
*
|
|
111
|
+
* REPLAY-SAFETY CONTRACT — the *value* you pass must be REPLAY-STABLE: a
|
|
112
|
+
* constant or derived purely from the replay-stable tool `input` (never
|
|
113
|
+
* `Date.now()`/`Math.random()`). A step that FAILS mid-body is retried
|
|
114
|
+
* at-least-once, and the retry re-runs the whole `execute` against state a
|
|
115
|
+
* prior attempt may already have written (the dispatch is a real committed
|
|
116
|
+
* mutation, not rolled back). Re-applying a replay-stable value is a no-op,
|
|
117
|
+
* so the absolute set converges. A value derived from
|
|
118
|
+
* {@link AgentToolContext.getState} is NOT replay-stable — a naive
|
|
119
|
+
* read-modify-write (`setState({ count: (await getState()).count + 1 })`)
|
|
120
|
+
* DOUBLE-ADVANCES on a retry because the retry re-reads the already-written
|
|
121
|
+
* value. For a read-modify-write, make the tool idempotent on
|
|
122
|
+
* {@link AgentToolContext.idempotencyKey}: record the key in the state and
|
|
123
|
+
* skip the write when it is already present.
|
|
124
|
+
*/
|
|
125
|
+
setState: (state: Record<string, unknown>) => Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* The durable-step handle (`step.do`/`waitForEvent`). ALWAYS present:
|
|
128
|
+
* `agent-loop.ts` threads it into every tool's context unconditionally, and
|
|
129
|
+
* it is required rather than optional so a missing handle is a compile
|
|
130
|
+
* error instead of a silent durability downgrade. `codeTool` uses it to give
|
|
131
|
+
* each script step its OWN nested durable boundary — see `code-tool.ts` — so
|
|
132
|
+
* a failure at script step 3 retries only step 3, not steps 1–2's
|
|
133
|
+
* already-committed side effects. Cloudflare Workflows supports a `step.do`
|
|
134
|
+
* nested inside another `step.do`'s callback (the codeTool call's own
|
|
135
|
+
* enclosing step). Most tools never touch this directly; a test driving
|
|
136
|
+
* `execute`/`runToolScript` by hand passes a pass-through double.
|
|
137
|
+
*/
|
|
138
|
+
step: AgentStepLike;
|
|
139
|
+
/** The thread this tool call belongs to. */
|
|
140
|
+
threadKey: string;
|
|
141
|
+
/** The provider-issued tool-call id. */
|
|
142
|
+
toolCallId: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* The view of {@link AgentToolContext} handed to a `needsApproval` gate
|
|
146
|
+
* function — every field except `setState`, `step`, and `reportProgress`.
|
|
147
|
+
*
|
|
148
|
+
* `setState` is dropped because a gate that mutates thread state is a side
|
|
149
|
+
* effect inside a decision predicate, and `reportProgress` because emitting a
|
|
150
|
+
* live event is the same thing in observable form — the decision is what the
|
|
151
|
+
* loop reports, not the deciding. `step` goes because the loop already runs the
|
|
152
|
+
* gate inside a durable step of its own, so a gate has no business opening
|
|
153
|
+
* another.
|
|
154
|
+
*
|
|
155
|
+
* `getState` and `run` stay: reads are legitimate gate inputs (gate on the
|
|
156
|
+
* caller's plan tier, on a spend total), and the gate resolves inside its own
|
|
157
|
+
* durable step, so they are replay-safe there. Note this makes the type a
|
|
158
|
+
* NARROWING, not a proof of purity — `run` takes any
|
|
159
|
+
* {@link AgentFunctionReference}, so a gate can still dispatch a mutation
|
|
160
|
+
* before approval. Nothing in the type system distinguishes a query reference
|
|
161
|
+
* from a mutation one; keeping the gate side-effect-free is the author's.
|
|
162
|
+
* @experimental
|
|
163
|
+
*/
|
|
164
|
+
type AgentApprovalContext = Omit<AgentToolContext, "reportProgress" | "setState" | "step">;
|
|
165
|
+
/**
|
|
166
|
+
* An agent tool. Unlike a raw AI SDK tool, `execute` is NOT handed to the
|
|
167
|
+
* model call — the loop runs it itself inside a named durable step so a
|
|
168
|
+
* completed call never re-runs on replay, and passes the
|
|
169
|
+
* {@link AgentToolContext} alongside the input.
|
|
170
|
+
* @experimental
|
|
171
|
+
*/
|
|
172
|
+
interface AgentToolDefinition<Input = unknown, Output = unknown> {
|
|
173
|
+
/** What the tool does — shown to the model. */
|
|
174
|
+
description: string;
|
|
175
|
+
/** Run the tool. The returned value is persisted as the tool message content. */
|
|
176
|
+
execute: (input: Input, context: AgentToolContext) => Promise<Output> | Output;
|
|
177
|
+
/** The input schema shown to the model (a zod schema or `jsonSchema(...)`). */
|
|
178
|
+
inputSchema: FlexibleSchema<Input>;
|
|
179
|
+
/** Runtime brand. */
|
|
180
|
+
isLunoraAgentTool: true;
|
|
181
|
+
/**
|
|
182
|
+
* Gate the tool behind a human approval (mirrors the AI SDK's
|
|
183
|
+
* `needsApproval`). When it resolves truthy the durable run PAUSES — the
|
|
184
|
+
* thread moves to `"awaiting_input"` and the workflow hibernates on
|
|
185
|
+
* `approval:<toolCallId>` — until a client calls `agents:agentResolveApproval`.
|
|
186
|
+
* On approve the tool runs exactly as normal; on reject it is skipped and a
|
|
187
|
+
* tool result explaining the rejection is persisted so the next turn recovers.
|
|
188
|
+
* A boolean gates statically; a function gates per input. Default: `false`
|
|
189
|
+
* (unchanged behavior).
|
|
190
|
+
*
|
|
191
|
+
* The boolean/`undefined` forms are compile-time constants re-derived
|
|
192
|
+
* identically on every replay — no durable step. The FUNCTION form runs
|
|
193
|
+
* inside its OWN durable step (`tool:approval-gate:<toolCallId>`, distinct
|
|
194
|
+
* from the tool's own step), so it now runs exactly once per call, not once
|
|
195
|
+
* per replay. It must still be otherwise pure: deterministic given its
|
|
196
|
+
* inputs (no `Date.now()`/`Math.random()`) and free of side effects — the
|
|
197
|
+
* context it receives is {@link AgentApprovalContext}, which has neither
|
|
198
|
+
* `setState` nor `reportProgress`; state writes and progress events belong
|
|
199
|
+
* only in `execute`, inside the tool's own memoized step. It still holds
|
|
200
|
+
* `run`, which the type cannot narrow to reads — see
|
|
201
|
+
* {@link AgentApprovalContext}.
|
|
202
|
+
*/
|
|
203
|
+
needsApproval?: ((input: Input, context: AgentApprovalContext) => boolean | Promise<boolean>) | boolean;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Author-supplied tool config (see {@link AgentToolDefinition}).
|
|
207
|
+
* @experimental
|
|
208
|
+
*/
|
|
209
|
+
type AgentToolConfig<Input = unknown, Output = unknown> = Omit<AgentToolDefinition<Input, Output>, "isLunoraAgentTool">;
|
|
210
|
+
/**
|
|
211
|
+
* Generic-erased tool for tool maps. `Input` sits in both a covariant
|
|
212
|
+
* (`inputSchema`) and a contravariant (`execute`) position, so no single
|
|
213
|
+
* non-`any` instantiation admits every concrete tool — the same reason the AI
|
|
214
|
+
* SDK's `ToolSet` erases its generics.
|
|
215
|
+
* @experimental
|
|
216
|
+
*/
|
|
217
|
+
type AnyAgentTool = AgentToolDefinition<any, any>;
|
|
218
|
+
/**
|
|
219
|
+
* The model an agent runs on: a Workers AI model id (resolved via `env.AI`),
|
|
220
|
+
* a prebuilt AI SDK {@link LanguageModel}, or a thunk building one from the
|
|
221
|
+
* Worker env (for providers that need API keys off `env`).
|
|
222
|
+
* @experimental
|
|
223
|
+
*/
|
|
224
|
+
type AgentModelInput = LanguageModel | ((env: Record<string, unknown>) => LanguageModel);
|
|
225
|
+
/**
|
|
226
|
+
* Memory wiring: the path (or reference) of an app **action** taking
|
|
227
|
+
* `{ query: string }` and returning `@lunora/ai/rag`'s `RetrieveResult`
|
|
228
|
+
* (`{ context, chunks, sources }`) — typically three lines over
|
|
229
|
+
* `defineRag(...)(ctx).retrieve`. Dispatching to a real action (instead of
|
|
230
|
+
* embedding vector plumbing here) keeps retrieval inside a fully wired ctx:
|
|
231
|
+
* codegen-resolved vector bindings, RLS, observability.
|
|
232
|
+
*
|
|
233
|
+
* Two retrieval MODES. `"inject"` (default) runs the action as a durable step at
|
|
234
|
+
* turn start and injects `.context` as one system message (one-shot top-k).
|
|
235
|
+
* `"agentic"` skips auto-injection; the source instead mints a `searchMemory`
|
|
236
|
+
* tool the MODEL calls mid-reasoning (Recursive-LM / "read what you need") — each
|
|
237
|
+
* call a memoized durable step, so multi-hop retrieval is crash-safe for free.
|
|
238
|
+
* @experimental
|
|
239
|
+
*/
|
|
240
|
+
interface AgentMemoryOptions {
|
|
241
|
+
/**
|
|
242
|
+
* Episodic-tier options — read only when {@link AgentMemoryOptions.kind} is
|
|
243
|
+
* `"episodic"`. `recall` bounds how many recent episodes are injected per run
|
|
244
|
+
* (default 5, capped at 20); `extractionModel` overrides the (optionally
|
|
245
|
+
* cheaper) model that summarizes each run into an episode (defaults to the
|
|
246
|
+
* agent's own model).
|
|
247
|
+
*/
|
|
248
|
+
episodic?: {
|
|
249
|
+
/** Model for the run-end episode-summary step (defaults to the agent's model). */
|
|
250
|
+
extractionModel?: AgentModelInput;
|
|
251
|
+
/** Max recent episodes recalled per run (default 5, max 20). */
|
|
252
|
+
recall?: number;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Graph-tier bounds and extraction model — read only when
|
|
256
|
+
* {@link AgentMemoryOptions.kind} is `"graph"`. `depth`/`maxSeeds`/`fanOut`/
|
|
257
|
+
* `maxNodes` bound the run-time BFS traversal; `extractionModel` overrides the
|
|
258
|
+
* (optionally cheaper) model used for the run-end entity/relation extraction
|
|
259
|
+
* step (defaults to the agent's own model).
|
|
260
|
+
*/
|
|
261
|
+
graph?: {
|
|
262
|
+
/** Max BFS hops from a seed entity (default 2). */
|
|
263
|
+
depth?: number;
|
|
264
|
+
/** Model for the run-end extraction step (defaults to the agent's model). */
|
|
265
|
+
extractionModel?: AgentModelInput;
|
|
266
|
+
/** Max edges expanded per visited node (default 8). */
|
|
267
|
+
fanOut?: number;
|
|
268
|
+
/** Max entities visited across the whole traversal (default 32). */
|
|
269
|
+
maxNodes?: number;
|
|
270
|
+
/** Max seed entities matched from the query (default 4). */
|
|
271
|
+
maxSeeds?: number;
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* `"semantic"` (default) is vector RAG over {@link AgentMemoryOptions.source}.
|
|
275
|
+
* `"graph"` traverses the owner-scoped entity/relation graph (auto-extracted
|
|
276
|
+
* on write, keyed by the thread's `owner`) via the built-in traverse function
|
|
277
|
+
* and ignores `source`. `"episodic"` recalls a recency-ordered timeline of the
|
|
278
|
+
* owner's past runs (each summarized at run end); it also ignores `source`.
|
|
279
|
+
* See {@link AgentMemoryOptions}.
|
|
280
|
+
*/
|
|
281
|
+
kind?: "episodic" | "graph" | "semantic";
|
|
282
|
+
/**
|
|
283
|
+
* `"inject"` (default) auto-injects one top-k context system message per
|
|
284
|
+
* run; `"agentic"` skips injection and mints a `searchMemory` tool the model
|
|
285
|
+
* drives itself. Applies to `"semantic"` kind only. See
|
|
286
|
+
* {@link AgentMemoryOptions}.
|
|
287
|
+
*/
|
|
288
|
+
mode?: "agentic" | "inject";
|
|
289
|
+
/**
|
|
290
|
+
* Agentic-only. An optional fetch-by-id **action** `{ id: string } -> string`
|
|
291
|
+
* that mints a companion `readMemory` tool so the model can pull a full
|
|
292
|
+
* document after `searchMemory` surfaces its id (typically a ~3-line action
|
|
293
|
+
* mapping `${sourceId}#${n}` → text). Ignored in `"inject"` mode.
|
|
294
|
+
*/
|
|
295
|
+
read?: AgentFunctionReference | string;
|
|
296
|
+
/**
|
|
297
|
+
* Agentic-only. Per-result snippet truncation (chars) applied to each
|
|
298
|
+
* `searchMemory` hit for token economy. Default 240. Ignored in `"inject"`
|
|
299
|
+
* mode.
|
|
300
|
+
*/
|
|
301
|
+
snippetChars?: number;
|
|
302
|
+
/**
|
|
303
|
+
* The memory action: a function path (`"rag:searchDocs"`) or reference.
|
|
304
|
+
* Required for `"semantic"` kind (enforced at `defineAgent`); ignored — and
|
|
305
|
+
* therefore optional — for `"graph"` kind, which dispatches the built-in
|
|
306
|
+
* traverse function instead.
|
|
307
|
+
*/
|
|
308
|
+
source?: AgentFunctionReference | string;
|
|
309
|
+
/** Retrieval depth forwarded to the action as `topK`. */
|
|
310
|
+
topK?: number;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* One keyed memory-retrieval source the loop dispatches per run. `defineAgent`
|
|
314
|
+
* folds them onto {@link AgentDefinition.memorySources}: the default source
|
|
315
|
+
* (`key: "default"`) from {@link AgentConfig.memory}, then one per skill that
|
|
316
|
+
* carries `knowledge` (keyed by the skill's name). The key names the durable
|
|
317
|
+
* step — the default source keeps the historic `"memory:retrieve"`, a skill
|
|
318
|
+
* source uses `"memory:retrieve:<key>"` — so replay stays deterministic.
|
|
319
|
+
* @experimental
|
|
320
|
+
*/
|
|
321
|
+
interface AgentMemorySource extends AgentMemoryOptions {
|
|
322
|
+
/** Stable source key: `"default"` for `memory`, else the contributing skill's name. */
|
|
323
|
+
key: string;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Cumulative or per-turn token usage — AI SDK `LanguageModelUsage` field names.
|
|
327
|
+
* @experimental
|
|
328
|
+
*/
|
|
329
|
+
interface AgentUsage {
|
|
330
|
+
/** Prompt (input) tokens. */
|
|
331
|
+
inputTokens?: number;
|
|
332
|
+
/** Completion (output) tokens. */
|
|
333
|
+
outputTokens?: number;
|
|
334
|
+
/** Input + output tokens. */
|
|
335
|
+
totalTokens?: number;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Context handed to a dynamic {@link AgentConfig.instructions} function.
|
|
339
|
+
* @experimental
|
|
340
|
+
*/
|
|
341
|
+
interface AgentInstructionsContext {
|
|
342
|
+
/** The Worker environment bindings. */
|
|
343
|
+
env: Record<string, unknown>;
|
|
344
|
+
/** The user message that started this run. */
|
|
345
|
+
input: string;
|
|
346
|
+
/** The thread this run belongs to. */
|
|
347
|
+
threadKey: string;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Author-supplied config for {@link SkillDefinition} — a reusable bundle of
|
|
351
|
+
* expertise (an instruction fragment, tools, and retrieval knowledge) an agent
|
|
352
|
+
* composes in via `defineAgent({ skills: [...] })`. Reuse-first: `tools` carry
|
|
353
|
+
* the SAME {@link AnyAgentTool} shape agents already use
|
|
354
|
+
* (`functionTool`/`mcpTools`/`agentAsTool`), and `knowledge` reuses the
|
|
355
|
+
* {@link AgentMemoryOptions} retrieval verbatim.
|
|
356
|
+
* @experimental
|
|
357
|
+
*/
|
|
358
|
+
interface SkillConfig {
|
|
359
|
+
/**
|
|
360
|
+
* An instruction fragment merged into the agent's system prompt — a static
|
|
361
|
+
* string or a thunk over the run context (same shape as
|
|
362
|
+
* {@link AgentConfig.instructions}). Fragments compose in order: the agent's
|
|
363
|
+
* own instructions first, then each skill's in `skills` array order.
|
|
364
|
+
*/
|
|
365
|
+
instructions?: string | ((context: AgentInstructionsContext) => string);
|
|
366
|
+
/**
|
|
367
|
+
* Retrieval-augmented knowledge for this skill — see
|
|
368
|
+
* {@link AgentMemoryOptions}. Retrieved as its own durable step at run start
|
|
369
|
+
* (keyed by the skill `name`) and injected alongside the agent's `memory`.
|
|
370
|
+
*/
|
|
371
|
+
knowledge?: AgentMemoryOptions;
|
|
372
|
+
/**
|
|
373
|
+
* The skill's identifier — namespaces this skill's `knowledge` memory source
|
|
374
|
+
* (the durable step `memory:retrieve:<name>`). Must be identifier-shaped.
|
|
375
|
+
*/
|
|
376
|
+
name: string;
|
|
377
|
+
/**
|
|
378
|
+
* Tools this skill contributes, merged into the agent's FLAT tool namespace.
|
|
379
|
+
* A name collision with the agent's own tools (or another skill's) is an
|
|
380
|
+
* error at `defineAgent` — the agent owns the model-facing namespace.
|
|
381
|
+
*/
|
|
382
|
+
tools?: Record<string, AnyAgentTool>;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* The code-side parts of a markdown-authored skill — everything `SKILL.md`
|
|
386
|
+
* cannot carry. `name` and `instructions` come from the file itself.
|
|
387
|
+
* @experimental
|
|
388
|
+
*/
|
|
389
|
+
type SkillMarkdownExtras = Omit<SkillConfig, "instructions" | "name">;
|
|
390
|
+
/**
|
|
391
|
+
* A `defineSkill` result — config plus the brand the agent merge checks.
|
|
392
|
+
* @experimental
|
|
393
|
+
*/
|
|
394
|
+
interface SkillDefinition extends SkillConfig {
|
|
395
|
+
/** Runtime brand check (see `isSkillDefinition`). */
|
|
396
|
+
readonly isLunoraSkill: true;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* One prior turn, as {@link AgentConfig.prepareStep} and `stopWhen` observe it.
|
|
400
|
+
* @experimental
|
|
401
|
+
*/
|
|
402
|
+
interface AgentStepInfo {
|
|
403
|
+
/** The assistant text of the turn. */
|
|
404
|
+
text: string;
|
|
405
|
+
/** The tool calls the turn issued (AI SDK `StepResult.toolCalls` shape). */
|
|
406
|
+
toolCalls: ReadonlyArray<{
|
|
407
|
+
input: unknown;
|
|
408
|
+
toolCallId: string;
|
|
409
|
+
toolName: string;
|
|
410
|
+
}>;
|
|
411
|
+
/** The turn's token usage, when the model reported it. */
|
|
412
|
+
usage?: AgentUsage;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* The turn summary handed to {@link AgentConfig.onStepFinish}.
|
|
416
|
+
* @experimental
|
|
417
|
+
*/
|
|
418
|
+
interface AgentStepFinishInfo {
|
|
419
|
+
/** The assistant text produced this turn. */
|
|
420
|
+
text: string;
|
|
421
|
+
/** The tool calls issued this turn (empty on the final answer). */
|
|
422
|
+
toolCalls: ReadonlyArray<AgentToolCall>;
|
|
423
|
+
/** The zero-based turn index. */
|
|
424
|
+
turn: number;
|
|
425
|
+
/** The turn's token usage, when the model reported it. */
|
|
426
|
+
usage?: AgentUsage;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Called after each LLM turn with that turn's text, tool calls, and usage. Runs
|
|
430
|
+
* inside a named durable step (`agent:step-finish:<turn>`) so it fires exactly
|
|
431
|
+
* once per turn even across a workflow replay.
|
|
432
|
+
* @experimental
|
|
433
|
+
*/
|
|
434
|
+
type AgentOnStepFinish = (info: AgentStepFinishInfo) => Promise<void> | void;
|
|
435
|
+
/**
|
|
436
|
+
* What {@link AgentConfig.onReply} is called with when a triggered run reaches
|
|
437
|
+
* its final answer.
|
|
438
|
+
* @experimental
|
|
439
|
+
*/
|
|
440
|
+
interface AgentReplyInfo {
|
|
441
|
+
/** The worker `env` — where an outbound credential (a bot token, a mailer binding) lives. */
|
|
442
|
+
env: Record<string, unknown>;
|
|
443
|
+
/** Where to reply, captured by the mapper that started this run. */
|
|
444
|
+
replyRef: AgentReplyRef;
|
|
445
|
+
/** The run's final answer. */
|
|
446
|
+
result: AgentRunResult;
|
|
447
|
+
/** The thread this run belongs to. */
|
|
448
|
+
threadKey: string;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Send a triggered run's answer back where it came from — the outbound half of
|
|
452
|
+
* an `onEmail` / `onInbound` trigger.
|
|
453
|
+
*
|
|
454
|
+
* Called once, automatically, when a run started with a `replyRef` produces a
|
|
455
|
+
* final answer; a run without one (an ordinary in-app `ctx.agents.<name>.run`)
|
|
456
|
+
* never calls it. It runs inside a named durable step, so a transient failure
|
|
457
|
+
* is retried and a workflow replay does not send the answer twice.
|
|
458
|
+
*
|
|
459
|
+
* Delivery is the app's call, because only the app has the credential: email
|
|
460
|
+
* has a home already (`replyToEmail(mailer, ref, body)` from
|
|
461
|
+
* `@lunora/agent/reply`), while Slack/GitHub/Discord bot tokens have no
|
|
462
|
+
* framework-owned store yet — read them from `info.env` and make the provider
|
|
463
|
+
* call.
|
|
464
|
+
* @experimental
|
|
465
|
+
*/
|
|
466
|
+
type AgentOnReply = (info: AgentReplyInfo) => Promise<void> | void;
|
|
467
|
+
/**
|
|
468
|
+
* The input {@link AgentConfig.prepareStep} sees before a turn runs.
|
|
469
|
+
* @experimental
|
|
470
|
+
*/
|
|
471
|
+
interface AgentPrepareStepInput {
|
|
472
|
+
/** The messages assembled for this turn (instructions + memory + history). */
|
|
473
|
+
messages: ReadonlyArray<ModelMessage>;
|
|
474
|
+
/** The zero-based index of the turn about to run. */
|
|
475
|
+
stepNumber: number;
|
|
476
|
+
/** The turns already completed this run. */
|
|
477
|
+
steps: ReadonlyArray<AgentStepInfo>;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Per-turn overrides {@link AgentConfig.prepareStep} may return. A returned
|
|
481
|
+
* `messages` array **replaces** the assembled history for that turn — the seam
|
|
482
|
+
* where history compaction lives.
|
|
483
|
+
* @experimental
|
|
484
|
+
*/
|
|
485
|
+
interface AgentPrepareStepResult {
|
|
486
|
+
/** Restrict the tools exposed to the model this turn (by name). */
|
|
487
|
+
activeTools?: ReadonlyArray<string>;
|
|
488
|
+
/** Replace the assembled messages for this turn (history compaction). */
|
|
489
|
+
messages?: ReadonlyArray<ModelMessage>;
|
|
490
|
+
/** Swap the model for this turn. */
|
|
491
|
+
model?: AgentModelInput;
|
|
492
|
+
/** Prepend a system message for this turn. */
|
|
493
|
+
system?: string;
|
|
494
|
+
/** Override the tool-choice strategy for this turn. */
|
|
495
|
+
toolChoice?: ToolChoice<ToolSet>;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Adjust the next turn before it runs — mirrors AI SDK's `prepareStep`. Invoked
|
|
499
|
+
* inside the turn's durable step so its effect is memoized on replay.
|
|
500
|
+
* @experimental
|
|
501
|
+
*/
|
|
502
|
+
type AgentPrepareStep = (input: AgentPrepareStepInput) => AgentPrepareStepResult | Promise<AgentPrepareStepResult | undefined> | undefined;
|
|
503
|
+
/**
|
|
504
|
+
* The subset of {@link AgentRunInput} an {@link AgentEmailMapper} returns to
|
|
505
|
+
* start a run from an inbound email.
|
|
506
|
+
* @experimental
|
|
507
|
+
*/
|
|
508
|
+
/**
|
|
509
|
+
* Where a triggered run should send its answer back to — captured by the
|
|
510
|
+
* mapper, from fields the verified inbound payload already carries, and carried
|
|
511
|
+
* through the run so {@link AgentConfig.onReply} can answer in the same place
|
|
512
|
+
* the question was asked.
|
|
513
|
+
*
|
|
514
|
+
* A discriminated union rather than an opaque blob because each channel threads
|
|
515
|
+
* differently: email by RFC 5322 `In-Reply-To`/`References`, Slack by
|
|
516
|
+
* `thread_ts`, GitHub by issue number, Discord by channel (plus the triggering
|
|
517
|
+
* message for a true reply).
|
|
518
|
+
* @experimental
|
|
519
|
+
*/
|
|
520
|
+
type AgentReplyRef = {
|
|
521
|
+
channel: "discord";
|
|
522
|
+
channelId: string;
|
|
523
|
+
messageId?: string;
|
|
524
|
+
} | {
|
|
525
|
+
channel: "email";
|
|
526
|
+
from: string;
|
|
527
|
+
messageId: string;
|
|
528
|
+
references?: string;
|
|
529
|
+
to: string[];
|
|
530
|
+
} | {
|
|
531
|
+
channel: "github";
|
|
532
|
+
commentId?: number;
|
|
533
|
+
issueNumber: number;
|
|
534
|
+
owner: string;
|
|
535
|
+
repo: string;
|
|
536
|
+
} | {
|
|
537
|
+
channel: "slack";
|
|
538
|
+
channelId: string;
|
|
539
|
+
threadTs: string;
|
|
540
|
+
};
|
|
541
|
+
interface AgentEmailRun {
|
|
542
|
+
/** The user message that starts (or continues) the thread — the model's prompt. */
|
|
543
|
+
input: string;
|
|
544
|
+
/**
|
|
545
|
+
* Verified owner of the thread (its RLS scope). SECURITY: inbound `from` is
|
|
546
|
+
* spoofable and the run dispatches RLS-bypassed — derive this from a verified
|
|
547
|
+
* signal (a DKIM-checked address, a mapped account), never blindly from
|
|
548
|
+
* `email.from`.
|
|
549
|
+
*/
|
|
550
|
+
owner?: string;
|
|
551
|
+
/**
|
|
552
|
+
* Where to send the answer — see {@link AgentReplyRef}. Populate it from the
|
|
553
|
+
* event the mapper already has (`captureEmailReplyRef(email)` does it for
|
|
554
|
+
* email) and the run calls {@link AgentConfig.onReply} with it once the
|
|
555
|
+
* final answer is ready. Omit it and the run simply never replies.
|
|
556
|
+
*/
|
|
557
|
+
replyRef?: AgentReplyRef;
|
|
558
|
+
/** The thread key — reuse to continue a conversation (e.g. a ticket id parsed from the subject). */
|
|
559
|
+
threadKey: string;
|
|
560
|
+
/** Optional thread title, set on first creation. */
|
|
561
|
+
title?: string;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Map an inbound email into an agent run, or `null`/`undefined` to DROP it (a
|
|
565
|
+
* failed DKIM/SPF/DMARC check, or a message not addressed to this agent). Wired
|
|
566
|
+
* by codegen onto the worker's top-level `email()` handler when set — see
|
|
567
|
+
* `@lunora/agent/inbound`.
|
|
568
|
+
*
|
|
569
|
+
* SECURITY: the parsed `from`/`subject`/body are attacker-controlled and the run
|
|
570
|
+
* is dispatched with RLS bypassed. Gate on `email.authentication` (DKIM/SPF/DMARC
|
|
571
|
+
* verdicts) here before returning a run, and treat every returned field as
|
|
572
|
+
* untrusted input.
|
|
573
|
+
* @experimental
|
|
574
|
+
*/
|
|
575
|
+
type AgentEmailMapper = (email: InboundEmail) => AgentEmailRun | null | Promise<AgentEmailRun | null | undefined> | undefined;
|
|
576
|
+
/** The inbound webhook channels an agent can be triggered from — see `@lunora/agent/channels`. */
|
|
577
|
+
type AgentInboundChannelKind = "discord" | "github" | "slack";
|
|
578
|
+
/** The run-input an inbound-channel mapper returns — the same shape as {@link AgentEmailRun}. */
|
|
579
|
+
type AgentChannelRun = AgentEmailRun;
|
|
580
|
+
/**
|
|
581
|
+
* The verified, parsed webhook event handed to an {@link AgentInboundChannel.map} mapper.
|
|
582
|
+
* @experimental
|
|
583
|
+
*/
|
|
584
|
+
interface InboundChannelEvent {
|
|
585
|
+
/** Which channel delivered it. */
|
|
586
|
+
channel: AgentInboundChannelKind;
|
|
587
|
+
/** The raw request headers. */
|
|
588
|
+
headers: Headers;
|
|
589
|
+
/** Parse the body as JSON (throws on malformed JSON). */
|
|
590
|
+
json: () => unknown;
|
|
591
|
+
/** The raw (verified) request body. */
|
|
592
|
+
rawBody: string;
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Map a VERIFIED inbound channel event into an agent run, or `null`/`undefined`
|
|
596
|
+
* to DECLINE it. Runs only AFTER the channel signature check passes.
|
|
597
|
+
*
|
|
598
|
+
* SECURITY: the payload is attacker-controlled and the run dispatches RLS
|
|
599
|
+
* bypassed. Derive the run `owner` from the verified channel identity (the
|
|
600
|
+
* workspace/installation the signing secret belongs to), never from an arbitrary
|
|
601
|
+
* payload field, and treat every returned field as untrusted input.
|
|
602
|
+
*/
|
|
603
|
+
type AgentChannelMapper = (event: InboundChannelEvent) => AgentChannelRun | null | Promise<AgentChannelRun | null | undefined> | undefined;
|
|
604
|
+
/**
|
|
605
|
+
* Trigger an agent from a verified inbound webhook. Codegen wires the developer's
|
|
606
|
+
* chosen HTTP route to `dispatchAgentChannel(...)` (from `@lunora/agent/channels`),
|
|
607
|
+
* which verifies the channel signature over the raw body before calling `map`.
|
|
608
|
+
*/
|
|
609
|
+
interface AgentInboundChannel {
|
|
610
|
+
/** Which channel this agent listens on (selects the signature scheme). */
|
|
611
|
+
channel: AgentInboundChannelKind;
|
|
612
|
+
/** Map a verified event to a run (or `null` to decline). */
|
|
613
|
+
map: AgentChannelMapper;
|
|
614
|
+
/**
|
|
615
|
+
* The verification credential, from `env`: an env-var NAME (string) or an
|
|
616
|
+
* `(env) => value` resolver. Slack → signing secret; GitHub → webhook secret;
|
|
617
|
+
* Discord → the application's Ed25519 public key (hex).
|
|
618
|
+
*/
|
|
619
|
+
secret: string | ((env: Record<string, unknown>) => string | undefined);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* `AgentConfig` is part of the experimental `@lunora/agent` API and may change without a major version bump.
|
|
623
|
+
* @experimental
|
|
624
|
+
*/
|
|
625
|
+
interface AgentConfig {
|
|
626
|
+
/** Restrict the tools the model may call, by name. Default: all tools. */
|
|
627
|
+
activeTools?: ReadonlyArray<string>;
|
|
628
|
+
/**
|
|
629
|
+
* How long a human-in-the-loop tool approval may stay pending before the
|
|
630
|
+
* run stops waiting — a Cloudflare Workflows duration: milliseconds, or a
|
|
631
|
+
* `"<n> <unit>"` string like `"3 days"` (the unit set is the host's, so a
|
|
632
|
+
* typo is a compile error). Default `"3 days"`.
|
|
633
|
+
*
|
|
634
|
+
* On timeout the call is treated as REJECTED (the run records why and
|
|
635
|
+
* continues down the normal rejection path), so a run whose approver never
|
|
636
|
+
* answers ends instead of hibernating forever.
|
|
637
|
+
*
|
|
638
|
+
* CLAMPED to one week. A longer wait would outlive the thread's
|
|
639
|
+
* abandoned-run horizon, letting a new run reclaim the thread while the
|
|
640
|
+
* approval is still pending — which is the exact failure this timeout
|
|
641
|
+
* exists to prevent, so it cannot be configured back into existence.
|
|
642
|
+
*/
|
|
643
|
+
approvalTimeout?: `${number} ${"day" | "hour" | "minute" | "month" | "second" | "week" | "year"}${"s" | ""}` | number;
|
|
644
|
+
/**
|
|
645
|
+
* Automatic thread-history compaction. When the persisted history exceeds
|
|
646
|
+
* `maxMessages`, the loop summarizes the older messages (all but the most
|
|
647
|
+
* recent `keepRecent`, default `ceil(maxMessages / 2)`) into one system-message
|
|
648
|
+
* brief and prompts the model with that brief plus the recent tail — keeping
|
|
649
|
+
* the context bounded as a conversation grows. The summary is produced inside
|
|
650
|
+
* the turn's memoized durable step (replay-safe) by `model` (a cheaper model
|
|
651
|
+
* may be set, else the agent's). Absent (the default) disables compaction —
|
|
652
|
+
* the full history is sent every turn, exactly as before. A manual
|
|
653
|
+
* {@link AgentConfig.prepareStep} still runs after and can override further.
|
|
654
|
+
*/
|
|
655
|
+
compaction?: {
|
|
656
|
+
/** How many most-recent messages to keep verbatim (default `ceil(maxMessages / 2)`). */
|
|
657
|
+
keepRecent?: number;
|
|
658
|
+
/** Compact once history exceeds this many messages. */
|
|
659
|
+
maxMessages: number;
|
|
660
|
+
/** Model for the summarization step (defaults to the agent's model). */
|
|
661
|
+
model?: AgentModelInput;
|
|
662
|
+
};
|
|
663
|
+
/**
|
|
664
|
+
* Seed the thread's synced state — a static, JSON-serializable object set on
|
|
665
|
+
* the thread row at creation only (first writer wins, like `owner`/`title`),
|
|
666
|
+
* so a `useAgentState` client sees it immediately. Keep it DETERMINISTIC (no
|
|
667
|
+
* `Date.now()`/`Math.random()`); it is written once by the durable bootstrap.
|
|
668
|
+
* Runtime-only config — invisible to codegen (agent-free and agent-ful
|
|
669
|
+
* `_generated/*` output is unchanged by its presence).
|
|
670
|
+
*/
|
|
671
|
+
initialState?: Record<string, unknown>;
|
|
672
|
+
/**
|
|
673
|
+
* System prompt prepended to every model call — a static string or a thunk
|
|
674
|
+
* derived from the run context (dynamic instructions).
|
|
675
|
+
*/
|
|
676
|
+
instructions?: string | ((context: AgentInstructionsContext) => string);
|
|
677
|
+
/** Cap the tokens generated per turn (AI SDK `maxOutputTokens`). */
|
|
678
|
+
maxOutputTokens?: number;
|
|
679
|
+
/**
|
|
680
|
+
* Cost/step cap: maximum LLM turns per run. Default 8. Composes with
|
|
681
|
+
* {@link AgentConfig.stopWhen} — the loop ends when EITHER triggers.
|
|
682
|
+
*/
|
|
683
|
+
maxTurns?: number;
|
|
684
|
+
/** Retrieval-augmented memory — see {@link AgentMemoryOptions}. */
|
|
685
|
+
memory?: AgentMemoryOptions;
|
|
686
|
+
/** The model — see {@link AgentModelInput}. */
|
|
687
|
+
model: AgentModelInput;
|
|
688
|
+
/**
|
|
689
|
+
* Optional override for the deployed workflow name (`wrangler.jsonc`
|
|
690
|
+
* `workflows[].name`). Defaults to `agent-<kebab-cased export name>`. Does
|
|
691
|
+
* NOT change the binding name, which is always derived from the export
|
|
692
|
+
* name (`support` → `AGENT_SUPPORT`).
|
|
693
|
+
*/
|
|
694
|
+
name?: string;
|
|
695
|
+
/**
|
|
696
|
+
* Policy when a run starts on a thread that already has a DIFFERENT run in
|
|
697
|
+
* flight (the thread's `status` is `"running"` under another workflow
|
|
698
|
+
* instance) — the guard that stops two runs from interleaving messages on
|
|
699
|
+
* the shared per-thread seq counter:
|
|
700
|
+
*
|
|
701
|
+
* - `"reject"` (default) — fail the new run fast with a `CONFLICT` error.
|
|
702
|
+
* - `"replace"` — terminate the in-flight instance and take the thread over.
|
|
703
|
+
* - `"queue"` — park the new run behind the one in flight (FIFO, up to five deep) and hibernate it until the thread is handed over.
|
|
704
|
+
*
|
|
705
|
+
* Each parked run is a live workflow instance waiting on an event, so the
|
|
706
|
+
* queue's depth cap is a real resource bound: past it, a start is rejected
|
|
707
|
+
* exactly as `"reject"` would. A `"replace"` arriving later supersedes the
|
|
708
|
+
* run in FLIGHT, not the queue — parked runs still take their turn after it.
|
|
709
|
+
*
|
|
710
|
+
* A dispatch with no instance id (the inbound-email / inbound-channel
|
|
711
|
+
* paths) cannot be parked, because nothing later can tell it apart from
|
|
712
|
+
* another such dispatch to wake it; `"queue"` rejects those.
|
|
713
|
+
*
|
|
714
|
+
* A workflow REPLAY re-enters the bootstrap under the SAME instance id and
|
|
715
|
+
* is never a concurrent run (the guard compares the stored instance id).
|
|
716
|
+
*/
|
|
717
|
+
onConcurrentRun?: "queue" | "reject" | "replace";
|
|
718
|
+
/**
|
|
719
|
+
* Map an inbound email into an agent run — see {@link AgentEmailMapper}.
|
|
720
|
+
* When set, codegen wires this agent onto the worker's top-level `email()`
|
|
721
|
+
* handler (via `@lunora/agent/inbound`) so a received message starts a
|
|
722
|
+
* durable run. Return `null`/`undefined` to drop the message.
|
|
723
|
+
*/
|
|
724
|
+
onEmail?: AgentEmailMapper;
|
|
725
|
+
/**
|
|
726
|
+
* Trigger this agent from a verified inbound webhook (Slack / GitHub /
|
|
727
|
+
* Discord) — see {@link AgentInboundChannel}. Mount `dispatchAgentChannel(...)`
|
|
728
|
+
* (from `@lunora/agent/channels`) on an HTTP route; it verifies the channel
|
|
729
|
+
* signature over the raw body before calling `map`.
|
|
730
|
+
*/
|
|
731
|
+
onInbound?: AgentInboundChannel;
|
|
732
|
+
/**
|
|
733
|
+
* Called with the final answer when the run was triggered from a channel
|
|
734
|
+
* that gave it a `replyRef` — see {@link AgentOnReply}. This is how an
|
|
735
|
+
* inbound-triggered agent answers where it was asked.
|
|
736
|
+
*/
|
|
737
|
+
onReply?: AgentOnReply;
|
|
738
|
+
/** Called after each LLM turn — see {@link AgentOnStepFinish}. */
|
|
739
|
+
onStepFinish?: AgentOnStepFinish;
|
|
740
|
+
/**
|
|
741
|
+
* Structured final answer: a zod schema or `jsonSchema(...)`. When set the
|
|
742
|
+
* loop runs the model with AI SDK `Output.object({ schema })` and returns
|
|
743
|
+
* the parsed object in {@link AgentRunResult.output}.
|
|
744
|
+
*/
|
|
745
|
+
output?: FlexibleSchema<unknown>;
|
|
746
|
+
/** Adjust the next turn before it runs — see {@link AgentPrepareStep}. */
|
|
747
|
+
prepareStep?: AgentPrepareStep;
|
|
748
|
+
/**
|
|
749
|
+
* Opt this agent into being STARTED over the public RPC boundary — i.e. via
|
|
750
|
+
* the auto-registered `agents:agentRun` mutation an HTTP-only client (e.g.
|
|
751
|
+
* the `@lunora/mcp` server) calls. Default `false`: an agent is startable
|
|
752
|
+
* only from server-side app code (`ctx.agents.<name>.run(...)`), so declaring
|
|
753
|
+
* an agent does NOT expose it to arbitrary RPC callers. Fail-closed — the run
|
|
754
|
+
* mutation refuses an agent that has not opted in, regardless of any MCP-side
|
|
755
|
+
* `allowAgents` configuration. A started thread is still owner-scoped to
|
|
756
|
+
* `ctx.auth.userId`. Deploy configuration codegen reads STATICALLY (a boolean
|
|
757
|
+
* literal), so it must be inline in the `defineAgent({ ... })` object literal.
|
|
758
|
+
*/
|
|
759
|
+
publicRun?: boolean;
|
|
760
|
+
/**
|
|
761
|
+
* Repair a malformed tool call the model emits (AI SDK
|
|
762
|
+
* `experimental_repairToolCall`): given the failing `{ toolCall, error, tools,
|
|
763
|
+
* inputSchema, messages, system }`, return a corrected tool call or `null` to
|
|
764
|
+
* give up. Runs inside the model turn, so keep it deterministic (it re-runs on
|
|
765
|
+
* a replay). Unset (the default) leaves the loop's existing
|
|
766
|
+
* unknown-tool-name recovery as the only repair path.
|
|
767
|
+
*/
|
|
768
|
+
repairToolCall?: ToolCallRepairFunction<ToolSet>;
|
|
769
|
+
/**
|
|
770
|
+
* Reusable {@link SkillDefinition}s to compose in — each contributes an
|
|
771
|
+
* instruction fragment, tools (merged into the flat namespace; collisions
|
|
772
|
+
* throw), and retrieval `knowledge` (its own keyed memory source). Folded at
|
|
773
|
+
* declaration time; the tool namespace and memory sources on the returned
|
|
774
|
+
* {@link AgentDefinition} already reflect the merge.
|
|
775
|
+
*/
|
|
776
|
+
skills?: ReadonlyArray<SkillDefinition>;
|
|
777
|
+
/**
|
|
778
|
+
* Extra loop-stop conditions (AI SDK `StopCondition`s). Composes with
|
|
779
|
+
* {@link AgentConfig.maxTurns} — the loop ends when EITHER triggers.
|
|
780
|
+
*/
|
|
781
|
+
stopWhen?: ReadonlyArray<StopCondition<ToolSet>> | StopCondition<ToolSet>;
|
|
782
|
+
/** Passed to `generateText` as its stable `telemetry` option. */
|
|
783
|
+
telemetry?: TelemetryOptions;
|
|
784
|
+
/** Sampling temperature forwarded to the model. */
|
|
785
|
+
temperature?: number;
|
|
786
|
+
/** Tool-choice strategy (AI SDK `ToolChoice`). Default: `"auto"`. */
|
|
787
|
+
toolChoice?: ToolChoice<ToolSet>;
|
|
788
|
+
/** The tools the model may call, by name. */
|
|
789
|
+
tools?: Record<string, AnyAgentTool>;
|
|
790
|
+
/**
|
|
791
|
+
* Opt into a real-time voice session — see {@link AgentVoiceConfig}. Its mere
|
|
792
|
+
* presence tells codegen to emit the `VOICE_...` hibernatable-WebSocket DO
|
|
793
|
+
* (`VoiceSessionDO` subclass) and the `api.agents.{name}Voice` client
|
|
794
|
+
* reference; an agent without it is byte-identical to before. Runtime config
|
|
795
|
+
* for the in-DO pipeline (models/voice) rides the same object.
|
|
796
|
+
*/
|
|
797
|
+
voice?: AgentVoiceConfig;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Opt an agent into a real-time VOICE session — a dedicated hibernatable-
|
|
801
|
+
* WebSocket Durable Object (`VoiceSessionDO`) that runs a per-turn
|
|
802
|
+
* STT→LLM→TTS pipeline IN-DO and SHARES the agent's existing thread tables
|
|
803
|
+
* (`agent_threads`/`agent_messages`) via the runtime dispatch seam. Presence of
|
|
804
|
+
* this block is what codegen keys on to emit the `VOICE_...` DO class + the
|
|
805
|
+
* `api.agents.{name}Voice` client reference; agents without it are byte-
|
|
806
|
+
* identical.
|
|
807
|
+
*
|
|
808
|
+
* v1 slice: conversational turns only. In-DO voice turns are NOT replay-durable
|
|
809
|
+
* and get NO Workflow tool-loop — tool calls are deferred. All fields are
|
|
810
|
+
* optional; the defaults target Workers AI (`@cf/openai/whisper-large-v3-turbo`
|
|
811
|
+
* for STT, `@cf/deepgram/aura-1` for TTS).
|
|
812
|
+
* @experimental
|
|
813
|
+
*/
|
|
814
|
+
interface AgentVoiceConfig {
|
|
815
|
+
/**
|
|
816
|
+
* Server→client audio container. `"mp3"` (default) matches the TTS model's
|
|
817
|
+
* native stream; carried to the client so it decodes the returned frames
|
|
818
|
+
* with the right codec.
|
|
819
|
+
*/
|
|
820
|
+
audioFormat?: "mp3" | "wav";
|
|
821
|
+
/**
|
|
822
|
+
* Spoken on connect before the first user turn — a fixed greeting synthesized
|
|
823
|
+
* through the TTS model. Omit for a silent-until-spoken-to session.
|
|
824
|
+
*
|
|
825
|
+
* Synthesized once per THREAD, not once per socket: the greeting's persisted
|
|
826
|
+
* row is keyed per thread, and a reconnect onto a thread that already exists
|
|
827
|
+
* gets the `ready` frame without paying for the same line again.
|
|
828
|
+
*/
|
|
829
|
+
greeting?: string;
|
|
830
|
+
/**
|
|
831
|
+
* Cap on how many turns one voice socket may run before it is closed with
|
|
832
|
+
* code `4002`. Every turn is a full LLM generation plus sentence-by-sentence
|
|
833
|
+
* TTS — billed and persisted — on a hibernatable socket that can live for
|
|
834
|
+
* days, and the one-turn-in-flight guard throttles nothing. Defaults to 100;
|
|
835
|
+
* a client that hits the cap reconnects for a fresh budget.
|
|
836
|
+
*/
|
|
837
|
+
maxTurns?: number;
|
|
838
|
+
/**
|
|
839
|
+
* TTS voice/speaker id forwarded to the TTS model (e.g. a Deepgram Aura voice
|
|
840
|
+
* like `"aura-asteria-en"`). Model-specific; omitted when unset so the model
|
|
841
|
+
* uses its own default voice.
|
|
842
|
+
*/
|
|
843
|
+
speaker?: string;
|
|
844
|
+
/**
|
|
845
|
+
* Speech-to-text model id (a Workers AI id, resolved via `env.AI`). Defaults
|
|
846
|
+
* to `@cf/openai/whisper-large-v3-turbo` — batch per-utterance transcription
|
|
847
|
+
* (the client marks utterance boundaries; continuous STT is deferred).
|
|
848
|
+
*/
|
|
849
|
+
stt?: string;
|
|
850
|
+
/**
|
|
851
|
+
* Text-to-speech model id (a Workers AI id, resolved via `env.AI`). Defaults
|
|
852
|
+
* to `@cf/deepgram/aura-1` — streamed MP3 synthesized sentence-by-sentence
|
|
853
|
+
* from the LLM's token stream.
|
|
854
|
+
*/
|
|
855
|
+
tts?: string;
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* The input the parent model provides when delegating to a sub-agent tool.
|
|
859
|
+
* @experimental
|
|
860
|
+
*/
|
|
861
|
+
interface AgentSubToolInput {
|
|
862
|
+
/** The task or question to hand to the sub-agent. */
|
|
863
|
+
prompt: string;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Options for {@link AgentDefinition.asTool} (`agent.asTool(...)`).
|
|
867
|
+
* @experimental
|
|
868
|
+
*/
|
|
869
|
+
interface AgentAsToolOptions {
|
|
870
|
+
/** What the sub-agent does — shown to the parent's model (it decides from it). */
|
|
871
|
+
description: string;
|
|
872
|
+
/** Cap on child-run status polls before giving up — a positive integer. Default 600 (with the 500 ms default interval, a five-minute budget). */
|
|
873
|
+
maxPolls?: number;
|
|
874
|
+
/**
|
|
875
|
+
* The child agent's export name — selects its `AGENT_<NAME>` Workflow
|
|
876
|
+
* binding (e.g. `"researcher"` → `AGENT_RESEARCHER`). The model-facing tool
|
|
877
|
+
* name is the KEY assigned in the parent's `tools` map, not this.
|
|
878
|
+
*/
|
|
879
|
+
name: string;
|
|
880
|
+
/** Delay (ms) between child-run status polls. Default 500. */
|
|
881
|
+
pollIntervalMs?: number;
|
|
882
|
+
/**
|
|
883
|
+
* Test seam replacing the between-poll wait. Production uses a real timer;
|
|
884
|
+
* tests inject an immediate resolve so polling runs without wall-clock delay.
|
|
885
|
+
*/
|
|
886
|
+
wait?: (ms: number) => Promise<void>;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* A `defineAgent` result — config plus the brand codegen discovers.
|
|
890
|
+
* @experimental
|
|
891
|
+
*/
|
|
892
|
+
interface AgentDefinition extends AgentConfig {
|
|
893
|
+
/**
|
|
894
|
+
* Adapt this agent into a tool a PARENT agent can call: the returned tool's
|
|
895
|
+
* `execute` starts a durable child run on the named agent's Workflow binding
|
|
896
|
+
* and returns its final answer. Mirrors `@lunora/ai/rag`'s `asTool()`. The
|
|
897
|
+
* child run is correlated by a replay-stable `threadKey` derived from the
|
|
898
|
+
* parent's `toolCallId`, so a retried step reuses the same sub-run.
|
|
899
|
+
*/
|
|
900
|
+
asTool: (options: AgentAsToolOptions) => AgentToolDefinition<AgentSubToolInput, string>;
|
|
901
|
+
/** Runtime brand check (see `isAgentDefinition`). */
|
|
902
|
+
readonly isLunoraAgent: true;
|
|
903
|
+
/**
|
|
904
|
+
* The keyed memory sources the loop dispatches per run — the merge of
|
|
905
|
+
* {@link AgentConfig.memory} (as `key: "default"`) and each skill's
|
|
906
|
+
* `knowledge`. Populated by `defineAgent`; `memory` stays on the config for
|
|
907
|
+
* back-compat and direct authoring. When absent the loop falls back to
|
|
908
|
+
* `memory` alone (preserving the historic `"memory:retrieve"` step).
|
|
909
|
+
*/
|
|
910
|
+
memorySources?: ReadonlyArray<AgentMemorySource>;
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* What `agentEnsureThread` reports back to the loop.
|
|
914
|
+
*
|
|
915
|
+
* A discriminated union rather than a bag of optional booleans: the four
|
|
916
|
+
* outcomes are mutually exclusive, and the data each carries only exists for its
|
|
917
|
+
* own case. `queued` has a position, `replaced` has the instance it took the
|
|
918
|
+
* thread from, and the other two have nothing — encoding that as five
|
|
919
|
+
* independent optional fields made every reader re-derive which combination was
|
|
920
|
+
* legal.
|
|
921
|
+
* @experimental
|
|
922
|
+
*/
|
|
923
|
+
type EnsureThreadOutcome = {
|
|
924
|
+
outcome: "continued" | "created";
|
|
925
|
+
} | {
|
|
926
|
+
outcome: "queued";
|
|
927
|
+
position: number;
|
|
928
|
+
} | {
|
|
929
|
+
outcome: "replaced";
|
|
930
|
+
priorInstanceId: string;
|
|
931
|
+
};
|
|
932
|
+
/**
|
|
933
|
+
* Params of one agent run (the compiled workflow's payload).
|
|
934
|
+
* @experimental
|
|
935
|
+
*/
|
|
936
|
+
interface AgentRunInput {
|
|
937
|
+
/**
|
|
938
|
+
* Sub-agent delegation depth. A run a user starts omits it (0); each
|
|
939
|
+
* `agent.asTool` hop stamps its child one deeper, and the tool refuses to
|
|
940
|
+
* delegate past the bound — `maxTurns` bounds one level's turns, this bounds
|
|
941
|
+
* the TREE (every hop mints a distinct child `threadKey`, so the per-thread
|
|
942
|
+
* run-queue cap never applies across them).
|
|
943
|
+
*/
|
|
944
|
+
depth?: number;
|
|
945
|
+
/** The user message that starts (or continues) the thread. */
|
|
946
|
+
input: string;
|
|
947
|
+
/**
|
|
948
|
+
* Verified identity of the thread owner — pass `ctx.auth.userId` from the
|
|
949
|
+
* mutation/action starting the run. An owned thread's public queries
|
|
950
|
+
* (`agents:agentThread` / `agents:agentMessages`) only answer for this
|
|
951
|
+
* identity; omitting it leaves the thread readable by anyone who knows the
|
|
952
|
+
* key (only appropriate for single-tenant/anonymous apps). Immutable after
|
|
953
|
+
* the first run.
|
|
954
|
+
*/
|
|
955
|
+
owner?: string;
|
|
956
|
+
/** Where a triggered run sends its answer — see {@link AgentReplyRef}. */
|
|
957
|
+
replyRef?: AgentReplyRef;
|
|
958
|
+
/** The thread key — reuse to continue a conversation. */
|
|
959
|
+
threadKey: string;
|
|
960
|
+
/** Optional thread title, set on first creation. */
|
|
961
|
+
title?: string;
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Output of one agent run (the compiled workflow's return value).
|
|
965
|
+
* @experimental
|
|
966
|
+
*/
|
|
967
|
+
interface AgentRunResult {
|
|
968
|
+
/** The parsed structured answer, when {@link AgentConfig.output} is set. */
|
|
969
|
+
output?: unknown;
|
|
970
|
+
/**
|
|
971
|
+
* Why the run ended: a final answer, a `stopWhen` condition, or the
|
|
972
|
+
* `maxTurns` cap.
|
|
973
|
+
*/
|
|
974
|
+
stopped: "final" | "maxTurns" | "stopCondition";
|
|
975
|
+
/** The final assistant text (absent when stopped by `maxTurns`). */
|
|
976
|
+
text?: string;
|
|
977
|
+
/** LLM turns consumed. */
|
|
978
|
+
turns: number;
|
|
979
|
+
/** Cumulative token usage across the run's turns, when the model reported it. */
|
|
980
|
+
usage?: AgentUsage;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Function paths of the agent runtime functions (the `agentComponent()`
|
|
984
|
+
* functions the app re-exports from `lunora/agents.ts`, so codegen registers
|
|
985
|
+
* them under the `agents:` namespace).
|
|
986
|
+
* @experimental
|
|
987
|
+
*/
|
|
988
|
+
interface AgentFunctionPaths {
|
|
989
|
+
appendMessage: string;
|
|
990
|
+
/**
|
|
991
|
+
* The internal `agents:agentCompleteRun` mutation the loop dispatches at the
|
|
992
|
+
* end of a run: it writes the terminal status AND hands the thread to the
|
|
993
|
+
* next queued run in the same mutation.
|
|
994
|
+
*/
|
|
995
|
+
completeRun: string;
|
|
996
|
+
/**
|
|
997
|
+
* The internal `agents:agentDeleteMessage` mutation the loop dispatches to
|
|
998
|
+
* retire the HITL approval marker once the decision has landed.
|
|
999
|
+
*/
|
|
1000
|
+
deleteMessage: string;
|
|
1001
|
+
ensureThread: string;
|
|
1002
|
+
/** The internal `agents:agentEpisodeRecall` query the loop dispatches for an episodic-kind read. */
|
|
1003
|
+
episodeRecall: string;
|
|
1004
|
+
/** The internal `agents:agentEpisodeUpsert` mutation the loop dispatches on run-end episode extraction. */
|
|
1005
|
+
episodeUpsert: string;
|
|
1006
|
+
/** The internal `agents:agentGraphTraverse` query the loop dispatches for a graph-kind read. */
|
|
1007
|
+
graphTraverse: string;
|
|
1008
|
+
/** The internal `agents:agentGraphUpsert` mutation the loop dispatches on run-end graph extraction. */
|
|
1009
|
+
graphUpsert: string;
|
|
1010
|
+
listMessages: string;
|
|
1011
|
+
patchThread: string;
|
|
1012
|
+
/**
|
|
1013
|
+
* The public `agents:agentRun` mutation an HTTP-only client (e.g. the
|
|
1014
|
+
* `@lunora/mcp` server) calls to START a durable run — owner-scoped to
|
|
1015
|
+
* `ctx.auth.userId`, deterministic (the caller supplies `threadKey`).
|
|
1016
|
+
*/
|
|
1017
|
+
run: string;
|
|
1018
|
+
/** The internal `agents:agentSetState` mutation the loop dispatches for `setState`. */
|
|
1019
|
+
setState: string;
|
|
1020
|
+
/** The public owner-gated `agents:agentState` query (`getState` + `useAgentState`). */
|
|
1021
|
+
state: string;
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Approval lifecycle marker on a message: `"awaiting_approval"` on the
|
|
1025
|
+
* placeholder written while a run pauses on a gated tool, then `"approved"` /
|
|
1026
|
+
* `"rejected"` on the tool result once a client resolves it. Absent on ordinary
|
|
1027
|
+
* messages. `"awaiting_approval"` rows are filtered out of the model prompt.
|
|
1028
|
+
* @experimental
|
|
1029
|
+
*/
|
|
1030
|
+
type AgentMessageStatus = "approved" | "awaiting_approval" | "rejected";
|
|
1031
|
+
/**
|
|
1032
|
+
* One persisted thread message, as the loop reads it back.
|
|
1033
|
+
* @experimental
|
|
1034
|
+
*/
|
|
1035
|
+
interface AgentMessageRow {
|
|
1036
|
+
content: string;
|
|
1037
|
+
role: "assistant" | "system" | "tool" | "user";
|
|
1038
|
+
seq: number;
|
|
1039
|
+
/** Approval lifecycle marker — see {@link AgentMessageStatus}. */
|
|
1040
|
+
status?: AgentMessageStatus;
|
|
1041
|
+
toolCallId?: string;
|
|
1042
|
+
toolCalls?: ReadonlyArray<AgentToolCall>;
|
|
1043
|
+
toolName?: string;
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* One model-issued tool call.
|
|
1047
|
+
* @experimental
|
|
1048
|
+
*/
|
|
1049
|
+
interface AgentToolCall {
|
|
1050
|
+
id: string;
|
|
1051
|
+
input: unknown;
|
|
1052
|
+
/**
|
|
1053
|
+
* Why this call was REJECTED before it could run: the model's arguments
|
|
1054
|
+
* failed the tool's input schema, or did not parse as JSON. Present only on
|
|
1055
|
+
* a rejected call — the loop records it as a recoverable tool result and
|
|
1056
|
+
* never executes the tool, because the `input` above is the raw value the
|
|
1057
|
+
* provider sent, not a validated one.
|
|
1058
|
+
*/
|
|
1059
|
+
invalid?: string;
|
|
1060
|
+
name: string;
|
|
1061
|
+
}
|
|
1062
|
+
/**
|
|
1063
|
+
* Normalized result of one LLM turn (the `generate` seam's return value).
|
|
1064
|
+
* @experimental
|
|
1065
|
+
*/
|
|
1066
|
+
interface AgentGenerateResult {
|
|
1067
|
+
/** The parsed structured answer, when {@link AgentConfig.output} is set. */
|
|
1068
|
+
output?: unknown;
|
|
1069
|
+
text: string;
|
|
1070
|
+
toolCalls: ReadonlyArray<AgentToolCall>;
|
|
1071
|
+
/** Token usage the model reported for this turn. */
|
|
1072
|
+
usage?: AgentUsage;
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
1075
|
+
* Options passed to the {@link AgentGenerate} seam for one LLM turn.
|
|
1076
|
+
* @experimental
|
|
1077
|
+
*/
|
|
1078
|
+
interface AgentGenerateOptions {
|
|
1079
|
+
/** Restrict the tools exposed to the model this turn (by name). */
|
|
1080
|
+
activeTools?: ReadonlyArray<string>;
|
|
1081
|
+
/** The assembled conversation for this turn. */
|
|
1082
|
+
messages: ReadonlyArray<unknown>;
|
|
1083
|
+
/** A per-turn model override (from {@link AgentConfig.prepareStep}). */
|
|
1084
|
+
model?: LanguageModel;
|
|
1085
|
+
/**
|
|
1086
|
+
* Abort the in-flight turn (the streaming seam forwards it to `streamText`'s
|
|
1087
|
+
* `abortSignal`). On abort the streaming seam returns the text streamed so
|
|
1088
|
+
* far rather than rejecting, so a barge-in can persist the spoken prefix.
|
|
1089
|
+
* The durable (non-streaming) loop never sets it.
|
|
1090
|
+
*/
|
|
1091
|
+
signal?: AbortSignal;
|
|
1092
|
+
/** A per-turn tool-choice override. */
|
|
1093
|
+
toolChoice?: ToolChoice<ToolSet>;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* The LLM-turn seam: given the assembled conversation, return the model's
|
|
1097
|
+
* decision. Production wires AI SDK `generateText`; tests inject a script.
|
|
1098
|
+
* @experimental
|
|
1099
|
+
*/
|
|
1100
|
+
type AgentGenerate = (options: AgentGenerateOptions) => Promise<AgentGenerateResult>;
|
|
1101
|
+
/**
|
|
1102
|
+
* The entities and relations extracted from one run's exchange, upserted into
|
|
1103
|
+
* the owner-scoped graph. Endpoint names in `relations` reference entities by
|
|
1104
|
+
* name (normalized on write); `confidence` (0..1) seeds an edge's weight.
|
|
1105
|
+
*/
|
|
1106
|
+
interface AgentGraphExtraction {
|
|
1107
|
+
entities: ReadonlyArray<{
|
|
1108
|
+
name: string;
|
|
1109
|
+
type?: string;
|
|
1110
|
+
}>;
|
|
1111
|
+
relations: ReadonlyArray<{
|
|
1112
|
+
confidence?: number;
|
|
1113
|
+
dst: string;
|
|
1114
|
+
label: string;
|
|
1115
|
+
src: string;
|
|
1116
|
+
}>;
|
|
1117
|
+
}
|
|
1118
|
+
/**
|
|
1119
|
+
* The run-end graph-extraction seam: given the run's exchange (user input +
|
|
1120
|
+
* final answer) and the model to run it on, return the extracted entities and
|
|
1121
|
+
* relations. Production wires AI SDK `generateObject` over a fixed schema
|
|
1122
|
+
* (`createGraphExtract`); the durable loop calls it inside a memoized
|
|
1123
|
+
* `memory:extract` step so the model never re-runs on replay. Absent (the
|
|
1124
|
+
* default) disables extraction, so an agent with no graph memory — and every
|
|
1125
|
+
* unit test that doesn't opt in — is byte-identical.
|
|
1126
|
+
*/
|
|
1127
|
+
type AgentGraphExtract = (input: {
|
|
1128
|
+
/** The run's final assistant answer. */
|
|
1129
|
+
assistantText: string;
|
|
1130
|
+
/** The Worker env, for resolving a Workers AI model id. */
|
|
1131
|
+
env: Record<string, unknown>;
|
|
1132
|
+
/** The extraction model (the source's `extractionModel`, else the agent's). */
|
|
1133
|
+
model: AgentModelInput;
|
|
1134
|
+
/** The user message that started the run. */
|
|
1135
|
+
userInput: string;
|
|
1136
|
+
}) => Promise<AgentGraphExtraction>;
|
|
1137
|
+
/**
|
|
1138
|
+
* The history-compaction seam: given the OLDER model messages to condense (and
|
|
1139
|
+
* the model to run it on), return a summary brief. Production wires AI SDK
|
|
1140
|
+
* `generateText` (`createCompact`); the loop calls it INSIDE the turn's memoized
|
|
1141
|
+
* `llm:turn:N` step so the summarization is replay-safe. Absent (the default)
|
|
1142
|
+
* disables compaction, so an agent with no `compaction` config — and every unit
|
|
1143
|
+
* test that doesn't opt in — is byte-identical.
|
|
1144
|
+
*/
|
|
1145
|
+
type AgentCompact = (input: {
|
|
1146
|
+
/** The Worker env, for resolving a Workers AI model id. */
|
|
1147
|
+
env: Record<string, unknown>;
|
|
1148
|
+
/** The older conversation messages to summarize (already assembled). */
|
|
1149
|
+
messages: ModelMessage[];
|
|
1150
|
+
/** The compaction model (the config's `model`, else the agent's). */
|
|
1151
|
+
model: AgentModelInput;
|
|
1152
|
+
}) => Promise<string>;
|
|
1153
|
+
/** The one-line summary an episodic-memory run records for later recency recall. */
|
|
1154
|
+
interface AgentEpisodeExtraction {
|
|
1155
|
+
summary: string;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* The run-end episode-extraction seam: given the run's exchange and the model to
|
|
1159
|
+
* run it on, return a one/two-sentence summary. Production wires AI SDK
|
|
1160
|
+
* `generateText` (`createEpisodeExtract`); the durable loop calls it inside a
|
|
1161
|
+
* memoized `memory:episode` step so the model never re-runs on replay. Absent
|
|
1162
|
+
* (the default) disables extraction, so an agent with no episodic memory — and
|
|
1163
|
+
* every unit test that doesn't opt in — is byte-identical.
|
|
1164
|
+
*/
|
|
1165
|
+
type AgentEpisodeExtract = (input: {
|
|
1166
|
+
/** The run's final assistant answer. */
|
|
1167
|
+
assistantText: string;
|
|
1168
|
+
/** The Worker env, for resolving a Workers AI model id. */
|
|
1169
|
+
env: Record<string, unknown>;
|
|
1170
|
+
/** The extraction model (the source's `extractionModel`, else the agent's). */
|
|
1171
|
+
model: AgentModelInput;
|
|
1172
|
+
/** The user message that started the run. */
|
|
1173
|
+
userInput: string;
|
|
1174
|
+
}) => Promise<AgentEpisodeExtraction>;
|
|
1175
|
+
/**
|
|
1176
|
+
* A live token delta produced while a turn streams. Ephemeral — deltas are
|
|
1177
|
+
* pushed to the sink as the model generates and are NEVER replayed (the
|
|
1178
|
+
* persisted assistant message is the single source of truth). Keyed by
|
|
1179
|
+
* `threadKey` + the zero-based `turn` so a client can correlate a delta to the
|
|
1180
|
+
* in-flight turn.
|
|
1181
|
+
* @experimental
|
|
1182
|
+
*/
|
|
1183
|
+
interface AgentTokenDelta {
|
|
1184
|
+
/**
|
|
1185
|
+
* Discriminates the token arm of {@link AgentLiveEvent}. Optional and left
|
|
1186
|
+
* unset on the wire (a token is the default) so the existing emit site and
|
|
1187
|
+
* its structural test assertions stay byte-identical; narrow the union with
|
|
1188
|
+
* `event.kind === "progress"` to isolate the {@link AgentProgressEvent} arm.
|
|
1189
|
+
*/
|
|
1190
|
+
kind?: "token";
|
|
1191
|
+
/** The incremental text chunk the model just produced. */
|
|
1192
|
+
text: string;
|
|
1193
|
+
/** The thread this delta belongs to. */
|
|
1194
|
+
threadKey: string;
|
|
1195
|
+
/** The zero-based index of the turn producing the delta. */
|
|
1196
|
+
turn: number;
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* A live, ephemeral progress event a tool emits via
|
|
1200
|
+
* {@link AgentToolContext.reportProgress} while its durable step runs. Rides the
|
|
1201
|
+
* SAME live-only sink as {@link AgentTokenDelta} — NEVER persisted, NEVER
|
|
1202
|
+
* replayed. Correlated to the in-flight tool call (and its persisted tool row)
|
|
1203
|
+
* by `toolCallId` rather than a turn index, since a single turn can fan out many
|
|
1204
|
+
* tool calls.
|
|
1205
|
+
* @experimental
|
|
1206
|
+
*/
|
|
1207
|
+
interface AgentProgressEvent {
|
|
1208
|
+
/** The arbitrary, JSON-serializable payload the tool reported. */
|
|
1209
|
+
data: unknown;
|
|
1210
|
+
/** Discriminates the progress arm of {@link AgentLiveEvent}. */
|
|
1211
|
+
kind: "progress";
|
|
1212
|
+
/** The thread this event belongs to. */
|
|
1213
|
+
threadKey: string;
|
|
1214
|
+
/** The tool call this progress belongs to (correlates with the persisted tool row). */
|
|
1215
|
+
toolCallId: string;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* A single event on the agent's live-only channel: either a streamed token
|
|
1219
|
+
* {@link AgentTokenDelta} (turn-keyed) or a tool {@link AgentProgressEvent}
|
|
1220
|
+
* (`toolCallId`-keyed). Both are ephemeral and never replayed — the persisted
|
|
1221
|
+
* thread messages remain the single source of truth. Discriminate on `kind`
|
|
1222
|
+
* (`"progress"` for the progress arm; token deltas leave it unset).
|
|
1223
|
+
* @experimental
|
|
1224
|
+
*/
|
|
1225
|
+
type AgentLiveEvent = AgentProgressEvent | AgentTokenDelta;
|
|
1226
|
+
/**
|
|
1227
|
+
* A live-only sink for the agent's ephemeral channel — streamed token deltas
|
|
1228
|
+
* ({@link AgentTokenDelta}) and tool progress events
|
|
1229
|
+
* ({@link AgentProgressEvent}). The runtime provides it (teeing to the existing
|
|
1230
|
+
* stream transport); tests capture it. Invoked ONLY on the first execution of a
|
|
1231
|
+
* turn's / tool's durable step — a workflow replay serves the memoized result
|
|
1232
|
+
* without re-running the body, so no event is re-emitted.
|
|
1233
|
+
*
|
|
1234
|
+
* At-least-once caveat: if a turn's step *fails mid-stream* (before it commits)
|
|
1235
|
+
* the workflow retries the not-yet-memoized step and re-tees that turn's deltas
|
|
1236
|
+
* from scratch. This is the standard durable-step retry contract, not a replay
|
|
1237
|
+
* of a completed turn. Consumers should therefore reset/dedupe accumulated text
|
|
1238
|
+
* per `threadKey`+`turn` boundary so a step retry cannot visually double-append;
|
|
1239
|
+
* the persisted assistant message remains the single source of truth.
|
|
1240
|
+
* @experimental
|
|
1241
|
+
*/
|
|
1242
|
+
type AgentTokenSink = (event: AgentLiveEvent) => void;
|
|
1243
|
+
/**
|
|
1244
|
+
* The streaming LLM-turn seam: like {@link AgentGenerate} but tees each text
|
|
1245
|
+
* delta to `onDelta` as the model produces it, then resolves the SAME
|
|
1246
|
+
* {@link AgentGenerateResult} the non-streaming seam returns — so the value the
|
|
1247
|
+
* durable `llm:turn:N` step memoizes (and persists) is identical whether the
|
|
1248
|
+
* turn streamed or not. Production wires AI SDK `streamText`; tests inject a
|
|
1249
|
+
* script. Deltas are live-only — a workflow replay never re-invokes the seam.
|
|
1250
|
+
* @experimental
|
|
1251
|
+
*/
|
|
1252
|
+
type AgentStreamGenerate = (options: AgentGenerateOptions, onDelta: (text: string) => void) => Promise<AgentGenerateResult>;
|
|
1253
|
+
/**
|
|
1254
|
+
* Spec entry codegen emits per agent: `{ binding: "AGENT_SUPPORT", exportName: "support" }`.
|
|
1255
|
+
* @experimental
|
|
1256
|
+
*/
|
|
1257
|
+
interface AgentBindingSpec {
|
|
1258
|
+
binding: string;
|
|
1259
|
+
exportName: string;
|
|
1260
|
+
/**
|
|
1261
|
+
* Whether the app author opted this agent into public run-starts (via
|
|
1262
|
+
* `defineAgent({ publicRun: true })`) — codegen emits it only when `true`.
|
|
1263
|
+
* Carried onto the {@link AgentHandle} so the public `agents:agentRun`
|
|
1264
|
+
* mutation gates on it fail-closed: an agent without it can be started only
|
|
1265
|
+
* from server-side app code, never by an external RPC client.
|
|
1266
|
+
*/
|
|
1267
|
+
publicRun?: boolean;
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* The lifecycle status stored on an agent thread: `"running"` while a run is in
|
|
1271
|
+
* flight, `"idle"` after it finishes (or stops on a condition), `"error"` on a
|
|
1272
|
+
* terminal failure, `"cancelled"` when a run was terminated via
|
|
1273
|
+
* {@link AgentHandle.cancel}, and `"awaiting_input"` while the run is paused on
|
|
1274
|
+
* a human-in-the-loop tool approval. Mirrored by the `status` `v.union` in
|
|
1275
|
+
* `component.ts`.
|
|
1276
|
+
* @experimental
|
|
1277
|
+
*/
|
|
1278
|
+
type AgentThreadStatus = "awaiting_input" | "cancelled" | "error" | "idle" | "running";
|
|
1279
|
+
/**
|
|
1280
|
+
* Structural subset of a Cloudflare Workflow instance the producer surface needs.
|
|
1281
|
+
* @experimental
|
|
1282
|
+
*/
|
|
1283
|
+
interface AgentWorkflowInstanceLike {
|
|
1284
|
+
/** Deliver an external event to the running instance (resumes a `waitForEvent`). */
|
|
1285
|
+
sendEvent: (event: {
|
|
1286
|
+
payload: unknown;
|
|
1287
|
+
type: string;
|
|
1288
|
+
}) => Promise<void>;
|
|
1289
|
+
status: () => Promise<unknown>;
|
|
1290
|
+
terminate: () => Promise<void>;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Structural subset of a Cloudflare Workflow binding the producer surface needs.
|
|
1294
|
+
* @experimental
|
|
1295
|
+
*/
|
|
1296
|
+
interface AgentWorkflowBindingLike {
|
|
1297
|
+
create: (options?: {
|
|
1298
|
+
id?: string;
|
|
1299
|
+
params?: unknown;
|
|
1300
|
+
}) => Promise<{
|
|
1301
|
+
id: string;
|
|
1302
|
+
}>;
|
|
1303
|
+
get: (id: string) => Promise<AgentWorkflowInstanceLike>;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* A started agent run (a workflow instance).
|
|
1307
|
+
* @experimental
|
|
1308
|
+
*/
|
|
1309
|
+
interface AgentRunHandle {
|
|
1310
|
+
/** The workflow instance id. */
|
|
1311
|
+
id: string;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* The `ctx.agents.<name>` producer handle.
|
|
1315
|
+
* @experimental
|
|
1316
|
+
*/
|
|
1317
|
+
interface AgentHandle {
|
|
1318
|
+
/**
|
|
1319
|
+
* Cancel a run by its workflow instance id: terminate the instance and mark
|
|
1320
|
+
* its thread `"cancelled"`. Safe to call on an already-finished run.
|
|
1321
|
+
*/
|
|
1322
|
+
cancel: (id: string) => Promise<void>;
|
|
1323
|
+
/**
|
|
1324
|
+
* Whether this agent may be started over the PUBLIC `agents:agentRun`
|
|
1325
|
+
* mutation (the `defineAgent({ publicRun: true })` opt-in, carried from the
|
|
1326
|
+
* codegen wiring spec). `false` by default — the run mutation refuses it
|
|
1327
|
+
* fail-closed, so declaring an agent never exposes it to arbitrary RPC
|
|
1328
|
+
* callers. Does not affect the server-side `run(...)` path below.
|
|
1329
|
+
*/
|
|
1330
|
+
publicRun: boolean;
|
|
1331
|
+
/** Start a durable agent run for a thread. */
|
|
1332
|
+
run: (input: AgentRunInput, options?: {
|
|
1333
|
+
id?: string;
|
|
1334
|
+
}) => Promise<AgentRunHandle>;
|
|
1335
|
+
/**
|
|
1336
|
+
* Deliver an external event to a run by its workflow instance id — the path
|
|
1337
|
+
* `agents:agentResolveApproval` uses to resume a run paused on a
|
|
1338
|
+
* human-in-the-loop tool approval.
|
|
1339
|
+
*/
|
|
1340
|
+
sendEvent: (id: string, event: {
|
|
1341
|
+
payload: unknown;
|
|
1342
|
+
type: string;
|
|
1343
|
+
}) => Promise<void>;
|
|
1344
|
+
/** Read a run's workflow status by instance id. */
|
|
1345
|
+
status: (id: string) => Promise<unknown>;
|
|
1346
|
+
}
|
|
1347
|
+
export { AgentVoiceConfig as $, AgentDefinition as A, AgentApprovalContext as B, AgentEmailMapper as C, AgentEmailRun as D, AgentGenerateOptions as E, AgentGenerateResult as F, AgentInstructionsContext as G, AgentLiveEvent as H, InboundChannelEvent as I, AgentMemoryOptions as J, AgentMemorySource as K, AgentMessageStatus as L, AgentOnReply as M, AgentOnStepFinish as N, AgentPrepareStep as O, AgentPrepareStepInput as P, AgentPrepareStepResult as Q, AgentProgressEvent as R, SkillMarkdownExtras as S, AgentReplyInfo as T, AgentRunHandle as U, AgentStepFinishInfo as V, AgentStepInfo as W, AgentThreadStatus as X, AgentTokenDelta as Y, AgentToolCall as Z, AgentUsage as _, AgentReplyRef as a, AgentWorkflowBindingLike as a0, AgentWorkflowInstanceLike as a1, EnsureThreadOutcome as a2, AgentToolContext as b, AgentToolDefinition as c, SkillDefinition as d, AgentCompact as e, AgentEpisodeExtract as f, AgentGraphExtract as g, AgentGenerate as h, AgentTokenSink as i, AgentRunInput as j, AgentFunctionPaths as k, AgentRunFunction as l, AgentStepLike as m, AgentStreamGenerate as n, AgentRunResult as o, AgentMessageRow as p, AgentConfig as q, AnyAgentTool as r, AgentAsToolOptions as s, AgentSubToolInput as t, AgentBindingSpec as u, AgentHandle as v, AgentToolConfig as w, AgentFunctionReference as x, AgentModelInput as y, SkillConfig as z };
|