@ai-matrx/agents 0.17.4 → 0.18.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/CHANGELOG.md +53 -0
- package/README.md +36 -1
- package/dist/content-transfer/index.cjs.map +1 -1
- package/dist/content-transfer/index.js.map +1 -1
- package/dist/content-transfer/react/index.cjs.map +1 -1
- package/dist/content-transfer/react/index.js.map +1 -1
- package/dist/mandates/index.cjs +2 -2
- package/dist/mandates/index.cjs.map +1 -1
- package/dist/mandates/index.d.cts +4 -4
- package/dist/mandates/index.d.ts +4 -4
- package/dist/mandates/index.js +2 -2
- package/dist/mandates/index.js.map +1 -1
- package/dist/sources/index.cjs.map +1 -1
- package/dist/sources/index.d.cts +7 -0
- package/dist/sources/index.d.ts +7 -0
- package/dist/sources/index.js.map +1 -1
- package/dist/sources/react/index.cjs +1163 -0
- package/dist/sources/react/index.cjs.map +1 -0
- package/dist/sources/react/index.d.cts +746 -0
- package/dist/sources/react/index.d.ts +746 -0
- package/dist/sources/react/index.js +1143 -0
- package/dist/sources/react/index.js.map +1 -0
- package/dist/sources/runtime/index.cjs +1354 -0
- package/dist/sources/runtime/index.cjs.map +1 -0
- package/dist/sources/runtime/index.d.cts +959 -0
- package/dist/sources/runtime/index.d.ts +959 -0
- package/dist/sources/runtime/index.js +1331 -0
- package/dist/sources/runtime/index.js.map +1 -0
- package/mandates/snapshots/keys.0.17.5.json +652 -0
- package/mandates/snapshots/keys.0.18.0.json +652 -0
- package/mandates/snapshots/keys.0.18.1.json +652 -0
- package/package.json +22 -2
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ai-matrx/agents/sources` — THE ONE SOURCE PAYLOAD (contract v1, frozen 2026-09-27).
|
|
3
|
+
*
|
|
4
|
+
* Every request that is built from a person's Sources carries them as a
|
|
5
|
+
* `SourceSet`: a list of `SourceRef` pointers (never blobs) plus how the model
|
|
6
|
+
* should be grounded in them. The server answers with a `SourceManifest`
|
|
7
|
+
* (sizes and states, never bodies — POST /sources/manifest) or a
|
|
8
|
+
* `ResolvedSourceSet` (grounded text for generators — POST /sources/resolve).
|
|
9
|
+
*
|
|
10
|
+
* Contract of record: common-docs `projects/unified-source-input/DESIGN.md`
|
|
11
|
+
* § "Contract v1 — FROZEN". A shape change here is an AMENDMENT there first,
|
|
12
|
+
* with its Pydantic twin beside aidream's `parse_resource_reference`.
|
|
13
|
+
*
|
|
14
|
+
* `SourceRef` IS the existing `resource_ref` pointer already on the wire
|
|
15
|
+
* (aidream `services/conversation_context/resource_context.py`), extended with
|
|
16
|
+
* optional fields only — never coin a second pointer type.
|
|
17
|
+
*
|
|
18
|
+
* Pure module: no React, no I/O, no "use client".
|
|
19
|
+
*/
|
|
20
|
+
declare const RESOURCE_REF_KIND: "resource_ref";
|
|
21
|
+
declare const SOURCE_SET_KIND: "source_set";
|
|
22
|
+
declare const SOURCE_MANIFEST_KIND: "source_manifest";
|
|
23
|
+
declare const RESOLVED_SOURCE_SET_KIND: "resolved_source_set";
|
|
24
|
+
/** The wire version every `SourceSet` carries. */
|
|
25
|
+
declare const SOURCE_SET_VERSION: 1;
|
|
26
|
+
/** A bounded preview of one representation promoted into the prompt. */
|
|
27
|
+
interface ResourcePromotion {
|
|
28
|
+
representation: string;
|
|
29
|
+
max_chars?: number;
|
|
30
|
+
}
|
|
31
|
+
type SourceDelivery = "direct" | "context";
|
|
32
|
+
type SourceGrounding = "whole" | "selected" | "retrieve";
|
|
33
|
+
/** The pointer — the existing `resource_ref` envelope, extended (all new fields optional). */
|
|
34
|
+
interface SourceRef {
|
|
35
|
+
__kind: typeof RESOURCE_REF_KIND;
|
|
36
|
+
/** Server resource token: "file" (A2; "cld_file" is a server alias) | "processed_document" | "note" | "fc_set" | … */
|
|
37
|
+
resource_type: string;
|
|
38
|
+
resource_id: string;
|
|
39
|
+
/** "clean" | "raw" | "pdf" | a family representation key. Omitted = server's Clean → Raw → original fallback. */
|
|
40
|
+
representation?: string;
|
|
41
|
+
/** Hand-picked Segment ids (rag.kg_chunks ids or "<resource_id>:<n>"). */
|
|
42
|
+
include_segments?: string[];
|
|
43
|
+
/** Include the text (default "direct") | the AI fetches it on demand ("context"). */
|
|
44
|
+
delivery?: SourceDelivery;
|
|
45
|
+
/** Per-Source cap chosen on the review page. */
|
|
46
|
+
max_chars?: number;
|
|
47
|
+
promote?: ResourcePromotion | ResourcePromotion[];
|
|
48
|
+
exclude?: string[];
|
|
49
|
+
}
|
|
50
|
+
interface SourceSet {
|
|
51
|
+
__kind: typeof SOURCE_SET_KIND;
|
|
52
|
+
version: typeof SOURCE_SET_VERSION;
|
|
53
|
+
sources: SourceRef[];
|
|
54
|
+
/** Only for "Just a topic". */
|
|
55
|
+
topic?: string;
|
|
56
|
+
/** Default "whole". */
|
|
57
|
+
grounding?: SourceGrounding;
|
|
58
|
+
/** Required in spirit when grounding = "retrieve". */
|
|
59
|
+
retrieve_query?: string;
|
|
60
|
+
/** The model whose context window sets the budget. */
|
|
61
|
+
target_model_id?: string;
|
|
62
|
+
}
|
|
63
|
+
type SourceState = "ready" | "processing" | "failed" | "unavailable";
|
|
64
|
+
interface SourceManifestForm {
|
|
65
|
+
form: string;
|
|
66
|
+
label: string;
|
|
67
|
+
chars: number;
|
|
68
|
+
available: boolean;
|
|
69
|
+
}
|
|
70
|
+
interface SourceManifestSegment {
|
|
71
|
+
id: string;
|
|
72
|
+
/** "Page 7", "Pages 22–24" (a part spanning pages), "0:04–2:31", a heading path, "Part 3". */
|
|
73
|
+
label: string;
|
|
74
|
+
page?: number;
|
|
75
|
+
chars: number;
|
|
76
|
+
/**
|
|
77
|
+
* Amendment A5: the part's opening words (about 160 characters, whitespace
|
|
78
|
+
* collapsed) — never its body. Lets a person see what a part says and find
|
|
79
|
+
* a part by its words.
|
|
80
|
+
*/
|
|
81
|
+
preview?: string;
|
|
82
|
+
}
|
|
83
|
+
interface SourceManifestEntry {
|
|
84
|
+
ref: SourceRef;
|
|
85
|
+
label: string;
|
|
86
|
+
resource_type: string;
|
|
87
|
+
state: SourceState;
|
|
88
|
+
state_detail?: string;
|
|
89
|
+
forms: SourceManifestForm[];
|
|
90
|
+
default_form: string;
|
|
91
|
+
segments?: SourceManifestSegment[];
|
|
92
|
+
}
|
|
93
|
+
/** POST /sources/manifest { source_set } → sizes and states, never bodies. */
|
|
94
|
+
interface SourceManifest {
|
|
95
|
+
__kind: typeof SOURCE_MANIFEST_KIND;
|
|
96
|
+
sources: SourceManifestEntry[];
|
|
97
|
+
total_chars: number;
|
|
98
|
+
estimated_tokens: number;
|
|
99
|
+
/** null = unknown model; the UI says so. */
|
|
100
|
+
model_context_tokens: number | null;
|
|
101
|
+
}
|
|
102
|
+
interface ResolvedSourceSegment {
|
|
103
|
+
id: string;
|
|
104
|
+
page?: number;
|
|
105
|
+
chars: number;
|
|
106
|
+
}
|
|
107
|
+
interface ResolvedSource {
|
|
108
|
+
ref: SourceRef;
|
|
109
|
+
label: string;
|
|
110
|
+
form_used: string;
|
|
111
|
+
/** Grounded: "### Chunk <id> (page N)" blocks; never re-chunked. */
|
|
112
|
+
text: string;
|
|
113
|
+
segments: ResolvedSourceSegment[];
|
|
114
|
+
file_id?: string;
|
|
115
|
+
processed_document_id?: string;
|
|
116
|
+
/** "processing" = the raw fallback was used. */
|
|
117
|
+
state: "ready" | "processing";
|
|
118
|
+
truncated: boolean;
|
|
119
|
+
/** Every stand-in announces itself. */
|
|
120
|
+
notes: string[];
|
|
121
|
+
}
|
|
122
|
+
/** A1 (2026-09-27): "not_ready" = a file not yet read. */
|
|
123
|
+
type DroppedSourceReason = "no_access" | "missing" | "failed" | "over_budget" | "not_ready";
|
|
124
|
+
interface DroppedSource {
|
|
125
|
+
ref: SourceRef;
|
|
126
|
+
reason: DroppedSourceReason;
|
|
127
|
+
/** A1: what happened and what to do. */
|
|
128
|
+
detail?: string;
|
|
129
|
+
}
|
|
130
|
+
/** POST /sources/resolve { source_set } → grounded text for generators. */
|
|
131
|
+
interface ResolvedSourceSet {
|
|
132
|
+
__kind: typeof RESOLVED_SOURCE_SET_KIND;
|
|
133
|
+
sources: ResolvedSource[];
|
|
134
|
+
dropped: DroppedSource[];
|
|
135
|
+
total_chars: number;
|
|
136
|
+
}
|
|
137
|
+
/** Builder options. `undefined` is accepted everywhere so callers can forward an existing ref's optional fields. */
|
|
138
|
+
interface SourceRefOptions {
|
|
139
|
+
representation?: string | undefined;
|
|
140
|
+
include_segments?: readonly string[] | undefined;
|
|
141
|
+
delivery?: SourceDelivery | undefined;
|
|
142
|
+
max_chars?: number | undefined;
|
|
143
|
+
promote?: ResourcePromotion | ResourcePromotion[] | undefined;
|
|
144
|
+
exclude?: readonly string[] | undefined;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The Source input's runtime types — what a list of picked Sources IS while a
|
|
149
|
+
* person is still choosing, on top of the frozen v1 wire contract
|
|
150
|
+
* (`@ai-matrx/agents/sources`), which is never re-declared here.
|
|
151
|
+
*
|
|
152
|
+
* UI-free: no React, no framework, no store library. Every client (the web
|
|
153
|
+
* app, the browser extension, the desktop app, mobile) holds this same shape
|
|
154
|
+
* in its own store through a `SourceSetStore` adapter.
|
|
155
|
+
*
|
|
156
|
+
* Contract of record: common-docs `projects/unified-source-input/DESIGN.md`
|
|
157
|
+
* § "One core, many screens".
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
/** Every tile a host can allow: the Add new doors, plus "existing" (Use existing + search). */
|
|
161
|
+
type SourceTileId = "upload" | "paste" | "web" | "youtube" | "audio" | "image" | "topic" | "existing";
|
|
162
|
+
/**
|
|
163
|
+
* What a picked Source is while the person chooses: the tile it came through,
|
|
164
|
+
* or — for something they already had — "files", "notes", "your_sources"
|
|
165
|
+
* (a Source screen) or "records" (any other registry kind).
|
|
166
|
+
*/
|
|
167
|
+
type SourceKindId = SourceTileId | "your_sources" | "files" | "notes" | "records";
|
|
168
|
+
/** The input behind a Source that is still landing — enough to land it again. */
|
|
169
|
+
interface SourceIntakeInput {
|
|
170
|
+
/** Pasted text (paste). */
|
|
171
|
+
text?: string | undefined;
|
|
172
|
+
/** The name the person typed for pasted text. */
|
|
173
|
+
name?: string | undefined;
|
|
174
|
+
/** A web page or YouTube link (web, youtube). */
|
|
175
|
+
url?: string | undefined;
|
|
176
|
+
/** A recording that already finished uploading (audio) — transcribe from here. */
|
|
177
|
+
fileId?: string | undefined;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* One picked Source while the person is still choosing. JSON only — it is
|
|
181
|
+
* persisted as a draft so a refresh never loses a pick.
|
|
182
|
+
*
|
|
183
|
+
* `ref` is null only while a NEW Source is still landing (reading a page,
|
|
184
|
+
* transcribing, uploading); it becomes the pointer the moment the door
|
|
185
|
+
* answers. Nothing is ever sent to the server as a blob.
|
|
186
|
+
*/
|
|
187
|
+
interface SourceDraft {
|
|
188
|
+
kind: SourceKindId;
|
|
189
|
+
/** What the person sees: a file name, a page title, the first line of a paste. */
|
|
190
|
+
label: string;
|
|
191
|
+
ref: SourceRef | null;
|
|
192
|
+
/** Where it came from, in plain words (a web address, "Pasted text"). */
|
|
193
|
+
origin?: string | undefined;
|
|
194
|
+
/** The Source screen id when known (a landed Source) — every card opens. */
|
|
195
|
+
processedDocumentId?: string | undefined;
|
|
196
|
+
/**
|
|
197
|
+
* How a reused Source was captured (`processed_documents.source_kind`), so
|
|
198
|
+
* the card and the review say "Transcript" or "Web page" — never "Document".
|
|
199
|
+
*/
|
|
200
|
+
sourceKind?: string | undefined;
|
|
201
|
+
/** The stored file behind it, when it is one (the form chooser reads its family). */
|
|
202
|
+
fileId?: string | undefined;
|
|
203
|
+
/** Every stand-in announces itself: a reader fallback, a door notice. */
|
|
204
|
+
notes?: string[] | undefined;
|
|
205
|
+
/** The person asked to wait for the clean version before anything runs. */
|
|
206
|
+
waitForClean?: boolean | undefined;
|
|
207
|
+
/**
|
|
208
|
+
* What the person handed over, kept ONLY while the Source is still landing
|
|
209
|
+
* (or failed) so a reload or a failure never loses it: the pasted text, the
|
|
210
|
+
* link, or an already-uploaded recording's file. Never file bytes. Cleared
|
|
211
|
+
* the moment the Source settles.
|
|
212
|
+
*/
|
|
213
|
+
input?: SourceIntakeInput | undefined;
|
|
214
|
+
}
|
|
215
|
+
type SourceCardStatus = "pending" | "resolving" | "ready" | "error";
|
|
216
|
+
/** One card: the draft, its lifecycle, and what the server measured. */
|
|
217
|
+
interface SourceCardModel {
|
|
218
|
+
id: string;
|
|
219
|
+
draft: SourceDraft;
|
|
220
|
+
status: SourceCardStatus;
|
|
221
|
+
/** A sentence with its remedy — never a code. */
|
|
222
|
+
error: string | null;
|
|
223
|
+
/** The server's measurement (sizes, forms, parts, state). Null until read. */
|
|
224
|
+
manifest: SourceManifestEntry | null;
|
|
225
|
+
}
|
|
226
|
+
/** The entity the Sources are being used for — passed to the door as `attach_to`. */
|
|
227
|
+
interface SourceAttachTo {
|
|
228
|
+
entityType: string;
|
|
229
|
+
entityId: string;
|
|
230
|
+
label?: string | undefined;
|
|
231
|
+
}
|
|
232
|
+
/** The whole state of one Source input: its cards (in the order picked) and the topic. */
|
|
233
|
+
interface SourceSetState {
|
|
234
|
+
cards: readonly SourceCardModel[];
|
|
235
|
+
topic: string;
|
|
236
|
+
}
|
|
237
|
+
/** What a draft store keeps for a reload: the cards (without measurements) and the topic. */
|
|
238
|
+
interface PersistedSourceCard {
|
|
239
|
+
id: string;
|
|
240
|
+
draft: SourceDraft;
|
|
241
|
+
status: SourceCardStatus;
|
|
242
|
+
error: string | null;
|
|
243
|
+
}
|
|
244
|
+
interface PersistedSourceInput {
|
|
245
|
+
sources: PersistedSourceCard[];
|
|
246
|
+
topic: string;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Finding a part of a Source — THE one matcher, used by the Source card's
|
|
251
|
+
* "Choose parts" and by "Review what goes in".
|
|
252
|
+
*
|
|
253
|
+
* A query is one of:
|
|
254
|
+
* - a page number ("12") — the part that covers that page;
|
|
255
|
+
* - a page range ("3-10", "3–10", "3 to 10") — every part inside it;
|
|
256
|
+
* - words — every word must appear in the part's label or its preview (the
|
|
257
|
+
* opening words the manifest carries, contract amendment A5), matched here;
|
|
258
|
+
* or the part is one the server found holding every word in its full text
|
|
259
|
+
* (`POST /sources/parts/search`, via `useSourcePartsSearch`).
|
|
260
|
+
*
|
|
261
|
+
* A part spanning several pages ("Pages 22–24") covers each of them: the
|
|
262
|
+
* manifest's numeric `page` is its first page and the label names the span.
|
|
263
|
+
*
|
|
264
|
+
* V1-A: the search promised "words" but matched only labels, so
|
|
265
|
+
* "photosynthesis" found nothing in a biology course.
|
|
266
|
+
*/
|
|
267
|
+
|
|
268
|
+
/** The ids of parts the server found holding every word (`useSourcePartsSearch`). */
|
|
269
|
+
type PartMatchIds = ReadonlySet<string>;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* fileSource — how a stored file on a Source card becomes its Source, decided
|
|
273
|
+
* from the SERVER's state alone (USI-3e). Pure: the one read
|
|
274
|
+
* (`/files/{id}/rag-status`) and the file's own organization are INJECTED by
|
|
275
|
+
* the host (`SourceFileState` below), never imported.
|
|
276
|
+
*
|
|
277
|
+
* The server owns reading a file: every upload's finalize already starts the
|
|
278
|
+
* one processing run (the file adapters → a `processed_documents` row), and
|
|
279
|
+
* `/files/{id}/rag-status` merges that run's lifecycle row with the Source it
|
|
280
|
+
* made. So a card never starts its own run while one is live, a reload simply
|
|
281
|
+
* reads the state again (re-attach, never a second job), and a file someone
|
|
282
|
+
* already had ("use the one you already have", or picked from Files) is kept
|
|
283
|
+
* the moment its existing Source is seen.
|
|
284
|
+
*/
|
|
285
|
+
|
|
286
|
+
/** The part of the server's `/files/{id}/rag-status` answer the decision reads. */
|
|
287
|
+
interface FileSourceStatus {
|
|
288
|
+
state: string;
|
|
289
|
+
processed_document_id?: string | null | undefined;
|
|
290
|
+
error?: {
|
|
291
|
+
message?: string | null | undefined;
|
|
292
|
+
[detail: string]: unknown;
|
|
293
|
+
} | null | undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* THE one client for the Source doors on the server (aidream
|
|
298
|
+
* `api/routers/source_sets.py`):
|
|
299
|
+
*
|
|
300
|
+
* - `POST /sources/manifest` — sizes, states, forms and parts (never bodies);
|
|
301
|
+
* - `POST /sources/resolve` — the full grounded text a generator reads;
|
|
302
|
+
* - `POST /sources/parts/search` — the ids of one Source's parts that hold
|
|
303
|
+
* every word of a query (the manifest's own part ids; never a body).
|
|
304
|
+
*
|
|
305
|
+
* All three are READS decided by access, never by the selected organization:
|
|
306
|
+
* the server names them in `BODY_CARRIED_READS` (aidream `api/read_by_access.py`),
|
|
307
|
+
* so every call goes out marked `bodyCarriedRead` — with an organization it is
|
|
308
|
+
* still named, with none it is sent naming none instead of being refused on
|
|
309
|
+
* the client (V1-A: "Select an organization before sending this request" was a
|
|
310
|
+
* dead end the server never asked for).
|
|
311
|
+
*
|
|
312
|
+
* The TRANSPORT is injected: auth, base URL and error shape belong to the
|
|
313
|
+
* host. A web app binds its typed API client; any other client can use
|
|
314
|
+
* `createFetchSourcesTransport` below.
|
|
315
|
+
*/
|
|
316
|
+
|
|
317
|
+
/** Options every Source door call carries. */
|
|
318
|
+
interface SourceCallOptions {
|
|
319
|
+
organizationId?: string | undefined;
|
|
320
|
+
signal?: AbortSignal | undefined;
|
|
321
|
+
}
|
|
322
|
+
/** The server's answer to a part search. */
|
|
323
|
+
interface SourcePartsSearchResult {
|
|
324
|
+
segment_ids?: string[] | null | undefined;
|
|
325
|
+
truncated?: boolean | null | undefined;
|
|
326
|
+
/** Set when the Source cannot be searched ("no_access", …). */
|
|
327
|
+
unavailable?: string | null | undefined;
|
|
328
|
+
/** What happened and what to do, in the server's words. */
|
|
329
|
+
detail?: string | null | undefined;
|
|
330
|
+
}
|
|
331
|
+
interface SourcesClient {
|
|
332
|
+
manifest(sourceSet: SourceSet, options?: SourceCallOptions): Promise<SourceManifest>;
|
|
333
|
+
resolve(sourceSet: SourceSet, options?: SourceCallOptions): Promise<ResolvedSourceSet>;
|
|
334
|
+
/** The ids of `ref`'s parts whose label or text holds every word of `query`. */
|
|
335
|
+
searchParts(ref: SourceRef, query: string, options?: SourceCallOptions): Promise<SourcePartsSearchResult>;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* The Source-set controller — the imperative face of the state machine, with
|
|
340
|
+
* every outside dependency injected through one adapter:
|
|
341
|
+
*
|
|
342
|
+
* - `store` where the cards live (a Redux slice, a Zustand store, the
|
|
343
|
+
* in-memory store below — the host's choice);
|
|
344
|
+
* - `persistence` the draft store a reload reads back (optional);
|
|
345
|
+
* - `client` the Source doors (`createSourcesClient(transport)`);
|
|
346
|
+
* - `config` the host's choices: default form, organization, the
|
|
347
|
+
* deliveries it can use, the most Sources it takes.
|
|
348
|
+
*
|
|
349
|
+
* Every screen (the Studio grid, a compact strip, chat's "+", the review)
|
|
350
|
+
* drives the SAME controller; `@ai-matrx/agents/sources/react` binds it to
|
|
351
|
+
* React with `useSourceSet(adapter)`.
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
/** Where the cards live. `getState` must return the SAME object until something changes. */
|
|
355
|
+
interface SourceSetStore {
|
|
356
|
+
getState(): SourceSetState;
|
|
357
|
+
setState(next: SourceSetState): void;
|
|
358
|
+
subscribe(listener: () => void): () => void;
|
|
359
|
+
/** Called once when a screen mounts (a store that registers entries lazily). */
|
|
360
|
+
init?: (() => void) | undefined;
|
|
361
|
+
}
|
|
362
|
+
/** The draft store a reload reads back. `load` returns the raw saved value (stable until it changes). */
|
|
363
|
+
interface SourceDraftStorage {
|
|
364
|
+
load(): unknown;
|
|
365
|
+
save(value: PersistedSourceInput): void;
|
|
366
|
+
subscribe?: ((listener: () => void) => () => void) | undefined;
|
|
367
|
+
}
|
|
368
|
+
interface SourceSetConfig {
|
|
369
|
+
/** The form each new Source starts on ("clean" | "raw"). Omitted = the server's choice. */
|
|
370
|
+
defaultForm?: string | undefined;
|
|
371
|
+
/** The organization named on the door calls (they answer without one too). */
|
|
372
|
+
organizationId?: string | null | undefined;
|
|
373
|
+
/** How the AI may get the Sources on this surface. Omitted = both. */
|
|
374
|
+
deliveries?: readonly SourceDelivery[] | undefined;
|
|
375
|
+
/** Most Sources the person may pick. Omitted = no limit. */
|
|
376
|
+
max?: number | undefined;
|
|
377
|
+
}
|
|
378
|
+
interface SourceSetAdapter {
|
|
379
|
+
store: SourceSetStore;
|
|
380
|
+
persistence?: SourceDraftStorage | undefined;
|
|
381
|
+
/** Needed for `manifest()` and `resolve()`. */
|
|
382
|
+
client?: SourcesClient | undefined;
|
|
383
|
+
config?: SourceSetConfig | undefined;
|
|
384
|
+
/** A new card's id (default: a random UUID). */
|
|
385
|
+
createId?: (() => string) | undefined;
|
|
386
|
+
/** A failed call as one sentence with its remedy (default: the error's message). */
|
|
387
|
+
describeError?: ((error: unknown) => string) | undefined;
|
|
388
|
+
}
|
|
389
|
+
/** The measurement's own state (not persisted, not shared across controllers). */
|
|
390
|
+
interface SourceSetMeta {
|
|
391
|
+
measuring: boolean;
|
|
392
|
+
/** Why the measurement failed, with its remedy. */
|
|
393
|
+
manifestError: string | null;
|
|
394
|
+
}
|
|
395
|
+
/** The mutations every intake and screen drives. */
|
|
396
|
+
interface SourceSetActions {
|
|
397
|
+
/** Add a Source that is already a pointer (a stored record, a landed Source). */
|
|
398
|
+
addReady(draft: SourceDraft): string;
|
|
399
|
+
/** Add a Source that is still landing; finish it with `settle` or `fail`. */
|
|
400
|
+
addPending(draft: Omit<SourceDraft, "ref">): string;
|
|
401
|
+
settle(id: string, patch: Partial<SourceDraft> & {
|
|
402
|
+
ref: SourceRef;
|
|
403
|
+
}): void;
|
|
404
|
+
fail(id: string, sentence: string): void;
|
|
405
|
+
/** Back to landing. False when the card is gone (removed) — nothing to land. */
|
|
406
|
+
restart(id: string): boolean;
|
|
407
|
+
/** Change what a card that is still landing says or keeps (label, input, fileId, notes). */
|
|
408
|
+
updateDraft(id: string, patch: Partial<Omit<SourceDraft, "ref">>): void;
|
|
409
|
+
remove(id: string): void;
|
|
410
|
+
/** Change the pointer's choices (form, parts, cap, delivery). */
|
|
411
|
+
updateRef(id: string, options: SourceRefOptions): void;
|
|
412
|
+
setWaitForClean(id: string, wait: boolean): void;
|
|
413
|
+
setTopic(topic: string): void;
|
|
414
|
+
/** True when this pointer is already picked. */
|
|
415
|
+
hasRef(resourceType: string, resourceId: string): boolean;
|
|
416
|
+
/** How many Sources are picked RIGHT NOW (read from the store, never a stale render). */
|
|
417
|
+
liveCount(): number;
|
|
418
|
+
/** POST /sources/manifest for the ready Sources; cards update with it. */
|
|
419
|
+
manifest(): Promise<SourceManifest | null>;
|
|
420
|
+
}
|
|
421
|
+
interface SourceSetController extends SourceSetActions {
|
|
422
|
+
getState(): SourceSetState;
|
|
423
|
+
subscribe(listener: () => void): () => void;
|
|
424
|
+
getMeta(): SourceSetMeta;
|
|
425
|
+
subscribeMeta(listener: () => void): () => void;
|
|
426
|
+
/** Replace the host's choices (called on every render by a React binding). */
|
|
427
|
+
configure(config: SourceSetConfig): void;
|
|
428
|
+
readonly config: SourceSetConfig;
|
|
429
|
+
/** Bring back a saved draft once — only into an empty input. True when it restored. */
|
|
430
|
+
restore(saved: unknown): boolean;
|
|
431
|
+
/** The frozen v1 payload, built from the ready Sources. */
|
|
432
|
+
toSourceSet(options?: {
|
|
433
|
+
targetModelId?: string | undefined;
|
|
434
|
+
}): SourceSet;
|
|
435
|
+
/** Apply a set the review returned (forms, parts, caps, delivery). */
|
|
436
|
+
applySourceSet(set: SourceSet): void;
|
|
437
|
+
/** POST /sources/resolve — the grounded text for a generator, named as the cards name them. */
|
|
438
|
+
resolve(options?: {
|
|
439
|
+
targetModelId?: string | undefined;
|
|
440
|
+
}): Promise<ResolvedSourceSet>;
|
|
441
|
+
/** Switch back any Source set to a delivery the host cannot use (with a note). */
|
|
442
|
+
fitDeliveries(deliveries?: readonly SourceDelivery[]): void;
|
|
443
|
+
/** How many more Sources fit under the host's max. */
|
|
444
|
+
roomLeft(): number;
|
|
445
|
+
totalChars(): number;
|
|
446
|
+
settled(): boolean;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Source intake — every way NEW material becomes a Source, through the doors
|
|
451
|
+
* that already exist on the server (DESIGN.md amendment A3). Each call adds a
|
|
452
|
+
* card at once (so the person sees it working), then settles it to a pointer
|
|
453
|
+
* or fails it with a sentence and a remedy. Nothing is ever sent onward as a
|
|
454
|
+
* blob.
|
|
455
|
+
*
|
|
456
|
+
* pasted text → `POST /sources/land`, kept.
|
|
457
|
+
* a web page → the host's scraper lands it (`addScrapedPage`), or a link
|
|
458
|
+
* typed before a reload is scraped again (`addWebPage`);
|
|
459
|
+
* `POST /sources/{id}/keep` files it against the thing being made.
|
|
460
|
+
* a file/image → the host's uploader hands over file ids (`addUploaded`); the
|
|
461
|
+
* file is filed against `attachTo` in the direction the
|
|
462
|
+
* association registry declares, and its Source is kept once
|
|
463
|
+
* reading makes one (`fileLanded`, driven by the recovery).
|
|
464
|
+
* existing → a registry item (`addExisting`).
|
|
465
|
+
* YouTube/audio → the transcript comes from the host's readers and lands
|
|
466
|
+
* through `POST /sources/land`.
|
|
467
|
+
*
|
|
468
|
+
* Never lose input: every landing keeps what the person handed over in the
|
|
469
|
+
* draft (`SourceDraft.input` — text, link, uploaded recording; never bytes)
|
|
470
|
+
* until it settles, so `resume` can land it again after a reload, a failure,
|
|
471
|
+
* or once an organization is chosen (the recovery replays every card waiting
|
|
472
|
+
* for one — from state, never an in-memory queue). Landing twice is safe: the
|
|
473
|
+
* door dedupes by (organization, identity, content hash).
|
|
474
|
+
*
|
|
475
|
+
* Every outside dependency is INJECTED (`SourceIntakeDeps`): the doors, the
|
|
476
|
+
* organization gate, the signed-in person, the failure wording.
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
/** A door's notice: a sentence, and sometimes a remedy code ("save_it…"). */
|
|
480
|
+
interface SourceLandingNotice {
|
|
481
|
+
message: string;
|
|
482
|
+
remedy?: string | null | undefined;
|
|
483
|
+
}
|
|
484
|
+
/** The target a door files a Source against (`attach_to`). */
|
|
485
|
+
interface SourceAttachTarget {
|
|
486
|
+
entity_type: string;
|
|
487
|
+
entity_id: string;
|
|
488
|
+
label: string | null;
|
|
489
|
+
signal: boolean;
|
|
490
|
+
}
|
|
491
|
+
/** The part of a landing body the intake reads or sets — the rest is the host's door shape. */
|
|
492
|
+
interface SourceLandingBodyBase {
|
|
493
|
+
name: string;
|
|
494
|
+
canonical_identity?: string | null | undefined;
|
|
495
|
+
provenance?: Record<string, unknown> | null | undefined;
|
|
496
|
+
}
|
|
497
|
+
/** A landed (or kept) Source's answer. */
|
|
498
|
+
interface SourceLandingAnswer {
|
|
499
|
+
processed_document_id: string;
|
|
500
|
+
notices?: SourceLandingNotice[] | null | undefined;
|
|
501
|
+
}
|
|
502
|
+
/** What the host's web reader returns for a link. Null = the page could not be read. */
|
|
503
|
+
interface ScrapedSourcePage {
|
|
504
|
+
processedDocumentId: string | null | undefined;
|
|
505
|
+
sourceNotices: SourceLandingNotice[];
|
|
506
|
+
pageTitle?: string | null | undefined;
|
|
507
|
+
}
|
|
508
|
+
/** What the host's YouTube reader returns. `source` = the server landed a timed transcript Source. */
|
|
509
|
+
interface YouTubeTranscriptResult {
|
|
510
|
+
text: string | null | undefined;
|
|
511
|
+
note?: string | null | undefined;
|
|
512
|
+
source?: {
|
|
513
|
+
processed_document_id: string;
|
|
514
|
+
title?: string | null | undefined;
|
|
515
|
+
} | null | undefined;
|
|
516
|
+
}
|
|
517
|
+
/** One file the host's uploader already uploaded. */
|
|
518
|
+
interface UploadedSourceFile {
|
|
519
|
+
fileId: string;
|
|
520
|
+
name: string;
|
|
521
|
+
}
|
|
522
|
+
/** The association write (the host's one chokepoint). */
|
|
523
|
+
interface SourceAssociationEdge {
|
|
524
|
+
sourceType: string;
|
|
525
|
+
sourceId: string;
|
|
526
|
+
targetType: string;
|
|
527
|
+
targetId: string;
|
|
528
|
+
orgId: string;
|
|
529
|
+
}
|
|
530
|
+
type SourceAssociationResult = {
|
|
531
|
+
ok: true;
|
|
532
|
+
} | {
|
|
533
|
+
ok: false;
|
|
534
|
+
error: {
|
|
535
|
+
message: string;
|
|
536
|
+
};
|
|
537
|
+
};
|
|
538
|
+
interface SourceIntakeDoors<Body extends SourceLandingBodyBase = SourceLandingBodyBase> {
|
|
539
|
+
/** `POST /sources/land` with the body as given (keep + attach_to set by the intake). */
|
|
540
|
+
land(body: Body & {
|
|
541
|
+
keep: true;
|
|
542
|
+
attach_to: SourceAttachTarget[];
|
|
543
|
+
}): Promise<SourceLandingAnswer>;
|
|
544
|
+
/** `POST /sources/{id}/keep`, filed against the targets. */
|
|
545
|
+
keep(processedDocumentId: string, options: {
|
|
546
|
+
attachTo: SourceAttachTarget[];
|
|
547
|
+
organizationId: string;
|
|
548
|
+
}): Promise<{
|
|
549
|
+
notices?: SourceLandingNotice[] | null | undefined;
|
|
550
|
+
}>;
|
|
551
|
+
/** Build the landing body for text the person handed over. */
|
|
552
|
+
buildTextLanding(input: {
|
|
553
|
+
text: string;
|
|
554
|
+
name?: string | undefined;
|
|
555
|
+
organizationId: string;
|
|
556
|
+
userId: string;
|
|
557
|
+
}): Promise<Body>;
|
|
558
|
+
/** Read a web page (the scraper lands it). Null = it could not be read. */
|
|
559
|
+
scrapeUrl(url: string): Promise<ScrapedSourcePage | null>;
|
|
560
|
+
/** Read a YouTube video's transcript. */
|
|
561
|
+
fetchYouTubeTranscript(url: string): Promise<YouTubeTranscriptResult>;
|
|
562
|
+
/** Write out a recording that is already stored. */
|
|
563
|
+
transcribeFile(input: {
|
|
564
|
+
fileId: string;
|
|
565
|
+
organizationId: string;
|
|
566
|
+
}): Promise<{
|
|
567
|
+
text?: string | null | undefined;
|
|
568
|
+
}>;
|
|
569
|
+
/** File one edge through the host's association chokepoint. */
|
|
570
|
+
associate(edge: SourceAssociationEdge): Promise<SourceAssociationResult>;
|
|
571
|
+
}
|
|
572
|
+
interface SourceIntakeDeps<Body extends SourceLandingBodyBase = SourceLandingBodyBase> {
|
|
573
|
+
doors: SourceIntakeDoors<Body>;
|
|
574
|
+
/** The organization to land in — asks the person when none is chosen (the host's gate). */
|
|
575
|
+
ensureOrganization(): Promise<string>;
|
|
576
|
+
/** Run a landing the person asked for so the gate can carry the click intent. Default: run it. */
|
|
577
|
+
holdIntent?: (<T>(run: () => Promise<T>) => Promise<T | undefined>) | undefined;
|
|
578
|
+
/** True when a landing stopped only because no organization is chosen yet. */
|
|
579
|
+
waitsForOrganization(error: unknown): boolean;
|
|
580
|
+
/** A failed landing as one sentence with its remedy. */
|
|
581
|
+
failureSentence(error: unknown): string;
|
|
582
|
+
/** The signed-in person (null while sign-in is still loading). */
|
|
583
|
+
userId(): string | null | undefined;
|
|
584
|
+
/** The YouTube video id in a link, or null. */
|
|
585
|
+
youtubeId(url: string): string | null | undefined;
|
|
586
|
+
/** The largest pasted text kept in the draft for a reload (null = unknown — nothing kept). */
|
|
587
|
+
maxKeptChars(): number | null;
|
|
588
|
+
/** The thing being made — new Sources are filed against it and kept. */
|
|
589
|
+
attachTo?: SourceAttachTo | undefined;
|
|
590
|
+
}
|
|
591
|
+
interface SourceIntake {
|
|
592
|
+
addPastedText(text: string, name?: string): Promise<void>;
|
|
593
|
+
addWebPage(url: string): Promise<void>;
|
|
594
|
+
/** A page the host's web picker already read (and the scraper landed); null id = land its text instead. */
|
|
595
|
+
addScrapedPage(page: {
|
|
596
|
+
url: string;
|
|
597
|
+
title: string;
|
|
598
|
+
text: string;
|
|
599
|
+
processedDocumentId: string | null;
|
|
600
|
+
}): Promise<void>;
|
|
601
|
+
/** Files the host's uploader already uploaded. */
|
|
602
|
+
addUploaded(files: readonly UploadedSourceFile[], kind: SourceKindId): Promise<void>;
|
|
603
|
+
/** A recording already uploaded — written out, then landed. */
|
|
604
|
+
addUploadedRecording(file: UploadedSourceFile): Promise<void>;
|
|
605
|
+
/** Something the person already has (Use existing / search). Returns false when already picked. */
|
|
606
|
+
addExisting(item: {
|
|
607
|
+
token: string;
|
|
608
|
+
id: string;
|
|
609
|
+
title: string;
|
|
610
|
+
}): boolean;
|
|
611
|
+
addYouTube(url: string): Promise<void>;
|
|
612
|
+
/** Land a card's kept input again (after a reload or a failure). False when nothing was kept. */
|
|
613
|
+
resume(card: SourceCardModel): boolean;
|
|
614
|
+
/** The person chose the file again (uploaded) for a card whose upload was cut off. */
|
|
615
|
+
retryFile(card: SourceCardModel, file: UploadedSourceFile): Promise<void>;
|
|
616
|
+
/** Reading an uploaded file made its Source: keep it and file it against `attachTo`. */
|
|
617
|
+
fileLanded(card: SourceCardModel, processedDocumentId: string): Promise<void>;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Recovery — the UI-free half of "never lose input" (USI-3b), as decisions and
|
|
622
|
+
* one step function. A React binding (`@ai-matrx/agents/sources/react`
|
|
623
|
+
* `useSourceRecovery`) schedules them; any other client can call them itself.
|
|
624
|
+
*
|
|
625
|
+
* 1. A Source a reload cut off while it was landing, whose input the draft
|
|
626
|
+
* kept (`interrupted.ts`), is handed back to the door once — the door
|
|
627
|
+
* dedupes by content hash, so a second landing reuses the same Source.
|
|
628
|
+
* 2. A landing that waited for an organization is landed again the moment one
|
|
629
|
+
* is set — from the card's kept input, or, for a read Source whose keep
|
|
630
|
+
* waited, by keeping it again. State, never an in-memory queue.
|
|
631
|
+
* 3. A stored file's Source (new upload, reused copy, or picked file) is read
|
|
632
|
+
* from the SERVER's state (`fileSource.ts`): kept and filed once it exists,
|
|
633
|
+
* the one run started only when nothing is reading it, re-attached after a
|
|
634
|
+
* reload — never a duplicate run.
|
|
635
|
+
*
|
|
636
|
+
* The once-per-card guards live on `globalThis` (one registry however many
|
|
637
|
+
* loader graphs import this module), so two inputs on one surface, or a
|
|
638
|
+
* re-mount, never land the same card twice.
|
|
639
|
+
*/
|
|
640
|
+
|
|
641
|
+
/** The host's processing runner, as far as the recovery needs it. */
|
|
642
|
+
interface SourceFileRunner {
|
|
643
|
+
jobs: ReadonlyArray<{
|
|
644
|
+
cldFileId?: string | null | undefined;
|
|
645
|
+
status: string;
|
|
646
|
+
processedDocumentId?: string | null | undefined;
|
|
647
|
+
}>;
|
|
648
|
+
runForCldFile(fileId: string, label: string, reason: string): Promise<unknown>;
|
|
649
|
+
}
|
|
650
|
+
interface FileRecoveryDeps {
|
|
651
|
+
/** The organization the person picked (null = none yet). */
|
|
652
|
+
organizationId: string | null | undefined;
|
|
653
|
+
/** The server's `/files/{id}/rag-status` read. */
|
|
654
|
+
readFileState(fileId: string, signal?: AbortSignal): Promise<FileSourceStatus>;
|
|
655
|
+
/** The host's lookup of a file's own organization (omitted = unknown). */
|
|
656
|
+
fileOrganizationId?: ((fileId: string) => string | null | undefined) | undefined;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* `@ai-matrx/agents/sources/react` — thin React hooks over the Source runtime
|
|
661
|
+
* (`@ai-matrx/agents/sources/runtime`). No logic lives here that a non-React
|
|
662
|
+
* client could not reach through the runtime itself: the hooks subscribe,
|
|
663
|
+
* schedule, and hand back the same controller.
|
|
664
|
+
*
|
|
665
|
+
* const set = useSourceSet(adapter, { persistenceReady });
|
|
666
|
+
* const intake = useSourceIntake(set, deps);
|
|
667
|
+
* useSourceRecovery(set, intake, runner, { organizationId, readFileState });
|
|
668
|
+
* const search = useSourcePartsSearch(client, ref, query);
|
|
669
|
+
*
|
|
670
|
+
* A host binds the adapter to its own store once (memoized per surface) —
|
|
671
|
+
* the web app binds Redux; another client binds its own.
|
|
672
|
+
*/
|
|
673
|
+
|
|
674
|
+
interface UseSourceSetOptions {
|
|
675
|
+
/**
|
|
676
|
+
* The draft store has been read back (a device store loads a moment after
|
|
677
|
+
* the page). While false, "nothing picked" is not known — `restoring` stays
|
|
678
|
+
* true. Default true.
|
|
679
|
+
*/
|
|
680
|
+
persistenceReady?: boolean | undefined;
|
|
681
|
+
/** The host's choices, re-read on every render. Default: the adapter's config. */
|
|
682
|
+
config?: SourceSetConfig | undefined;
|
|
683
|
+
}
|
|
684
|
+
interface UseSourceSetResult extends SourceSetActions {
|
|
685
|
+
/** Every picked Source, in the order picked. */
|
|
686
|
+
sources: SourceCardModel[];
|
|
687
|
+
topic: string;
|
|
688
|
+
/** The frozen v1 payload, built from the ready Sources. */
|
|
689
|
+
toSourceSet: (options?: {
|
|
690
|
+
targetModelId?: string | undefined;
|
|
691
|
+
}) => SourceSet;
|
|
692
|
+
/** Apply a set the review page returned (forms, parts, caps, delivery). */
|
|
693
|
+
applySourceSet: (set: SourceSet) => void;
|
|
694
|
+
/** POST /sources/resolve — the grounded text for a generator. */
|
|
695
|
+
resolve: (options?: {
|
|
696
|
+
targetModelId?: string | undefined;
|
|
697
|
+
}) => Promise<ResolvedSourceSet>;
|
|
698
|
+
/** Switch back any Source set to a delivery the host cannot use (with a note). */
|
|
699
|
+
fitDeliveries: (deliveries?: readonly SourceDelivery[]) => void;
|
|
700
|
+
/** How many more Sources fit under the host's max. */
|
|
701
|
+
roomLeft: () => number;
|
|
702
|
+
/** Characters that will go in (the server's measurement; 0 until measured). */
|
|
703
|
+
totalChars: number;
|
|
704
|
+
measuring: boolean;
|
|
705
|
+
/** Why the measurement failed, with its remedy. */
|
|
706
|
+
manifestError: string | null;
|
|
707
|
+
/** Every Source finished landing (nothing pending) and none failed. */
|
|
708
|
+
settled: boolean;
|
|
709
|
+
/** The saved draft is not read back yet — show a loading state, never an empty one. */
|
|
710
|
+
restoring: boolean;
|
|
711
|
+
/** The controller itself, for non-React code on the same surface. */
|
|
712
|
+
controller: SourceSetController;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* THE hook for one Source input. `adapter` must be stable for a surface
|
|
716
|
+
* (memoize it); a new adapter builds a new controller.
|
|
717
|
+
*/
|
|
718
|
+
declare function useSourceSet(adapter: SourceSetAdapter, options?: UseSourceSetOptions): UseSourceSetResult;
|
|
719
|
+
/** Every landing door, over the set. Rebuilt each render (closures only — nothing to keep). */
|
|
720
|
+
declare function useSourceIntake<Body extends SourceLandingBodyBase>(set: SourceSetActions, deps: SourceIntakeDeps<Body>): SourceIntake;
|
|
721
|
+
interface UseSourceRecoveryOptions extends FileRecoveryDeps {
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Never lose input, and follow stored files' Sources — call once per screen
|
|
725
|
+
* that sits on `useSourceSet` + `useSourceIntake`.
|
|
726
|
+
*/
|
|
727
|
+
declare function useSourceRecovery(set: Pick<UseSourceSetResult, "sources" | "fail" | "manifest">, intake: Pick<SourceIntake, "resume" | "fileLanded">, runner: SourceFileRunner, options: UseSourceRecoveryOptions): void;
|
|
728
|
+
interface SourcePartsSearchState {
|
|
729
|
+
/** Ids of the parts whose text holds every word (undefined until answered). */
|
|
730
|
+
matches: PartMatchIds | undefined;
|
|
731
|
+
/** The server is being asked about the current words ("Searching inside the text…"). */
|
|
732
|
+
reading: boolean;
|
|
733
|
+
/** Said under the search when the text could not be searched (titles and previews still match). */
|
|
734
|
+
error: string | null;
|
|
735
|
+
/** More parts matched than the server returns; the list shown is the first of them. */
|
|
736
|
+
truncated: boolean;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Which of one Source's parts hold every word a person typed, answered by the
|
|
740
|
+
* server (`POST /sources/parts/search` — ids only; the whole text never comes
|
|
741
|
+
* to the client). Asked only for words (never a page or a range), after the
|
|
742
|
+
* person pauses typing.
|
|
743
|
+
*/
|
|
744
|
+
declare function useSourcePartsSearch(client: Pick<SourcesClient, "searchParts">, ref: SourceRef | null, query: string): SourcePartsSearchState;
|
|
745
|
+
|
|
746
|
+
export { type SourcePartsSearchState, type UseSourceRecoveryOptions, type UseSourceSetOptions, type UseSourceSetResult, useSourceIntake, useSourcePartsSearch, useSourceRecovery, useSourceSet };
|