@mikedown/core 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +121 -2
  3. package/dist/ai/engines-page.d.ts +130 -0
  4. package/dist/ai/engines-page.js +10 -0
  5. package/dist/ai/engines-page.js.map +1 -0
  6. package/dist/ai/index.d.ts +1531 -0
  7. package/dist/ai/index.js +395 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/ai-ui-C7ENTWWU.js +3370 -0
  10. package/dist/ai-ui-C7ENTWWU.js.map +1 -0
  11. package/dist/chunk-7A5HEPLK.js +4910 -0
  12. package/dist/chunk-7A5HEPLK.js.map +1 -0
  13. package/dist/chunk-GWCRPPCX.js +18 -0
  14. package/dist/chunk-GWCRPPCX.js.map +1 -0
  15. package/dist/chunk-JZWKEBCI.js +3899 -0
  16. package/dist/chunk-JZWKEBCI.js.map +1 -0
  17. package/dist/chunk-L4FO63DF.js +113 -0
  18. package/dist/chunk-L4FO63DF.js.map +1 -0
  19. package/dist/chunk-LXZG7IKN.js +39 -0
  20. package/dist/chunk-LXZG7IKN.js.map +1 -0
  21. package/dist/chunk-QDVY7WW6.js +165 -0
  22. package/dist/chunk-QDVY7WW6.js.map +1 -0
  23. package/dist/chunk-QWKT57TR.js +62 -0
  24. package/dist/chunk-QWKT57TR.js.map +1 -0
  25. package/dist/chunk-TSP7BAV3.js +22 -0
  26. package/dist/chunk-TSP7BAV3.js.map +1 -0
  27. package/dist/index.d.ts +19 -463
  28. package/dist/index.js +6511 -1258
  29. package/dist/index.js.map +1 -1
  30. package/dist/schema-DYzxkuSb.d.ts +46 -0
  31. package/dist/shared/anchoring.js +4 -16
  32. package/dist/shared/anchoring.js.map +1 -1
  33. package/dist/shared/imagePaste.js +3 -57
  34. package/dist/shared/imagePaste.js.map +1 -1
  35. package/dist/shared/sha1.js +3 -57
  36. package/dist/shared/sha1.js.map +1 -1
  37. package/dist/shared/wordCount.js +4 -12
  38. package/dist/shared/wordCount.js.map +1 -1
  39. package/dist/styles.css +3642 -127
  40. package/dist/types-CgEeIUhi.d.ts +432 -0
  41. package/dist/types-CsVNVSNY.d.ts +597 -0
  42. package/package.json +11 -1
