@kedataindo/docflow-core 0.0.32 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,313 @@
1
+ /**
2
+ * The library's only knowledge of LLMs (pluggable AI provider — issue #119).
3
+ *
4
+ * `AIProvider` is a thin transport: one prompt in, a stream of events out.
5
+ * The library imports only this type — never an implementation. Hosts plug in
6
+ * `openaiCompatibleProvider` (the default we ship) or their own class wrapping
7
+ * an agent SDK, a proxy, or an in-process model. Agent orchestration, tool
8
+ * calling, RAG, and multi-turn memory live behind the host's endpoint, not in
9
+ * the library. See docs/plans/PLUGGABLE_AI_PROVIDER.md §4.1.
10
+ */
11
+ type StreamEvent = {
12
+ type: 'delta';
13
+ text: string;
14
+ } | {
15
+ type: 'done';
16
+ stopReason?: string;
17
+ } | {
18
+ type: 'error';
19
+ error: Error;
20
+ };
21
+ interface AICompleteRequest {
22
+ system?: string;
23
+ prompt: string;
24
+ signal?: AbortSignal;
25
+ }
26
+ interface AIProvider {
27
+ complete(req: AICompleteRequest): AsyncIterable<StreamEvent>;
28
+ }
29
+
30
+ /**
31
+ * Shared AI action types (Phase 7; pluggable AI provider — issue #119).
32
+ *
33
+ * The library (aiPlugin) is provider-agnostic: it never names a URL or holds a
34
+ * key. The host injects `aiStream` / `aiDraft` via `EditorOptions` — either a
35
+ * hand-written function or `toAIStreamFn(openaiCompatibleProvider({...}))`
36
+ * (see ./provider.ts, ./openaiCompatibleProvider.ts).
37
+ */
38
+
39
+ /**
40
+ * Factory shape for hosts that hand the library a provider object rather than
41
+ * a plain function (plan §4.2). `toAIStreamFn` adapts an `AIProvider` to the
42
+ * `AIStreamFn` port, so both shapes are accepted at the boundary.
43
+ */
44
+ type AIProviderFactory = (config: {
45
+ signal?: AbortSignal;
46
+ }) => AIProvider;
47
+ type AIAction = 'rewrite' | 'summarize' | 'grammar' | 'tone' | 'translate' | 'expand' | 'shorten' | 'generate' | 'chat' | 'draft';
48
+ interface AIActionRequest {
49
+ action: AIAction;
50
+ /** Selected text (7B). */
51
+ selection?: string;
52
+ /** Bounded surrounding text — never the whole document. */
53
+ context?: {
54
+ before: string;
55
+ after: string;
56
+ };
57
+ /** User instruction (/ai prompt, chat message, tone target, target language). */
58
+ prompt?: string;
59
+ /** For doc-aware chat / RAG scoping. */
60
+ documentId?: string;
61
+ options?: Record<string, unknown>;
62
+ }
63
+ /**
64
+ * The app implements this and injects it; the library never knows the URL.
65
+ * Yields streamed text chunks in order; stops at end of stream, throws on error.
66
+ */
67
+ type AIStreamFn = (req: AIActionRequest, signal: AbortSignal) => AsyncIterable<string>;
68
+ /** One entry in the citation table carried by the SSE `done` event (§3.4). */
69
+ interface AIDraftCitation {
70
+ /** 1-based index matching a `[n]` marker in the streamed text. */
71
+ ref: number;
72
+ /** `Source._id` string — the library maps markers through this table. */
73
+ sourceId: string;
74
+ /** "first author family + year" tag, e.g. "Doe (2024)". */
75
+ label: string;
76
+ }
77
+ /**
78
+ * Events yielded by `aiDraft` (§3.5). The `done` event carries the citation
79
+ * table; the `error` event does NOT (the bug-hunter carry-forward — the client
80
+ * treats BOTH `done` and `error` as terminal; Insert is enabled only on `done`).
81
+ */
82
+ type AIDraftEvent = {
83
+ type: 'delta';
84
+ text: string;
85
+ } | {
86
+ type: 'done';
87
+ citations: AIDraftCitation[];
88
+ } | {
89
+ type: 'error';
90
+ message: string;
91
+ };
92
+ /**
93
+ * The app implements this and injects it; the library never knows the URL.
94
+ * Mirrors `AIStreamFn` but yields richer `AIDraftEvent`s (carries the citation
95
+ * table on the terminal `done` event). Injected exactly like `aiStream`.
96
+ */
97
+ type AIDraftFn = (req: {
98
+ prompt: string;
99
+ context?: {
100
+ before: string;
101
+ after: string;
102
+ };
103
+ k?: number;
104
+ }, signal: AbortSignal) => AsyncIterable<AIDraftEvent>;
105
+
106
+ /**
107
+ * Injectable ports — the narrow interfaces through which the library reaches
108
+ * host-provided backend capabilities (storage, persistence, …). The library
109
+ * declares them; the host app implements them. See docs/LIBRARY_CONTRACT.md.
110
+ */
111
+ interface ImageUploadResult {
112
+ /** Resolved URL/href the host stored the file at. */
113
+ src: string;
114
+ alt?: string;
115
+ title?: string;
116
+ }
117
+ /**
118
+ * Host-supplied image upload handler. Called with the file picked by the user;
119
+ * resolves to the stored location to insert into the document. Reject to abort
120
+ * the insert (the failure is logged, nothing is inserted).
121
+ */
122
+ type ImageUploadHandler = (file: File) => Promise<ImageUploadResult>;
123
+ /** CSL-JSON name (see https://citeproc-js.readthedocs.io/csl-json/). */
124
+ interface CslName {
125
+ family?: string;
126
+ given?: string;
127
+ literal?: string;
128
+ }
129
+ /** CSL-JSON date. */
130
+ interface CslDate {
131
+ 'date-parts'?: number[][];
132
+ raw?: string;
133
+ literal?: string;
134
+ }
135
+ /**
136
+ * A CSL-JSON item — the structured source metadata citeproc-js consumes.
137
+ * Nodes store only `{ sourceId, locator }`; all rendered text is derived from
138
+ * this data by the citation engine (never persisted in the document).
139
+ */
140
+ interface CslItemData {
141
+ id: string;
142
+ type: string;
143
+ title?: string;
144
+ author?: CslName[];
145
+ editor?: CslName[];
146
+ issued?: CslDate;
147
+ 'container-title'?: string;
148
+ publisher?: string;
149
+ 'publisher-place'?: string;
150
+ page?: string;
151
+ volume?: string;
152
+ issue?: string;
153
+ DOI?: string;
154
+ URL?: string;
155
+ ISBN?: string;
156
+ abstract?: string;
157
+ [k: string]: unknown;
158
+ }
159
+ /**
160
+ * Host-supplied citation port. The library never fetches sources itself — the
161
+ * host owns the reference library and injects CSL-JSON through this port,
162
+ * mirroring the `onImageUpload` injection pattern.
163
+ */
164
+ interface CitationPort {
165
+ /** CSL-JSON provider — a snapshot array or a getter for live data. */
166
+ sources: CslItemData[] | (() => CslItemData[]);
167
+ /** Active CSL style id (e.g. 'chicago-notes-bibliography'). */
168
+ style?: string;
169
+ /**
170
+ * Called when the user inserts a citation: the host opens its source
171
+ * picker and resolves with the chosen source id (null = cancelled).
172
+ */
173
+ onSourceRequest?: () => Promise<string | null>;
174
+ /**
175
+ * Called when the set of cited source ids changes, so the host can update
176
+ * its embedded per-document source snapshot.
177
+ */
178
+ onSourcesChange?: (ids: string[]) => void;
179
+ /**
180
+ * Optional importer (Phase 6C-2): resolve a DOI/URL into a new persisted
181
+ * source via the host's backend (CrossRef lookup). Returns the created
182
+ * (or deduplicated) source, null on failure.
183
+ */
184
+ onImportDoi?: (doi: string) => Promise<CslItemData | null>;
185
+ /**
186
+ * Optional importer (Phase 6C-3): parse + persist a BibTeX/RIS document
187
+ * via the host's backend. Returns the imported sources and how many
188
+ * entries failed.
189
+ */
190
+ onImportBibliography?: (payload: {
191
+ format: 'bibtex' | 'ris';
192
+ text: string;
193
+ }) => Promise<{
194
+ imported: CslItemData[];
195
+ failed: number;
196
+ }>;
197
+ }
198
+
199
+ declare function toAIStreamFn(provider: AIProvider): AIStreamFn;
200
+
201
+ /**
202
+ * Per-action prompt assembly (pluggable AI provider — issue #119).
203
+ *
204
+ * Ported from the Phase 7 server proxy (`apps/server/src/ai/context.ts`) so
205
+ * the browser can call the LLM directly: the selection + BOUNDED surrounding
206
+ * text is sent — never the whole document (cost + privacy). Untrusted document
207
+ * text stays in the user prompt; instructions live in the system prompt
208
+ * (prompt-injection hygiene).
209
+ */
210
+
211
+ /** Max characters of surrounding context sent on each side of the selection. */
212
+ declare const CONTEXT_CHAR_CAP = 1500;
213
+ /** Last `CONTEXT_CHAR_CAP` chars of the preceding text (nearest context wins). */
214
+ declare function trimContextBefore(text: string): string;
215
+ /** First `CONTEXT_CHAR_CAP` chars of the following text. */
216
+ declare function trimContextAfter(text: string): string;
217
+ /**
218
+ * Assemble the per-action system prompt + user prompt from a bounded request.
219
+ * `draft` is rejected: RAG-cited drafting goes through the `aiDraft` port,
220
+ * which owns its grounded-prompt assembly (Phase 7E).
221
+ */
222
+ declare function buildAIPrompt(req: AIActionRequest): {
223
+ system: string;
224
+ prompt: string;
225
+ };
226
+
227
+ /**
228
+ * Where the AI provider's config (base URL + credentials + model) lives —
229
+ * the host decides (pluggable AI provider — issue #119, plan §4.4).
230
+ *
231
+ * The URL and the key are a pair and travel together. The library ships three
232
+ * reference implementations; hosts may write their own (e.g. keyed per-tenant
233
+ * in their own backend). No URL is hardcoded anywhere — `httpKeyStorage`
234
+ * receives its endpoints from the host, keeping the library backend-agnostic
235
+ * (docs/LIBRARY_CONTRACT.md rule 3).
236
+ */
237
+ type Auth = {
238
+ type: 'bearer';
239
+ apiKey: string;
240
+ } | {
241
+ type: 'header';
242
+ name: string;
243
+ value: string;
244
+ } | {
245
+ type: 'none';
246
+ };
247
+ interface AIConfig {
248
+ baseUrl: string;
249
+ auth: Auth;
250
+ model: string;
251
+ /**
252
+ * Who may receive this config in their browser (issue #126).
253
+ * - `shared` (default): any authenticated user gets it via GET and calls
254
+ * the LLM browser-direct — the #119 model.
255
+ * - `admin-only`: only tenant admins get it; everyone else must complete
256
+ * through the server's `/api/ai/complete` proxy so the key never leaves
257
+ * the server.
258
+ */
259
+ visibility?: 'shared' | 'admin-only';
260
+ }
261
+ interface KeyStorage {
262
+ get(): Promise<AIConfig | null>;
263
+ set(value: AIConfig): Promise<void>;
264
+ clear(): Promise<void>;
265
+ }
266
+ /** In-memory storage — for tests and SSR (no persistence). */
267
+ declare function memoryKeyStorage(initial?: AIConfig | null): KeyStorage;
268
+ declare const LOCAL_STORAGE_KEY = "docflow:ai-config";
269
+ /**
270
+ * Browser `localStorage` persistence — for embedded apps that want the config
271
+ * kept client-side. Note: this stores AI *credentials*, not document data, so
272
+ * it is the host's deliberate choice, not a library persistence leak.
273
+ */
274
+ declare function localStorageKeyStorage(key?: string, storage?: Storage): KeyStorage;
275
+ interface HttpKeyStorageUrls {
276
+ /** GET → 200 with the AIConfig JSON body, or 404 when unset. */
277
+ getUrl: string;
278
+ /** POST with the AIConfig JSON body. */
279
+ setUrl: string;
280
+ /** DELETE to clear. */
281
+ deleteUrl: string;
282
+ /** Optional fetch override (tests, custom credentials mode). Defaults to global fetch. */
283
+ fetchImpl?: typeof fetch;
284
+ }
285
+ /**
286
+ * Server-backed storage — for our SaaS web app (`/api/ai/config`) and any
287
+ * embedded app that wants credentials kept on its own backend. Endpoints are
288
+ * host-supplied; the library never names a path.
289
+ */
290
+ declare function httpKeyStorage(urls: HttpKeyStorageUrls): KeyStorage;
291
+
292
+ /**
293
+ * Default `AIProvider` implementation (pluggable AI provider — issue #119,
294
+ * plan §4.3): OpenAI-shaped `POST {baseUrl}/chat/completions` with SSE
295
+ * streaming. Covers OpenAI, Azure-style gateways, Ollama, vLLM, and most
296
+ * OpenAI-compatible proxies.
297
+ *
298
+ * Cancellation-safe: closing the async iterator early (e.g. the user accepts
299
+ * or cancels mid-stream) aborts the in-flight `fetch`; an aborted `req.signal`
300
+ * ends the stream silently rather than yielding an error event.
301
+ */
302
+
303
+ interface OpenAICompatibleConfig {
304
+ /** Base URL of the OpenAI-compatible endpoint, e.g. `https://api.openai.com/v1`. */
305
+ baseUrl: string;
306
+ auth: Auth;
307
+ model: string;
308
+ /** Prepended to every call's system prompt so hosts don't repeat it. */
309
+ systemPrompt?: string;
310
+ }
311
+ declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
312
+
313
+ export { type AIStreamFn as A, type CitationPort as C, type HttpKeyStorageUrls as H, type ImageUploadHandler as I, type KeyStorage as K, LOCAL_STORAGE_KEY as L, type OpenAICompatibleConfig as O, type StreamEvent as S, type AIDraftFn as a, type AIAction as b, type AIActionRequest as c, type AICompleteRequest as d, type AIConfig as e, type AIDraftCitation as f, type AIDraftEvent as g, type AIProvider as h, type AIProviderFactory as i, type Auth as j, CONTEXT_CHAR_CAP as k, type CslDate as l, type CslItemData as m, type CslName as n, type ImageUploadResult as o, buildAIPrompt as p, httpKeyStorage as q, localStorageKeyStorage as r, memoryKeyStorage as s, openaiCompatibleProvider as t, toAIStreamFn as u, trimContextAfter as v, trimContextBefore as w };
@@ -0,0 +1,313 @@
1
+ /**
2
+ * The library's only knowledge of LLMs (pluggable AI provider — issue #119).
3
+ *
4
+ * `AIProvider` is a thin transport: one prompt in, a stream of events out.
5
+ * The library imports only this type — never an implementation. Hosts plug in
6
+ * `openaiCompatibleProvider` (the default we ship) or their own class wrapping
7
+ * an agent SDK, a proxy, or an in-process model. Agent orchestration, tool
8
+ * calling, RAG, and multi-turn memory live behind the host's endpoint, not in
9
+ * the library. See docs/plans/PLUGGABLE_AI_PROVIDER.md §4.1.
10
+ */
11
+ type StreamEvent = {
12
+ type: 'delta';
13
+ text: string;
14
+ } | {
15
+ type: 'done';
16
+ stopReason?: string;
17
+ } | {
18
+ type: 'error';
19
+ error: Error;
20
+ };
21
+ interface AICompleteRequest {
22
+ system?: string;
23
+ prompt: string;
24
+ signal?: AbortSignal;
25
+ }
26
+ interface AIProvider {
27
+ complete(req: AICompleteRequest): AsyncIterable<StreamEvent>;
28
+ }
29
+
30
+ /**
31
+ * Shared AI action types (Phase 7; pluggable AI provider — issue #119).
32
+ *
33
+ * The library (aiPlugin) is provider-agnostic: it never names a URL or holds a
34
+ * key. The host injects `aiStream` / `aiDraft` via `EditorOptions` — either a
35
+ * hand-written function or `toAIStreamFn(openaiCompatibleProvider({...}))`
36
+ * (see ./provider.ts, ./openaiCompatibleProvider.ts).
37
+ */
38
+
39
+ /**
40
+ * Factory shape for hosts that hand the library a provider object rather than
41
+ * a plain function (plan §4.2). `toAIStreamFn` adapts an `AIProvider` to the
42
+ * `AIStreamFn` port, so both shapes are accepted at the boundary.
43
+ */
44
+ type AIProviderFactory = (config: {
45
+ signal?: AbortSignal;
46
+ }) => AIProvider;
47
+ type AIAction = 'rewrite' | 'summarize' | 'grammar' | 'tone' | 'translate' | 'expand' | 'shorten' | 'generate' | 'chat' | 'draft';
48
+ interface AIActionRequest {
49
+ action: AIAction;
50
+ /** Selected text (7B). */
51
+ selection?: string;
52
+ /** Bounded surrounding text — never the whole document. */
53
+ context?: {
54
+ before: string;
55
+ after: string;
56
+ };
57
+ /** User instruction (/ai prompt, chat message, tone target, target language). */
58
+ prompt?: string;
59
+ /** For doc-aware chat / RAG scoping. */
60
+ documentId?: string;
61
+ options?: Record<string, unknown>;
62
+ }
63
+ /**
64
+ * The app implements this and injects it; the library never knows the URL.
65
+ * Yields streamed text chunks in order; stops at end of stream, throws on error.
66
+ */
67
+ type AIStreamFn = (req: AIActionRequest, signal: AbortSignal) => AsyncIterable<string>;
68
+ /** One entry in the citation table carried by the SSE `done` event (§3.4). */
69
+ interface AIDraftCitation {
70
+ /** 1-based index matching a `[n]` marker in the streamed text. */
71
+ ref: number;
72
+ /** `Source._id` string — the library maps markers through this table. */
73
+ sourceId: string;
74
+ /** "first author family + year" tag, e.g. "Doe (2024)". */
75
+ label: string;
76
+ }
77
+ /**
78
+ * Events yielded by `aiDraft` (§3.5). The `done` event carries the citation
79
+ * table; the `error` event does NOT (the bug-hunter carry-forward — the client
80
+ * treats BOTH `done` and `error` as terminal; Insert is enabled only on `done`).
81
+ */
82
+ type AIDraftEvent = {
83
+ type: 'delta';
84
+ text: string;
85
+ } | {
86
+ type: 'done';
87
+ citations: AIDraftCitation[];
88
+ } | {
89
+ type: 'error';
90
+ message: string;
91
+ };
92
+ /**
93
+ * The app implements this and injects it; the library never knows the URL.
94
+ * Mirrors `AIStreamFn` but yields richer `AIDraftEvent`s (carries the citation
95
+ * table on the terminal `done` event). Injected exactly like `aiStream`.
96
+ */
97
+ type AIDraftFn = (req: {
98
+ prompt: string;
99
+ context?: {
100
+ before: string;
101
+ after: string;
102
+ };
103
+ k?: number;
104
+ }, signal: AbortSignal) => AsyncIterable<AIDraftEvent>;
105
+
106
+ /**
107
+ * Injectable ports — the narrow interfaces through which the library reaches
108
+ * host-provided backend capabilities (storage, persistence, …). The library
109
+ * declares them; the host app implements them. See docs/LIBRARY_CONTRACT.md.
110
+ */
111
+ interface ImageUploadResult {
112
+ /** Resolved URL/href the host stored the file at. */
113
+ src: string;
114
+ alt?: string;
115
+ title?: string;
116
+ }
117
+ /**
118
+ * Host-supplied image upload handler. Called with the file picked by the user;
119
+ * resolves to the stored location to insert into the document. Reject to abort
120
+ * the insert (the failure is logged, nothing is inserted).
121
+ */
122
+ type ImageUploadHandler = (file: File) => Promise<ImageUploadResult>;
123
+ /** CSL-JSON name (see https://citeproc-js.readthedocs.io/csl-json/). */
124
+ interface CslName {
125
+ family?: string;
126
+ given?: string;
127
+ literal?: string;
128
+ }
129
+ /** CSL-JSON date. */
130
+ interface CslDate {
131
+ 'date-parts'?: number[][];
132
+ raw?: string;
133
+ literal?: string;
134
+ }
135
+ /**
136
+ * A CSL-JSON item — the structured source metadata citeproc-js consumes.
137
+ * Nodes store only `{ sourceId, locator }`; all rendered text is derived from
138
+ * this data by the citation engine (never persisted in the document).
139
+ */
140
+ interface CslItemData {
141
+ id: string;
142
+ type: string;
143
+ title?: string;
144
+ author?: CslName[];
145
+ editor?: CslName[];
146
+ issued?: CslDate;
147
+ 'container-title'?: string;
148
+ publisher?: string;
149
+ 'publisher-place'?: string;
150
+ page?: string;
151
+ volume?: string;
152
+ issue?: string;
153
+ DOI?: string;
154
+ URL?: string;
155
+ ISBN?: string;
156
+ abstract?: string;
157
+ [k: string]: unknown;
158
+ }
159
+ /**
160
+ * Host-supplied citation port. The library never fetches sources itself — the
161
+ * host owns the reference library and injects CSL-JSON through this port,
162
+ * mirroring the `onImageUpload` injection pattern.
163
+ */
164
+ interface CitationPort {
165
+ /** CSL-JSON provider — a snapshot array or a getter for live data. */
166
+ sources: CslItemData[] | (() => CslItemData[]);
167
+ /** Active CSL style id (e.g. 'chicago-notes-bibliography'). */
168
+ style?: string;
169
+ /**
170
+ * Called when the user inserts a citation: the host opens its source
171
+ * picker and resolves with the chosen source id (null = cancelled).
172
+ */
173
+ onSourceRequest?: () => Promise<string | null>;
174
+ /**
175
+ * Called when the set of cited source ids changes, so the host can update
176
+ * its embedded per-document source snapshot.
177
+ */
178
+ onSourcesChange?: (ids: string[]) => void;
179
+ /**
180
+ * Optional importer (Phase 6C-2): resolve a DOI/URL into a new persisted
181
+ * source via the host's backend (CrossRef lookup). Returns the created
182
+ * (or deduplicated) source, null on failure.
183
+ */
184
+ onImportDoi?: (doi: string) => Promise<CslItemData | null>;
185
+ /**
186
+ * Optional importer (Phase 6C-3): parse + persist a BibTeX/RIS document
187
+ * via the host's backend. Returns the imported sources and how many
188
+ * entries failed.
189
+ */
190
+ onImportBibliography?: (payload: {
191
+ format: 'bibtex' | 'ris';
192
+ text: string;
193
+ }) => Promise<{
194
+ imported: CslItemData[];
195
+ failed: number;
196
+ }>;
197
+ }
198
+
199
+ declare function toAIStreamFn(provider: AIProvider): AIStreamFn;
200
+
201
+ /**
202
+ * Per-action prompt assembly (pluggable AI provider — issue #119).
203
+ *
204
+ * Ported from the Phase 7 server proxy (`apps/server/src/ai/context.ts`) so
205
+ * the browser can call the LLM directly: the selection + BOUNDED surrounding
206
+ * text is sent — never the whole document (cost + privacy). Untrusted document
207
+ * text stays in the user prompt; instructions live in the system prompt
208
+ * (prompt-injection hygiene).
209
+ */
210
+
211
+ /** Max characters of surrounding context sent on each side of the selection. */
212
+ declare const CONTEXT_CHAR_CAP = 1500;
213
+ /** Last `CONTEXT_CHAR_CAP` chars of the preceding text (nearest context wins). */
214
+ declare function trimContextBefore(text: string): string;
215
+ /** First `CONTEXT_CHAR_CAP` chars of the following text. */
216
+ declare function trimContextAfter(text: string): string;
217
+ /**
218
+ * Assemble the per-action system prompt + user prompt from a bounded request.
219
+ * `draft` is rejected: RAG-cited drafting goes through the `aiDraft` port,
220
+ * which owns its grounded-prompt assembly (Phase 7E).
221
+ */
222
+ declare function buildAIPrompt(req: AIActionRequest): {
223
+ system: string;
224
+ prompt: string;
225
+ };
226
+
227
+ /**
228
+ * Where the AI provider's config (base URL + credentials + model) lives —
229
+ * the host decides (pluggable AI provider — issue #119, plan §4.4).
230
+ *
231
+ * The URL and the key are a pair and travel together. The library ships three
232
+ * reference implementations; hosts may write their own (e.g. keyed per-tenant
233
+ * in their own backend). No URL is hardcoded anywhere — `httpKeyStorage`
234
+ * receives its endpoints from the host, keeping the library backend-agnostic
235
+ * (docs/LIBRARY_CONTRACT.md rule 3).
236
+ */
237
+ type Auth = {
238
+ type: 'bearer';
239
+ apiKey: string;
240
+ } | {
241
+ type: 'header';
242
+ name: string;
243
+ value: string;
244
+ } | {
245
+ type: 'none';
246
+ };
247
+ interface AIConfig {
248
+ baseUrl: string;
249
+ auth: Auth;
250
+ model: string;
251
+ /**
252
+ * Who may receive this config in their browser (issue #126).
253
+ * - `shared` (default): any authenticated user gets it via GET and calls
254
+ * the LLM browser-direct — the #119 model.
255
+ * - `admin-only`: only tenant admins get it; everyone else must complete
256
+ * through the server's `/api/ai/complete` proxy so the key never leaves
257
+ * the server.
258
+ */
259
+ visibility?: 'shared' | 'admin-only';
260
+ }
261
+ interface KeyStorage {
262
+ get(): Promise<AIConfig | null>;
263
+ set(value: AIConfig): Promise<void>;
264
+ clear(): Promise<void>;
265
+ }
266
+ /** In-memory storage — for tests and SSR (no persistence). */
267
+ declare function memoryKeyStorage(initial?: AIConfig | null): KeyStorage;
268
+ declare const LOCAL_STORAGE_KEY = "docflow:ai-config";
269
+ /**
270
+ * Browser `localStorage` persistence — for embedded apps that want the config
271
+ * kept client-side. Note: this stores AI *credentials*, not document data, so
272
+ * it is the host's deliberate choice, not a library persistence leak.
273
+ */
274
+ declare function localStorageKeyStorage(key?: string, storage?: Storage): KeyStorage;
275
+ interface HttpKeyStorageUrls {
276
+ /** GET → 200 with the AIConfig JSON body, or 404 when unset. */
277
+ getUrl: string;
278
+ /** POST with the AIConfig JSON body. */
279
+ setUrl: string;
280
+ /** DELETE to clear. */
281
+ deleteUrl: string;
282
+ /** Optional fetch override (tests, custom credentials mode). Defaults to global fetch. */
283
+ fetchImpl?: typeof fetch;
284
+ }
285
+ /**
286
+ * Server-backed storage — for our SaaS web app (`/api/ai/config`) and any
287
+ * embedded app that wants credentials kept on its own backend. Endpoints are
288
+ * host-supplied; the library never names a path.
289
+ */
290
+ declare function httpKeyStorage(urls: HttpKeyStorageUrls): KeyStorage;
291
+
292
+ /**
293
+ * Default `AIProvider` implementation (pluggable AI provider — issue #119,
294
+ * plan §4.3): OpenAI-shaped `POST {baseUrl}/chat/completions` with SSE
295
+ * streaming. Covers OpenAI, Azure-style gateways, Ollama, vLLM, and most
296
+ * OpenAI-compatible proxies.
297
+ *
298
+ * Cancellation-safe: closing the async iterator early (e.g. the user accepts
299
+ * or cancels mid-stream) aborts the in-flight `fetch`; an aborted `req.signal`
300
+ * ends the stream silently rather than yielding an error event.
301
+ */
302
+
303
+ interface OpenAICompatibleConfig {
304
+ /** Base URL of the OpenAI-compatible endpoint, e.g. `https://api.openai.com/v1`. */
305
+ baseUrl: string;
306
+ auth: Auth;
307
+ model: string;
308
+ /** Prepended to every call's system prompt so hosts don't repeat it. */
309
+ systemPrompt?: string;
310
+ }
311
+ declare function openaiCompatibleProvider(config: OpenAICompatibleConfig): AIProvider;
312
+
313
+ export { type AIStreamFn as A, type CitationPort as C, type HttpKeyStorageUrls as H, type ImageUploadHandler as I, type KeyStorage as K, LOCAL_STORAGE_KEY as L, type OpenAICompatibleConfig as O, type StreamEvent as S, type AIDraftFn as a, type AIAction as b, type AIActionRequest as c, type AICompleteRequest as d, type AIConfig as e, type AIDraftCitation as f, type AIDraftEvent as g, type AIProvider as h, type AIProviderFactory as i, type Auth as j, CONTEXT_CHAR_CAP as k, type CslDate as l, type CslItemData as m, type CslName as n, type ImageUploadResult as o, buildAIPrompt as p, httpKeyStorage as q, localStorageKeyStorage as r, memoryKeyStorage as s, openaiCompatibleProvider as t, toAIStreamFn as u, trimContextAfter as v, trimContextBefore as w };