@matatbread/matbot-plugin-api 0.3.8 → 0.4.1
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/package.json +3 -2
- package/src/hooks.ts +6 -6
- package/src/host-machine.ts +192 -0
- package/src/host.ts +43 -0
- package/src/index.ts +27 -9
- package/src/invoke-tool.ts +25 -3
- package/src/notify.ts +3 -0
- package/src/plugin.ts +40 -190
- package/src/session.ts +30 -1
- package/src/types/events.ts +81 -0
- package/src/types/files.ts +46 -0
- package/src/types/frontend.ts +11 -0
- package/src/types/health.ts +6 -0
- package/src/types/hooks.ts +217 -0
- package/src/types/knowledge.ts +27 -0
- package/src/types/messages.ts +154 -0
- package/src/types/primitives.ts +5 -0
- package/src/types/principal.ts +10 -0
- package/src/types/provider.ts +157 -0
- package/src/types/registries.ts +84 -0
- package/src/types/session-runner.ts +69 -0
- package/src/types/steering.ts +33 -0
- package/src/types/storage.ts +16 -0
- package/src/types/system-context.ts +15 -0
- package/src/types/tools.ts +262 -0
- package/src/types/typescript.ts +50 -0
- package/src/types/vault.ts +37 -0
- package/src/types.ts +27 -1089
package/src/types.ts
CHANGED
|
@@ -1,1089 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ProviderConfig {
|
|
32
|
-
name: string;
|
|
33
|
-
/** Plugin module specifier (npm name or file URL) that provides the adapter for this provider. */
|
|
34
|
-
module: string;
|
|
35
|
-
model: string;
|
|
36
|
-
credentials?: Record<string, string>;
|
|
37
|
-
endpoint?: string;
|
|
38
|
-
parameters?: ModelParameters;
|
|
39
|
-
fallback?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Token (and optional cost) counters for one provider call — pure accounting data, never conversation
|
|
44
|
-
* content. Recorded onto the message stream for cost reckoning but elided from provider submission. The
|
|
45
|
-
* producing provider is recorded alongside at each storage site (an assistant turn carries it on the
|
|
46
|
-
* message's `providerName`; a tool-invoked completion pairs it explicitly — see `tool-result`), since a
|
|
47
|
-
* tool may run a completion against any configured provider, each with its own rates.
|
|
48
|
-
*/
|
|
49
|
-
export interface Usage {
|
|
50
|
-
inputTokens: number;
|
|
51
|
-
outputTokens: number;
|
|
52
|
-
costUsd?: number;
|
|
53
|
-
cacheReadTokens?: number;
|
|
54
|
-
cacheCreationTokens?: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** One provider call's usage, tagged with the provider billed — the unit a tool accrues (a tool may
|
|
58
|
-
* run completions against any provider, each with its own rates) and the element persisted on a
|
|
59
|
-
* `tool-result`'s `usage` addendum. See the ambient usage carrier (`recordUsage`/`currentUsageSink`). */
|
|
60
|
-
export interface UsageRecord {
|
|
61
|
-
provider: string;
|
|
62
|
-
usage: Usage;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Opaque, provider-specific round-trip metadata attached to a tool-call — captured from a completion,
|
|
67
|
-
* persisted, and re-sent verbatim when the call is replayed in history (e.g. a Gemini 3 "thought
|
|
68
|
-
* signature", which every historical `functionCall` must carry or the request 400s). The harness never
|
|
69
|
-
* interprets it: it stores, renders, or elides it. Empty by default — a provider package augments its
|
|
70
|
-
* OWN slice from its own module (namespaced by provider family, so slices from different providers in a
|
|
71
|
-
* mixed-provider session never collide), so adding a provider that needs round-trip state touches NO
|
|
72
|
-
* core code:
|
|
73
|
-
*
|
|
74
|
-
* declare module '@matatbread/matbot-plugin-api' {
|
|
75
|
-
* interface ProviderMeta { google?: { thoughtSignature?: string } }
|
|
76
|
-
* }
|
|
77
|
-
*
|
|
78
|
-
* A single session interleaves tool-calls from many providers, so this must be an open, additive union
|
|
79
|
-
* (interface augmentation) — a generic type parameter can bind only one provider per instantiation and
|
|
80
|
-
* so could not type a heterogeneous transcript.
|
|
81
|
-
*/
|
|
82
|
-
export interface ProviderMeta {}
|
|
83
|
-
|
|
84
|
-
export type CompletionEvent =
|
|
85
|
-
| { type: 'text-delta'; delta: string }
|
|
86
|
-
| { type: 'tool-call'; id: string; name: string; input: unknown;
|
|
87
|
-
/** Provider-specific round-trip metadata for this call, opaque to the harness — see `ProviderMeta`. */
|
|
88
|
-
meta?: ProviderMeta }
|
|
89
|
-
| { type: 'tool-result'; id: string; result: unknown }
|
|
90
|
-
| { type: 'thinking'; delta: string }
|
|
91
|
-
| { type: 'thinking-block'; thinking: string; signature: string }
|
|
92
|
-
| { type: 'redacted-thinking'; data: string }
|
|
93
|
-
| { type: 'reasoning-block'; reasoning: string }
|
|
94
|
-
| { type: 'refusal'; text: string }
|
|
95
|
-
| { type: 'unknown-block'; blockType: string; raw: unknown }
|
|
96
|
-
| ({ type: 'usage' } & Usage)
|
|
97
|
-
| { type: 'done' };
|
|
98
|
-
|
|
99
|
-
export interface ProviderAdapter {
|
|
100
|
-
readonly name: string;
|
|
101
|
-
complete(
|
|
102
|
-
messages: Message[],
|
|
103
|
-
config: ProviderConfig,
|
|
104
|
-
tools: readonly Tool[],
|
|
105
|
-
signal: AbortSignal
|
|
106
|
-
): AsyncIterable<CompletionEvent>;
|
|
107
|
-
health(): Promise<HealthStatus>;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── Messages & Session ────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
export type MessageRole = 'user' | 'assistant' | 'tool' | 'system' | 'marker';
|
|
113
|
-
|
|
114
|
-
export type MessageContent = (
|
|
115
|
-
| { type: 'text'; text: string }
|
|
116
|
-
| { type: 'thinking'; thinking: string; signature: string }
|
|
117
|
-
| { type: 'redacted-thinking'; data: string }
|
|
118
|
-
| { type: 'reasoning'; reasoning: string }
|
|
119
|
-
| { type: 'image'; data: string; mimeType: MimeType }
|
|
120
|
-
| { type: 'image-url'; url: string; detail?: 'low' | 'high' | 'auto' }
|
|
121
|
-
| { type: 'document'; data: string; mimeType: MimeType; name?: string }
|
|
122
|
-
| { type: 'audio'; data: string; mimeType: MimeType }
|
|
123
|
-
| { type: 'tool-call'; id: string; name: string; input: unknown;
|
|
124
|
-
/** Provider-specific round-trip metadata, persisted and re-sent verbatim on replay — see
|
|
125
|
-
* `ProviderMeta` and the `tool-call` `CompletionEvent`. */
|
|
126
|
-
meta?: ProviderMeta }
|
|
127
|
-
| { type: 'tool-result'; id: string; result: unknown; isError?: boolean;
|
|
128
|
-
/**
|
|
129
|
-
* Accounting addendum: completions the tool itself ran (e.g. `single_turn`, `dream_time`'s ranker/
|
|
130
|
-
* merger), captured ambiently via the usage carrier and recorded for cost reckoning. Elided from
|
|
131
|
-
* provider submission — adapters serialise only `id`/`result`/`isError`. One entry per provider
|
|
132
|
-
* call, each tagged with the provider billed; never summed at write time, as rates differ.
|
|
133
|
-
*/
|
|
134
|
-
usage?: UsageRecord[] }
|
|
135
|
-
| { type: 'refusal'; text: string }
|
|
136
|
-
| { type: 'file-ref'; fileId: string; name: string; mimeType: MimeType }
|
|
137
|
-
| { type: 'form'; fields: FormField[]; submitLabel?: string }
|
|
138
|
-
| { type: 'form-response'; values: Record<string, string> }
|
|
139
|
-
| { type: 'marker'; creator: string; data: unknown }
|
|
140
|
-
| { type: 'unknown-content'; blockType: string; raw: unknown }
|
|
141
|
-
) & {
|
|
142
|
-
/**
|
|
143
|
-
* Authorship provenance, for *presentation only* — orthogonal to the message's `role`, which is
|
|
144
|
-
* the LLM-protocol identity. Absent ⇒ authored per the role (a human for `user`, the model for
|
|
145
|
-
* `assistant`). `'robo'` ⇒ machine-authored by matbot — a `followup` resubmission, or a hook-
|
|
146
|
-
* injected fragment inside a human turn. It is still carried to the model as ordinary
|
|
147
|
-
* role-appropriate content (the model sees a `user` block either way); the flag is OOB metadata
|
|
148
|
-
* that frontends use to present it agent-side (a robot indicator) rather than as the user's words.
|
|
149
|
-
*/
|
|
150
|
-
origin?: 'robo';
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* A marker is opaque, durable annotation carried in the message stream: links, status,
|
|
155
|
-
* cross-references that are meaningful to a frontend but transparent to the LLM. They are
|
|
156
|
-
* persisted unchanged, elided from provider submission, and deliberately preserved by
|
|
157
|
-
* session compaction (removing one can break things — e.g. a pointer back to an ancestor
|
|
158
|
-
* session). Any code with session access may emit one, normally as its own message.
|
|
159
|
-
*
|
|
160
|
-
* `creator` is the emitting plugin's reference; `data` is anything serialisable. For
|
|
161
|
-
* per-creator type safety, augment `MarkerData` and read/write via `Marker<'your-creator'>`:
|
|
162
|
-
*
|
|
163
|
-
* declare module '@matatbread/matbot-plugin-api' {
|
|
164
|
-
* interface MarkerData { 'split-session': { peerSessionId: string } }
|
|
165
|
-
* }
|
|
166
|
-
*
|
|
167
|
-
* Unregistered creators fall back to `data: unknown`. The base `MessageContent` member stays
|
|
168
|
-
* loose (`creator: string`) so the union and its exhaustive switches are unaffected.
|
|
169
|
-
*/
|
|
170
|
-
export interface MarkerData {
|
|
171
|
-
/** Emitted by the hook dispatcher when a hook handler threw: the hook was skipped (treated as a
|
|
172
|
-
* no-op) and this records it once, so a misconfigured/throwing hook degrades visibly instead of
|
|
173
|
-
* bricking the turn. `channel` is the hook point, `pluginName` the owning plugin if known. */
|
|
174
|
-
'matbot-hooks': { channel: HookPoint; pluginName?: string; message: string };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export type Marker<K extends string = string> = {
|
|
178
|
-
type: 'marker';
|
|
179
|
-
creator: K;
|
|
180
|
-
data: K extends keyof MarkerData ? MarkerData[K] : unknown;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
export interface FormField {
|
|
184
|
-
name: string;
|
|
185
|
-
label: string;
|
|
186
|
-
type: 'text' | 'password' | 'select' | 'confirm';
|
|
187
|
-
options?: string[];
|
|
188
|
-
/** select-only: render an "Other…" affordance that lets the user type a free-form answer instead
|
|
189
|
-
* of picking an option. The typed value is returned verbatim, on the same channel as a pick —
|
|
190
|
-
* callers never learn whether the answer was a preset or free text. Ignored for other types. */
|
|
191
|
-
allowOther?: boolean;
|
|
192
|
-
default?: string;
|
|
193
|
-
required?: boolean;
|
|
194
|
-
/** Presentation hint only (default true): whether the frontend offers a cancel affordance (the
|
|
195
|
-
* "give up" path — see `PromptFn`). Set false to render a hard-blocking prompt with no out. The
|
|
196
|
-
* runner and server never consult this; a frontend with no cancel UI simply can't fire one. */
|
|
197
|
-
cancelable?: boolean;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Canonical values a `type: 'confirm'` prompt resolves to. The rendered label and buttons are a
|
|
202
|
-
* cosmetic, host-specific concern (and may be localised), so consumers MUST branch on these tokens
|
|
203
|
-
* — never on the displayed string. Compare case-insensitively to tolerate host casing differences.
|
|
204
|
-
*/
|
|
205
|
-
export const CONFIRM_YES = 'yes';
|
|
206
|
-
export const CONFIRM_NO = 'no';
|
|
207
|
-
|
|
208
|
-
export interface Message {
|
|
209
|
-
id: string;
|
|
210
|
-
role: MessageRole;
|
|
211
|
-
content: MessageContent[];
|
|
212
|
-
createdAt: ISODate;
|
|
213
|
-
traceId: string;
|
|
214
|
-
providerName?: string;
|
|
215
|
-
/** Token/cost accounting for the provider call that produced this message (assistant turns). The
|
|
216
|
-
* billed provider is this message's `providerName`. Accounting only — elided from provider submission. */
|
|
217
|
-
usage?: Usage;
|
|
218
|
-
metadata?: Record<string, unknown>;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export type SessionStatus = 'active' | 'archived' | 'pinned';
|
|
222
|
-
|
|
223
|
-
export interface Session {
|
|
224
|
-
id: string;
|
|
225
|
-
version: string;
|
|
226
|
-
// Legacy: persisted sessions may still carry `ownerPrincipalId` / `actorPrincipalId` / `persona`.
|
|
227
|
-
// They were never read — ownership-at-rest is structural (the storage partition), resolved via the
|
|
228
|
-
// backend, not a field. Left undeclared; old data keeps them harmlessly as excess properties.
|
|
229
|
-
title?: string;
|
|
230
|
-
status: SessionStatus;
|
|
231
|
-
contexts: string[];
|
|
232
|
-
messages: Message[];
|
|
233
|
-
parentSessionId?: string;
|
|
234
|
-
branchPointMessageId?: string;
|
|
235
|
-
createdAt: ISODate;
|
|
236
|
-
updatedAt: ISODate;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ── System context ────────────────────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
export type SystemContextContributor = (ctx: {
|
|
242
|
-
session: Session;
|
|
243
|
-
signal: AbortSignal;
|
|
244
|
-
}) => string | null | Promise<string | null>;
|
|
245
|
-
|
|
246
|
-
export interface SystemContextRegistry {
|
|
247
|
-
register(contributor: SystemContextContributor, pluginName?: string): void;
|
|
248
|
-
removeByPlugin(pluginName: string): void;
|
|
249
|
-
/** Calls all contributors and joins non-null, non-empty results with double newlines. */
|
|
250
|
-
build(ctx: { session: Session; signal: AbortSignal }): Promise<string | null>;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ── Pipeline hooks ────────────────────────────────────────────────────────────
|
|
254
|
-
|
|
255
|
-
export interface RunConfig {
|
|
256
|
-
provider: string;
|
|
257
|
-
sessionId?: string;
|
|
258
|
-
traceId?: string;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Hooks are sorted by the *job* they do, not by lifecycle position — the channel name is the
|
|
263
|
-
* contract. Each channel has a fixed home, cadence, and effect-ceiling (a hook may always do
|
|
264
|
-
* less: returning nothing makes it a pure observer). The discriminated union on `on` is what
|
|
265
|
-
* keeps the effects honest — `contribute` hands you a read-only session, `toolcall`/`react` can't
|
|
266
|
-
* return one at all, so a write that goes nowhere won't type-check.
|
|
267
|
-
*
|
|
268
|
-
* screen runner, once per turn before the first provider call. The only channel that may
|
|
269
|
-
* durably mutate history. Returns any of: a replacement `session` (persisted),
|
|
270
|
-
* turn-scoped `ephemeral` context (appended onto the tail of this turn's outgoing
|
|
271
|
-
* messages — the freshest input the model reads — never persisted, and placed at the
|
|
272
|
-
* tail rather than a system prefix so a "do X now" directive keeps its salience and
|
|
273
|
-
* doesn't bust the cached prefix), `durable` context (the persisted, visible twin of
|
|
274
|
-
* `ephemeral`: folded onto this turn's user message as `origin: 'robo'` blocks and
|
|
275
|
-
* carried live as a `robo-user` event, so it survives into the next turn's history),
|
|
276
|
-
* or `abort`. Mix freely. This is where the durable-vs-ephemeral choice for incoming
|
|
277
|
-
* user input lives.
|
|
278
|
-
* contribute runner, before *every* provider call. Ephemeral by construction (it re-fires, so a
|
|
279
|
-
* durable mutation would accumulate). Returns a transformed copy of `outgoing` — the
|
|
280
|
-
* message array about to be sent — and never touches the stored session. Mind prompt
|
|
281
|
-
* caching: vary the *tail* (newest turn) freely, but a transform that rewrites the
|
|
282
|
-
* cached prefix (system / early history) busts the cache on every call.
|
|
283
|
-
* toolcall runner, before each tool execution. Read-only. Returns `rejectTool` (skip this call,
|
|
284
|
-
* feed an error result back so the model self-corrects, without breaking the
|
|
285
|
-
* tool_use/tool_result pairing) and/or `abort`.
|
|
286
|
-
* toolresult runner, after each tool execution, before the result is recorded/yielded. Folds the
|
|
287
|
-
* result through each hook: return `{ result }` to replace it (hard redaction, truncation),
|
|
288
|
-
* or nothing to observe (auditing — the context carries args, result, isError and
|
|
289
|
-
* `durationMs`). It owns the LLM-facing + persisted result and the `tool:end` event;
|
|
290
|
-
* note it does NOT see the live `tool:stdout/stderr` chunks, which stream before the
|
|
291
|
-
* result exists.
|
|
292
|
-
* followup pump, once after a turn commits (post-persist). May `resubmit` a robo follow-up turn
|
|
293
|
-
* (head-enqueued, so it runs next as its own real turn; `resubmitDepth` is the chain
|
|
294
|
-
* length for the hook's own budget — the runner also hard-caps it), `retractAndRerun`
|
|
295
|
-
* (pop the committed turn into a marker and re-run the originating user turn with
|
|
296
|
-
* ephemeral context — supersede rather than follow), and/or append durable `markers`
|
|
297
|
-
* to the committed session (LLM-invisible annotations — the second durable-write point
|
|
298
|
-
* after `screen`, safe because it too fires once per turn).
|
|
299
|
-
*/
|
|
300
|
-
export type HookPoint = 'screen' | 'contribute' | 'toolcall' | 'toolresult' | 'followup';
|
|
301
|
-
|
|
302
|
-
export interface ScreenContext {
|
|
303
|
-
session: Session;
|
|
304
|
-
config: RunConfig;
|
|
305
|
-
signal: AbortSignal;
|
|
306
|
-
/** The turn's interactive prompt, when a frontend supplied one (a live user behind this turn).
|
|
307
|
-
* A hook that drives an interactive tool (e.g. a trigger invoking `ask_user`) forwards this into
|
|
308
|
-
* the tool's context; absent (cron/background/no frontend) the tool gets a rejecting prompt. */
|
|
309
|
-
prompt?: PromptFn;
|
|
310
|
-
/** Unregister the hook currently running. For one-shot hooks that should fire at most once. */
|
|
311
|
-
removeHook(): void;
|
|
312
|
-
}
|
|
313
|
-
/**
|
|
314
|
-
* A raced screen verdict the runner folds in WITHOUT gating the turn on it. A `screen` hook that starts
|
|
315
|
-
* expensive work concurrently (e.g. a triggers classifier judging the user message) returns immediately
|
|
316
|
-
* and hands back one of these instead of blocking on it. The runner polls `claim()` — synchronously,
|
|
317
|
-
* never awaiting — at each turn-loop edge (before a provider call, on every stream event, and just
|
|
318
|
-
* before committing). The first time `claim()` returns blocks (the work has settled WITH a correction),
|
|
319
|
-
* the runner DISCARDS the uncommitted in-progress response and re-runs the loop with those blocks
|
|
320
|
-
* tail-folded as ephemeral context — an in-situ redo: no store pop, no retraction marker, cleaner than a
|
|
321
|
-
* post-commit retract. Because the mid-stream poll runs before each event is emitted, a verdict faster
|
|
322
|
-
* than time-to-first-token is caught before any token reaches the frontend (the clean path); a slower
|
|
323
|
-
* one aborts the in-flight provider request, saving the unstreamed remainder. If it never fires before
|
|
324
|
-
* the turn commits, the turn commits normally and any correction is left to a post-commit `followup`.
|
|
325
|
-
*
|
|
326
|
-
* `claim()` MUST be exactly-once: return the correction on the first successful poll and `undefined`
|
|
327
|
-
* forever after (not settled, no correction, or already claimed). The hook uses that single delivery to
|
|
328
|
-
* coordinate with its own post-commit path — a claimed verdict is never also delivered by `followup`.
|
|
329
|
-
*/
|
|
330
|
-
export interface DeferredScreen {
|
|
331
|
-
claim(): DeferredCorrection | undefined;
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* A claimed raced-verdict correction. `ephemeral` is tail-folded onto the re-run's outgoing messages
|
|
335
|
-
* and never persisted (the transient "for this answer only" twin); `durable` is folded onto the turn's
|
|
336
|
-
* user message — persisted, visible (the hook marks it `origin: 'robo'`), and carried live as a
|
|
337
|
-
* `robo-user` event — so it updates the conversation rather than informing one answer (a `contextual`
|
|
338
|
-
* trigger's semantics, preserved even though the verdict now lands mid-turn instead of before it). At
|
|
339
|
-
* least one is non-empty when returned.
|
|
340
|
-
*/
|
|
341
|
-
export interface DeferredCorrection {
|
|
342
|
-
ephemeral?: MessageContent[];
|
|
343
|
-
durable?: MessageContent[];
|
|
344
|
-
}
|
|
345
|
-
export interface ScreenResult {
|
|
346
|
-
session?: Session;
|
|
347
|
-
/**
|
|
348
|
-
* A raced verdict the runner folds in without gating on it (see {@link DeferredScreen}). Lets a
|
|
349
|
-
* screen hook race expensive work — a classifier — against generation: return immediately, hand this
|
|
350
|
-
* back, and the runner restarts the turn in-situ if the verdict fires before commit. A hook that
|
|
351
|
-
* would rather gate the turn (block until the verdict) just returns `ephemeral` as usual instead.
|
|
352
|
-
*/
|
|
353
|
-
deferred?: DeferredScreen;
|
|
354
|
-
/** Turn-scoped context appended onto the tail of this turn's outgoing messages (the freshest
|
|
355
|
-
* input the model reads), never persisted. At the tail, not a system prefix, so a directive
|
|
356
|
-
* keeps its salience and the cached system/history prefix stays stable across turns. */
|
|
357
|
-
ephemeral?: MessageContent[];
|
|
358
|
-
/**
|
|
359
|
-
* The persisted, visible twin of `ephemeral`: context that should outlive this turn rather than
|
|
360
|
-
* inform it once. The runner folds these blocks onto this turn's user message (so they ride into
|
|
361
|
-
* the stored history and every subsequent provider call) AND carries them live as a `robo-user`
|
|
362
|
-
* event, so a live draw and a reload render the same thing. They are LLM-visible (unlike
|
|
363
|
-
* `markers`) and machine-authored, so a caller marks them `origin: 'robo'` for presentation.
|
|
364
|
-
* Use when a fired hook produces context that genuinely updates the conversation, not a one-shot
|
|
365
|
-
* corrective for the turn about to run.
|
|
366
|
-
*/
|
|
367
|
-
durable?: MessageContent[];
|
|
368
|
-
/**
|
|
369
|
-
* Durable `marker` blocks to append to this turn's session (LLM-invisible). The dispatcher both
|
|
370
|
-
* appends them to the persisted session AND carries them live on the turn's event stream, so a
|
|
371
|
-
* live draw and a reload render the same thing. Use instead of hand-appending to `session` when
|
|
372
|
-
* you just want to annotate (e.g. a fired trigger's silent tool recording what it did).
|
|
373
|
-
*/
|
|
374
|
-
markers?: MessageContent[];
|
|
375
|
-
abort?: string;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
export interface ContributeContext {
|
|
379
|
-
readonly outgoing: readonly Message[];
|
|
380
|
-
readonly session: Session;
|
|
381
|
-
config: RunConfig;
|
|
382
|
-
signal: AbortSignal;
|
|
383
|
-
/** Unregister the hook currently running. For one-shot hooks that should fire at most once. */
|
|
384
|
-
removeHook(): void;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
export interface ToolCallContext {
|
|
388
|
-
readonly session: Session;
|
|
389
|
-
readonly toolCall: { id: string; name: string; input: unknown };
|
|
390
|
-
readonly tool: Tool;
|
|
391
|
-
config: RunConfig;
|
|
392
|
-
signal: AbortSignal;
|
|
393
|
-
/** Unregister the hook currently running. For one-shot hooks that should fire at most once. */
|
|
394
|
-
removeHook(): void;
|
|
395
|
-
}
|
|
396
|
-
export interface ToolCallResult {
|
|
397
|
-
rejectTool?: { message: string };
|
|
398
|
-
abort?: string;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export interface ToolResultContext {
|
|
402
|
-
readonly session: Session;
|
|
403
|
-
readonly toolCall: { id: string; name: string; input: unknown };
|
|
404
|
-
readonly tool: Tool;
|
|
405
|
-
readonly result: unknown;
|
|
406
|
-
readonly isError: boolean;
|
|
407
|
-
readonly durationMs: number;
|
|
408
|
-
config: RunConfig;
|
|
409
|
-
signal: AbortSignal;
|
|
410
|
-
/** Unregister the hook currently running. For one-shot hooks that should fire at most once. */
|
|
411
|
-
removeHook(): void;
|
|
412
|
-
}
|
|
413
|
-
// The toolresult hook returns `{ result }` to replace the tool's result, or nothing to leave it
|
|
414
|
-
// (and just observe) — a trivial single-field return, inlined in the Hook union like `contribute`'s.
|
|
415
|
-
|
|
416
|
-
export interface FollowupContext {
|
|
417
|
-
readonly session: Session;
|
|
418
|
-
readonly resubmitDepth: number;
|
|
419
|
-
config: RunConfig;
|
|
420
|
-
signal: AbortSignal;
|
|
421
|
-
/** The turn's interactive prompt, when a frontend supplied one (a live user behind this turn).
|
|
422
|
-
* A hook that drives an interactive tool (e.g. a trigger invoking `ask_user` as a proactive
|
|
423
|
-
* follow-up question) forwards this into the tool's context; absent (cron/background/no frontend)
|
|
424
|
-
* the tool gets a rejecting prompt. Note this prompt fires *post-commit*, out of band from the
|
|
425
|
-
* turn's `done`, and blocks the pump until the human answers. */
|
|
426
|
-
prompt?: PromptFn;
|
|
427
|
-
/** Unregister the hook currently running. For one-shot hooks that should fire at most once. */
|
|
428
|
-
removeHook(): void;
|
|
429
|
-
}
|
|
430
|
-
export interface FollowupResult {
|
|
431
|
-
resubmit?: { content: MessageContent[] };
|
|
432
|
-
/**
|
|
433
|
-
* Retract-and-rerun: supersede the just-committed turn instead of following it. The pump pops the
|
|
434
|
-
* committed turn back to (and excluding) the last user message, stashes the popped content in a
|
|
435
|
-
* durable retraction marker (LLM-elided like every marker, so a frontend can render it
|
|
436
|
-
* struck-through and a post-mortem can audit it), then re-runs that same user turn with `context`
|
|
437
|
-
* injected EPHEMERALLY (tail-folded, never persisted) — agent-phase injection time-shifted onto a
|
|
438
|
-
* committed turn. This is the inverse of `resubmit`, which leaves the response in place and appends
|
|
439
|
-
* a new robo turn after it. Self-terminating by design: a well-formed trigger fires on a *curable*
|
|
440
|
-
* defect that the injected context dissolves on the redo, so it won't re-fire; `resubmitDepth` (a
|
|
441
|
-
* redo carries parent+1) caps an ill-formed one. `resubmit` and `retractAndRerun` are independent
|
|
442
|
-
* capabilities — a single turn returning both is not expected, but both head-enqueue if it does.
|
|
443
|
-
*
|
|
444
|
-
* `context` is folded EPHEMERALLY onto the redo (for-this-answer-only). `durable` is instead folded
|
|
445
|
-
* onto the re-run's user message — persisted, visible (mark it `origin: 'robo'`), carried live as a
|
|
446
|
-
* `robo-user` event — so a `contextual`-kind correction updates the conversation durably even when it
|
|
447
|
-
* lands post-commit. At least one of the two is present.
|
|
448
|
-
*/
|
|
449
|
-
retractAndRerun?: { context?: MessageContent[]; durable?: MessageContent[] };
|
|
450
|
-
/**
|
|
451
|
-
* Durable `marker` blocks to append to the just-committed session (LLM-invisible; for tracing /
|
|
452
|
-
* cross-references). The second durable-write capability after `screen` — safe here for the same
|
|
453
|
-
* reason: `followup` fires once per turn, so an append can't accumulate the way a per-call channel
|
|
454
|
-
* would. The pump persists them AND emits them live (post-commit, like a `queued` event) so a live
|
|
455
|
-
* draw matches a reload. Use for recording what a post-commit reaction (e.g. a fired trigger's
|
|
456
|
-
* silent tool) actually did.
|
|
457
|
-
*/
|
|
458
|
-
markers?: MessageContent[];
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
export type Hook =
|
|
462
|
-
| { on: 'screen'; priority?: number; pluginName?: string; handler(ctx: ScreenContext): ScreenResult | void | Promise<ScreenResult | void> }
|
|
463
|
-
| { on: 'contribute'; priority?: number; pluginName?: string; handler(ctx: ContributeContext): Message[] | void | Promise<Message[] | void> }
|
|
464
|
-
| { on: 'toolcall'; priority?: number; pluginName?: string; handler(ctx: ToolCallContext): ToolCallResult | void | Promise<ToolCallResult | void> }
|
|
465
|
-
| { on: 'toolresult'; priority?: number; pluginName?: string; handler(ctx: ToolResultContext): { result: unknown } | void | Promise<{ result: unknown } | void> }
|
|
466
|
-
| { on: 'followup'; priority?: number; pluginName?: string; handler(ctx: FollowupContext): FollowupResult | void | Promise<FollowupResult | void> };
|
|
467
|
-
|
|
468
|
-
// ── Storage ───────────────────────────────────────────────────────────────────
|
|
469
|
-
// The query grammar (Filter AST, StoreQuery, QueryResult, StoreQueryError) lives in ./store-query.
|
|
470
|
-
|
|
471
|
-
export type CASResult<T> =
|
|
472
|
-
| { ok: true; doc: T }
|
|
473
|
-
| { ok: false; current: T | null };
|
|
474
|
-
|
|
475
|
-
export interface Store<T extends { id: string; version: string }> {
|
|
476
|
-
get(id: string): Promise<T | null>;
|
|
477
|
-
set(id: string, value: T): Promise<void>;
|
|
478
|
-
cas(id: string, expected: string, next: T): Promise<CASResult<T>>;
|
|
479
|
-
delete(id: string, expectedVersion?: string): Promise<boolean>;
|
|
480
|
-
query(q: StoreQuery): Promise<QueryResult<T>>;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// ── Knowledge index ───────────────────────────────────────────────────────────
|
|
484
|
-
|
|
485
|
-
export interface KnowledgeEntry {
|
|
486
|
-
id: string;
|
|
487
|
-
version: string;
|
|
488
|
-
entities: string[];
|
|
489
|
-
tags: string[];
|
|
490
|
-
summary: string;
|
|
491
|
-
content: string;
|
|
492
|
-
contentHash?: string;
|
|
493
|
-
source: { type: string; uuid: string };
|
|
494
|
-
confidence?: number;
|
|
495
|
-
createdAt: string;
|
|
496
|
-
updatedAt: string;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
export interface KnowledgeIndex {
|
|
500
|
-
index(entry: KnowledgeEntry): Promise<void>;
|
|
501
|
-
/** Remove the entry with this id, if present (idempotent — removing an absent id is a no-op). The
|
|
502
|
-
* id is the index's sole primary key (`index` replaces by it), so retraction is by id alone; the
|
|
503
|
-
* index never inspects an entry's opaque `source`. The party that indexed an entry owns retracting
|
|
504
|
-
* it — e.g. a skill manager retracts a hidden/deleted skill rather than the index policing tenants. */
|
|
505
|
-
remove(id: string): Promise<void>;
|
|
506
|
-
search(terms: Array<{ term: string; context?: string }>, signal: AbortSignal): Promise<KnowledgeEntry[]>;
|
|
507
|
-
/** Enumerate all indexed entries. When present, register('KnowledgeIndex', …) drains these into the incoming backend. */
|
|
508
|
-
entries?(): Iterable<KnowledgeEntry>;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// ── TypeScript ──────────────────────────────────────────────────────────────
|
|
512
|
-
|
|
513
|
-
/**
|
|
514
|
-
* Erases TypeScript types from a source string, returning runnable JavaScript. Type-stripping is
|
|
515
|
-
* foundational — the harness ships raw `.ts` and any code compiled at runtime must have its types
|
|
516
|
-
* removed before it can execute — so every execution environment provides one, and it lives as a fixed
|
|
517
|
-
* runtime capability (on {@link MatbotRuntime}) the host supplies per platform, not a swappable service:
|
|
518
|
-
* node uses its built-in `stripTypeScriptTypes`, the browser bundle uses sucrase. A consumer that
|
|
519
|
-
* compiles source at runtime (e.g. `tool_function`) calls this instead of importing a platform stripper.
|
|
520
|
-
*
|
|
521
|
-
* `strip` may be async: a platform whose stripper loads lazily (the browser fetches sucrase on first use)
|
|
522
|
-
* returns a promise; a synchronous stripper (node) returns the string directly — callers `await` either.
|
|
523
|
-
* It is type-erasure, not full transpilation: node's stripper is erasable-only (enums/namespaces throw),
|
|
524
|
-
* sucrase is more permissive, so authors should keep to erasable TypeScript to stay portable.
|
|
525
|
-
*/
|
|
526
|
-
export interface TypeScriptStripper {
|
|
527
|
-
strip(source: string): string | Promise<string>;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Optional, node-only developer-experience service: the live `.d.ts` of the types the loaded tools
|
|
532
|
-
* expose — what `toolResult(invokeTool(…, name, …))`, or a `function-tools` `await tool.name(…)`, resolves
|
|
533
|
-
* to — derived by compiling each loaded plugin's `declare module '@matatbread/matbot-plugin-api'`
|
|
534
|
-
* augmentations. The runtime registry can't supply this (result types are erased); only a TypeScript
|
|
535
|
-
* program reading the source can. Consumers that generate or compose tool-calling code use it so the model
|
|
536
|
-
* isn't guessing return shapes. Absent where no TypeScript program can run (the browser today) — a consumer
|
|
537
|
-
* must degrade (guess-and-run) when `services.ToolTypeIndex` is undefined.
|
|
538
|
-
*
|
|
539
|
-
* The result is rebuilt lazily and cached, invalidated when the tool set changes. Tools the source scan
|
|
540
|
-
* can't reach (a `function-tools` function) contribute their types by declaring a `toolContract` string on
|
|
541
|
-
* their registered {@link Tool} — identical in shape to a `ToolContracts` arm; the index splices it off the
|
|
542
|
-
* live registry, so no separate registration step is needed.
|
|
543
|
-
*/
|
|
544
|
-
export interface ToolTypeIndex {
|
|
545
|
-
/** Self-contained type declarations as a `.d.ts` string: the source-derived `ToolContracts` augmentations
|
|
546
|
-
* merged with the arms of every other live tool, plus `declare const tool: ToolProxy` — the overloaded
|
|
547
|
-
* proxy a generator writes `await tool.x(params)` against. */
|
|
548
|
-
dts(): Promise<string>;
|
|
549
|
-
/** Type-check a TypeScript `snippet` against exactly the {@link dts} above — the `tool` proxy ({@link
|
|
550
|
-
* ToolProxy}: each multi-action tool an overload set, so `await tool.x(params)` narrows its result by the
|
|
551
|
-
* params) is in scope. Returns human-readable diagnostics scoped to the snippet ([] means clean). A
|
|
552
|
-
* composer uses it to catch bad tool-call code before running/registering it. */
|
|
553
|
-
check(snippet: string): Promise<string[]>;
|
|
554
|
-
/** Per live tool (that has a contract): the `params`/`result` wire text, flattened from the one contract —
|
|
555
|
-
* a source tool's `ToolContracts` arms (via the source scan) or a source-less tool's `toolContract` string.
|
|
556
|
-
* The single contract is thus also the source of the wire description; the host folds this into the
|
|
557
|
-
* outgoing tool descriptions at the turn's dispatch edge. A tool with only a loose `inputSchema` (no
|
|
558
|
-
* contract) is absent. */
|
|
559
|
-
wireContracts(): Promise<Record<string, { params: string; result: string }>>;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// ── Tools ─────────────────────────────────────────────────────────────────────
|
|
563
|
-
|
|
564
|
-
export type ToolEvent<Result = unknown> =
|
|
565
|
-
| { type: 'stdout'; chunk: string }
|
|
566
|
-
| { type: 'stderr'; chunk: string }
|
|
567
|
-
| { type: 'progress'; pct: number; message?: string }
|
|
568
|
-
| { type: 'result'; value: Result }
|
|
569
|
-
| { type: 'file'; handle: FileHandle }
|
|
570
|
-
// A durable, LLM-invisible annotation the tool emits as it runs (a link, a status, a trace of a
|
|
571
|
-
// side-effect). Persisted as a `marker`-role message; elided from provider submission like any
|
|
572
|
-
// marker. Independent of `result` — a tool may emit markers and no result (a silent side-effect,
|
|
573
|
-
// e.g. a trigger-fired tool), a result and no markers, or both.
|
|
574
|
-
| { type: 'marker'; creator: string; data: unknown }
|
|
575
|
-
| { type: 'error'; message: string; code?: number; stdout?: string; stderr?: string };
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Per-tool result-type registry: maps a tool's `name` to the type of the `value` it yields in its
|
|
579
|
-
* `result` event. Augment it exactly like {@link MarkerData} / `MatbotServices`, so a caller gets the
|
|
580
|
-
* concrete result type back from {@link invokeTool} + `toolResult` instead of `unknown`:
|
|
581
|
-
*
|
|
582
|
-
* declare module '@matatbread/matbot-plugin-api' {
|
|
583
|
-
* interface ToolContracts { find_fact: string[] | null }
|
|
584
|
-
* }
|
|
585
|
-
*
|
|
586
|
-
* The tool name is the discriminator: `invokeTool(machine, 'find_fact', …)` is typed
|
|
587
|
-
* `AsyncIterable<ToolEvent<string[] | null>>`, so `toolResult(…)` resolves to `string[] | null`. A
|
|
588
|
-
* name with no registered entry resolves to `unknown` (the caller must narrow). This is a pure
|
|
589
|
-
* type-level construct — no runtime validation; it pins, at the call site, the contract a tool's
|
|
590
|
-
* executor already produces. Keep the entry in sync with what the executor actually yields.
|
|
591
|
-
*
|
|
592
|
-
* **Multi-action tools** are a weird form of overloaded function: the same tool returns different
|
|
593
|
-
* shapes depending on its params. Register such a tool as a union of {@link ToolContract} *arms*, each
|
|
594
|
-
* pairing a result with the discriminating params *pattern* that selects it — `invokeTool` matches the
|
|
595
|
-
* call's literal params against the patterns and narrows the result to the matching arm:
|
|
596
|
-
*
|
|
597
|
-
* declare module '@matatbread/matbot-plugin-api' {
|
|
598
|
-
* interface ToolContracts {
|
|
599
|
-
* session_action:
|
|
600
|
-
* | ToolContract<Session, { action: 'get' }>
|
|
601
|
-
* | ToolContract<{ id: string; title: string }, { action: 'rename' }>;
|
|
602
|
-
* }
|
|
603
|
-
* }
|
|
604
|
-
* // invokeTool(machine, 'session_action', { action: 'get', sessionId }) → result is Session
|
|
605
|
-
*
|
|
606
|
-
* The pattern is *any* discriminating field(s), not just `action` (a tool keying on `interval`'s
|
|
607
|
-
* presence registers `ToolContract<…, { interval: string }>`). It is a *pattern*, not the full input:
|
|
608
|
-
* key only on the discriminant so a call carrying just that field still matches. When no arm's pattern
|
|
609
|
-
* matches (a non-literal discriminant, or an absence-discriminant the positive patterns can't express),
|
|
610
|
-
* the result falls back to the union of every arm — always sound, just less narrow.
|
|
611
|
-
*/
|
|
612
|
-
export interface ToolContracts {}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* One overload arm of a multi-action tool registered in {@link ToolContracts}: the `Result` it yields
|
|
616
|
-
* for a call whose params match the discriminating pattern `Args`. `Args` defaults to `unknown` (an
|
|
617
|
-
* arm that matches any params). Purely type-level — the `__` fields are phantom and never constructed.
|
|
618
|
-
*/
|
|
619
|
-
export interface ToolContract<Result, Args = unknown> {
|
|
620
|
-
readonly __result: Result;
|
|
621
|
-
readonly __args: Args;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
type ToolResultArmed<E> = E extends ToolContract<unknown, unknown> ? true : false;
|
|
625
|
-
type ToolResultUnion<E> = E extends ToolContract<infer R, unknown> ? R : never;
|
|
626
|
-
type ToolResultMatched<E, P> = E extends ToolContract<infer R, infer A> ? (P extends A ? R : never) : never;
|
|
627
|
-
|
|
628
|
-
/** The full set of `result` values a tool named `K` may yield — the union over all its arms (for an
|
|
629
|
-
* arm-based entry), the entry itself (for a plain entry), or `unknown` when `K` is unregistered. This
|
|
630
|
-
* is what an executor binds against (`ToolExecutor<ToolResultOf<'my_tool'>>`): it must cover every arm. */
|
|
631
|
-
export type ToolResultOf<K extends string> =
|
|
632
|
-
K extends keyof ToolContracts
|
|
633
|
-
? ToolResultArmed<ToolContracts[K]> extends true ? ToolResultUnion<ToolContracts[K]> : ToolContracts[K]
|
|
634
|
-
: unknown;
|
|
635
|
-
|
|
636
|
-
/** The `result` a call to tool `K` with params `P` yields: for an arm-based entry, the arm whose
|
|
637
|
-
* pattern `P` matches (or the full union when none match — always sound); otherwise {@link ToolResultOf}. */
|
|
638
|
-
export type ToolResultFor<K extends string, P> =
|
|
639
|
-
K extends keyof ToolContracts
|
|
640
|
-
? ToolResultArmed<ToolContracts[K]> extends true
|
|
641
|
-
? ([ToolResultMatched<ToolContracts[K], P>] extends [never]
|
|
642
|
-
? ToolResultUnion<ToolContracts[K]>
|
|
643
|
-
: ToolResultMatched<ToolContracts[K], P>)
|
|
644
|
-
: ToolContracts[K]
|
|
645
|
-
: unknown;
|
|
646
|
-
|
|
647
|
-
// Distribute a union to its intersection: `A | B` → `A & B`. An intersection of function types is an
|
|
648
|
-
// overload set, so this turns a union of per-arm call signatures into an overloaded function.
|
|
649
|
-
type UnionToIntersection<U> =
|
|
650
|
-
(U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
651
|
-
|
|
652
|
-
// One `ToolContracts` arm → its call signature. A fully-untyped arm (`ToolContract<unknown, unknown>`, the
|
|
653
|
-
// fallback for a foreign/MCP tool carrying only a JSON-Schema `inputSchema`) keeps a loose, optional-param
|
|
654
|
-
// signature so it stays callable; a typed arm requires its declared params.
|
|
655
|
-
type ArmCallSig<E> =
|
|
656
|
-
E extends ToolContract<infer R, infer P>
|
|
657
|
-
? (unknown extends P ? (params?: unknown) => Promise<R> : (params: P) => Promise<R>)
|
|
658
|
-
: never;
|
|
659
|
-
|
|
660
|
-
// The union of a tool's arm params — the parameter type of the trailing catch-all signature below.
|
|
661
|
-
type ArmArgsUnion<E> = E extends ToolContract<unknown, infer A> ? A : never;
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* The type of the injected `tool` proxy that `function-tools` and compiled skills call
|
|
665
|
-
* (`await tool.name(params)`). Each key is derived from its {@link ToolContracts} entry: a multi-arm
|
|
666
|
-
* (multi-action) entry becomes an **overload set** — one `(params) => Promise<result>` signature per arm —
|
|
667
|
-
* so `await tool.name(params)` narrows its result by the params passed, exactly the contract a code
|
|
668
|
-
* generator needs to write a correct call first time. A single-arm entry is one signature. This is what the
|
|
669
|
-
* generated `dts()` declares `tool` as; it is *derived* — never hand-authored — from the same `ToolContracts`
|
|
670
|
-
* augmentation that types authors' `invokeTool`/`toolResult` calls, so the human editor and the LLM see one
|
|
671
|
-
* source of truth.
|
|
672
|
-
*
|
|
673
|
-
* The trailing catch-all signature (params union → result union) exists for soundness at the *meta-type*
|
|
674
|
-
* level, not for call sites: overload resolution is first-match, so a well-formed call still hits its
|
|
675
|
-
* specific arm and narrows precisely, and a malformed call matches nothing and still errors. What it
|
|
676
|
-
* changes: `ReturnType<typeof tool.x>` — which on a bare overload set resolves to an arbitrary (last) arm,
|
|
677
|
-
* a real-world codegen trap — now degrades to the sound full union; and a params value typed as a union of
|
|
678
|
-
* arms (dynamic multi-action dispatch) becomes callable, returning the result union.
|
|
679
|
-
*/
|
|
680
|
-
export type ToolProxy = { [K in keyof ToolContracts]:
|
|
681
|
-
UnionToIntersection<ArmCallSig<ToolContracts[K]>>
|
|
682
|
-
& ((params: ArmArgsUnion<ToolContracts[K]>) => Promise<ToolResultUnion<ToolContracts[K]>>) };
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Ask the user a question and resolve with their answer. The host supplies the
|
|
686
|
-
* implementation — readline in the CLI, an SSE round-trip in the web frontend. This is
|
|
687
|
-
* matbot's single mechanism for eliciting user input at runtime; it is injected both into
|
|
688
|
-
* tool execution (`ToolContext.prompt`) and into plugin loading (collision resolution in setup).
|
|
689
|
-
*
|
|
690
|
-
* Two call forms:
|
|
691
|
-
* - `(question, defaultValue?)` — free text; resolves to the typed string (or the default).
|
|
692
|
-
* - `(field: FormField)` — a single structured field (`select`/`confirm`/`password`/`text`),
|
|
693
|
-
* letting rich frontends render real controls (buttons, masked input). Frontends that can't
|
|
694
|
-
* render it fall back to `field.label` as plain text. Resolves to the chosen/typed value.
|
|
695
|
-
*
|
|
696
|
-
* Cancellation is the "we can't proceed — give up" path (distinct from a graceful "decline", which
|
|
697
|
-
* is simply one of the offered `options`). The host rejects the promise with `PromptCancelledError`
|
|
698
|
-
* and abandons the current turn, returning to idle; a caller's surrounding try/catch turns the
|
|
699
|
-
* rejection into a tool error that closes the tool call cleanly.
|
|
700
|
-
* This reuses `FormField` rather than a parallel type; it is a one-shot request/response and
|
|
701
|
-
* deliberately does NOT engage the session-bound `form`/`form-response` flow.
|
|
702
|
-
*/
|
|
703
|
-
export interface PromptFn {
|
|
704
|
-
(question: string, defaultValue?: string): Promise<string>;
|
|
705
|
-
(field: FormField): Promise<string>;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
export interface ToolContext {
|
|
709
|
-
callId: string;
|
|
710
|
-
session: Session;
|
|
711
|
-
signal: AbortSignal;
|
|
712
|
-
vault: Vault;
|
|
713
|
-
/** The provider key driving the current turn (`RunConfig.provider`). A tool that spawns further
|
|
714
|
-
* work should default to this so the child inherits the same model rather than the config default. */
|
|
715
|
-
provider?: string;
|
|
716
|
-
workdir?: string;
|
|
717
|
-
configPath?: string;
|
|
718
|
-
files?: FileStore;
|
|
719
|
-
/** Prompt the user for input. The host provides a readline or form implementation. */
|
|
720
|
-
prompt: PromptFn;
|
|
721
|
-
/** Hot-load a plugin by specifier without restarting the process. Returns the loaded plugin.
|
|
722
|
-
* `refresh` (default false) forces a remote (github/http) plugin to be re-downloaded rather than
|
|
723
|
-
* re-loaded from its `.plugins/` cache — pass true to pick up changed upstream source. */
|
|
724
|
-
loadPlugin(specifier: string, refresh?: boolean): Promise<MatbotPlugin>;
|
|
725
|
-
/**
|
|
726
|
-
* Hot-unload a plugin by specifier, removing its tools, hooks, and system context contributions.
|
|
727
|
-
* Resolves `true` if a plugin was actually resident and unloaded, `false` if there was nothing
|
|
728
|
-
* to unload. A failed `teardown()` (e.g. timeout) still throws — the plugin was resident in that case.
|
|
729
|
-
*/
|
|
730
|
-
unloadPlugin(specifier: string): Promise<boolean>;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* The read-only facts about the call a composed function is running under, injected into a
|
|
735
|
-
* `tool_function` body as the ambient `context` binding beside `tool`/`toolInContext`. Deliberately
|
|
736
|
-
* narrower than {@link ToolContext}: the identity of the call, not its capabilities — `vault`,
|
|
737
|
-
* `files`, `prompt` and plugin (un)loading stay off a surface authored by a model and reachable only
|
|
738
|
-
* through the tool contracts. A tool with real source takes `ToolContext` and needs none of this.
|
|
739
|
-
*
|
|
740
|
-
* Not `ToolCallContext` — that name belongs to the `toolcall` hook's context; this is the composed
|
|
741
|
-
* caller's own view, not a hook's view of a call about to run.
|
|
742
|
-
*/
|
|
743
|
-
export interface ComposedCallContext {
|
|
744
|
-
readonly callId: string;
|
|
745
|
-
readonly sessionId: string;
|
|
746
|
-
/** The provider key driving the turn — the same one nested `tool.x(…)` calls inherit. */
|
|
747
|
-
readonly provider?: string;
|
|
748
|
-
readonly workdir?: string;
|
|
749
|
-
readonly signal: AbortSignal;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
/**
|
|
753
|
-
* A tool's runtime. `R` is the type of the `value` carried by its `result` event — declared once,
|
|
754
|
-
* at the source, so the executor's yields and the tool's {@link ToolContracts} registry entry can't
|
|
755
|
-
* silently drift. The `unknown` default keeps untyped executors compiling untouched, and covariance
|
|
756
|
-
* means a narrower `ToolExecutor<X>` still satisfies `ToolExecutor` (i.e. `ToolExecutor<unknown>`),
|
|
757
|
-
* so the heterogeneous registry boundary (`Tool[]`) accepts any executor. Bind `R` to the registry
|
|
758
|
-
* entry with `ToolExecutor<ToolResultOf<'my_tool'>>` so the single augmentation is the source of truth.
|
|
759
|
-
*/
|
|
760
|
-
export interface ToolExecutor<R = unknown> {
|
|
761
|
-
execute(input: unknown, ctx: ToolContext): AsyncIterable<ToolEvent<R>>;
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
export interface Tool<R = unknown> {
|
|
765
|
-
name: string;
|
|
766
|
-
description: string;
|
|
767
|
-
inputSchema: JSONSchema;
|
|
768
|
-
/** The call contract as TypeScript source — a {@link ToolContract}`<Result, Params>` (or a union of arms
|
|
769
|
-
* for a multi-action tool), *identical in shape* to what a `ToolContracts` augmentation declares. Only a
|
|
770
|
-
* tool with **no scannable source** carries this — a `function-tools` function (whose signature supplies
|
|
771
|
-
* both halves), and any similarly runtime-defined tool: the host splices it verbatim into the generated
|
|
772
|
-
* dts's `ToolContracts`, and derives the wire `params`/`result` text from it. A tool WITH source declares
|
|
773
|
-
* its contract as a `ToolContracts` augmentation instead and omits this. Foreign tools (e.g. MCP proxies)
|
|
774
|
-
* that have neither fall back to the loose `inputSchema`. */
|
|
775
|
-
toolContract?: string;
|
|
776
|
-
executor: ToolExecutor<R>;
|
|
777
|
-
pluginName?: string;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// ── Files ─────────────────────────────────────────────────────────────────────
|
|
781
|
-
|
|
782
|
-
export interface FileMetaData {
|
|
783
|
-
id: string;
|
|
784
|
-
version: string;
|
|
785
|
-
name: string;
|
|
786
|
-
mimeType: MimeType;
|
|
787
|
-
size: number;
|
|
788
|
-
createdAt: ISODate;
|
|
789
|
-
sessionId?: string;
|
|
790
|
-
messageId?: string;
|
|
791
|
-
namespace?: string;
|
|
792
|
-
/** Whether this file may be served at a public URL. Default-deny: absent ⇒ not servable. The
|
|
793
|
-
* flag rides in the file's own persisted metadata (no separate allow-list), so the same read that
|
|
794
|
-
* resolves a request also gates it. Producers opt in per put (the workspace tool sets it true). */
|
|
795
|
-
allowed?: boolean;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
export interface FileHandle extends FileMetaData {
|
|
799
|
-
stream(signal?: AbortSignal): AsyncIterable<Uint8Array>;
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
export interface FileFilter {
|
|
803
|
-
sessionId?: string;
|
|
804
|
-
mimeType?: string;
|
|
805
|
-
namespace?: string;
|
|
806
|
-
createdAfter?: ISODate;
|
|
807
|
-
createdBefore?: ISODate;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
export interface FileStore {
|
|
811
|
-
/** Store a file. When `name` is provided, upserts by (name + namespace); otherwise always creates a new entry. */
|
|
812
|
-
put(
|
|
813
|
-
name: string | undefined,
|
|
814
|
-
mimeType: MimeType,
|
|
815
|
-
data: AsyncIterable<Uint8Array>,
|
|
816
|
-
meta?: { sessionId?: string; messageId?: string; namespace?: string; allowed?: boolean }
|
|
817
|
-
): Promise<FileHandle>;
|
|
818
|
-
get(id: string): Promise<FileHandle | null>;
|
|
819
|
-
getByName(name: string, namespace?: string): Promise<FileHandle | null>;
|
|
820
|
-
delete(id: string): Promise<void>;
|
|
821
|
-
list(filter?: FileFilter): AsyncIterable<FileHandle>;
|
|
822
|
-
putTemp(name: string, mimeType: MimeType, data: AsyncIterable<Uint8Array>): Promise<FileHandle>;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
// ── Frontend ──────────────────────────────────────────────────────────────────
|
|
826
|
-
|
|
827
|
-
/**
|
|
828
|
-
* Passed to `services.registerFrontend()` by a plugin declaring itself a frontend. A frontend
|
|
829
|
-
* owns its own I/O (an HTTP server, a bot connection, a REPL); matbot only needs to know it
|
|
830
|
-
* exists. This object is the growth point for frontend-level capability advertisement
|
|
831
|
-
* (accepted/produced MIME types, size limits, …) as media support lands.
|
|
832
|
-
*/
|
|
833
|
-
export interface FrontendInfo {
|
|
834
|
-
name: string;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// ── Vault ─────────────────────────────────────────────────────────────────────
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* The primitives a vault backend must implement. Resolution, redaction, and the three
|
|
841
|
-
* low-level store operations the smart `createSecret` policy composes. A backend implements
|
|
842
|
-
* `Vault` (which is `VaultSpec` plus that policy); plugins are handed a `Vault`.
|
|
843
|
-
*/
|
|
844
|
-
export interface VaultSpec {
|
|
845
|
-
/** Resolve ${NAME} placeholders by looking up the named value; throws MissingSecretError for any miss. */
|
|
846
|
-
resolve(ref: string): Promise<string>;
|
|
847
|
-
scrub(text: string): string;
|
|
848
|
-
/**
|
|
849
|
-
* Store `value` under exactly `name`, overwriting. The literal write; no reference/dedup logic.
|
|
850
|
-
* An empty `value` removes the key instead — there is no such thing as an empty secret, so the
|
|
851
|
-
* empty string is the removal signal (idempotent: removing an absent name is a no-op).
|
|
852
|
-
*/
|
|
853
|
-
writeSecret(name: string, value: string): Promise<void>;
|
|
854
|
-
/** Whether a secret is stored under this exact name. */
|
|
855
|
-
hasKey(name: string): boolean;
|
|
856
|
-
/**
|
|
857
|
-
* The name a value is already stored under, if any. Optional: backends that can't (or won't)
|
|
858
|
-
* reverse-index omit it, and `createSecret`'s dedup step is skipped.
|
|
859
|
-
*/
|
|
860
|
-
findByValue?(value: string): string | undefined;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
export interface Vault extends VaultSpec {
|
|
864
|
-
/**
|
|
865
|
-
* Store a secret coming (often) from a user via the LLM, where we cannot tell a real value
|
|
866
|
-
* from a key name they typed by mistake. Returns the key name callers MUST reference — not
|
|
867
|
-
* necessarily the `name` requested:
|
|
868
|
-
* - `value` is already a known key name → returns `value` (it was a reference, not a secret)
|
|
869
|
-
* - `value` already stored under another name → returns that name (dedup)
|
|
870
|
-
* - otherwise → writes under `name` and returns `name`
|
|
871
|
-
*/
|
|
872
|
-
createSecret(name: string, value: string): Promise<string>;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// ── Health ────────────────────────────────────────────────────────────────────
|
|
876
|
-
|
|
877
|
-
export type HealthStatus =
|
|
878
|
-
| { status: 'ok'; latencyMs?: number }
|
|
879
|
-
| { status: 'degraded'; reason: string; latencyMs?: number }
|
|
880
|
-
| { status: 'down'; reason: string };
|
|
881
|
-
|
|
882
|
-
// ── Registries ────────────────────────────────────────────────────────────────
|
|
883
|
-
|
|
884
|
-
/** Tool CRUD is observed on the {@link Notifier}, as a `RegistryChange` with `registry: 'tools'` — this
|
|
885
|
-
* registry had its own broadcaster over the same primitive, which is duplication, not layering. One
|
|
886
|
-
* notification per tool (removeByPlugin announces a `removed` per matched tool); the registering
|
|
887
|
-
* plugin's name rides in `detail`, advisory as ever — resolve the name for anything authoritative. */
|
|
888
|
-
export interface ToolRegistry {
|
|
889
|
-
register(tool: Tool): void;
|
|
890
|
-
remove(name: string): void;
|
|
891
|
-
resolve(name: string): Tool | null;
|
|
892
|
-
list(): readonly Tool[];
|
|
893
|
-
removeByPlugin(pluginName: string): void;
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
/** What the runner tells a {@link ToolPresenter} about the call it's about to make. */
|
|
897
|
-
export interface PresentContext {
|
|
898
|
-
/** The session as of this provider call — the persisted user turn plus any assistant/tool turns
|
|
899
|
-
* taken so far this turn. A presenter ranks the tool set against this. */
|
|
900
|
-
session: Session;
|
|
901
|
-
/** The provider profile name this turn runs under, so a presenter can tailor to provider
|
|
902
|
-
* capabilities (e.g. Anthropic's in-context tool_reference expansion). */
|
|
903
|
-
provider: string;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* Chooses which tools are advertised to the model for a single provider call — a read-only *view* over
|
|
908
|
-
* the turn's tool snapshot, NOT tool management. Presentation is orthogonal to the {@link ToolRegistry},
|
|
909
|
-
* which stays the single source of registration, resolution, and type-contracts (its fragile, load-bearing
|
|
910
|
-
* machinery is never a swap surface). The runner consults the presenter before *every* provider call, so
|
|
911
|
-
* an impl may present a subset (deferring a large library behind a search tool) and grow it mid-turn as the
|
|
912
|
-
* model discovers tools. Executor resolution is unaffected — a tool withheld here stays callable by name
|
|
913
|
-
* through the live registry — so presentation only ever changes what the model is *told about*.
|
|
914
|
-
*
|
|
915
|
-
* Optional {@link MatbotServices} member (offer-loosely): absent ⇒ the runner advertises the whole
|
|
916
|
-
* snapshot, byte-identical to today. `present` may return synchronously or async (a reranker).
|
|
917
|
-
*/
|
|
918
|
-
export interface ToolPresenter {
|
|
919
|
-
present(tools: readonly Tool[], ctx: PresentContext): readonly Tool[] | Promise<readonly Tool[]>;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* The named provider profiles the runtime resolves by name. A `ReadonlyMap` for the many read-only
|
|
924
|
-
* consumers (`services.providers.get`/`has`/`keys`/…), plus a sanctioned write path so a plugin can
|
|
925
|
-
* *contribute* profiles live — a storage backend replaying provider definitions it captured in its own
|
|
926
|
-
* medium, or the built-in `provider` tool — exactly as a plugin contributes tools via {@link ToolRegistry}.
|
|
927
|
-
* Keyed by `config.name`; `register` upserts.
|
|
928
|
-
*/
|
|
929
|
-
export interface ProviderRegistry extends ReadonlyMap<string, ProviderConfig> {
|
|
930
|
-
register(config: ProviderConfig): void;
|
|
931
|
-
/** Explicit delete (the `provider` tool's `remove`) — removes the profile outright. */
|
|
932
|
-
remove(name: string): boolean;
|
|
933
|
-
/**
|
|
934
|
-
* Undo a contribution: restore the boot-baseline profile for `name` if one existed (a plugin-supplied
|
|
935
|
-
* profile that shadowed a configured one), otherwise delete it. A plugin that registers providers
|
|
936
|
-
* (e.g. a storage backend replaying them from its medium) calls this for each in its `teardown()`, so
|
|
937
|
-
* unloading it reverts the provider set to the host's boot condition — the multi-valued analogue of a
|
|
938
|
-
* swap-member reverting to its captured boot default.
|
|
939
|
-
*/
|
|
940
|
-
revert(name: string): void;
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// ── Pipeline events ─────────────────────────────────────────────────────────────
|
|
944
|
-
|
|
945
|
-
/**
|
|
946
|
-
* The streaming output of a single turn. Every event carries the `traceId` of the turn that
|
|
947
|
-
* produced it — that is the correlation key a frontend uses to route events to the right
|
|
948
|
-
* message/bubble (see `SessionRunner`). A turn ends with exactly one terminal event:
|
|
949
|
-
* `done` (completed), `aborted` (interrupted mid-flight), or `error`. `cancelled` is emitted
|
|
950
|
-
* for a queued submission that was dropped before it ever ran (e.g. by `SessionRunner.abort`),
|
|
951
|
-
* so it carries no session — there is nothing to persist.
|
|
952
|
-
*/
|
|
953
|
-
export type PipelineEvent =
|
|
954
|
-
| { type: 'text-delta'; delta: string; traceId: string }
|
|
955
|
-
| { type: 'thinking'; delta: string; traceId: string }
|
|
956
|
-
| { type: 'tool:start'; callId: string; name: string; input: unknown; traceId: string }
|
|
957
|
-
| { type: 'tool:stdout'; callId: string; chunk: string; traceId: string }
|
|
958
|
-
| { type: 'tool:stderr'; callId: string; chunk: string; traceId: string }
|
|
959
|
-
| { type: 'tool:progress'; callId: string; pct: number; message?: string; traceId: string }
|
|
960
|
-
| { type: 'tool:end'; callId: string; result: unknown; isError: boolean; traceId: string }
|
|
961
|
-
| { type: 'file'; handle: FileHandle; traceId: string }
|
|
962
|
-
| ({ type: 'usage'; traceId: string } & Usage)
|
|
963
|
-
| { type: 'done'; session: Session; traceId: string }
|
|
964
|
-
| { type: 'aborted'; reason: string; session: Session; traceId: string }
|
|
965
|
-
| { type: 'cancelled'; sessionId: string; traceId: string }
|
|
966
|
-
// Session-level (not turn-level, hence no traceId): the runner has fully drained its queue and is
|
|
967
|
-
// now idle. Emitted once per busy period, *after* `running` flips false, so a consumer can map it
|
|
968
|
-
// to an authoritative busy→idle transition without racing the internal state flip.
|
|
969
|
-
| { type: 'idle'; sessionId: string }
|
|
970
|
-
// A queued (not-yet-running) submission, carried on the stream so a frontend renders it as part
|
|
971
|
-
// of the live "delta" (everything after the last committed message), never from stored state.
|
|
972
|
-
// `queued` is the number of submissions ahead of it (0 ⇒ about to run). Emitted live on enqueue
|
|
973
|
-
// and replayed (in queue order) to anyone subscribing mid-flight.
|
|
974
|
-
| { type: 'queued'; content: MessageContent[]; queued: number; concatQueue: boolean; traceId: string; rootTraceId: string }
|
|
975
|
-
// Machine-authored content folded onto the running turn's user message (a `screen` hook's
|
|
976
|
-
// `durable` result — e.g. a fired `contextual` trigger), carried live so a frontend draws it
|
|
977
|
-
// immediately. The blocks are already persisted in that user message (origin: 'robo'); this is
|
|
978
|
-
// purely the live-delivery channel, so a live draw matches the reload (which splits the user
|
|
979
|
-
// turn's robo blocks into their own agent-side bubble).
|
|
980
|
-
| { type: 'robo-user'; content: MessageContent[]; traceId: string }
|
|
981
|
-
// Marker blocks appended to the session this turn (e.g. the dispatcher's record of a hook that
|
|
982
|
-
// threw), carried live so a frontend renders them without waiting for a session reload. The blocks
|
|
983
|
-
// are already persisted in the session; this event is purely the live-delivery channel.
|
|
984
|
-
| { type: 'marker'; content: MessageContent[]; traceId: string }
|
|
985
|
-
// A mid-turn steer that INTERRUPTED the running turn (see `SteeringPolicy`). Emitted synchronously
|
|
986
|
-
// when the steer is accepted, so the new user bubble lands in correct stream order and a frontend
|
|
987
|
-
// knows the imminent `aborted` (reason 'steer') on `interruptedTraceId` is a yield, not a dead-end:
|
|
988
|
-
// keep that turn's partial work rendered and expect a continuation. `traceId`/`rootTraceId` identify
|
|
989
|
-
// the steer submission itself (the continuation turn). Late subscribers reconstruct the bubble from
|
|
990
|
-
// the pump's per-turn `queued` replay seed / committed store order — this is purely live delivery.
|
|
991
|
-
| { type: 'steer'; content: MessageContent[]; interruptedTraceId: string; traceId: string; rootTraceId: string }
|
|
992
|
-
| { type: 'error'; error: string; traceId: string }
|
|
993
|
-
| { type: 'system-context'; text: string; traceId: string };
|
|
994
|
-
|
|
995
|
-
// ── Session runner ──────────────────────────────────────────────────────────────
|
|
996
|
-
|
|
997
|
-
/**
|
|
998
|
-
* A view onto a session returned by `SessionRunner.open`. `session` is the authoritative
|
|
999
|
-
* server-side state — committed messages plus an overlay of any queued-but-not-yet-run
|
|
1000
|
-
* submissions (each carrying `metadata.pending: true`). `events` is a lazy, per-session live
|
|
1001
|
-
* tap: accessing it subscribes to the turn event stream from now (replaying the in-flight
|
|
1002
|
-
* turn, if any); never touching it costs nothing.
|
|
1003
|
-
*/
|
|
1004
|
-
export interface SessionView {
|
|
1005
|
-
session: Session;
|
|
1006
|
-
/** Submissions waiting behind the current turn (does not count the running turn). */
|
|
1007
|
-
queued: number;
|
|
1008
|
-
/** Correlation id of the submission this call enqueued — present only when content was supplied. */
|
|
1009
|
-
traceId?: string;
|
|
1010
|
-
readonly events: AsyncIterable<PipelineEvent>;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
/** Observe a session without submitting anything. */
|
|
1014
|
-
export interface OpenOpts {
|
|
1015
|
-
sessionId: string;
|
|
1016
|
-
signal: AbortSignal;
|
|
1017
|
-
/** Optional caller-supplied correlation id; one is generated when absent. */
|
|
1018
|
-
traceId?: string;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
/** Observe a session AND enqueue a submission. The compiler enforces provider/principal here;
|
|
1022
|
-
* a remote frontend deserializing a request body must still validate the wire input itself. */
|
|
1023
|
-
export interface SubmitOpenOpts extends OpenOpts {
|
|
1024
|
-
content: MessageContent[];
|
|
1025
|
-
provider: string;
|
|
1026
|
-
principal: Principal;
|
|
1027
|
-
/** When true, this submission may be merged with others drained in the same batch. Default false
|
|
1028
|
-
* (queue mode: one turn per submission). */
|
|
1029
|
-
concatQueue?: boolean;
|
|
1030
|
-
/** Disposition for a submission arriving while a turn is running (see {@link SteeringPolicy}):
|
|
1031
|
-
* 'queue' waits for the turn boundary (default), 'interrupt' stops the running turn — keeping its
|
|
1032
|
-
* committed partial work — and runs this next, 'auto' defers to the registered SteeringPolicy (else
|
|
1033
|
-
* the host default). Meaningless when nothing is running (degrades to a plain enqueue). */
|
|
1034
|
-
mode?: SteeringMode;
|
|
1035
|
-
/** Interactive prompt implementation for this submission's turn. The frontend owns delivery —
|
|
1036
|
-
* it must target the frontend's per-session client connections, not a single request. */
|
|
1037
|
-
prompt?: PromptFn;
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Serialises turns per session. A submission never executes concurrently with another for the
|
|
1042
|
-
* same session; the in-memory queue (lost on process restart, by design) absorbs anything that
|
|
1043
|
-
* arrives mid-turn. The server is the source of truth: a frontend renders whatever `open()`
|
|
1044
|
-
* returns and treats the live `events` stream purely as an optimisation.
|
|
1045
|
-
*/
|
|
1046
|
-
export interface SessionRunner {
|
|
1047
|
-
open(opts: OpenOpts | SubmitOpenOpts): Promise<SessionView>;
|
|
1048
|
-
/** Abort the running turn (if any) and drop all queued submissions, emitting `cancelled` for each. */
|
|
1049
|
-
abort(sessionId: string): void;
|
|
1050
|
-
/** Abandon the running turn (if any) WITHOUT touching the queue — `pump` advances to the next
|
|
1051
|
-
* queued submission, or idles. The "give up on this turn" path (a prompt cancel); contrast
|
|
1052
|
-
* `abort`, which also clears the queue. A no-op if nothing is running. */
|
|
1053
|
-
cancelTurn(sessionId: string): void;
|
|
1054
|
-
/** Snapshot of a session's live state: whether a turn is running and how many submissions wait
|
|
1055
|
-
* behind it. `busy` is `running || queued > 0`. */
|
|
1056
|
-
status(sessionId: string): { busy: boolean; running: boolean; queued: number };
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
// ── Steering ────────────────────────────────────────────────────────────────────
|
|
1060
|
-
|
|
1061
|
-
/** Disposition of a submission that arrives while a turn is running. `interrupt` stops the running
|
|
1062
|
-
* turn (its committed partial work is preserved) and runs the new message next; `queue` waits for the
|
|
1063
|
-
* turn boundary. */
|
|
1064
|
-
export type SteeringDecision = 'queue' | 'interrupt';
|
|
1065
|
-
/** The wire-level request (see `SubmitOpenOpts.mode`). `auto` defers the decision to the registered
|
|
1066
|
-
* {@link SteeringPolicy} (else the host default). */
|
|
1067
|
-
export type SteeringMode = SteeringDecision | 'auto';
|
|
1068
|
-
|
|
1069
|
-
/**
|
|
1070
|
-
* How the runner disposes of a mid-turn submission under `mode: 'auto'`, and how an interrupt's
|
|
1071
|
-
* continuation is nudged. An optional, registerable service ({@link MatbotServices}); absent ⇒ the
|
|
1072
|
-
* runner uses its own defaults. Both members are optional so a plugin may override one, the other, or
|
|
1073
|
-
* both. `classify` is deliberately NOT assumed to be an LLM — a regex or a semantic classifier is a
|
|
1074
|
-
* first-class implementation (return synchronously); an LLM `singleTurn` returns a promise.
|
|
1075
|
-
*/
|
|
1076
|
-
export interface SteeringPolicy {
|
|
1077
|
-
/**
|
|
1078
|
-
* Consulted only for `mode: 'auto'` while a turn is running. `session` is the COMMITTED session
|
|
1079
|
-
* (history up to the running turn's start — the in-flight partial is not reachable at submit time);
|
|
1080
|
-
* `steer` is the incoming submission. Return synchronously (regex) or async (semantic / singleTurn).
|
|
1081
|
-
* Absent ⇒ the host default disposition.
|
|
1082
|
-
*/
|
|
1083
|
-
classify?(ctx: { session: Session; steer: MessageContent[] }): SteeringDecision | Promise<SteeringDecision>;
|
|
1084
|
-
/**
|
|
1085
|
-
* The ephemeral "keep going, noting the above" context folded onto an interrupt's continuation turn
|
|
1086
|
-
* (never persisted). Absent ⇒ the host default nudge.
|
|
1087
|
-
*/
|
|
1088
|
-
nudge?(ctx: { session: Session; steer: MessageContent[] }): MessageContent[];
|
|
1089
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* The plugin-facing type surface, one file per domain. It was a single 1,195-line module — the first
|
|
3
|
+
* thing a third-party author opens and the last thing they can hold in their head. The `── Section ──`
|
|
4
|
+
* banners it had already grown are the split, so each file is the unit its banner described.
|
|
5
|
+
*
|
|
6
|
+
* This barrel re-exports them in the original order, so every `from './types.js'` import across the
|
|
7
|
+
* repo — and `index.ts`'s `export type *` — is byte-identical in effect.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export * from './types/primitives.js';
|
|
11
|
+
export * from './types/principal.js';
|
|
12
|
+
export * from './types/provider.js';
|
|
13
|
+
export * from './types/messages.js';
|
|
14
|
+
export * from './types/system-context.js';
|
|
15
|
+
export * from './types/hooks.js';
|
|
16
|
+
export * from './types/storage.js';
|
|
17
|
+
export * from './types/knowledge.js';
|
|
18
|
+
export * from './types/typescript.js';
|
|
19
|
+
export * from './types/tools.js';
|
|
20
|
+
export * from './types/files.js';
|
|
21
|
+
export * from './types/frontend.js';
|
|
22
|
+
export * from './types/vault.js';
|
|
23
|
+
export * from './types/health.js';
|
|
24
|
+
export * from './types/registries.js';
|
|
25
|
+
export * from './types/events.js';
|
|
26
|
+
export * from './types/session-runner.js';
|
|
27
|
+
export * from './types/steering.js';
|