@intentface/latch-core 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ /**
2
+ * extension.ts — the extension model.
3
+ *
4
+ * An extension attaches handlers to MOMENTS of a turn. A handler returns an
5
+ * EFFECT, never a rewritten world; `undefined` means no effect. The effect type
6
+ * for each moment says exactly what is possible there, so you cannot do
7
+ * everything everywhere, and several extensions on one moment compose by a
8
+ * rule declared once per moment (see `compose.ts`).
9
+ *
10
+ * WHAT PERSISTS. The stored `UIMessage[]` is the only durable thing; everything
11
+ * else is recomputed each step. Three moments change it:
12
+ *
13
+ * before_agent_start injects or replaces messages
14
+ * step_end appends parts (append-only; never rewrites)
15
+ * tool_result the override IS the stored result, and what the model reads
16
+ *
17
+ * The rest are lenses and vanish after the step:
18
+ *
19
+ * instructions, context what the MODEL sees
20
+ * render what the CUSTOMER sees, live AND on load
21
+ *
22
+ * So: mask a secret in `tool_result` and it is gone everywhere (usually right).
23
+ * Use `render` only for the rarer case where the model should keep the truth
24
+ * and the person should not see it.
25
+ *
26
+ * Ported from `experiment/agents-sdk`, where the same model ran 118/119 of its
27
+ * suite over a plain `streamText` loop — the moments do not depend on who owns
28
+ * the loop, which is why they can sit over `ToolLoopAgent` here.
29
+ */
30
+ import type { Instructions, LanguageModel, ModelMessage, ToolSet, UIMessage, UIMessagePart } from "ai";
31
+ /**
32
+ * Who a run is for, as an extension sees it. EMPTY on purpose, and augmented by
33
+ * the application:
34
+ *
35
+ * declare module "@intentface/latch-core" {
36
+ * interface Principal { orgId: string; userId: string }
37
+ * }
38
+ *
39
+ * Augmentation rather than a `<P>` type parameter, because a parameter has to
40
+ * be repeated at every declaration that touches it — the runtime's own `<P>`
41
+ * already rides on twenty-odd exported symbols, and every moment type would
42
+ * inherit the noise. One `declare module` gives the same type safety with no
43
+ * generics in any handler signature. The trade is one principal shape per
44
+ * process, which is what an application has anyway. The runtime hands its `P`
45
+ * through as this type; the two describe the same object.
46
+ *
47
+ * The framework never reads a field of this. Isolation is enforced wherever
48
+ * the principal is USED — the storage owner key, a connection lookup.
49
+ *
50
+ * CONTRACT: principals are persisted with a run and replayed on resume or a
51
+ * scheduled fire, possibly months later. Durable identifiers only: no secrets
52
+ * or tokens, no PII beyond ids, nothing volatile like roles or flags.
53
+ */
54
+ export interface Principal {
55
+ }
56
+ /** What is known before the first step: no step number yet, no messages of its own. */
57
+ export interface RunContext {
58
+ runId: string;
59
+ principal: Principal;
60
+ }
61
+ export interface StepContext extends RunContext {
62
+ stepNumber: number;
63
+ /** The stored messages at this step. */
64
+ messages: UIMessage[];
65
+ }
66
+ /**
67
+ * Token counts for one step. The cache split is carried separately because
68
+ * cached input is billed at a fraction of the normal rate, so a cost extension
69
+ * that only sees `inputTokens` overcharges every cached turn.
70
+ */
71
+ export interface StepUsage {
72
+ inputTokens?: number;
73
+ outputTokens?: number;
74
+ totalTokens?: number;
75
+ /** Cached input tokens read. Billed at a discount by most providers. */
76
+ cacheReadTokens?: number;
77
+ /** Input tokens written to the cache. Usually billed at a premium. */
78
+ cacheWriteTokens?: number;
79
+ reasoningTokens?: number;
80
+ }
81
+ /** What a completed step cost and how it ended. The basis for evals and budgets. */
82
+ export interface StepInfo extends StepContext {
83
+ usage: StepUsage;
84
+ finishReason: string;
85
+ model: {
86
+ provider: string;
87
+ modelId: string;
88
+ };
89
+ durationMs: number;
90
+ toolCalls: Array<{
91
+ toolName: string;
92
+ toolCallId: string;
93
+ }>;
94
+ }
95
+ export type Part = UIMessagePart<any, any>;
96
+ /**
97
+ * `replace` is how a store says "ignore what the client sent, this is the
98
+ * truth". `inject` adds a message WITHOUT disturbing the response anchor: it
99
+ * lands immediately before the final message, so a synthetic message can never
100
+ * become the assistant message the run streams into. See `compose.messagesIn`.
101
+ */
102
+ export type StartEffect = {
103
+ inject?: UIMessage;
104
+ } | {
105
+ replace?: UIMessage[];
106
+ } | undefined | void;
107
+ /** Append-only: nothing can rewrite the past. History only grows. */
108
+ export type StepEffect = {
109
+ append?: Part[];
110
+ } | undefined | void;
111
+ /**
112
+ * `append` adds a paragraph of text. `replace` hands over the whole
113
+ * `Instructions` value, which may be a `SystemModelMessage[]` carrying
114
+ * `providerOptions` — that is how prompt-cache breakpoints are set on the
115
+ * system prompt, so this deliberately is NOT narrowed to `string`.
116
+ */
117
+ export type InstructionsEffect = {
118
+ append?: string;
119
+ } | {
120
+ replace?: Instructions;
121
+ } | undefined | void;
122
+ export type ContextEffect = {
123
+ messages?: ModelMessage[];
124
+ } | undefined | void;
125
+ /** One rule over a part, applied to the live stream AND to stored messages on load. */
126
+ export type ViewEffect = {
127
+ hide?: true;
128
+ } | {
129
+ replace?: Part;
130
+ } | undefined | void;
131
+ export type ToolCallEffect = {
132
+ block?: true;
133
+ reason?: string;
134
+ } | undefined | void;
135
+ export type ToolResultEffect = {
136
+ output?: unknown;
137
+ isError?: boolean;
138
+ } | undefined | void;
139
+ /**
140
+ * `transform` rewrites the text before the run starts. `handled` skips the
141
+ * model entirely — `reply` becomes the assistant's answer, so the conversation
142
+ * still reads normally.
143
+ *
144
+ * `replace` exists because a command that answers directly may also need to
145
+ * rewrite what came before (`/compact` is the case); deciding the reply and
146
+ * the rewrite in one place keeps them from disagreeing about whether anything
147
+ * happened.
148
+ */
149
+ export type InputEffect = {
150
+ transform?: string;
151
+ } | {
152
+ handled: true;
153
+ reply?: string | Part[];
154
+ replace?: UIMessage[];
155
+ } | undefined | void;
156
+ /**
157
+ * Before each model call. `stop` ends the run before this step; `model` and
158
+ * `activeTools` apply to this step only.
159
+ */
160
+ export type StepControlEffect = {
161
+ stop?: true;
162
+ model?: LanguageModel;
163
+ activeTools?: string[];
164
+ } | undefined | void;
165
+ /**
166
+ * EVERY moment receives identity. That is the point of carrying the principal
167
+ * on the turn rather than letting each extension close over one: a per-tenant
168
+ * toolset, a per-tenant veto, a per-tenant redaction rule and per-tenant error
169
+ * reporting all read the SAME principal, so two extensions cannot disagree
170
+ * about who a run is for.
171
+ *
172
+ * It arrives as a trailing `run` argument, which existing handlers may ignore:
173
+ * a function with fewer parameters stays assignable.
174
+ */
175
+ export interface Hooks {
176
+ /** User text arrived, before anything runs. Earliest per-message moment. */
177
+ input?: Array<(info: {
178
+ text: string;
179
+ source: "user" | "extension";
180
+ messages: UIMessage[];
181
+ }, run: RunContext) => InputEffect | Promise<InputEffect>>;
182
+ /** First run of a conversation (no assistant message yet). Before before_agent_start. */
183
+ session_start?: Array<(messages: UIMessage[], run: RunContext) => StartEffect | Promise<StartEffect>>;
184
+ /**
185
+ * Every run, before the first step. Receives identity as well as messages: a
186
+ * store has to know who a chat belongs to at CLAIM time, not at the first
187
+ * step, or a scheduled run has nothing to rebuild the principal from.
188
+ */
189
+ before_agent_start?: Array<(messages: UIMessage[], run: RunContext) => StartEffect | Promise<StartEffect>>;
190
+ step_end?: Array<(messages: UIMessage[], info: StepInfo) => StepEffect | Promise<StepEffect>>;
191
+ instructions?: Array<(current: Instructions | undefined, ctx: StepContext) => InstructionsEffect | Promise<InstructionsEffect>>;
192
+ context?: Array<(messages: ModelMessage[], ctx: StepContext) => ContextEffect | Promise<ContextEffect>>;
193
+ /**
194
+ * Identity is OPTIONAL here alone, because `renderTranscript` runs on a load
195
+ * route where no run exists. Pass it when the caller knows it, which an
196
+ * authenticated route does.
197
+ */
198
+ render?: Array<(part: Part, ctx: {
199
+ stored: boolean;
200
+ } & Partial<RunContext>) => ViewEffect>;
201
+ register_tools?: Array<(tools: ToolSet, run: RunContext) => ToolSet>;
202
+ tool_call?: Array<(info: {
203
+ toolName: string;
204
+ toolCallId: string;
205
+ input: unknown;
206
+ }, run: RunContext) => ToolCallEffect | Promise<ToolCallEffect>>;
207
+ tool_result?: Array<(info: {
208
+ toolName: string;
209
+ toolCallId: string;
210
+ input: unknown;
211
+ output: unknown;
212
+ isError: boolean;
213
+ source: "server" | "client";
214
+ }, run: RunContext) => ToolResultEffect | Promise<ToolResultEffect>>;
215
+ step_start?: Array<(ctx: StepContext) => StepControlEffect | Promise<StepControlEffect>>;
216
+ /** The run failed. Fires before agent_end, which still fires. */
217
+ error?: Array<(info: {
218
+ error: unknown;
219
+ stepNumber: number;
220
+ messages: UIMessage[];
221
+ }, run: RunContext) => void | Promise<void>>;
222
+ agent_end?: Array<(info: {
223
+ runId: string;
224
+ principal: Principal;
225
+ messages: UIMessage[];
226
+ stepCount: number;
227
+ isAborted: boolean;
228
+ error?: unknown;
229
+ usage: StepUsage;
230
+ }) => void | Promise<void>>;
231
+ }
232
+ export type Moment = keyof Hooks;
233
+ /** What a command answers with: text, parts, or parts plus a rewritten history. */
234
+ export type CommandReply = string | Part[] | {
235
+ reply?: string | Part[];
236
+ replace?: UIMessage[];
237
+ };
238
+ export interface CommandOptions {
239
+ description: string;
240
+ /**
241
+ * Answer directly, no model call.
242
+ *
243
+ * `ctx.messages` is the FULL stored conversation, because commands run after
244
+ * the start moments — a client that posts only its newest turn does not
245
+ * shrink what a command can see.
246
+ */
247
+ handler?: (args: string, ctx: {
248
+ messages: UIMessage[];
249
+ }) => CommandReply | Promise<CommandReply>;
250
+ /** Or rewrite the user's text and let the run proceed. */
251
+ expand?: (args: string) => string | Promise<string>;
252
+ }
253
+ export interface RegisteredCommand extends CommandOptions {
254
+ name: string;
255
+ extension: string;
256
+ }
257
+ export interface Extension {
258
+ name: string;
259
+ hooks: Hooks;
260
+ commands: RegisteredCommand[];
261
+ /**
262
+ * Fail CLOSED. A throwing handler on a critical extension fails the run
263
+ * instead of being reported and skipped.
264
+ *
265
+ * Default is false, because one broken feature must never break the loop.
266
+ * But that rule is wrong for anything the run's correctness depends on: a
267
+ * persistence extension whose database is down has NOT "produced no effect",
268
+ * it has lost the step. Mark those critical.
269
+ */
270
+ critical?: boolean;
271
+ }
272
+ /** What the client needs to render a command palette. */
273
+ export declare const listCommands: (extensions: Extension[]) => Array<{
274
+ name: string;
275
+ description: string;
276
+ extension: string;
277
+ }>;
278
+ export interface ContextOptions {
279
+ /** 'step' every call · 'run' once per request (default) · 'session' once per conversation, stored */
280
+ scope?: "step" | "run" | "session";
281
+ /** 'before-user' (default) prepends to the latest user message · 'system' appends to instructions */
282
+ position?: "before-user" | "system";
283
+ }
284
+ type Handler<K extends Moment> = NonNullable<Hooks[K]>[number];
285
+ export interface ExtensionAPI {
286
+ /** Attach a handler to a moment. Several handlers on one moment run in order. */
287
+ on<K extends Moment>(moment: K, handler: Handler<K>): void;
288
+ /** Add a tool the model can call. */
289
+ registerTool(name: string, definition: ToolSet[string]): void;
290
+ /**
291
+ * A slash command. Sugar over `on('input', …)`: matches `/name …` and passes
292
+ * the rest as args. Return text from `handler` to answer without calling the
293
+ * model, or a string from `expand` to rewrite the prompt and let it run.
294
+ */
295
+ registerCommand(name: string, options: CommandOptions): void;
296
+ /**
297
+ * Change what the CUSTOMER sees, live and on load, without touching storage
298
+ * or the model's view. To hide a value from everyone, use `tool_result`.
299
+ */
300
+ registerRenderer(fn: Handler<"render">): void;
301
+ /**
302
+ * Intent: give the model information. Assembles the moments for you. The
303
+ * provider sees the transcript and the run — `run.principal` is how a
304
+ * per-tenant context is derived.
305
+ */
306
+ context(provider: (ctx: {
307
+ messages: UIMessage[];
308
+ run: RunContext;
309
+ }) => string | undefined | Promise<string | undefined>, options?: ContextOptions): void;
310
+ /** Intent: tell the customer something. The model never sees it. */
311
+ notify(provider: (info: StepInfo) => string | undefined | Promise<string | undefined>): void;
312
+ }
313
+ /** A conversation has not started while nothing has been said back. */
314
+ export declare const isNewSession: (messages: UIMessage[]) => boolean;
315
+ export interface DefineExtensionOptions {
316
+ /** See `Extension.critical`. */
317
+ critical?: boolean;
318
+ }
319
+ export declare function defineExtension(name: string, setup: (ext: ExtensionAPI) => void, options?: DefineExtensionOptions): Extension;
320
+ export declare function defineExtension(name: string, setup: (ext: ExtensionAPI) => Promise<void>, options?: DefineExtensionOptions): Promise<Extension>;
321
+ export {};
322
+ //# sourceMappingURL=extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../../src/extensions/extension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,OAAO,EACP,SAAS,EACT,aAAa,EACd,MAAM,IAAI,CAAC;AAEZ;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,SAAS;CAAG;AAE7B,uFAAuF;AACvF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,QAAQ,EAAE,SAAS,EAAE,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,oFAAoF;AACpF,MAAM,WAAW,QAAS,SAAQ,WAAW;IAC3C,KAAK,EAAE,SAAS,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAED,MAAM,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAI3C;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,OAAO,CAAC,EAAE,SAAS,EAAE,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAAC;AAEhG,qEAAqE;AACrE,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAAC;AAEhE;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,OAAO,CAAC,EAAE,YAAY,CAAA;CAAE,GAC1B,SAAS,GACT,IAAI,CAAC;AAET,MAAM,MAAM,aAAa,GAAG;IAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAAC;AAE7E,uFAAuF;AACvF,MAAM,MAAM,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,OAAO,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAAC;AAEjF,MAAM,MAAM,cAAc,GAAG;IAAE,KAAK,CAAC,EAAE,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAAC;AAElF,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,GAAG,IAAI,CAAC;AAE1F;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAA;CAAE,GACjE,SAAS,GACT,IAAI,CAAC;AAET;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,CAAC,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC9D,SAAS,GACT,IAAI,CAAC;AAET;;;;;;;;;GASG;AACH,MAAM,WAAW,KAAK;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,KAAK,CACX,CACE,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,GAAG,WAAW,CAAC;QAC7B,QAAQ,EAAE,SAAS,EAAE,CAAC;KACvB,EACD,GAAG,EAAE,UAAU,KACZ,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CACxC,CAAC;IACF,yFAAyF;IACzF,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,UAAU,KAAK,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACtG;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,UAAU,KAAK,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IAC3G,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,KAAK,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9F,YAAY,CAAC,EAAE,KAAK,CAClB,CACE,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,GAAG,EAAE,WAAW,KACb,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CACtD,CAAC;IACF,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,EAAE,GAAG,EAAE,WAAW,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;IACxG;;;;OAIG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,UAAU,CAAC,CAAC;IAC3F,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,UAAU,KAAK,OAAO,CAAC,CAAC;IACrE,SAAS,CAAC,EAAE,KAAK,CACf,CACE,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,OAAO,CAAC;KAChB,EACD,GAAG,EAAE,UAAU,KACZ,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAC9C,CAAC;IACF,WAAW,CAAC,EAAE,KAAK,CACjB,CACE,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,OAAO,CAAC;QACf,MAAM,EAAE,OAAO,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;KAC7B,EACD,GAAG,EAAE,UAAU,KACZ,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAClD,CAAC;IACF,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACzF,iEAAiE;IACjE,KAAK,CAAC,EAAE,KAAK,CACX,CACE,IAAI,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,SAAS,EAAE,CAAA;KAAE,EACnE,GAAG,EAAE,UAAU,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAC1B,CAAC;IACF,SAAS,CAAC,EAAE,KAAK,CACf,CAAC,IAAI,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,SAAS,CAAC;QACrB,QAAQ,EAAE,SAAS,EAAE,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE,OAAO,CAAC;QAChB,KAAK,EAAE,SAAS,CAAC;KAClB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAC3B,CAAC;CACH;AAED,MAAM,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC;AAEjC,mFAAmF;AACnF,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,IAAI,EAAE,GACN;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,CAAC;IAAC,OAAO,CAAC,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAEvD,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,SAAS,EAAE,CAAA;KAAE,KAAK,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACjG,0DAA0D;IAC1D,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yDAAyD;AACzD,eAAO,MAAM,YAAY,GACvB,YAAY,SAAS,EAAE,KACtB,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAG9D,CAAC;AAIJ,MAAM,WAAW,cAAc;IAC7B,qGAAqG;IACrG,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;IACnC,qGAAqG;IACrG,QAAQ,CAAC,EAAE,aAAa,GAAG,QAAQ,CAAC;CACrC;AAED,KAAK,OAAO,CAAC,CAAC,SAAS,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/D,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,EAAE,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC3D,qCAAqC;IACrC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9D;;;;OAIG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC7D;;;OAGG;IACH,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC;IAC9C;;;;OAIG;IACH,OAAO,CACL,QAAQ,EAAE,CAAC,GAAG,EAAE;QAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QAAC,GAAG,EAAE,UAAU,CAAA;KAAE,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,EAC/G,OAAO,CAAC,EAAE,cAAc,GACvB,IAAI,CAAC;IACR,oEAAoE;IACpE,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;CAC9F;AAED,uEAAuE;AACvE,eAAO,MAAM,YAAY,GAAI,UAAU,SAAS,EAAE,YAAkD,CAAC;AAErG,MAAM,WAAW,sBAAsB;IACrC,gCAAgC;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,EAClC,OAAO,CAAC,EAAE,sBAAsB,GAC/B,SAAS,CAAC;AACb,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,EAC3C,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * extension.ts — the extension model.
3
+ *
4
+ * An extension attaches handlers to MOMENTS of a turn. A handler returns an
5
+ * EFFECT, never a rewritten world; `undefined` means no effect. The effect type
6
+ * for each moment says exactly what is possible there, so you cannot do
7
+ * everything everywhere, and several extensions on one moment compose by a
8
+ * rule declared once per moment (see `compose.ts`).
9
+ *
10
+ * WHAT PERSISTS. The stored `UIMessage[]` is the only durable thing; everything
11
+ * else is recomputed each step. Three moments change it:
12
+ *
13
+ * before_agent_start injects or replaces messages
14
+ * step_end appends parts (append-only; never rewrites)
15
+ * tool_result the override IS the stored result, and what the model reads
16
+ *
17
+ * The rest are lenses and vanish after the step:
18
+ *
19
+ * instructions, context what the MODEL sees
20
+ * render what the CUSTOMER sees, live AND on load
21
+ *
22
+ * So: mask a secret in `tool_result` and it is gone everywhere (usually right).
23
+ * Use `render` only for the rarer case where the model should keep the truth
24
+ * and the person should not see it.
25
+ *
26
+ * Ported from `experiment/agents-sdk`, where the same model ran 118/119 of its
27
+ * suite over a plain `streamText` loop — the moments do not depend on who owns
28
+ * the loop, which is why they can sit over `ToolLoopAgent` here.
29
+ */
30
+ /** What the client needs to render a command palette. */
31
+ export const listCommands = (extensions) => extensions.flatMap((e) => e.commands.map((c) => ({ name: c.name, description: c.description, extension: c.extension })));
32
+ /** A conversation has not started while nothing has been said back. */
33
+ export const isNewSession = (messages) => !messages.some((m) => m.role === "assistant");
34
+ export function defineExtension(name, setup, options = {}) {
35
+ const hooks = {};
36
+ const on = (k, fn) => {
37
+ (hooks[k] ??= []).push(fn);
38
+ };
39
+ const registered = {};
40
+ const commands = [];
41
+ const api = {
42
+ on,
43
+ registerTool: (n, d) => {
44
+ registered[n] = d;
45
+ },
46
+ registerCommand: (cmdName, opts) => {
47
+ // Caught here, at registration, rather than as a throw the moment a user
48
+ // types the command — which a non-critical extension would swallow,
49
+ // sending the raw `/name` to the model as if nothing had matched.
50
+ if (!opts.handler && !opts.expand)
51
+ throw new Error(`Command "/${cmdName}" on extension "${name}" needs a handler or an expand.`);
52
+ commands.push({ ...opts, name: cmdName, extension: name });
53
+ on("input", async ({ text, messages }) => {
54
+ if (text !== `/${cmdName}` && !text.startsWith(`/${cmdName} `))
55
+ return;
56
+ const args = text.slice(cmdName.length + 1).trim();
57
+ if (opts.expand)
58
+ return { transform: await opts.expand(args) };
59
+ const answer = await opts.handler(args, { messages });
60
+ if (typeof answer === "string" || Array.isArray(answer))
61
+ return { handled: true, reply: answer };
62
+ return { handled: true, reply: answer.reply, replace: answer.replace };
63
+ });
64
+ },
65
+ registerRenderer: (fn) => on("render", fn),
66
+ context(provider, { scope = "run", position = "before-user" } = {}) {
67
+ const type = `data-ctx-${name}`;
68
+ const place = (m, text) => {
69
+ const i = m.map((x) => x.role).lastIndexOf("user");
70
+ if (i < 0)
71
+ return;
72
+ const u = m[i];
73
+ const content = typeof u.content === "string" ? [{ type: "text", text: u.content }] : u.content;
74
+ return {
75
+ messages: [
76
+ ...m.slice(0, i),
77
+ { ...u, content: [{ type: "text", text }, ...content] },
78
+ ...m.slice(i + 1),
79
+ ],
80
+ };
81
+ };
82
+ const find = (msgs) => {
83
+ for (const m of msgs)
84
+ for (const p of m.parts)
85
+ if (p.type === type)
86
+ return String(p.data);
87
+ return undefined;
88
+ };
89
+ if (scope === "session") {
90
+ on("before_agent_start", async (msgs, run) => {
91
+ if (find(msgs))
92
+ return;
93
+ const text = await provider({ messages: msgs, run });
94
+ if (text == null)
95
+ return;
96
+ // Injected BEFORE the final message by `compose.messagesIn`, so this
97
+ // never becomes the assistant message the run streams into.
98
+ return {
99
+ inject: {
100
+ id: crypto.randomUUID(),
101
+ role: "assistant",
102
+ parts: [{ type, data: text }],
103
+ },
104
+ };
105
+ });
106
+ on("render", (p) => (p.type === type ? { hide: true } : undefined));
107
+ if (position === "system")
108
+ on("instructions", (_, ctx) => {
109
+ const t = find(ctx.messages);
110
+ return t ? { append: t } : undefined;
111
+ });
112
+ else
113
+ on("context", (m, ctx) => {
114
+ const t = find(ctx.messages);
115
+ return t ? place(m, t) : undefined;
116
+ });
117
+ return;
118
+ }
119
+ // One extension object serves every turn of a runtime, concurrently. A
120
+ // run-scoped value is therefore keyed by run id — a single variable would
121
+ // let an overlapping turn's context reach the wrong model call. Populated
122
+ // at the start moment when there is one; a continuation (resume, decision)
123
+ // skips those and fills its entry on first use. Released at the end.
124
+ const perRun = new Map();
125
+ const valueFor = (msgs, run) => {
126
+ let p = perRun.get(run.runId);
127
+ if (!p) {
128
+ p = Promise.resolve(provider({ messages: msgs, run }));
129
+ perRun.set(run.runId, p);
130
+ }
131
+ return p;
132
+ };
133
+ if (scope === "run") {
134
+ on("before_agent_start", (msgs, run) => {
135
+ perRun.set(run.runId, Promise.resolve(provider({ messages: msgs, run })));
136
+ });
137
+ on("agent_end", ({ runId }) => {
138
+ perRun.delete(runId);
139
+ });
140
+ }
141
+ const get = (msgs, run) => scope === "run" ? valueFor(msgs, run) : provider({ messages: msgs, run });
142
+ if (position === "system")
143
+ on("instructions", async (_, ctx) => {
144
+ const t = await get(ctx.messages, ctx);
145
+ return t ? { append: t } : undefined;
146
+ });
147
+ else
148
+ on("context", async (m, ctx) => {
149
+ const t = await get(ctx.messages, ctx);
150
+ return t ? place(m, t) : undefined;
151
+ });
152
+ },
153
+ notify(provider) {
154
+ const type = `data-notice-${name}`;
155
+ on("step_end", async (_msgs, info) => {
156
+ const text = await provider(info);
157
+ return text == null ? undefined : { append: [{ type, data: text }] };
158
+ });
159
+ },
160
+ };
161
+ const finish = () => {
162
+ if (Object.keys(registered).length)
163
+ on("register_tools", (t) => ({ ...t, ...registered }));
164
+ return { name, hooks, commands, critical: options.critical ?? false };
165
+ };
166
+ // An async factory is awaited once, before anything runs — the pattern for
167
+ // one-time startup work (load config, open a client, discover models).
168
+ const result = setup(api);
169
+ return result instanceof Promise ? result.then(finish) : finish();
170
+ }
171
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../../src/extensions/extension.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The extension seam: moments, effects, the composer, and the tool wiring.
3
+ *
4
+ * `RuntimeConfig.extensions` and `AgentConfig.extensions` take `Extension[]`.
5
+ * Omit both and the runtime behaves exactly as before — every moment is a
6
+ * no-op with no handlers attached.
7
+ */
8
+ export * from "./extension.js";
9
+ export * from "./compose.js";
10
+ export * from "./tools.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The extension seam: moments, effects, the composer, and the tool wiring.
3
+ *
4
+ * `RuntimeConfig.extensions` and `AgentConfig.extensions` take `Extension[]`.
5
+ * Omit both and the runtime behaves exactly as before — every moment is a
6
+ * no-op with no handlers attached.
7
+ */
8
+ export * from "./extension.js";
9
+ export * from "./compose.js";
10
+ export * from "./tools.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extensions/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * tools.ts — the tool-side wiring: `tool_call` vetoes and `tool_result`
3
+ * rewrites over a toolset, plus the small transcript helpers the runtime uses
4
+ * to apply start-moment and step-end effects.
5
+ */
6
+ import type { ToolSet, UIMessage } from "ai";
7
+ import type { Composed } from "./compose.js";
8
+ import type { Part, RunContext } from "./extension.js";
9
+ /** What a blocked tool call returns to the model. A result, not a throw, so the reason survives. */
10
+ export interface BlockedToolOutput {
11
+ blocked: true;
12
+ reason: string;
13
+ }
14
+ /**
15
+ * Wrap every executable tool so `tool_call` can veto and `tool_result` can
16
+ * rewrite what gets stored. With no listener on either moment the toolset is
17
+ * returned untouched — the identity of every tool object is preserved.
18
+ *
19
+ * The AI SDK's `executeTool` sniffs synchronously for `Symbol.asyncIterator`,
20
+ * so a wrapper that returns a promise turns a streaming tool into a
21
+ * non-streaming one: no progress chunks, and a generator object as the output.
22
+ * Two paths follow from that, chosen once per run:
23
+ *
24
+ * no `tool_call` listener call `execute` synchronously, pass the iterable
25
+ * through, hold one value back so the FINAL yield
26
+ * is the hooked one. Streaming preserved.
27
+ * a `tool_call` listener the veto is async and must resolve before
28
+ * `execute` runs, so the result is a promise and
29
+ * progress chunks are collapsed. A gated tool
30
+ * cannot also stream; that is the trade.
31
+ */
32
+ export declare function wrapTools(tools: ToolSet, hooks: Composed, run: RunContext): ToolSet;
33
+ /**
34
+ * Fire `tool_result` for outputs a CLIENT produced.
35
+ *
36
+ * A client tool has no server `execute`, so the wrapper above never sees it:
37
+ * its output arrives in a later request, already in the transcript. Without
38
+ * this, an extension that inspects or redacts tool results silently covers
39
+ * server tools only, while the type promises both.
40
+ *
41
+ * Only parts after the last `step-start` are considered — the same slice the
42
+ * SDK's own auto-resubmit uses — so outputs an earlier run already handled are
43
+ * not fired again.
44
+ */
45
+ export declare function fireClientToolResults(messages: UIMessage[], tools: ToolSet, hooks: Composed, run: RunContext): Promise<UIMessage[]>;
46
+ /** Append parts onto the run's assistant message (or start one). */
47
+ export declare function appendParts(messages: UIMessage[], parts: Part[]): UIMessage[];
48
+ /**
49
+ * Fold `step_end` parts into an assistant message the SDK assembled without
50
+ * them: each step's appended parts go before the NEXT `step-start`, the last
51
+ * step's at the end — exactly where an append at that step boundary would have
52
+ * landed. `priorSteps` skips step-start parts the message already carried
53
+ * before this run (a continuation reuses the assistant message).
54
+ */
55
+ export declare function insertStepParts(message: UIMessage, appendedByStep: Map<number, Part[]>, priorSteps?: number): UIMessage;
56
+ /** The text of the newest user message, if any. */
57
+ export declare const lastUserTextOf: (messages: UIMessage[]) => string | undefined;
58
+ /** Rewrite the first text part of the newest user message (an `input` transform). */
59
+ export declare const replaceLastUserText: (messages: UIMessage[], text: string) => UIMessage[];
60
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/extensions/tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC7C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAMvD,oGAAoG;AACpG,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAuFnF;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,SAAS,EAAE,EACrB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,QAAQ,EACf,GAAG,EAAE,UAAU,GACd,OAAO,CAAC,SAAS,EAAE,CAAC,CA+CtB;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,SAAS,EAAE,CAO7E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,SAAS,EAClB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,EACnC,UAAU,SAAI,GACb,SAAS,CAiBX;AAED,mDAAmD;AACnD,eAAO,MAAM,cAAc,GAAI,UAAU,SAAS,EAAE,KAAG,MAAM,GAAG,SAQ/D,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,GAAI,UAAU,SAAS,EAAE,EAAE,MAAM,MAAM,KAAG,SAAS,EAalF,CAAC"}