@@ -0,0 +1,1531 @@
1
+ import { b as AiModelCapabilities, c as AiCapabilityFlag, d as AiSendsScope, e as AiFeatureSettings, f as AiLocality, g as AiEngineConfig, h as AiEngineKind, i as AiModelConfig, j as AiDiscoveredModel, k as AiApiKeyFile, A as AiSettings, a as AiEngineStatus, l as AiFinishReason, m as AiUsage, n as AiErrorCode, o as AiCompletionResult, p as AiCompletionRequest, q as AiMessage, r as AiError, s as AiConsentRecord, t as AiLocalServerProbe, u as AiHardwareInfo, v as AiHostEngineProbe } from '../types-CgEeIUhi.js';
2
+ export { w as AI_CAPABILITY_FLAGS, x as AI_ENGINE_KINDS, y as AI_ERROR_CODES, z as AI_FALLBACK_MODES, B as AI_HOST_ENGINE_KINDS, C as AiCapabilitiesSource, D as AiConsentAbout, E as AiConsentReply, F as AiEngineStatusModel, G as AiFallbackMode, H as AiFeatureSetting, I as AiKeySource, J as AiLocalitySetting, K as AiMessagePart, L as AiRoute, N as AiRouting, O as AiServedBy, P as AiTiming, Q as DEFAULT_CONTEXT_TOKENS, M as MikeDownAiHost, R as ProviderAdapter } from '../types-CgEeIUhi.js';
3
+ export { a as AI_SETTINGS_KEYS, A as AiSettingsPatch, P as ParseAiSettingsOptions, g as generateAiId, i as isHostEngineKind, b as isKnownEngineKind, p as parseAiSettings, s as serializeAiSettings } from '../schema-DYzxkuSb.js';
4
+
5
+ /**
6
+ * Task taxonomy. A feature names a task; the router picks the engine and
7
+ * model routed to that task and checks the model has what the task needs.
8
+ * Task ids are the keys of `routing.tasks`; unknown ids saved by a newer
9
+ * version are kept and route like any other (with no requirements).
10
+ */
11
+
12
+ /** A task's needs. Booleans must be true on the model; `contextTokens` is a minimum. */
13
+ type AiCapabilityRequirements = Partial<AiModelCapabilities>;
14
+ interface AiTaskDefinition {
15
+ id: string;
16
+ label: string;
17
+ description: string;
18
+ requires: AiCapabilityRequirements;
19
+ /**
20
+ * Nice to have, never a gate: the router tries candidates that meet these
21
+ * first and still uses one that does not (a model without JSON mode, a
22
+ * small context window).
23
+ */
24
+ prefers?: AiCapabilityRequirements;
25
+ /** At least one of these flags must be true (ghost text: a FIM model, or a chat model with a continue prompt). */
26
+ requiresAny?: AiCapabilityFlag[];
27
+ /** Served by a host method rather than a model completion. */
28
+ hostMethod?: 'embed' | 'transcribe';
29
+ }
30
+ /** The "comfortable" context for `summarize`: preferred by the router, and the point below which the UI may warn. Not a gate. */
31
+ declare const SUMMARIZE_MIN_CONTEXT_TOKENS = 32768;
32
+ declare const AI_TASKS: readonly AiTaskDefinition[];
33
+ declare function getAiTask(id: string): AiTaskDefinition | undefined;
34
+ /** Merge two requirement sets: flags OR together, `contextTokens` takes the larger minimum. */
35
+ declare function combineRequirements(a: AiCapabilityRequirements, b: AiCapabilityRequirements): AiCapabilityRequirements;
36
+ /**
37
+ * The capabilities a model lacks for these requirements, as flag names (and
38
+ * `contextTokens` when the window is too small). Empty means it qualifies.
39
+ * An unknown context window counts as `DEFAULT_CONTEXT_TOKENS`.
40
+ */
41
+ declare function missingCapabilities(caps: AiModelCapabilities, requires: AiCapabilityRequirements, requiresAny?: readonly AiCapabilityFlag[], defaultContextTokens?: number): Array<keyof AiModelCapabilities>;
42
+
43
+ /**
44
+ * Feature registry. A feature's defaults (on or off, which task it uses,
45
+ * what else it needs from a model) live here, so adding a feature is one
46
+ * entry plus its UI and never a settings migration. `features.<id>` in
47
+ * settings overrides `enabled` and `task`.
48
+ */
49
+
50
+ interface AiFeatureDefinition {
51
+ id: string;
52
+ label: string;
53
+ description: string;
54
+ defaultEnabled: boolean;
55
+ defaultTask: string;
56
+ /** Needs on top of the task's own. */
57
+ requires: AiCapabilityRequirements;
58
+ /** `planned` features are listed greyed ("coming later") and never surface. */
59
+ status: 'available' | 'planned';
60
+ /** What a request sends, for the consent dialogs and tooltips (`whatIsSent`). Absent: a generic sentence. */
61
+ sends?: AiSendsScope;
62
+ }
63
+ declare const AI_FEATURES: readonly AiFeatureDefinition[];
64
+ declare function getAiFeature(id: string): AiFeatureDefinition | undefined;
65
+ interface ResolvedAiFeature {
66
+ id: string;
67
+ /** Saved value, else the registry default. Unknown features default to off. */
68
+ enabled: boolean;
69
+ /** Saved override, else the registry default task, else the feature id. */
70
+ task: string;
71
+ requires: AiCapabilityRequirements;
72
+ definition?: AiFeatureDefinition;
73
+ }
74
+ /** A feature's effective state from saved settings plus the registry. Ignores the master switch. */
75
+ declare function resolveAiFeature(features: AiFeatureSettings, id: string): ResolvedAiFeature;
76
+
77
+ /**
78
+ * Local or cloud, from an engine's base URL. Loopback, link-local, IPv6
79
+ * unique-local, `localhost`, `.local`, `.lan`, `.home.arpa` and `.internal`
80
+ * are always local; `localNetworks` adds CIDR ranges (RFC 1918 by default, a
81
+ * WireGuard range such as `10.0.98.0/24`) and `.suffix` host names. Anything
82
+ * else, including a URL that does not parse, is cloud.
83
+ */
84
+
85
+ /** Default `mikedown.ai.localNetworks`: RFC 1918. */
86
+ declare const DEFAULT_LOCAL_NETWORKS: readonly string[];
87
+ /** Lower-cased host of a URL (no port, no brackets), or null. Accepts a bare `host:port`. */
88
+ declare function hostOfUrl(url: string): string | null;
89
+ /**
90
+ * `local` or `cloud` for a base URL. `localNetworks` entries are IPv4 CIDR
91
+ * ranges (`10.0.98.0/24`, a bare address counts as /32) or host-name
92
+ * suffixes (`.ts.net`, `studio.example.com`).
93
+ */
94
+ declare function detectLocality(baseUrl: string, localNetworks?: readonly string[]): AiLocality;
95
+ /**
96
+ * The engine's effective locality: its pin, or `detectLocality` for `auto`.
97
+ * A `vscode-lm` engine is always cloud, whatever its pin: it has no address,
98
+ * and the text goes to GitHub's Copilot service.
99
+ */
100
+ declare function engineLocality(engine: Pick<AiEngineConfig, 'baseUrl' | 'locality'> & {
101
+ kind?: string;
102
+ }, localNetworks?: readonly string[]): AiLocality;
103
+ /**
104
+ * True when a pinned locality disagrees with the URL (cloud pinned on a
105
+ * private address, or local pinned on a public one). The indicator shows a
106
+ * warning glyph for it.
107
+ */
108
+ declare function localityMismatch(engine: Pick<AiEngineConfig, 'baseUrl' | 'locality'> & {
109
+ kind?: string;
110
+ }, localNetworks?: readonly string[]): boolean;
111
+
112
+ /**
113
+ * Capability detection for models the endpoint does not describe (oMLX and
114
+ * most OpenAI-compatible servers list bare ids). Order of trust: endpoint
115
+ * metadata, then these heuristics, then user edits, which always win and
116
+ * survive re-discovery (`capabilitiesSource: 'user'`).
117
+ *
118
+ * Local models: an ordered regex table of families; every matching rule
119
+ * applies in order, so later, more specific rules override earlier ones.
120
+ * Cloud models (Anthropic, OpenAI): static tables matched by the longest id
121
+ * prefix, so a new model in a known line inherits its line's values.
122
+ */
123
+
124
+ type CloudTable = Array<{
125
+ prefix: string;
126
+ caps: Partial<AiModelCapabilities>;
127
+ }>;
128
+ /** Anthropic models, matched by the longest prefix. Unknown `claude-*` ids get the generic row. */
129
+ declare const ANTHROPIC_MODEL_TABLE: CloudTable;
130
+ /** OpenAI models, matched by the longest prefix. */
131
+ declare const OPENAI_MODEL_TABLE: CloudTable;
132
+ /**
133
+ * OpenAI's reasoning models, which take only the default sampling: Chat
134
+ * Completions rejects a `temperature` or `top_p` other than 1 for them. The
135
+ * native `openai` kind matches on the id; any other kind only on an
136
+ * `openai/` id (OpenRouter), so a local model that happens to be called
137
+ * `o3-something` keeps its sampling. `-chat-latest` snapshots match too:
138
+ * the default sampling costs nothing there.
139
+ */
140
+ declare function isOpenAiReasoningModel(modelId: string, kind?: AiEngineKind): boolean;
141
+ /**
142
+ * The `reasoning_effort` that means "Off" for an OpenAI reasoning model, or
143
+ * null when `modelId` is not one (the caller leaves the request alone). The
144
+ * Thinking dial stores Off as `none`, which only some families accept:
145
+ *
146
+ * - `gpt-5.1` onwards in the 5.x line (5.5 included): `none`.
147
+ * - `gpt-5`, `gpt-5-mini`, `gpt-5-nano`: `minimal`.
148
+ * - everything else (o-series, `gpt-6`+, codex and pro variants, ids not
149
+ * documented yet): `low`, the one value every reasoning family accepts.
150
+ * Newer models reject `none` (and some `minimal`), so unknown means `low`.
151
+ */
152
+ declare function openAiOffReasoningEffort(modelId: string, kind?: AiEngineKind): 'none' | 'minimal' | 'low' | null;
153
+ interface DetectedCapabilities {
154
+ capabilities: AiModelCapabilities;
155
+ /** The family rule or table prefix that matched last, or null for the generic default. */
156
+ family: string | null;
157
+ }
158
+ /**
159
+ * Capabilities guessed from a model id. A `vscode-lm` model is a plain chat
160
+ * model (the endpoint gives its context window). Anthropic and OpenAI engines use
161
+ * their static tables; any engine whose id (after a `vendor/` prefix, as on
162
+ * OpenRouter) starts with `claude-` or a known OpenAI line does too. An id
163
+ * nothing matches is a plain chat model with `DEFAULT_CONTEXT_TOKENS`.
164
+ */
165
+ declare function detectCapabilities(modelId: string, kind?: AiEngineKind): DetectedCapabilities;
166
+ /** Endpoint values over heuristics: fields the endpoint left out (or null) come from the id. */
167
+ declare function completeCapabilities(partial: Partial<AiModelCapabilities>, modelId: string, kind?: AiEngineKind): AiModelCapabilities;
168
+ /**
169
+ * Merge one discovery into an engine's saved models, by id:
170
+ *
171
+ * - listed and saved: `endpoint`/`heuristic` rows get the fresh values, `user`
172
+ * rows keep theirs; `enabled`, `label` and `requestOverrides` are kept, and
173
+ * `missing` is cleared;
174
+ * - saved but not listed: marked `missing: true` (greyed, not deleted), except
175
+ * manual entries, which are left alone;
176
+ * - listed but new: appended in discovery order, enabled unless
177
+ * `opts.addEnabled` is false. Cloud engines pass false (OPEN-12): a
178
+ * 300-model list must not flood the routing pickers, so the user picks.
179
+ */
180
+ declare function mergeDiscoveredModels(existing: readonly AiModelConfig[], discovered: readonly AiDiscoveredModel[], kind?: AiEngineKind, opts?: {
181
+ addEnabled?: boolean;
182
+ }): AiModelConfig[];
183
+
184
+ /**
185
+ * "Add engine" presets. A preset only fills the new engine's name, kind, base
186
+ * URL, key variable and key file; everything else is the user's. Presets also
187
+ * drive the Express engine form: which family a card sits in, the caption
188
+ * under it, where to get a key, and which fetched models count as Popular.
189
+ *
190
+ * No model ids here: they go stale, and the endpoint is the source of truth.
191
+ * Popular rules are patterns matched against whatever the endpoint lists.
192
+ */
193
+
194
+ /**
195
+ * Where a preset runs: your own server, a cloud API you paste a key for, or
196
+ * a model API built into the host (`host`: VS Code's GitHub Copilot models,
197
+ * no address and no key; shown only where the host reports it).
198
+ */
199
+ type AiPresetFamily = 'local' | 'cloud' | 'host';
200
+ /**
201
+ * One Popular slot: the newest fetched id that matches `match` (and not
202
+ * `exclude`). An id `prefer` matches (an alias such as `-latest`) wins;
203
+ * otherwise "newest" compares the version numbers in the id, then a date
204
+ * stamp.
205
+ */
206
+ interface AiPopularRule {
207
+ /** Short family name, for the picker's tooltip ("Sonnet"). */
208
+ label: string;
209
+ match: RegExp;
210
+ exclude?: RegExp;
211
+ prefer?: RegExp;
212
+ }
213
+ interface AiEnginePreset {
214
+ id: string;
215
+ name: string;
216
+ kind: AiEngineKind;
217
+ baseUrl: string;
218
+ apiKeyEnv?: string;
219
+ apiKeyFile?: AiApiKeyFile;
220
+ /** Whether the endpoint expects a key at all (LM Studio accepts any). */
221
+ needsKey: boolean;
222
+ family: AiPresetFamily;
223
+ /** The quick-start card's one-line caption. */
224
+ caption: string;
225
+ /** Under the API key field: where to get one, or whether one is needed. A link to `keyUrl` when it has one. */
226
+ keyHint: string;
227
+ /** Cloud presets: the provider's page that creates API keys, opened through the host's `openLink`. */
228
+ keyUrl?: string;
229
+ /** Cloud presets: the Popular group, in order. The first rule that matches is the auto-picked default. */
230
+ popular?: readonly AiPopularRule[];
231
+ /**
232
+ * Local presets: the path (from the server root, without `/v1`) that local
233
+ * server detection GETs when it is not `{baseUrl}/models`. The native lists
234
+ * answer without a key.
235
+ */
236
+ detectPath?: string;
237
+ /** Cloud and host presets: the provider's own page on how API data is used (see `privacy.ts`). */
238
+ policy?: AiProviderPolicy;
239
+ /**
240
+ * Cloud presets: the provider's official API pricing page, linked as
241
+ * "Pricing" beside the key link. Only the link: prices change, so no
242
+ * figure is stated in code. URLs checked 2026-10-29.
243
+ */
244
+ pricingUrl?: string;
245
+ /**
246
+ * Local presets: the official download or install page, for a server that
247
+ * is not running yet ("Install", then Look again). URLs checked 2026-10-29.
248
+ */
249
+ installUrl?: string;
250
+ }
251
+ /**
252
+ * A provider's official privacy or API data-use page. Only the link: what a
253
+ * provider does with the text is set by its terms, which change, so no claim
254
+ * about training or retention is made in code. URLs checked 2026-10-29.
255
+ */
256
+ interface AiProviderPolicy {
257
+ /** Link text ("Anthropic privacy and data use"). */
258
+ label: string;
259
+ url: string;
260
+ /** One extra neutral sentence, for a provider that passes requests on (OpenRouter). */
261
+ note?: string;
262
+ }
263
+ interface AiPresetFamilyInfo {
264
+ family: AiPresetFamily;
265
+ /** Group heading over the quick-start cards. */
266
+ label: string;
267
+ /** The "two ways to connect" line for this family. */
268
+ explainer: string;
269
+ }
270
+ /**
271
+ * The two quick-start groups, in display order, with the "two ways to
272
+ * connect" copy. The `host` family is not in this list: it shows only on a
273
+ * host whose `detectHostEngines` reports a model (`AI_HOST_PRESET_FAMILY`).
274
+ */
275
+ declare const AI_PRESET_FAMILIES: readonly AiPresetFamilyInfo[];
276
+ /** The `host` group (VS Code's built-in models), for the hosts that report one through `detectHostEngines`. */
277
+ declare const AI_HOST_PRESET_FAMILY: AiPresetFamilyInfo;
278
+ declare const AI_ENGINE_PRESETS: readonly AiEnginePreset[];
279
+ /**
280
+ * The preset a saved engine most likely came from (engines do not store
281
+ * one): same kind and address, then the same key variable as a tiebreak
282
+ * (oMLX and vLLM share port 8000); else, for a cloud address, the preset on
283
+ * the same host; else the one preset of a dedicated kind. Undefined when
284
+ * nothing fits (a hand-made OpenAI-compatible engine).
285
+ */
286
+ declare function presetForEngine(engine: {
287
+ kind: string;
288
+ baseUrl: string;
289
+ apiKeyEnv?: string;
290
+ }, presets?: readonly AiEnginePreset[]): AiEnginePreset | undefined;
291
+ /** Local server detection waits this long for each address. */
292
+ declare const AI_LOCAL_DETECT_TIMEOUT_MS = 1500;
293
+ /** What `detectLocalServers` is asked about: every local preset but Custom, at its default address. */
294
+ declare function localDetectCandidates(presets?: readonly AiEnginePreset[]): {
295
+ presetId: string;
296
+ baseUrl: string;
297
+ }[];
298
+
299
+ /**
300
+ * Pure parsers for discovery responses, one per endpoint shape. Hosts do the
301
+ * HTTP and hand the parsed JSON here, so the VS Code adapters and the Rust
302
+ * port read the same fixtures (`test/ai-fixtures/discovery/`). Every parser
303
+ * tolerates junk: entries without a string id are skipped, never thrown on.
304
+ */
305
+
306
+ /** `GET {base}/models` on any OpenAI-compatible server: ids only, capabilities from heuristics. */
307
+ declare function parseOpenAiModelList(body: unknown): AiDiscoveredModel[];
308
+ /**
309
+ * LM Studio `GET /api/v0/models`: `type` (`llm`, `vlm`, `embeddings`),
310
+ * `max_context_length`, `state` (`loaded` / `not-loaded`), `arch` (a `moe`
311
+ * in it marks a mixture of experts; one ending `_mtp` is a speculative-decoding
312
+ * drafter, not a chat model), and on newer builds `capabilities`
313
+ * (`tool_use`).
314
+ */
315
+ declare function parseLmStudioModels(body: unknown): AiDiscoveredModel[];
316
+ /**
317
+ * oMLX `GET {base}/models/status` (beside its OpenAI `/models`): per model
318
+ * `loaded` and `model_type` (`llm`, `vlm`, `embedding`, or a non-text type
319
+ * such as `audio_stt` or `markitdown`, which can neither chat nor embed),
320
+ * `model_context_length`, `estimated_size` (bytes) and `config_model_type`
321
+ * (a `moe` in it marks a mixture of experts; one ending `_mtp` is a
322
+ * speculative-decoding drafter, not a chat model, though it says `llm`).
323
+ */
324
+ declare function parseOmlxModelStatus(body: unknown): AiDiscoveredModel[];
325
+ /**
326
+ * Fold a status listing (`parseOmlxModelStatus`) into the models `/models`
327
+ * listed: the listing decides which models exist, the status adds what it
328
+ * knows about each (capabilities, loaded state).
329
+ */
330
+ declare function withModelStatus(listed: readonly AiDiscoveredModel[], status: readonly AiDiscoveredModel[]): AiDiscoveredModel[];
331
+ /**
332
+ * Sizes in bytes by model id from a listing that has them: LM Studio
333
+ * `GET /api/v1/models` (`models[].key`, `size_bytes`; its `/api/v0` has
334
+ * none) and Ollama `GET /api/tags` (`models[].model` or `name`, `size`).
335
+ */
336
+ declare function parseModelSizes(body: unknown): Map<string, number>;
337
+ /** `models` with the sizes `parseModelSizes` found added; the rest as they were. */
338
+ declare function withModelSizes(models: readonly AiDiscoveredModel[], sizes: ReadonlyMap<string, number>): AiDiscoveredModel[];
339
+ /** Ollama `GET /api/tags`: the model names to ask `/api/show` about. */
340
+ declare function parseOllamaTags(body: unknown): string[];
341
+ /**
342
+ * Ollama `POST /api/show` for one model: `capabilities` (`completion`,
343
+ * `vision`, `tools`, `embedding`, `insert`) and the context length under
344
+ * `model_info["<arch>.context_length"]`.
345
+ */
346
+ declare function parseOllamaShow(name: string, body: unknown): AiDiscoveredModel;
347
+ /** Which Ollama models are loaded right now, from `GET /api/ps` (optional). */
348
+ declare function parseOllamaRunning(body: unknown): Set<string>;
349
+ /**
350
+ * Anthropic `GET /v1/models`: `display_name`, `max_input_tokens` (the context
351
+ * window) and the `capabilities` tree (`image_input.supported`,
352
+ * `structured_outputs.supported`). Tools are assumed on.
353
+ */
354
+ declare function parseAnthropicModels(body: unknown): AiDiscoveredModel[];
355
+ /** Anthropic pages its list; the next `after_id`, or null on the last page. */
356
+ declare function anthropicNextPage(body: unknown): string | null;
357
+ /** The URL detection GETs: `detectPath` under the server root (no `/v1`), else `{baseUrl}/models`. */
358
+ declare function localDetectUrl(baseUrl: string, detectPath?: string): string;
359
+ /**
360
+ * How many models a detection answer lists: `data` (OpenAI-style, LM Studio)
361
+ * or `models` (Ollama). LM Studio's embedding models are left out. Undefined
362
+ * for a body of neither shape (the server answered, the count is unknown).
363
+ */
364
+ declare function countDetectedModels(body: unknown): number | undefined;
365
+ /** What a host needs to probe one detection candidate. */
366
+ interface AiLocalDetectTarget {
367
+ presetId: string;
368
+ baseUrl: string;
369
+ /** The GET. */
370
+ url: string;
371
+ /** For the auth header shape. */
372
+ kind: AiEngineKind;
373
+ /** Where to read a key, only when `baseUrl` is the preset's own address. */
374
+ apiKeyEnv?: string;
375
+ apiKeyFile?: AiApiKeyFile;
376
+ }
377
+ /**
378
+ * A detection candidate checked against the presets. Null for an unknown or
379
+ * cloud preset, Custom, or an address that is not http(s). A key source
380
+ * (oMLX's key file) comes along only when the address is the preset's own,
381
+ * so a key never goes anywhere the user did not set up.
382
+ */
383
+ declare function localDetectTarget(candidate: {
384
+ presetId: string;
385
+ baseUrl: string;
386
+ }, presets?: readonly AiEnginePreset[]): AiLocalDetectTarget | null;
387
+
388
+ /**
389
+ * The settings before the host sends any. Its own module so the editor can
390
+ * start from them without loading the rest of the AI layer (see `ai-lite.ts`).
391
+ */
392
+
393
+ declare function defaultAiSettings(): AiSettings;
394
+
395
+ /**
396
+ * Task routing: which engine and model serve a feature's request. Pure and
397
+ * cheap, so the editor recomputes it on every request from the settings, the
398
+ * status cache of the last `listEngines`, and the consent answers so far.
399
+ *
400
+ * 1. The master switch and the feature must be on.
401
+ * 2. Route = `routing.tasks[task]`, else `routing.default`.
402
+ * 3. A candidate is usable when the engine exists, is enabled and of a known
403
+ * kind (a host kind such as `vscode-lm` only when the host lists a status
404
+ * for it); the model exists, is enabled and not `missing`; the model has every
405
+ * capability the task and feature need; the engine is local, or cloud with
406
+ * `allowCloud` on and consent not refused; and its last probe did not fail.
407
+ * 4. Otherwise `routing.fallback`: `none` stops, `default` tries the default
408
+ * route, `anyLocal` then every local engine/model in registry order, `any`
409
+ * then cloud engines through the same gate. Fallback never moves a local
410
+ * route to a cloud engine unless the mode is `any`.
411
+ * 5. The result names the pair and whether a fallback served it.
412
+ *
413
+ * A task may also `prefers` capabilities (JSON output for `structure`, a big
414
+ * context for `summarize`). That is never a rejection reason. The chosen
415
+ * route is honoured as is; when a fallback has several usable candidates in
416
+ * the same tier (the default route and the local engines, then the cloud
417
+ * ones), those meeting the preference come first, in registry order.
418
+ *
419
+ * Consent: a cloud candidate nobody has asked about yet is usable with
420
+ * `needsConsent: true`; the caller asks (`host.ai.requestCloudConsent`),
421
+ * records the answer and resolves again. A refusal (`false`) makes the
422
+ * engine unusable, which lets the fallback run.
423
+ */
424
+
425
+ /** Consent answers by engine id: true granted, false refused ("Not now"), absent never asked. */
426
+ type AiConsentState = Readonly<Record<string, boolean | undefined>>;
427
+ type AiUnusableCode = 'aiDisabled' | 'featureDisabled' | 'noRoute' | 'engineMissing' | 'engineDisabled' | 'engineUnsupported' | 'modelMissing' | 'modelDisabled' | 'modelNotListed' | 'capability' | 'cloudBlocked' | 'consentDenied' | 'probeFailed';
428
+ interface AiRouteOk {
429
+ engineId: string;
430
+ modelId: string;
431
+ viaFallback: boolean;
432
+ /** Why the preferred route was passed over, when `viaFallback`. */
433
+ reason?: string;
434
+ /** The task that was routed. */
435
+ task: string;
436
+ isLocal: boolean;
437
+ /** Cloud engine with no consent answer yet: ask before sending. */
438
+ needsConsent: boolean;
439
+ }
440
+ interface AiRouteUnusable {
441
+ unusable: AiUnusableCode;
442
+ /** Tooltip copy, e.g. "oMLX (Mac Studio) failed its last connection test". */
443
+ reason: string;
444
+ task?: string;
445
+ /** The engine the rejection is about (the routed one), when it is about one engine. */
446
+ engineId?: string;
447
+ }
448
+ type AiRouteResolution = AiRouteOk | AiRouteUnusable;
449
+ declare function isRouteUsable(r: AiRouteResolution): r is AiRouteOk;
450
+ interface ResolveRouteOptions {
451
+ feature: string;
452
+ /** Route this task instead of the feature's. */
453
+ task?: string;
454
+ }
455
+ declare function resolveRoute(settings: AiSettings, statuses: readonly AiEngineStatus[], consent: AiConsentState, opts: ResolveRouteOptions): AiRouteResolution;
456
+
457
+ /**
458
+ * Server-sent events, reassembled from arbitrary network chunks (a line, or
459
+ * a multi-byte character, may be split across chunks; the host decodes bytes
460
+ * with a streaming `TextDecoder` before pushing text here), plus readers for
461
+ * the two completion stream shapes:
462
+ *
463
+ * - OpenAI Chat Completions (OpenAI, oMLX, LM Studio, Ollama `/v1`, vLLM,
464
+ * llama.cpp): `data: {choices:[{delta:{content}}]}` chunks ending in
465
+ * `data: [DONE]`. `reasoning_content` deltas are thinking, not text.
466
+ * - Anthropic Messages: `message_start`, `content_block_delta` with
467
+ * `text_delta`, `message_delta` with `stop_reason` and usage, `message_stop`,
468
+ * and an `error` event.
469
+ *
470
+ * A stream that ends without its terminator (`[DONE]` or `message_stop`) is
471
+ * truncated: `finishReason: 'error'`.
472
+ */
473
+
474
+ interface SseEvent {
475
+ /** `event:` field; absent means the default `message`. */
476
+ event?: string;
477
+ data: string;
478
+ id?: string;
479
+ }
480
+ /** Incremental SSE parser (WHATWG event-stream rules: CRLF/CR/LF, `:` comments, multi-line `data`). */
481
+ declare class SseParser {
482
+ private buffer;
483
+ private started;
484
+ private dataLines;
485
+ private eventName;
486
+ private lastId;
487
+ /** Feed decoded text; returns the events it completed. */
488
+ push(chunk: string): SseEvent[];
489
+ /** The stream closed: flush a final event that had no trailing blank line. */
490
+ end(): SseEvent[];
491
+ private line;
492
+ }
493
+ /** Where a completion stream ended up. */
494
+ interface StreamOutcome {
495
+ text: string;
496
+ finishReason: AiFinishReason;
497
+ usage?: AiUsage;
498
+ /** The provider's own stop reason (`end_turn`, `length`, `refusal`...). */
499
+ stopReason?: string;
500
+ /** A stream-level error event, mapped to a code. */
501
+ error?: {
502
+ code: AiErrorCode;
503
+ message: string;
504
+ };
505
+ }
506
+ /** One completion stream: push decoded chunks, get text deltas back, then `finish`. */
507
+ interface CompletionStreamReader {
508
+ push(chunk: string): string[];
509
+ finish(): StreamOutcome;
510
+ /**
511
+ * Output seen so far, answer plus reasoning, in UTF-16 units: above zero
512
+ * once the model starts generating (the host's first-token time), and the
513
+ * basis of a token estimate when the engine reports no usage.
514
+ */
515
+ outputChars(): number;
516
+ }
517
+ /** Chat Completions stream (`openai-compatible`, `lmstudio`, `ollama`, `openai`). */
518
+ declare function createOpenAiStreamReader(): CompletionStreamReader;
519
+ /** Anthropic Messages stream. */
520
+ declare function createAnthropicStreamReader(): CompletionStreamReader;
521
+ /** The reader for an engine kind. */
522
+ declare function createCompletionStreamReader(kind: AiEngineKind): CompletionStreamReader;
523
+
524
+ /**
525
+ * How long a result took and how fast it came: the popover footer's
526
+ * "2.4 s · 38 tok/s", its tooltip, and the hosts' request log. Hosts measure
527
+ * (`AiCompletionResult.timing`); this only does the arithmetic and the words.
528
+ *
529
+ * Output tokens are the engine's own count when it reports one (OpenAI-style
530
+ * `usage` with `stream_options.include_usage`, Anthropic `message_delta`),
531
+ * else about one token per four characters of output, marked approximate
532
+ * ("~38 tok/s"). Speed is output tokens over the time from the first token to
533
+ * the end; without a first-token time, over the whole request.
534
+ *
535
+ * Desktop's `request_log.rs` ports `aiTimingLogParts`; keep them in step.
536
+ */
537
+
538
+ /** Characters per token for the estimate when the engine reports no usage. */
539
+ declare const AI_CHARS_PER_TOKEN = 4;
540
+ interface AiResultStats {
541
+ totalMs: number;
542
+ firstTokenMs?: number;
543
+ outputTokens?: number;
544
+ inputTokens?: number;
545
+ /** `outputTokens` (and so the speed) is estimated from the characters. */
546
+ approximate: boolean;
547
+ tokensPerSecond?: number;
548
+ }
549
+ /** The numbers behind the footer, or null when the host measured nothing (an older host). */
550
+ declare function aiResultStats(result: Pick<AiCompletionResult, 'text' | 'usage' | 'timing'>): AiResultStats | null;
551
+ /** "0.8 s", "2.4 s", "12 s". */
552
+ declare function formatAiSeconds(ms: number): string;
553
+ /** "38 tok/s", "~38 tok/s", "4.5 tok/s". */
554
+ declare function formatAiTokensPerSecond(tps: number, approximate: boolean): string;
555
+ /** The footer text: "2.4 s · 38 tok/s", or just "2.4 s" with no speed. */
556
+ declare function aiResultStatsText(stats: AiResultStats): string;
557
+ /** The footer tooltip, one fact per line. */
558
+ declare function aiResultStatsTitle(stats: AiResultStats): string;
559
+ /** The request log's additions: `first token N ms` and the speed, when measured. */
560
+ declare function aiTimingLogParts(result: Pick<AiCompletionResult, 'text' | 'usage' | 'timing'>): string[];
561
+
562
+ /**
563
+ * Request building per engine kind: endpoint URLs, auth headers and the
564
+ * completion and embedding bodies. Pure, so the VS Code adapters and the Rust
565
+ * port produce identical requests (checked against the same fixtures).
566
+ *
567
+ * Bodies:
568
+ * - Chat Completions kinds: `{ model, messages, stream, max_tokens,
569
+ * temperature, response_format }`; `openai` uses `max_completion_tokens`
570
+ * and asks for usage in the stream. OpenAI reasoning models (o-series,
571
+ * GPT-5 onwards) get no `temperature`: they accept only the default.
572
+ * - `anthropic`: Messages API, system prompts lifted into `system`,
573
+ * `max_tokens` always set (required). No sampling parameters: API version
574
+ * 2026-10-21 rejects `temperature`.
575
+ *
576
+ * `model.requestOverrides` merge last, deeply (objects merge, everything else
577
+ * replaces), so they win (OPEN-6); then the request's own `requestOverrides`
578
+ * (a per-request thinking level) merge over those the same way.
579
+ *
580
+ * A 400 that names a parameter the model does not take
581
+ * (`unsupportedRequestParam`) is retried once without it
582
+ * (`withoutRequestParam`); the hosts remember it per engine and model for
583
+ * the session.
584
+ */
585
+
586
+ /** `anthropic-version` sent by the Anthropic adapter. */
587
+ declare const ANTHROPIC_API_VERSION = "2026-10-21";
588
+ /** `max_tokens` for Anthropic when the request sets none (the API requires it). */
589
+ declare const ANTHROPIC_DEFAULT_MAX_TOKENS = 4096;
590
+ /** Appended to the system prompt for `format: 'json'` on kinds with no schema-free JSON mode. */
591
+ declare const JSON_ONLY_INSTRUCTION = "Respond with a single valid JSON value and nothing else.";
592
+ type Json$1 = Record<string, unknown>;
593
+ interface AiEngineEndpoints {
594
+ /** Streaming completion. */
595
+ completions: string;
596
+ /** Model list used for Test connection and bare discovery. */
597
+ models: string;
598
+ /** Embeddings, absent for kinds without them. */
599
+ embeddings?: string;
600
+ /** Native discovery endpoint (LM Studio `/api/v0/models`, Ollama `/api/tags`). */
601
+ nativeModels?: string;
602
+ /** Ollama `POST /api/show` (body `{ model }`). */
603
+ nativeShow?: string;
604
+ }
605
+ /**
606
+ * URLs for an engine. OpenAI-style base URLs include `/v1`
607
+ * (`http://127.0.0.1:8000/v1`); for LM Studio and Ollama a base without it
608
+ * gets `/v1` added for completions, and their native endpoints hang off the
609
+ * root. Anthropic's base is the host (`https://api.anthropic.com`), with or
610
+ * without `/v1`.
611
+ */
612
+ declare function engineEndpoints(engine: Pick<AiEngineConfig, 'kind' | 'baseUrl'>): AiEngineEndpoints;
613
+ /** Headers for every request to the engine (JSON content type included). */
614
+ declare function buildAuthHeaders(kind: AiEngineKind, apiKey: string | null): Record<string, string>;
615
+ /** Deep merge: nested plain objects merge, anything else in `overrides` replaces. Inputs are not mutated. */
616
+ declare function mergeRequestOverrides(body: Json$1, overrides: Json$1 | undefined): Json$1;
617
+ /** Standard base64 with padding, no line breaks. No `Buffer` or `btoa`, so it runs in every host. */
618
+ declare function bytesToBase64(bytes: Uint8Array): string;
619
+ /** Inverse of `bytesToBase64`; ignores whitespace and accepts URL-safe characters. */
620
+ declare function base64ToBytes(b64: string): Uint8Array;
621
+ interface BuildCompletionOptions {
622
+ /** Default true. */
623
+ stream?: boolean;
624
+ }
625
+ /** The JSON body for one completion against `model` on an engine of `kind`. */
626
+ declare function buildCompletionBody(kind: AiEngineKind, model: Pick<AiModelConfig, 'id' | 'requestOverrides'>, req: AiCompletionRequest, opts?: BuildCompletionOptions): Json$1;
627
+ /**
628
+ * Cloud hosts of the `openai-compatible` kind that take
629
+ * `stream_options.include_usage` (OpenRouter, Gemini, Groq, DeepSeek).
630
+ * Mistral and unknown cloud hosts are left out: a strict server may reject
631
+ * a field it does not know.
632
+ */
633
+ declare const STREAM_USAGE_HOSTS: readonly string[];
634
+ /**
635
+ * Whether a streamed completion may ask for usage in the stream. `openai`,
636
+ * LM Studio and Ollama take it; on `openai-compatible`, a server on this
637
+ * machine or network (oMLX, vLLM, llama.cpp all take it) or a host in
638
+ * `STREAM_USAGE_HOSTS`. Anthropic reports usage without being asked.
639
+ */
640
+ declare function streamUsageSupported(engine: Pick<AiEngineConfig, 'kind' | 'baseUrl'>): boolean;
641
+ /**
642
+ * `body` asking for usage in the stream (`stream_options.include_usage`)
643
+ * when the engine takes it, the body streams and the overrides did not set
644
+ * `stream_options` themselves. Hosts apply it after `buildCompletionBody`.
645
+ */
646
+ declare function withStreamUsage(body: Json$1, engine: Pick<AiEngineConfig, 'kind' | 'baseUrl'>): Json$1;
647
+ /**
648
+ * Body fields a server may reject for one model while taking them for
649
+ * others. Only these are retried: anything else in a 400 is a real error.
650
+ */
651
+ declare const RETRYABLE_REQUEST_PARAMS: readonly string[];
652
+ /**
653
+ * The parameter a failed completion was rejected for, or null. Only a 400
654
+ * whose error says the parameter (or its value) is unsupported counts:
655
+ * OpenAI's `{ error: { param, code: "unsupported_value" | "unsupported_parameter", message } }`,
656
+ * or any body whose message quotes one of `RETRYABLE_REQUEST_PARAMS` next to
657
+ * that wording (the first one quoted, so "Use 'max_completion_tokens'
658
+ * instead" does not count).
659
+ */
660
+ declare function unsupportedRequestParam(status: number, bodyText: string): string | null;
661
+ /**
662
+ * `body` without a parameter the server rejected: `max_tokens` and
663
+ * `max_completion_tokens` become each other (the documented replacement),
664
+ * anything else is dropped. Null when the body does not carry it, so there
665
+ * is nothing to retry.
666
+ */
667
+ declare function withoutRequestParam(body: Json$1, param: string): Json$1 | null;
668
+ /** Embeddings body (`POST .../embeddings`), or null for kinds without an embeddings endpoint. */
669
+ declare function buildEmbeddingsBody(kind: AiEngineKind, model: Pick<AiModelConfig, 'id' | 'requestOverrides'>, texts: string[]): Json$1 | null;
670
+ /** Vectors from an OpenAI-style embeddings response, in input order. */
671
+ declare function parseEmbeddingsResponse(body: unknown): number[][];
672
+
673
+ /**
674
+ * `AiCompletionRequest` over a JSON channel (VS Code `postMessage`, Tauri
675
+ * `invoke`): image bytes travel as base64, the same way `savePastedImage`
676
+ * sends pasted images.
677
+ */
678
+
679
+ type AiWireMessagePart = {
680
+ type: 'text';
681
+ text: string;
682
+ } | {
683
+ type: 'image';
684
+ mime: string;
685
+ dataBase64: string;
686
+ };
687
+ interface AiWireMessage {
688
+ role: AiMessage['role'];
689
+ content: string | AiWireMessagePart[];
690
+ }
691
+ type AiWireCompletionRequest = Omit<AiCompletionRequest, 'messages'> & {
692
+ messages: AiWireMessage[];
693
+ };
694
+ declare function toWireCompletionRequest(req: AiCompletionRequest): AiWireCompletionRequest;
695
+ declare function fromWireCompletionRequest(wire: AiWireCompletionRequest): AiCompletionRequest;
696
+
697
+ /**
698
+ * `AiError` helpers and the user-facing copy for each `AiErrorCode`, kept in
699
+ * one place so the rewrite popover, the AI tab and the indicator say the same
700
+ * thing. Hosts reject with `{ code, message }`; `message` is the technical
701
+ * detail, the copy here is what the user reads first.
702
+ */
703
+
704
+ interface AiErrorCopy {
705
+ title: string;
706
+ message: string;
707
+ /**
708
+ * Suggested next step, when there is one. `openEngine`: the failing
709
+ * engine's own page. `chooseModel`: the same page, to pick another model.
710
+ */
711
+ action?: 'openSettings' | 'openEngine' | 'chooseModel' | 'retry' | 'shortenSelection';
712
+ }
713
+ declare const AI_ERROR_COPY: Readonly<Record<AiErrorCode, AiErrorCopy>>;
714
+ /**
715
+ * A stream that stopped at its token limit (`finishReason: 'length'`). A
716
+ * thinking model counts its reasoning against that limit, so with thinking
717
+ * on the advice is to lower Thinking. `empty`: no answer text came back at
718
+ * all (shown as the error); otherwise the answer is shown with `message` as
719
+ * a note.
720
+ */
721
+ declare function aiLengthLimitCopy(opts: {
722
+ thinking: boolean;
723
+ empty: boolean;
724
+ }): AiErrorCopy;
725
+ declare function isAiErrorCode(v: unknown): v is AiErrorCode;
726
+ declare function isAiError(v: unknown): v is AiError;
727
+ declare function makeAiError(code: AiErrorCode, message?: string): AiError;
728
+ /** Any rejection value as an `AiError`: an `AiError` as is, an `AbortError` as `cancelled`, the rest `unknown`. */
729
+ declare function toAiError(err: unknown): AiError;
730
+ /**
731
+ * The copy for `code`. With `engine` (the display name of the engine the
732
+ * request went to), an engine problem names it and its action is
733
+ * `openEngine` (`chooseModel` for a model that did not load): the popover's
734
+ * button opens that engine instead of the AI tab. `model` names the model
735
+ * where the copy has room for it.
736
+ */
737
+ declare function aiErrorCopy(code: AiErrorCode, opts?: {
738
+ engine?: string;
739
+ model?: string;
740
+ }): AiErrorCopy;
741
+ /**
742
+ * A local server that could not load the model: LM Studio's `Failed to load
743
+ * model "x"`, llama.cpp's `error loading model`, Ollama's `llama runner
744
+ * process has terminated` and its out-of-memory refusal.
745
+ */
746
+ declare const MODEL_LOAD_FAILED_PHRASES: readonly string[];
747
+ /**
748
+ * Map an HTTP failure to a code (shared by both hosts): 401/403 auth, 404
749
+ * modelNotFound, a body that says the model failed to load modelLoadFailed,
750
+ * 413 or a 400 that mentions the context contextOverflow, 429 and 529
751
+ * rateLimited, 502/503/504 unreachable, everything else unknown.
752
+ */
753
+ declare function aiErrorCodeFromHttp(status: number, bodyText?: string): AiErrorCode;
754
+
755
+ /**
756
+ * Per-model thinking level (Off / Low / Medium / High) mapped onto each
757
+ * engine kind's request fields, stored in the model's `requestOverrides`
758
+ * (OPEN-6) so the adapters need no change: they merge the overrides into
759
+ * every request body last.
760
+ *
761
+ * Mappings:
762
+ * - `openai` and cloud `openai-compatible` (Gemini, Groq, OpenRouter...):
763
+ * `reasoning_effort` `none` / `low` / `medium` / `high`. Off is stored as
764
+ * `none`; `buildCompletionBody` swaps it for the lowest value an OpenAI
765
+ * reasoning model accepts (`openAiOffReasoningEffort`).
766
+ * - local `openai-compatible` (oMLX, vLLM, llama.cpp): tiers through
767
+ * `reasoning_effort`; Off through `chat_template_kwargs.enable_thinking:
768
+ * false`, which the chat template honours (oMLX ignores
769
+ * `reasoning_effort: "none"`).
770
+ * - `lmstudio`: on/off only, `reasoning_effort` `none` or `medium`.
771
+ * - `ollama`: `reasoning_effort` `none` / `low` / `medium` / `high` (its
772
+ * OpenAI-compatible endpoint maps it onto `think`).
773
+ * - `vscode-lm`: none (VS Code's language model API has no such field).
774
+ * - `anthropic`: adaptive thinking with `output_config.effort` `low` /
775
+ * `medium` / `high`. No Off: current models reject `thinking: disabled`
776
+ * (Opus 5.5, Sonnet 5.5, Fable), so Low is the lightest setting.
777
+ *
778
+ * "Model default" (no level) removes the kind's thinking fields and leaves
779
+ * everything else in `requestOverrides` alone.
780
+ *
781
+ * Per-task default (Q6): with the dial on Default and no level set on the
782
+ * model, MikeDown picks the level for the job (`taskThinkingDefault`): Off
783
+ * for the short edits (every rewrite variant, translate, suggest title,
784
+ * suggest properties), none for the rest (explain, summarize), so the model
785
+ * decides there. An explicit dial choice, or a level set on the model, wins
786
+ * (`resolveThinkingLevel`).
787
+ */
788
+ type AiThinkingLevel = 'off' | 'low' | 'medium' | 'high';
789
+ interface AiThinkingChoice {
790
+ level: AiThinkingLevel;
791
+ label: string;
792
+ }
793
+ interface AiThinkingOptions {
794
+ /** The engine's locality is local (changes the Off mapping for `openai-compatible`). */
795
+ local?: boolean;
796
+ }
797
+ type Json = Record<string, unknown>;
798
+ /** The levels a kind can express, in order. Empty means no known mapping: hide the control. */
799
+ declare function thinkingChoices(kind: string): AiThinkingChoice[];
800
+ /**
801
+ * The level a kind uses for `level`: the level itself when the kind has it,
802
+ * else the nearest one (Off on Anthropic is Low, any tier on LM Studio is
803
+ * On). Null for no level or a kind with no mapping.
804
+ */
805
+ declare function nearestThinkingLevel(kind: string, level: AiThinkingLevel | null): AiThinkingLevel | null;
806
+ /**
807
+ * What a request is for, as far as its default thinking level goes: a
808
+ * feature id (`rewrite`, `title`, `frontmatter`, `summary`) or a job that
809
+ * shares one (`explain` and `translate` run under `rewrite`).
810
+ */
811
+ type AiThinkingJob = string;
812
+ /**
813
+ * The level MikeDown picks for a job when the dial is on Default (Q6): Off
814
+ * for the short edits, null (the model's own) for everything else.
815
+ */
816
+ declare function taskThinkingDefault(job: string | undefined): AiThinkingLevel | null;
817
+ interface ResolvedThinkingLevel {
818
+ /** The level to send (already the kind's nearest), or null to send none. */
819
+ level: AiThinkingLevel | null;
820
+ /** True when `level` is the job's default rather than the user's choice. */
821
+ auto: boolean;
822
+ }
823
+ /**
824
+ * The level a request sends: the user's explicit choice (the dial) first,
825
+ * then nothing when the model has its own level set (`configured`), then the
826
+ * job's default (mapped to the kind's nearest: Off on Anthropic is Low).
827
+ * The explicit level is sent as given (`thinkingLevelFor` already maps a
828
+ * remembered one); one the kind cannot express sends nothing.
829
+ */
830
+ declare function resolveThinkingLevel(kind: string, explicit: AiThinkingLevel | null, configured: AiThinkingLevel | null, job: string | undefined, opts?: {
831
+ modelId?: string;
832
+ }): ResolvedThinkingLevel;
833
+ /** The request fields for one level, or `{}` when the kind cannot express it. */
834
+ declare function thinkingOverrides(kind: string, level: AiThinkingLevel, opts?: AiThinkingOptions): Json;
835
+ /** The level `overrides` express for this kind, or null for "model default" (or a value the menu has no entry for). */
836
+ declare function thinkingLevelFrom(kind: string, overrides: Json | undefined): AiThinkingLevel | null;
837
+ /**
838
+ * `requestOverrides` with the thinking level set (null: model default).
839
+ * Other keys, nested ones included, are kept. Undefined when nothing is left.
840
+ */
841
+ declare function withThinkingLevel(kind: string, overrides: Json | undefined, level: AiThinkingLevel | null, opts?: AiThinkingOptions): Json | undefined;
842
+ /**
843
+ * Output tokens a thinking model may spend reasoning before it answers, by
844
+ * level. A thinking model counts its reasoning against `max_tokens`, so an
845
+ * answer budget alone can be used up before the first visible word. Sized
846
+ * for local reasoning models (Qwen3 class), which often think 2,000 to
847
+ * 4,000 tokens even on a one-paragraph edit. It is a ceiling, not a spend.
848
+ */
849
+ declare const THINKING_HEADROOM_TOKENS: Readonly<Record<AiThinkingLevel, number>>;
850
+ /** "Model default" (no level set): the model may think, at a level we cannot see. */
851
+ declare const THINKING_DEFAULT_HEADROOM_TOKENS: number;
852
+ /** The most `thinkingMaxTokens` raises a request to (a larger answer budget is kept as is). */
853
+ declare const THINKING_MAX_TOKENS_CAP = 12288;
854
+ interface ThinkingBudgetOptions {
855
+ /** The model's context window, when known. */
856
+ contextTokens?: number | null;
857
+ /** Estimated prompt tokens; with `contextTokens`, the headroom never pushes past the window. */
858
+ promptTokens?: number;
859
+ }
860
+ /**
861
+ * The `max_tokens` to send for an answer budget of `answerTokens` on a model
862
+ * that can think: the answer plus reasoning headroom for the effective level
863
+ * (null: model default, counted as thinking). Off adds nothing. The raise is
864
+ * capped at `THINKING_MAX_TOKENS_CAP` and at what is left of the context
865
+ * window, and never goes below the answer budget.
866
+ */
867
+ declare function thinkingMaxTokens(answerTokens: number, level: AiThinkingLevel | null, opts?: ThinkingBudgetOptions): number;
868
+
869
+ /**
870
+ * Selection rewrite prompt (the `rewrite` feature). Pure: builds the chat
871
+ * messages from the selected markdown and a little context, and cleans what
872
+ * the model sends back before it is previewed or parsed into the document.
873
+ *
874
+ * Context budget (brainstorm constraint 8): the selection plus the text of
875
+ * the enclosing block before and after it, and the neighbouring block on each
876
+ * side, capped at `REWRITE_CONTEXT_CHARS` per side. Never the whole document.
877
+ */
878
+
879
+ type AiRewriteVariant = 'shorter' | 'longer' | 'expand' | 'simpler' | 'fixFlow' | 'formal' | 'casual' | 'readingLevel' | 'translate' | 'explain' | 'custom';
880
+ /** Menu group a variant belongs to (the popover's choose phase renders one section per group). */
881
+ type AiRewriteGroup = 'length' | 'tone' | 'audience' | 'translate' | 'explain' | 'custom';
882
+ /** `replace` swaps the selection; `readOnly` only shows text (Explain) and can insert it below. */
883
+ type AiRewriteOutput = 'replace' | 'readOnly';
884
+ type AiAudience = 'general' | 'expert' | 'plain' | 'grade8' | 'esl';
885
+ interface AiAudienceDefinition {
886
+ id: AiAudience;
887
+ label: string;
888
+ /** Instruction line sent to the model for `readingLevel`. */
889
+ instruction: string;
890
+ }
891
+ declare const AI_AUDIENCES: readonly AiAudienceDefinition[];
892
+ declare function getAiAudience(id: string): AiAudienceDefinition | undefined;
893
+ interface AiRewriteVariantDefinition {
894
+ id: AiRewriteVariant;
895
+ /** Menu label. */
896
+ label: string;
897
+ /** Tooltip, where the label alone does not tell two variants apart (Longer, Add detail). */
898
+ title?: string;
899
+ /**
900
+ * The instruction line sent to the model. Empty for `custom` (the user's own
901
+ * words); `{language}` and `{audience}` are filled from `RewritePromptInput.params`.
902
+ */
903
+ instruction: string;
904
+ group: AiRewriteGroup;
905
+ output: AiRewriteOutput;
906
+ /** The parameter this variant needs before it can run (a second-level picker supplies it). */
907
+ param?: 'language' | 'audience';
908
+ }
909
+ /** Order here is the menu order. */
910
+ declare const AI_REWRITE_VARIANTS: readonly AiRewriteVariantDefinition[];
911
+ declare function getAiRewriteVariant(id: string): AiRewriteVariantDefinition | undefined;
912
+ /** Context kept on each side of the selection, in characters (about 400 tokens). */
913
+ declare const REWRITE_CONTEXT_CHARS = 1600;
914
+ /** Longest custom instruction sent as is. */
915
+ declare const REWRITE_INSTRUCTION_MAX_CHARS = 1000;
916
+ interface RewritePromptInput {
917
+ variant: AiRewriteVariant;
918
+ /** The user's instruction for `custom` (ignored otherwise). */
919
+ instruction?: string;
920
+ /** The selected text as markdown. */
921
+ selection: string;
922
+ /** Markdown just before the selection (enclosing block, then the previous one). */
923
+ before?: string;
924
+ /** Markdown just after the selection. */
925
+ after?: string;
926
+ /** Values for the parameterised variants (`translate` needs `language`, `readingLevel` needs `audience`). */
927
+ params?: AiRewriteParams;
928
+ /**
929
+ * `document` for a whole-document run (`translate`): the passage is the body
930
+ * without its frontmatter and no context is sent. Default `selection`.
931
+ */
932
+ scope?: AiRewriteScope;
933
+ }
934
+ interface AiRewriteParams {
935
+ language?: string;
936
+ audience?: AiAudience;
937
+ }
938
+ type AiRewriteScope = 'selection' | 'document';
939
+ /** Longest target language name sent (it is free text from "Other…"). */
940
+ declare const REWRITE_LANGUAGE_MAX_CHARS = 60;
941
+ declare const REWRITE_SYSTEM_PROMPT: string;
942
+ /**
943
+ * Appended to the system prompt for `translate`. It overrides the "write in the
944
+ * language of the passage" rule; the target language is named only in the
945
+ * instruction line.
946
+ */
947
+ declare const TRANSLATE_SYSTEM_ADDENDUM: string;
948
+ /** Appended to the system prompt when the instruction asks for an explanation (the `explain` variant). */
949
+ declare const EXPLAIN_SYSTEM_ADDENDUM = "The instruction asks for an explanation of the passage, not a version of it: the \"version of the passage\" rules above do not apply. Reply with the explanation only, as Markdown prose, and do not repeat the passage.";
950
+ /** The whole-document variant of the translation note. */
951
+ declare const TRANSLATE_DOCUMENT_NOTE = "The passage is a whole document body (its frontmatter has been removed and is handled elsewhere). Translate all of it, from the first line to the last.";
952
+ /** Neutralise a closing tag inside user text so it cannot end its block early. */
953
+ declare function escapeClosingTag(text: string, tag: string): string;
954
+ /** Context trimmed to the budget at the far end, so the part next to the selection survives. */
955
+ declare function trimRewriteContext(text: string, side: 'before' | 'after', max?: number): string;
956
+ /** Target language as typed, flattened to one short line (it ends up inside a prompt). */
957
+ declare function normalizeRewriteLanguage(language: string | undefined): string;
958
+ /**
959
+ * The instruction line for a variant. A parameterised variant whose parameter
960
+ * is missing or unknown falls back to `fixFlow`, like an empty `custom`.
961
+ */
962
+ declare function rewriteInstruction(input: Pick<RewritePromptInput, 'variant' | 'instruction' | 'params'>): string;
963
+ /** The system prompt for a variant: the shared rules plus the translate or explain addendum. */
964
+ declare function rewriteSystemPrompt(variant: AiRewriteVariant, scope?: AiRewriteScope): string;
965
+ declare function buildRewriteMessages(input: RewritePromptInput): AiMessage[];
966
+ /** Output budget for `explain`: a short note, independent of the passage length. */
967
+ declare const EXPLAIN_MAX_TOKENS = 600;
968
+ /** Output budget: room for "longer" to double the passage, never below a short paragraph. */
969
+ declare function rewriteMaxTokens(selection: string, variant: AiRewriteVariant): number;
970
+ /**
971
+ * The text to show while it streams: reasoning blocks (`<think>…</think>`,
972
+ * which some local models put in the content) are hidden, including one that
973
+ * has not closed yet, and an echoed `<passage>` tag is dropped.
974
+ */
975
+ declare function rewriteVisibleText(raw: string): string;
976
+ /**
977
+ * The final replacement: the visible text without a wrapping code fence or
978
+ * quotes the model added, trimmed, with the selection's own leading and
979
+ * trailing whitespace put back (a selection of "word " stays one word plus a
980
+ * space).
981
+ */
982
+ declare function finalizeRewriteText(raw: string, selection: string): string;
983
+
984
+ /**
985
+ * Explain prompt (the `explain` variant of the `rewrite` feature). Pure: builds
986
+ * the chat messages for a read-only note about the selected passage. The reply
987
+ * is shown in the popover and can be inserted below the selection; it never
988
+ * replaces anything.
989
+ */
990
+
991
+ declare const EXPLAIN_SYSTEM_PROMPT: string;
992
+ interface ExplainPromptInput {
993
+ /** The selected text as markdown. */
994
+ selection: string;
995
+ /** Markdown just before the selection. */
996
+ before?: string;
997
+ /** Markdown just after the selection. */
998
+ after?: string;
999
+ /** The document title (frontmatter `title:` or the first heading), if there is one. */
1000
+ title?: string;
1001
+ }
1002
+ declare function buildExplainMessages(input: ExplainPromptInput): AiMessage[];
1003
+ /** Output budget: a short note whatever the passage length. */
1004
+ declare function explainMaxTokens(): number;
1005
+
1006
+ /**
1007
+ * Markdown structure post-check. Pure: counts the structural pieces of a
1008
+ * passage and compares the original with a model's result, so translate and
1009
+ * tone rewrites can show a warning line before Accept when something was
1010
+ * dropped. A warning only; it never blocks Accept.
1011
+ */
1012
+ interface StructureCounts {
1013
+ /** Headings by level, index 0 is level 1. */
1014
+ headings: [number, number, number, number, number, number];
1015
+ fencedCodeBlocks: number;
1016
+ inlineCodeSpans: number;
1017
+ /** URL of every link (inline, reference definition, autolink), in order. */
1018
+ links: string[];
1019
+ /** URL of every image, in order. */
1020
+ images: string[];
1021
+ listItems: number;
1022
+ tableRows: number;
1023
+ wikilinks: number;
1024
+ footnoteReferences: number;
1025
+ }
1026
+ interface StructureMismatch {
1027
+ kind: 'heading' | 'fencedCodeBlock' | 'inlineCode' | 'link' | 'image' | 'listItem' | 'tableRow' | 'wikilink' | 'footnote';
1028
+ original: number;
1029
+ result: number;
1030
+ /** A human sentence such as "2 links in the original, 1 in the result". */
1031
+ message: string;
1032
+ }
1033
+ interface StructureDiff {
1034
+ /** True when nothing differs. */
1035
+ ok: boolean;
1036
+ mismatches: StructureMismatch[];
1037
+ /** `mismatches[].message`, ready to show. */
1038
+ messages: string[];
1039
+ }
1040
+ declare function countMarkdownStructure(markdown: string): StructureCounts;
1041
+ /**
1042
+ * Compare the structure of a rewritten passage with the original. Zero
1043
+ * mismatches means every counted piece survived; a mismatch is a sentence for
1044
+ * the warning line, not an error.
1045
+ */
1046
+ declare function checkMarkdownStructure(original: string, result: string): StructureDiff;
1047
+
1048
+ /**
1049
+ * Lenient JSON for local models (D6). Many local models have no JSON mode, so
1050
+ * a reply can arrive as clean JSON, inside a code fence, after a sentence of
1051
+ * prose, behind a `<think>` block, with a trailing comma or with single
1052
+ * quotes. `parseLenientJson` takes all of those; plain text is `ok: false`
1053
+ * with a message the repair turn can quote. Pure, no DOM.
1054
+ */
1055
+
1056
+ type LenientJsonResult<T = unknown> = {
1057
+ ok: true;
1058
+ value: T;
1059
+ } | {
1060
+ ok: false;
1061
+ error: string;
1062
+ };
1063
+ /**
1064
+ * Strip `<think>`, look inside code fences, take the first balanced `{…}` or
1065
+ * `[…]` that parses (after one repair pass for the common slips) and return
1066
+ * it. A reply with no JSON, or one cut off before it closed, is `ok: false`.
1067
+ * `T` is only the caller's claim about the shape; validate before trusting it.
1068
+ */
1069
+ declare function parseLenientJson<T = unknown>(raw: string): LenientJsonResult<T>;
1070
+ /**
1071
+ * The one-shot repair turn: append these two messages to the original ones
1072
+ * and ask again. The failed reply goes back as the assistant turn (reasoning
1073
+ * removed, capped), then the parse error.
1074
+ */
1075
+ declare function jsonRepairMessages(previousRaw: string, error: string): AiMessage[];
1076
+ /**
1077
+ * The prompt line that fixes the reply shape: a one-line example object.
1078
+ * Pass the example as a value (serialised on one line) or as a string.
1079
+ */
1080
+ declare function JSON_SHAPE_HINT(example: unknown): string;
1081
+
1082
+ /**
1083
+ * Fit a markdown document to a model's context window by whole sections, and
1084
+ * say how much of it fit (never a silent cut). Used by `/summary`, `/title`
1085
+ * and Properties suggestions. Pure, no DOM.
1086
+ *
1087
+ * The kept text is always a prefix of the document: the frontmatter and the
1088
+ * opening sections, ending on a heading boundary. If even the first section
1089
+ * is too big it is cut on a blank line between blocks. A fenced code block is
1090
+ * never split, so a fence that opens inside the kept text also closes in it.
1091
+ */
1092
+ /** Characters per token the budget assumes (a little pessimistic for English). */
1093
+ declare const CHARS_PER_TOKEN = 3.5;
1094
+ interface DocumentBudgetOptions {
1095
+ /** The model's context window in tokens. */
1096
+ contextTokens: number;
1097
+ /** Tokens the answer may use. */
1098
+ outputTokens: number;
1099
+ /** Tokens for the system prompt, instructions and slack. */
1100
+ reserveTokens: number;
1101
+ }
1102
+ interface BudgetedDocument {
1103
+ /** The markdown to send: frontmatter plus the leading sections that fit. */
1104
+ text: string;
1105
+ /** Share of the document in `text`, 0 to 1 (1 when nothing was cut). */
1106
+ coverage: number;
1107
+ truncated: boolean;
1108
+ /** Sections in `text` (a partly kept first section counts as one). */
1109
+ sectionsIncluded: number;
1110
+ sectionsTotal: number;
1111
+ }
1112
+ interface HeadingOutlineEntry {
1113
+ /** 1 to 6. */
1114
+ level: number;
1115
+ text: string;
1116
+ }
1117
+ /** Heading levels and texts, skipping frontmatter and fenced code. */
1118
+ declare function headingsOutline(markdown: string): HeadingOutlineEntry[];
1119
+ /**
1120
+ * Keep the frontmatter and as many whole leading sections as fit in
1121
+ * `contextTokens - outputTokens - reserveTokens` (at `CHARS_PER_TOKEN`
1122
+ * characters a token). A first section bigger than the budget is cut on a
1123
+ * block boundary instead, and the first block is kept whatever the budget.
1124
+ */
1125
+ declare function budgetDocument(markdown: string, opts: DocumentBudgetOptions): BudgetedDocument;
1126
+
1127
+ /**
1128
+ * Summary prompts (the `summary` feature: `/summary` and `/outline`). Pure:
1129
+ * builds the chat messages from the budgeted document and tidies the reply
1130
+ * before it is previewed and inserted.
1131
+ *
1132
+ * TL;DR is wrapped in a `[!NOTE]` callout whose first line is **Summary**
1133
+ * (D11: the GFM five are the only kinds that round-trip on GitHub). Outline
1134
+ * stays a plain nested list.
1135
+ */
1136
+
1137
+ type AiSummaryKind = 'tldr' | 'outline';
1138
+ interface SummaryPromptInput {
1139
+ /** The document as markdown, already through `budgetDocument`. */
1140
+ document: string;
1141
+ kind: AiSummaryKind;
1142
+ /** Share of the document included, 0 to 1 (below 1 means it was cut). */
1143
+ coverage: number;
1144
+ }
1145
+ /** A document shorter than this gets a one-paragraph TL;DR instead of bullets. */
1146
+ declare const SUMMARY_SHORT_DOCUMENT_CHARS = 1200;
1147
+ declare const SUMMARY_SYSTEM_PROMPT: string;
1148
+ declare const OUTLINE_SYSTEM_PROMPT: string;
1149
+ declare function coveragePercent(coverage: number): number;
1150
+ declare function buildSummaryMessages(input: SummaryPromptInput): AiMessage[];
1151
+ /** Output budget: scales with the input, within a range a small model handles. */
1152
+ declare function summaryMaxTokens(kind: AiSummaryKind, inputChars: number): number;
1153
+ /** The text to show while it streams (reasoning hidden). */
1154
+ declare function summaryVisibleText(raw: string): string;
1155
+ /**
1156
+ * The preview once the stream ends: the body `finalizeSummary` will insert,
1157
+ * without the callout wrapper and the coverage line (the popover shows those
1158
+ * apart), so the cut fragment and lead-in are gone before Insert.
1159
+ */
1160
+ declare function summaryPreviewText(raw: string, kind: AiSummaryKind, cutOff?: boolean): string;
1161
+ /**
1162
+ * The markdown to insert. TL;DR becomes a `[!NOTE]` callout headed
1163
+ * **Summary**; Outline stays the list. When the document was cut
1164
+ * (`coverage` below 1) a "Covers the first N% of the document." line closes
1165
+ * it. An empty reply stays empty so the caller can report a bad output.
1166
+ * `cutOff` (the stream stopped at the length limit) drops a trailing
1167
+ * fragment and ends the text with an ellipsis.
1168
+ */
1169
+ declare function finalizeSummary(raw: string, kind: AiSummaryKind, coverage?: number, cutOff?: boolean): string;
1170
+
1171
+ /**
1172
+ * Title suggestions (`/title` and "Suggest titles…", the `title` feature).
1173
+ * Pure: the prompt asks for a small JSON object, `parseTitles` validates it.
1174
+ * The document is the budgeted prefix plus the full headings list, so a long
1175
+ * document is still judged as a whole.
1176
+ */
1177
+
1178
+ declare const TITLE_MAX_CHARS = 120;
1179
+ /** Longest headings list sent, so a huge outline cannot eat the budget. */
1180
+ declare const TITLE_OUTLINE_MAX_ITEMS = 60;
1181
+ interface TitlePromptInput {
1182
+ /** The document as markdown, already through `budgetDocument`. */
1183
+ document: string;
1184
+ /** All headings of the full document (not only the budgeted part). */
1185
+ outline: readonly HeadingOutlineEntry[];
1186
+ /** The current title, so the model does not hand it back. */
1187
+ existingTitle?: string;
1188
+ count?: number;
1189
+ }
1190
+ declare const TITLE_SYSTEM_PROMPT: string;
1191
+ /** The headings as an indented list, capped. Shared by the title and properties prompts. */
1192
+ declare function renderHeadingOutline(outline: readonly HeadingOutlineEntry[], max?: number): string;
1193
+ declare function buildTitleMessages(input: TitlePromptInput): AiMessage[];
1194
+ /** Output budget: a few short strings. */
1195
+ declare function titleMaxTokens(count?: number): number;
1196
+ type ParsedTitles = {
1197
+ ok: true;
1198
+ titles: string[];
1199
+ } | {
1200
+ ok: false;
1201
+ error: string;
1202
+ };
1203
+ /**
1204
+ * The titles in a reply: `{ "titles": [...] }` (a bare array or a single
1205
+ * `title` string is accepted too), trimmed to 120 characters, de-duplicated
1206
+ * case-insensitively, minus the existing title. No usable title is a failure
1207
+ * so the caller can repair or report `badOutput`.
1208
+ */
1209
+ declare function parseTitles(raw: string, existingTitle?: string): ParsedTitles;
1210
+
1211
+ interface ParsedEntry {
1212
+ key: string;
1213
+ /** Scalar string, or array of strings (for `tags: [a, b]`-style). */
1214
+ value: string | string[];
1215
+ }
1216
+
1217
+ /**
1218
+ * Properties suggestions (the `frontmatter` feature: "Suggest" in the
1219
+ * sidebar's Properties header). Pure: the prompt asks for a title, a
1220
+ * description and tags as JSON; `parsePropertySuggestions` validates the
1221
+ * reply and normalises tags through the shared tag syntax, so what reaches the
1222
+ * UI is already safe to apply.
1223
+ */
1224
+
1225
+ /** A frontmatter property as the sidebar holds it. */
1226
+ type AiFrontmatterEntry = ParsedEntry;
1227
+ declare const PROPERTY_MAX_TAGS = 8;
1228
+ declare const PROPERTY_DESCRIPTION_MAX_CHARS = 200;
1229
+ declare const PROPERTY_TITLE_MAX_CHARS = 120;
1230
+ /** Vocabulary tags listed in the prompt at most. */
1231
+ declare const PROPERTY_VOCABULARY_MAX = 80;
1232
+ interface PropertiesPromptInput {
1233
+ /** The document as markdown, already through `budgetDocument`. */
1234
+ document: string;
1235
+ /** All headings of the full document. */
1236
+ outline: readonly HeadingOutlineEntry[];
1237
+ /** The properties the document already has. */
1238
+ existing: readonly AiFrontmatterEntry[];
1239
+ /** Tags used elsewhere in the workspace, most used first. */
1240
+ vocabulary: readonly string[];
1241
+ }
1242
+ declare const PROPERTIES_SYSTEM_PROMPT: string;
1243
+ /** The tags the entries already carry, normalised (`tags:` or `tag:`, array or comma list). */
1244
+ declare function existingTagsOf(existing: readonly AiFrontmatterEntry[]): string[];
1245
+ declare function buildPropertiesMessages(input: PropertiesPromptInput): AiMessage[];
1246
+ /** Output budget: three short fields. */
1247
+ declare function propertiesMaxTokens(): number;
1248
+ interface SuggestedTag {
1249
+ /** Normalised: lowercase, no `#`. */
1250
+ tag: string;
1251
+ /** Already used somewhere in the workspace (it is in the vocabulary). */
1252
+ known: boolean;
1253
+ }
1254
+ interface PropertySuggestions {
1255
+ title?: string;
1256
+ description?: string;
1257
+ tags: SuggestedTag[];
1258
+ }
1259
+ type ParsedPropertySuggestions = {
1260
+ ok: true;
1261
+ suggestions: PropertySuggestions;
1262
+ } | {
1263
+ ok: false;
1264
+ error: string;
1265
+ };
1266
+ /**
1267
+ * Validate a reply: title and description trimmed to length and dropped when
1268
+ * empty or equal to what the document has, tags normalised, marked `known`
1269
+ * from the vocabulary, minus tags already present, de-duplicated and capped at
1270
+ * 8. A reply with no title, description or tag at all is a failure; one whose
1271
+ * suggestions are all already present succeeds with nothing left in it.
1272
+ */
1273
+ declare function parsePropertySuggestions(raw: string, existing: readonly AiFrontmatterEntry[], vocabulary: readonly string[]): ParsedPropertySuggestions;
1274
+
1275
+ /**
1276
+ * Privacy copy for cloud engines: the four lines every surface shows (the
1277
+ * privacy card, the disclaimer, the consent dialogs), what each feature
1278
+ * sends, each provider's policy link, and the consent dialog text both hosts
1279
+ * render (VS Code's modal, Desktop's native dialog; Desktop has a Rust port
1280
+ * checked against `test/ai-fixtures/consent/`).
1281
+ *
1282
+ * No claims about any provider's training or retention: those are set by the
1283
+ * provider's terms and change. The copy says "may" and points at the policy.
1284
+ */
1285
+
1286
+ /** The four lines of the privacy card, in display order. */
1287
+ declare const AI_CLOUD_PRIVACY: {
1288
+ readonly leaves: "Text you ask about is sent over the internet to the provider and processed on their servers.";
1289
+ readonly terms: string;
1290
+ readonly local: "A local engine keeps everything on this machine or your network.";
1291
+ readonly keys: "Your API key stays in this app's secure storage on this machine.";
1292
+ };
1293
+ /**
1294
+ * The privacy card's lines for an engine built into the host (VS Code's
1295
+ * Copilot models, `vscode-lm`): no API key, and the text goes through the
1296
+ * host to GitHub's Copilot service under the user's Copilot plan. No claim
1297
+ * about how a plan handles it; the policy link says.
1298
+ */
1299
+ declare const AI_HOST_PRIVACY: {
1300
+ readonly leaves: "Text you ask about goes through VS Code to GitHub’s Copilot service and is processed on GitHub’s servers, under your Copilot plan.";
1301
+ readonly terms: "Whether it is retained or used for training is set by your plan’s terms, which differ between individual and business plans and may change; read them before you decide.";
1302
+ readonly local: "A local engine keeps everything on this machine or your network.";
1303
+ readonly permission: "VS Code also asks for its own permission the first time MikeDown uses Copilot.";
1304
+ };
1305
+ /**
1306
+ * The sentence about the consent step, for the cloud privacy card: whether
1307
+ * the host asks before a cloud engine's first request (`hostAsks`: it has
1308
+ * `requestCloudConsent`) or sends without asking.
1309
+ */
1310
+ declare function aiConsentStepNote(hostAsks: boolean): string;
1311
+ /** What a request of each scope sends, as the object of "sends …". */
1312
+ declare const AI_SENDS_TEXT: Readonly<Record<AiSendsScope, string>>;
1313
+ /** For a request whose feature is unknown. */
1314
+ declare const AI_SENDS_FALLBACK = "the text you ask the AI about";
1315
+ /**
1316
+ * What a request sends, as the object of a sentence ("MikeDown sends …").
1317
+ * `sends` wins (whole-document Translate runs as the `rewrite` feature);
1318
+ * else the feature's own scope; else a generic phrase.
1319
+ */
1320
+ declare function whatIsSent(feature?: string, sends?: AiSendsScope): string;
1321
+ /** The policy of the cloud preset on `host` (an engine's base URL host), if one matches. */
1322
+ declare function aiProviderPolicyForHost(host: string | null | undefined, presets?: readonly AiEnginePreset[]): AiProviderPolicy | undefined;
1323
+ /**
1324
+ * The policy for an engine, matched by its base URL's host (engines do not
1325
+ * store a preset); a host kind with no address (`vscode-lm`) by its kind.
1326
+ */
1327
+ declare function aiProviderPolicy(engine: {
1328
+ baseUrl: string;
1329
+ kind?: string;
1330
+ }, presets?: readonly AiEnginePreset[]): AiProviderPolicy | undefined;
1331
+ /**
1332
+ * Where an engine sends text, as the consent dialog's `host`: the base URL's
1333
+ * host ("api.anthropic.com"), the URL itself when it has none, or the
1334
+ * service's name for a host kind ("GitHub Copilot" for `vscode-lm`).
1335
+ */
1336
+ declare function aiConsentHost(engine: {
1337
+ baseUrl: string;
1338
+ kind?: string;
1339
+ }): string;
1340
+ /** Where a consent answer is remembered: VS Code says workspace, Desktop folder (or session without one). */
1341
+ type AiConsentScopeWord = 'workspace' | 'folder' | 'session';
1342
+ interface AiConsentTextInput {
1343
+ engineName: string;
1344
+ /** The engine's kind; a host kind (`vscode-lm`) reads as a service built into VS Code, not an address. */
1345
+ kind?: string;
1346
+ /** The engine's host ("api.anthropic.com"), or its base URL when it has none. */
1347
+ host: string;
1348
+ feature?: string;
1349
+ sends?: AiSendsScope;
1350
+ scopeWord: AiConsentScopeWord;
1351
+ /** The provider's policy (see `aiProviderPolicyForHost`). */
1352
+ policy?: AiProviderPolicy | null;
1353
+ /**
1354
+ * Spell the policy link out in the body, for a dialog that can neither
1355
+ * link nor spare a button for it (Desktop's native dialog). VS Code's modal
1356
+ * has a "Read the provider's policy" button instead.
1357
+ */
1358
+ policyLine?: boolean;
1359
+ }
1360
+ interface AiConsentText {
1361
+ title: string;
1362
+ /** Paragraphs separated by a blank line. */
1363
+ body: string;
1364
+ /** Remembered for the workspace or folder (for the session without one). */
1365
+ allow: string;
1366
+ /** This request only; nothing is remembered and the next request asks again. */
1367
+ allowOnce: string;
1368
+ /** Declines; the host does not ask about this engine again until it restarts. */
1369
+ deny: string;
1370
+ /** The policy button's label, when there is a policy and the body does not spell it out. */
1371
+ readPolicy?: string;
1372
+ }
1373
+ /** The consent dialog's words, the single source for both hosts. */
1374
+ declare function aiConsentText(input: AiConsentTextInput): AiConsentText;
1375
+ /** Shown in the Cloud consents list when there is nothing stored. */
1376
+ declare const AI_NO_CONSENTS_TEXT = "No cloud engine has been allowed here.";
1377
+ /**
1378
+ * One line of the Cloud consents list, without the Forget button:
1379
+ * "Anthropic · this workspace · Allowed". A stored "never" (older versions)
1380
+ * reads "Not allowed"; this session's "Don't allow", "Not allowed until restart".
1381
+ */
1382
+ declare function aiConsentSummary(record: Pick<AiConsentRecord, 'answer' | 'scope'>, engineName: string): string;
1383
+
1384
+ /**
1385
+ * The setup wizard's first decision, as plain data: which path to offer
1386
+ * first (a running local server, VS Code's Copilot models, or a cloud
1387
+ * provider), whether this machine can run models locally, and how big a
1388
+ * model fits (OQ11, OQ13). No DOM, no network: the host reports the
1389
+ * hardware (`hardwareInfo`), the running servers (`detectLocalServers`) and
1390
+ * its built-in models (`detectHostEngines`).
1391
+ *
1392
+ * Every size here is a hint for a 4-bit quantised model, never a gate: the
1393
+ * user can pick any model.
1394
+ */
1395
+
1396
+ /** OQ11: Apple Silicon with this much unified memory can run local models. */
1397
+ declare const AI_LOCAL_MIN_UNIFIED_GB = 16;
1398
+ /** OQ11: any machine with this much memory can. */
1399
+ declare const AI_LOCAL_MIN_MEMORY_GB = 32;
1400
+ /** OQ11: or an NVIDIA GPU with this much video memory (Desktop only, where it can be read). */
1401
+ declare const AI_LOCAL_MIN_VRAM_GB = 8;
1402
+ type AiSetupPath = 'runningLocal' | 'vscodeLm' | 'cloud';
1403
+ interface AiSetupInput {
1404
+ /** `detectLocalServers` results (every candidate, running or not). */
1405
+ running: readonly AiLocalServerProbe[];
1406
+ /** `hardwareInfo()`, or null when the host cannot tell. */
1407
+ hardware: AiHardwareInfo | null;
1408
+ /** VS Code only: whether `vscode.lm` offers a Copilot model (see `vscodeLmAvailability`). */
1409
+ vscodeLm?: {
1410
+ available: boolean;
1411
+ models: number;
1412
+ };
1413
+ }
1414
+ interface AiSetupRecommendation {
1415
+ /** What the wizard offers first. */
1416
+ path: AiSetupPath;
1417
+ /** OQ11: this machine is above the threshold, so local is shown as an option (still under Advanced unless a server runs). */
1418
+ localCapable: boolean;
1419
+ /** The model-size sentence for this machine (OQ11); empty without hardware info. */
1420
+ localHint: string;
1421
+ /**
1422
+ * Step 1's hardware line: "Local is an option on this machine: Apple
1423
+ * Silicon, 32 GB. Models up to about 30B parameters run well." when
1424
+ * capable, the small-machine sentence when not, empty without hardware
1425
+ * info.
1426
+ */
1427
+ hardwareLine: string;
1428
+ /** `runningLocal`: the server to offer (most models first, then preset order). */
1429
+ running?: AiLocalServerProbe;
1430
+ /** Why, one short sentence per signal checked, in order (for tooltips, logs and tests). */
1431
+ reasons: string[];
1432
+ }
1433
+ /** Bytes to gigabytes, for display ("32 GB"). */
1434
+ declare function formatMemoryGb(bytes: number): string;
1435
+ /** OQ11's threshold: Apple Silicon with 16 GB or more, any machine with 32 GB or more, or an NVIDIA GPU with 8 GB of VRAM or more. */
1436
+ declare function isLocalCapable(hw: AiHardwareInfo | null): boolean;
1437
+ /**
1438
+ * The memory a local model runs in: unified memory on Apple Silicon, an
1439
+ * NVIDIA card's VRAM when the host read it, else system memory (CPU
1440
+ * inference: slower, but it runs).
1441
+ */
1442
+ declare function modelMemoryBytes(hw: AiHardwareInfo): number;
1443
+ /** OQ11's model-size sentence for a machine ("Models up to about 30B parameters run well."); empty for null. */
1444
+ declare function localSizeHint(hw: AiHardwareInfo | null): string;
1445
+ /** "Apple Silicon, 32 GB", "64 GB of memory, NVIDIA GeForce RTX 4090 (24 GB)", "16 GB of memory". */
1446
+ declare function describeHardware(hw: AiHardwareInfo): string;
1447
+ /** Shown on a machine below the OQ11 threshold. */
1448
+ declare const AI_LOCAL_SMALL_MACHINE = "This machine is on the small side for local models; cloud is the easier path.";
1449
+ /** Added when the host read no GPU (VS Code reads none): the hint comes from memory alone. */
1450
+ declare const AI_LOCAL_ROUGH_GUESS = "A rough guess from memory alone; the graphics card was not checked.";
1451
+ /**
1452
+ * The wizard's path, checked in order (OQ13):
1453
+ *
1454
+ * a. a local server is running and lists models → offer it first;
1455
+ * b. VS Code offers a Copilot model (`vscode-lm`, OQ12) → offer it first;
1456
+ * c. this machine is above the OQ11 threshold → cloud first, with "Local is
1457
+ * an option on this machine" shown;
1458
+ * d. otherwise cloud, with local under Advanced and no hint.
1459
+ *
1460
+ * `localCapable`, `localHint` and `hardwareLine` are filled whatever the path.
1461
+ */
1462
+ declare function recommendSetupPath(input: AiSetupInput): AiSetupRecommendation;
1463
+ /** The recommendation's `vscodeLm` input from `detectHostEngines` results. */
1464
+ declare function vscodeLmAvailability(probes: readonly AiHostEngineProbe[] | null | undefined): {
1465
+ available: boolean;
1466
+ models: number;
1467
+ };
1468
+ /**
1469
+ * A model's parameter count in billions, from its id: `7b`, `30b`, `1.5b`,
1470
+ * `270m`, a mixture `8x7b` (total). The first count wins, so `30b-a3b`
1471
+ * (active parameters) reads 30. Null when the id names none.
1472
+ */
1473
+ declare function modelParamsBillions(id: string): number | null;
1474
+ type AiModelSizeTag = 'fits' | 'tight' | 'too large';
1475
+ /**
1476
+ * "fits", "tight" or "too large" for a model on this machine: from the size
1477
+ * the server reported when there is one (`modelSizeHintFromBytes`), else the
1478
+ * parameter count in its id (OQ11's bands, 4-bit). Undefined when neither is
1479
+ * known or the hardware is unknown: no tag. A hint only; nothing is blocked.
1480
+ */
1481
+ declare function modelSizeHint(id: string, hardware: AiHardwareInfo | null, sizeBytes?: number): AiModelSizeTag | undefined;
1482
+ /**
1483
+ * "fits", "tight" or "too large" from a size the server reported (bytes on
1484
+ * disk or its load estimate), against this machine's model memory.
1485
+ */
1486
+ declare function modelSizeHintFromBytes(sizeBytes: number, hardware: AiHardwareInfo): AiModelSizeTag;
1487
+ /** A model size for a picker row: "16 GB", "2.6 GB", "0.2 GB". */
1488
+ declare function formatModelSize(bytes: number): string;
1489
+ /**
1490
+ * The active parameters (billions) an id names, as mixture-of-experts models
1491
+ * do: `Qwen3.6-35B-A3B`, `qwen3.5-122b-a10b`, `Nemotron-3-Super-120B-A12B`.
1492
+ * Null when the id names none.
1493
+ */
1494
+ declare function modelActiveParamsBillions(id: string): number | null;
1495
+ /**
1496
+ * Whether a model is a mixture of experts, from its id's active parameters or
1497
+ * the server's word for it (`moe`: LM Studio's `arch`, oMLX's
1498
+ * `config_model_type`). Only a few billion parameters run per token, so it is
1499
+ * much faster than its size suggests. A row hint only, never a reason to pick it.
1500
+ */
1501
+ declare function modelMoeHint(id: string, moe?: boolean): {
1502
+ activeBillions: number | null;
1503
+ } | null;
1504
+ /**
1505
+ * The picker's "MoE · fast" hint: a mixture of experts that also fits this
1506
+ * machine (`fits`). A tight fit (a 409 GB model on a 512 GB Mac) or one too
1507
+ * large runs slowly however few parameters work per token, and an unknown fit
1508
+ * proves nothing, so neither gets the speed hint (Q5).
1509
+ */
1510
+ declare function modelMoeFastHint(id: string, moe: boolean | undefined, size: AiModelSizeTag | undefined): {
1511
+ activeBillions: number | null;
1512
+ } | null;
1513
+ /** One model as `localDefaultModel` weighs it. */
1514
+ interface AiLocalModelChoice {
1515
+ id: string;
1516
+ /** Can chat (an `llm` or `vlm`; not embeddings, speech or a converter). */
1517
+ chat: boolean;
1518
+ missing?: boolean;
1519
+ /** Loaded on the server right now. */
1520
+ loaded?: boolean;
1521
+ }
1522
+ /**
1523
+ * The model a new local engine starts with: a chat model the server has
1524
+ * loaded, else null and the user picks. Size is a poor guide to speed (a
1525
+ * mixture of experts runs far faster than a dense model its size), so no
1526
+ * guess is made from it.
1527
+ */
1528
+ declare function localDefaultModel(models: readonly AiLocalModelChoice[]): string | null;
1529
+
1530
+ export { AI_AUDIENCES, AI_CHARS_PER_TOKEN, AI_CLOUD_PRIVACY, AI_ENGINE_PRESETS, AI_ERROR_COPY, AI_FEATURES, AI_HOST_PRESET_FAMILY, AI_HOST_PRIVACY, AI_LOCAL_DETECT_TIMEOUT_MS, AI_LOCAL_MIN_MEMORY_GB, AI_LOCAL_MIN_UNIFIED_GB, AI_LOCAL_MIN_VRAM_GB, AI_LOCAL_ROUGH_GUESS, AI_LOCAL_SMALL_MACHINE, AI_NO_CONSENTS_TEXT, AI_PRESET_FAMILIES, AI_REWRITE_VARIANTS, AI_SENDS_FALLBACK, AI_SENDS_TEXT, AI_TASKS, ANTHROPIC_API_VERSION, ANTHROPIC_DEFAULT_MAX_TOKENS, ANTHROPIC_MODEL_TABLE, AiApiKeyFile, AiCapabilityFlag, AiCompletionRequest, AiCompletionResult, AiConsentRecord, AiDiscoveredModel, AiEngineConfig, AiEngineKind, AiEngineStatus, AiError, AiErrorCode, AiFeatureSettings, AiFinishReason, AiHardwareInfo, AiHostEngineProbe, AiLocalServerProbe, AiLocality, AiMessage, AiModelCapabilities, AiModelConfig, AiSendsScope, AiSettings, AiUsage, CHARS_PER_TOKEN, DEFAULT_LOCAL_NETWORKS, EXPLAIN_MAX_TOKENS, EXPLAIN_SYSTEM_ADDENDUM, EXPLAIN_SYSTEM_PROMPT, JSON_ONLY_INSTRUCTION, JSON_SHAPE_HINT, MODEL_LOAD_FAILED_PHRASES, OPENAI_MODEL_TABLE, OUTLINE_SYSTEM_PROMPT, PROPERTIES_SYSTEM_PROMPT, PROPERTY_DESCRIPTION_MAX_CHARS, PROPERTY_MAX_TAGS, PROPERTY_TITLE_MAX_CHARS, PROPERTY_VOCABULARY_MAX, RETRYABLE_REQUEST_PARAMS, REWRITE_CONTEXT_CHARS, REWRITE_INSTRUCTION_MAX_CHARS, REWRITE_LANGUAGE_MAX_CHARS, REWRITE_SYSTEM_PROMPT, STREAM_USAGE_HOSTS, SUMMARIZE_MIN_CONTEXT_TOKENS, SUMMARY_SHORT_DOCUMENT_CHARS, SUMMARY_SYSTEM_PROMPT, SseParser, THINKING_DEFAULT_HEADROOM_TOKENS, THINKING_HEADROOM_TOKENS, THINKING_MAX_TOKENS_CAP, TITLE_MAX_CHARS, TITLE_OUTLINE_MAX_ITEMS, TITLE_SYSTEM_PROMPT, TRANSLATE_DOCUMENT_NOTE, TRANSLATE_SYSTEM_ADDENDUM, aiConsentHost, aiConsentStepNote, aiConsentSummary, aiConsentText, aiErrorCodeFromHttp, aiErrorCopy, aiLengthLimitCopy, aiProviderPolicy, aiProviderPolicyForHost, aiResultStats, aiResultStatsText, aiResultStatsTitle, aiTimingLogParts, anthropicNextPage, base64ToBytes, budgetDocument, buildAuthHeaders, buildCompletionBody, buildEmbeddingsBody, buildExplainMessages, buildPropertiesMessages, buildRewriteMessages, buildSummaryMessages, buildTitleMessages, bytesToBase64, checkMarkdownStructure, combineRequirements, completeCapabilities, countDetectedModels, countMarkdownStructure, coveragePercent, createAnthropicStreamReader, createCompletionStreamReader, createOpenAiStreamReader, defaultAiSettings, describeHardware, detectCapabilities, detectLocality, engineEndpoints, engineLocality, escapeClosingTag, existingTagsOf, explainMaxTokens, finalizeRewriteText, finalizeSummary, formatAiSeconds, formatAiTokensPerSecond, formatMemoryGb, formatModelSize, fromWireCompletionRequest, getAiAudience, getAiFeature, getAiRewriteVariant, getAiTask, headingsOutline, hostOfUrl, isAiError, isAiErrorCode, isLocalCapable, isOpenAiReasoningModel, isRouteUsable, jsonRepairMessages, localDefaultModel, localDetectCandidates, localDetectTarget, localDetectUrl, localSizeHint, localityMismatch, makeAiError, mergeDiscoveredModels, mergeRequestOverrides, missingCapabilities, modelActiveParamsBillions, modelMemoryBytes, modelMoeFastHint, modelMoeHint, modelParamsBillions, modelSizeHint, modelSizeHintFromBytes, nearestThinkingLevel, normalizeRewriteLanguage, openAiOffReasoningEffort, parseAnthropicModels, parseEmbeddingsResponse, parseLenientJson, parseLmStudioModels, parseModelSizes, parseOllamaRunning, parseOllamaShow, parseOllamaTags, parseOmlxModelStatus, parseOpenAiModelList, parsePropertySuggestions, parseTitles, presetForEngine, propertiesMaxTokens, recommendSetupPath, renderHeadingOutline, resolveAiFeature, resolveRoute, resolveThinkingLevel, rewriteInstruction, rewriteMaxTokens, rewriteSystemPrompt, rewriteVisibleText, streamUsageSupported, summaryMaxTokens, summaryPreviewText, summaryVisibleText, taskThinkingDefault, thinkingChoices, thinkingLevelFrom, thinkingMaxTokens, thinkingOverrides, titleMaxTokens, toAiError, toWireCompletionRequest, trimRewriteContext, unsupportedRequestParam, vscodeLmAvailability, whatIsSent, withModelSizes, withModelStatus, withStreamUsage, withThinkingLevel, withoutRequestParam };
1531
+ export type { AiAudience, AiAudienceDefinition, AiCapabilityRequirements, AiConsentScopeWord, AiConsentState, AiConsentText, AiConsentTextInput, AiEngineEndpoints, AiEnginePreset, AiErrorCopy, AiFeatureDefinition, AiFrontmatterEntry, AiLocalDetectTarget, AiLocalModelChoice, AiModelSizeTag, AiPopularRule, AiPresetFamily, AiPresetFamilyInfo, AiProviderPolicy, AiResultStats, AiRewriteGroup, AiRewriteOutput, AiRewriteParams, AiRewriteScope, AiRewriteVariant, AiRewriteVariantDefinition, AiRouteOk, AiRouteResolution, AiRouteUnusable, AiSetupInput, AiSetupPath, AiSetupRecommendation, AiSummaryKind, AiTaskDefinition, AiThinkingChoice, AiThinkingJob, AiThinkingLevel, AiThinkingOptions, AiUnusableCode, AiWireCompletionRequest, AiWireMessage, AiWireMessagePart, BudgetedDocument, BuildCompletionOptions, CompletionStreamReader, DetectedCapabilities, DocumentBudgetOptions, ExplainPromptInput, HeadingOutlineEntry, LenientJsonResult, ParsedPropertySuggestions, ParsedTitles, PropertiesPromptInput, PropertySuggestions, ResolveRouteOptions, ResolvedAiFeature, ResolvedThinkingLevel, RewritePromptInput, SseEvent, StreamOutcome, StructureCounts, StructureDiff, StructureMismatch, SuggestedTag, SummaryPromptInput, ThinkingBudgetOptions, TitlePromptInput };