theorum 0.1.10 → 0.1.13

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 (40) hide show
  1. package/README.md +25 -2
  2. package/docs/COMPACTION.md +227 -0
  3. package/docs/SECRETS.md +6 -1
  4. package/docs/STOP.md +85 -0
  5. package/esm/mod.d.ts +6 -2
  6. package/esm/mod.js +3 -1
  7. package/esm/src/cli/commands/bench.js +2 -4
  8. package/esm/src/cli/commands/fuzz-guardrails.js +195 -48
  9. package/esm/src/guardrails/injection.js +13 -8
  10. package/esm/src/guardrails/normalize.js +65 -38
  11. package/esm/src/guardrails/sensitive.js +1 -1
  12. package/esm/src/kernel/engine/compaction.d.ts +69 -0
  13. package/esm/src/kernel/engine/compaction.js +141 -0
  14. package/esm/src/kernel/engine/delta.js +30 -7
  15. package/esm/src/kernel/engine/history-tokens.d.ts +43 -0
  16. package/esm/src/kernel/engine/history-tokens.js +100 -0
  17. package/esm/src/kernel/engine/runner/mod.js +164 -62
  18. package/esm/src/kernel/engine/runner/state.d.ts +3 -1
  19. package/esm/src/kernel/engine/runner/steps.js +3 -0
  20. package/esm/src/kernel/mod.d.ts +4 -0
  21. package/esm/src/kernel/mod.js +2 -0
  22. package/esm/src/kernel/registry/profiles.js +37 -0
  23. package/esm/src/kernel/stop.d.ts +75 -0
  24. package/esm/src/kernel/stop.js +120 -0
  25. package/esm/src/kernel/types.d.ts +117 -1
  26. package/esm/src/providers/create-provider.d.ts +7 -0
  27. package/esm/src/providers/create-provider.js +24 -4
  28. package/esm/src/providers/expose-for-tests.js +5 -1
  29. package/esm/src/providers/local.d.ts +29 -0
  30. package/esm/src/providers/local.js +259 -0
  31. package/esm/src/providers/mod.d.ts +2 -0
  32. package/esm/src/providers/mod.js +1 -0
  33. package/esm/src/providers/openrouter.js +32 -13
  34. package/esm/src/providers/provider.js +1 -1
  35. package/esm/src/providers/speech.js +1 -1
  36. package/esm/src/streaming/mod.d.ts +3 -1
  37. package/esm/src/streaming/mod.js +2 -1
  38. package/package.json +8 -3
  39. package/docs/AGENT_PROFILE_CONTRACT.md +0 -189
  40. package/docs/CLI_SPEC.md +0 -183
package/README.md CHANGED
@@ -288,6 +288,8 @@ import { createProvider, runTurn } from "jsr:@theorum/core";
288
288
  const provider = createProvider(profile, {
289
289
  gemini: { vault: hostGeminiKeyVault, fetch },
290
290
  openRouter: { apiKey: hostSecrets.openRouterApiKey },
291
+ // openAi + local — optional; default baseUrl http://127.0.0.1:11434
292
+ local: { baseUrl: hostResolvedLocalBaseUrl },
291
293
  });
292
294
 
293
295
  for await (const event of runTurn({ profile: profile.id, input: { text: "…" } }, provider)) {
@@ -302,10 +304,21 @@ for await (const event of runTurn({ profile: profile.id, input: { text: "…" }
302
304
  | `geminiInteractions` + `google` | Google Interactions (chat, image, speech) |
303
305
  | `openAi` + `openrouter` (chat) | OpenRouter chat completions |
304
306
  | `openAi` + `openrouter` (speech role) | OpenRouter `/audio/speech` |
307
+ | `openAi` + `local` | Local OpenAI-compatible `/v1/chat/completions` (Ollama, llama.cpp, vLLM, LM Studio, …) |
305
308
 
306
- OpenRouter uses Vercel AI SDK Core inside THEORUM's provider adapter. The adapter still emits THEORUM `TurnEvent` values and preserves raw provider evidence for citations/provenance where the normalized SDK stream does not expose enough detail.
309
+ Local adapters take an optional `baseUrl` (default `http://127.0.0.1:11434`). THEORUM does not read `OLLAMA_HOST`; hosts that honor that env should resolve it and pass `local.baseUrl`. History `parts` (including images) are mapped on the wire; `done` events include a normalized `stop` from the OpenAI `finish_reason`.
307
310
 
308
- Advanced OpenRouter exports live under `theorum/openrouter` (`createOpenRouterProvider`, `toOpenRouterPayload`, …). Prefer `createProvider` for turns unless the host needs to wire the OpenRouter adapter directly.
311
+ OpenRouter uses Vercel AI SDK Core inside THEORUM's provider adapter. That stack
312
+ loads **lazily on the first `complete` call** for `openAi` + `openrouter` chat —
313
+ not when importing THEORUM, and not for Google or local providers. The adapter
314
+ still emits THEORUM `TurnEvent` values and preserves raw provider evidence for
315
+ citations/provenance where the normalized SDK stream does not expose enough detail.
316
+
317
+ Advanced OpenRouter exports live under `theorum/openrouter` (`createOpenRouterProvider`,
318
+ `toOpenRouterPayload`, …). Prefer `createProvider` for turns unless the host needs
319
+ to wire the OpenRouter adapter directly. Direct local construction is also available
320
+ as `createLocalProvider` from the main / providers entrypoints. Importing
321
+ `theorum/openrouter` loads the Vercel SDK immediately.
309
322
 
310
323
  ---
311
324
 
@@ -328,6 +341,16 @@ Internal files remain present in source for maintainability, but package consume
328
341
 
329
342
  ---
330
343
 
344
+ ## Documentation
345
+
346
+ | Doc | Topic |
347
+ | :--- | :--- |
348
+ | [`docs/SECRETS.md`](docs/SECRETS.md) | Host-owned credentials; `createProvider` args; no env reads |
349
+ | [`docs/COMPACTION.md`](docs/COMPACTION.md) | History / input meters, lazy BPE, `trigger`, signals |
350
+ | [`docs/STOP.md`](docs/STOP.md) | Normalized `done.stop`, resume policy, `continueFrom` |
351
+
352
+ ---
353
+
331
354
  ## Development
332
355
 
333
356
  ```bash
@@ -0,0 +1,227 @@
1
+ # Compaction
2
+
3
+ Optional, profile-driven compaction for managing conversation history when it
4
+ approaches a model's context window limit.
5
+
6
+ The kernel owns the trigger, the split logic, and the timing. The host (or the
7
+ kernel itself, for the easy path) owns the execution and reassembly.
8
+
9
+ ## Why
10
+
11
+ Long-running conversations accumulate history that eventually exceeds the
12
+ model's context window. Compaction types the trigger and split into a config
13
+ block; the host still owns how summaries are shaped and persisted.
14
+
15
+ ## Configuration
16
+
17
+ Compaction is configured per model in a profile's `model.config`:
18
+
19
+ ```ts
20
+ const speakerModel: ModelSpec = {
21
+ apiId: "gemini-3.5-flash",
22
+ // ...existing model config...
23
+ compaction: {
24
+ maxTokens: 2000, // budget for the chosen meter
25
+ compactAt: 0.75, // fire when 75% full
26
+ previousExchanges: 8, // keep last 8 exchanges verbatim
27
+ profile: "my.compactor", // profile that does the compacting
28
+ timing: "after", // 'before' or 'after' the turn
29
+ meter: "history", // default; or "input"
30
+ },
31
+ };
32
+ ```
33
+
34
+ ### `meter`
35
+
36
+ What the threshold compares:
37
+
38
+ | Value | Meaning |
39
+ | --- | --- |
40
+ | `'history'` (default) | Conversational history only. Host `input.historyTokens`, else local estimate of `input.history`. Excludes system, tool schemas, and this-turn attachments. |
41
+ | `'input'` | Full-prompt provider input tokens. For `timing: 'before'`, pass previous turn's usage as `input.inputTokens`. For `timing: 'after'`, the kernel uses this turn's `tokens.input`. |
42
+
43
+ Provider `tokens` events always stay on the stream for billing / observability.
44
+ With `meter: 'history'` they do **not** gate compaction. With `meter: 'input'`
45
+ they (or host-threaded `inputTokens`) **do**.
46
+
47
+ ### `maxTokens`
48
+
49
+ Budget compared as `tokens > compactAt * maxTokens`.
50
+
51
+ - With `meter: 'history'`: set from the model's context window minus known fixed
52
+ costs (system, tools, output headroom).
53
+ - With `meter: 'input'`: set as a full-prompt ceiling (or baseline + headroom).
54
+ Small history-style budgets (e.g. 2000) will fire often if system + tools are
55
+ large — that is expected for this meter.
56
+
57
+ ### How `meter: 'history'` counts
58
+
59
+ 1. If the host sets `input.historyTokens`, that value is used.
60
+ 2. Otherwise the kernel estimates from `input.history` (or `[]`):
61
+ - **Text** — tiktoken `o200k_base` (via `gpt-tokenizer`) over `content`,
62
+ text `parts`, and `tool_calls` arguments. Declared local BPE; Gemini has
63
+ no open JS tokenizer — pass `historyTokens` for Gemini `countTokens`.
64
+ The BPE ranks load **lazily on first text estimate** — not when the package
65
+ is imported. `meter: 'input'`, host `historyTokens`, empty history, and
66
+ media-only estimates never load the tokenizer.
67
+ - **Media parts** — minimum stubs when size/duration are unknown (not
68
+ payload bytes). Image/document: 258 (one still-image / page unit).
69
+ Audio: 32 (1s @ 32/s). Video: 263 (1s @ 263/s). Larger Gemini 2.x images
70
+ are 258×tiles; Gemini 3 uses `media_resolution` budgets (often 560–1120).
71
+ Prefer `historyTokens` when the host knows better.
72
+ - Current-turn `attachments` / `voice` are **not** history.
73
+
74
+ The same history estimator is used by `splitForCompaction` for fractional
75
+ `previousExchanges` (integer / `0` splits do not need the tokenizer). Empty or
76
+ missing history is **0** (does not fire).
77
+
78
+ ### `compactAt`
79
+
80
+ Fraction of `maxTokens` at which compaction fires. Must be in (0, 1).
81
+
82
+ ### `previousExchanges`
83
+
84
+ How many recent exchanges to preserve verbatim after compaction. An exchange
85
+ starts at each user message and includes all subsequent messages (assistant
86
+ replies, tool calls, tool results) until the next user message.
87
+
88
+ - **`>= 1` (integer)** — keep that many recent exchanges.
89
+ - **`(0, 1)` (fraction)** — retain exchanges that fit within this fraction of
90
+ `maxTokens` (history estimate), walking backwards. Must be less than
91
+ `compactAt`.
92
+ - **`0`** — compact everything; no tail is retained.
93
+
94
+ ### `profile`
95
+
96
+ Profile id of the compaction agent. Must be registered before the owning
97
+ profile.
98
+
99
+ ### `timing`
100
+
101
+ - **`'before'`** — compact synchronously before the turn.
102
+ - **`'after'`** — signal on the `done` event; host runs compaction async.
103
+
104
+ ### `trigger`
105
+
106
+ Optional custom gate. When set, it **replaces** the default
107
+ `tokens > compactAt * maxTokens` check. The kernel still resolves `{ meter,
108
+ tokens }` first and passes them as `CompactionTriggerContext` so the host can
109
+ combine token pressure with other signals (e.g. free RAM):
110
+
111
+ ```ts
112
+ compaction: {
113
+ maxTokens: 2000,
114
+ compactAt: 0.75,
115
+ previousExchanges: 8,
116
+ profile: "my.compactor",
117
+ timing: "after",
118
+ trigger: (ctx) =>
119
+ ctx.tokens > ctx.compactAt * ctx.maxTokens || hostRamPressure(),
120
+ },
121
+ ```
122
+
123
+ Sync and async triggers are both accepted. Omit `trigger` to keep the default
124
+ threshold.
125
+
126
+ ## The compaction profile
127
+
128
+ A compaction profile is a standard THEORUM profile. A simple summarizer:
129
+
130
+ ```ts
131
+ registerProfile(
132
+ defineProfile({
133
+ id: "my.compactor",
134
+ identity: {
135
+ handle: "Compactor",
136
+ system: "Summarize this conversation concisely. Preserve unresolved "
137
+ + "issues, decisions made, and key facts. Drop greetings and filler.",
138
+ },
139
+ model: {
140
+ ...modelAllow("gemini35FlashLite"),
141
+ thinking: "minimal",
142
+ maxSteps: 1,
143
+ },
144
+ tools: { allow: [] },
145
+ inputs: { text: true },
146
+ outputs: { structured: "my.summary.schema" },
147
+ guardrails: {
148
+ canary: false,
149
+ sanitizeInput: false,
150
+ redactSensitive: false,
151
+ },
152
+ }),
153
+ );
154
+ ```
155
+
156
+ ## Usage: `meter: 'history'` (default)
157
+
158
+ ```ts
159
+ const nextReq: TurnRequest = {
160
+ profile: "my.agent",
161
+ input: {
162
+ text: userMessage,
163
+ history: conversationHistory,
164
+ // optional: historyTokens: hostHistoryCount,
165
+ },
166
+ };
167
+ ```
168
+
169
+ ## Usage: `meter: 'input'`
170
+
171
+ ```ts
172
+ // timing: 'before' — thread previous turn's full-prompt usage
173
+ input: {
174
+ text: userMessage,
175
+ history: conversationHistory,
176
+ inputTokens: previousTokensInput,
177
+ }
178
+
179
+ // timing: 'after' — kernel reads this turn's tokens.input automatically
180
+ for await (const event of runTurn(req, provider)) {
181
+ if (event.type === "done" && event.compaction?.needed) {
182
+ const { history, tokens, meter, promptTokens } = event.compaction;
183
+ // meter === 'input'; tokens === promptTokens (when known)
184
+ }
185
+ }
186
+ ```
187
+
188
+ If the compaction profile uses a different provider, pass `compactionProvider`
189
+ on the `TurnRequest`.
190
+
191
+ ## Exported API
192
+
193
+ | Export | Kind | Description |
194
+ | --- | --- | --- |
195
+ | `CompactionSpec` | type | Config on `ModelSpec` |
196
+ | `CompactionMeter` | type | `'history' \| 'input'` |
197
+ | `CompactionTriggerContext` | type | Args for optional `trigger` |
198
+ | `CompactionSignal` | type | `done.compaction`: `meter`, `tokens`, optional `promptTokens`, `history` |
199
+ | `CompactionSplit` / `CompactionTokens` | type | Split result / resolved meter count |
200
+ | `resolveHistoryTokens` | async function | Host `historyTokens` or local history estimate |
201
+ | `resolveCompactionTokens` | async function | Resolve `{ meter, tokens }` for a turn |
202
+ | `estimateHistoryTokens` | async function | tiktoken `o200k_base` + media stubs (lazy BPE) |
203
+ | `HISTORY_TEXT_ENCODING` | const | `'o200k_base'` |
204
+ | `HISTORY_MEDIA_TOKENS` | const | Media minima: image/document 258, audio 32, video 263 |
205
+ | `compactionNeeded` | function | `(tokens, spec) => boolean` |
206
+ | `shouldCompact` | async function | Custom `trigger` or `compactionNeeded` |
207
+ | `splitForCompaction` | async function | `(history, spec) => { toCompact, toRetain }` |
208
+
209
+ ## Validation
210
+
211
+ At `registerProfile` time:
212
+
213
+ - `maxTokens` must be > 0
214
+ - `compactAt` must be in (0, 1)
215
+ - `previousExchanges` as a fraction must be < `compactAt`
216
+ - `previousExchanges` >= 1 must be an integer
217
+ - `meter`, when set, must be `'history'` or `'input'`
218
+ - The named compaction profile must already be registered
219
+
220
+ ## 0.1.13
221
+
222
+ - Default meter is **history** (local BPE + media stubs, or host `historyTokens`).
223
+ - Optional `meter: 'input'` gates on full-prompt provider usage.
224
+ - Budget field is `maxTokens` (not `maxHistoryTokens`).
225
+ - `CompactionSignal` uses `meter` + `tokens` (no deprecated aliases).
226
+ - Optional `trigger` replaces the default threshold check.
227
+ - History BPE (`gpt-tokenizer` / `o200k_base`) loads lazily on first text estimate.
package/docs/SECRETS.md CHANGED
@@ -30,6 +30,11 @@ const provider = createProvider(profile, {
30
30
  openRouter: {
31
31
  apiKey: hostResolvedOpenRouterKey,
32
32
  },
33
+ // openAi / local (Ollama, llama.cpp, vLLM, LM Studio, …)
34
+ // Hosts that honor OLLAMA_HOST should resolve it themselves — THEORUM does not.
35
+ local: {
36
+ baseUrl: hostResolvedLocalBaseUrl, // optional; default http://127.0.0.1:11434
37
+ },
33
38
  });
34
39
 
35
40
  for await (const event of runTurn({ profile: profile.id, input: { text: '…' } }, provider)) {
@@ -37,7 +42,7 @@ for await (const event of runTurn({ profile: profile.id, input: { text: '…' }
37
42
  }
38
43
  ```
39
44
 
40
- `createProvider` picks the transport from `profile.model.protocol` / `provider` (and whether the profile is a speech role). Hosts do not choose a separate speech constructor.
45
+ `createProvider` picks the transport from `profile.model.protocol` / `provider` (and whether the profile is a speech role). Hosts do not choose a separate speech constructor. OpenRouter's Vercel AI SDK dependency loads only when an `openAi` + `openrouter` chat provider first calls `complete` — Google and local paths never import it.
41
46
 
42
47
  ## 3. Tracing
43
48
 
package/docs/STOP.md ADDED
@@ -0,0 +1,85 @@
1
+ # Turn stop and resume
2
+
3
+ Providers end turns for many reasons (completed, length, tool call, filter,
4
+ network drop). THEORUM normalizes those into `TurnStop` on terminal `done`
5
+ events so hosts can decide Continue / auto-continue without parsing each
6
+ adapter.
7
+
8
+ ## `done.stop`
9
+
10
+ Adapters attach a `stop` when the turn ends cleanly enough to classify:
11
+
12
+ | `kind` | Meaning |
13
+ | --- | --- |
14
+ | `completed` | Normal completion |
15
+ | `length` | Output / budget cut off |
16
+ | `tool` | Model requested tool use |
17
+ | `filtered` | Content filter |
18
+ | `provider_error` | Upstream failure |
19
+ | `cancelled` | User / host abort |
20
+ | `stream_incomplete` | Stream ended without a terminal reason (tunnel drop, etc.) |
21
+
22
+ OpenRouter maps `finish_reason` (+ optional native reason). Google Interactions
23
+ maps terminal `status`. Local OpenAI-compat servers use the same OpenRouter
24
+ finish-reason mapping. Hosts classify client SSE drops with
25
+ `turnStopFromClientStreamEnd`.
26
+
27
+ ## Profile resume policy
28
+
29
+ Under `outputs.resume`:
30
+
31
+ ```ts
32
+ outputs: {
33
+ structured: null,
34
+ resume: {
35
+ allowContinue: ['length', 'stream_incomplete', 'provider_error'],
36
+ autoContinue: ['length', 'stream_incomplete'],
37
+ },
38
+ }
39
+ ```
40
+
41
+ - `allowContinue` — kinds eligible for a Continue CTA / `continueFrom` turn
42
+ (default: length, stream_incomplete, provider_error).
43
+ - `autoContinue` — kinds the host may silently resume **once** after a short
44
+ pause (`AUTO_CONTINUE_DELAY_MS`, 1500). Kernel does not loop; hosts call
45
+ `continueFrom` at most once. User `cancelled` is never auto-continued.
46
+
47
+ Helpers: `isResumeableStop`, `shouldAutoContinue`, `isUserCancelledStop`.
48
+
49
+ ## Continuing a turn
50
+
51
+ Pass `continueFrom` on the next `TurnRequest`. The kernel appends the fixed
52
+ `CONTINUE_INSTRUCTION` (do not invent per-app continue prompts):
53
+
54
+ ```ts
55
+ for await (const event of runTurn({
56
+ profile: "my.agent",
57
+ input: { text: "" },
58
+ continueFrom: {
59
+ stop: previousDone.stop,
60
+ partialText: bufferedAssistantText,
61
+ // optional: partialArtifact
62
+ },
63
+ }, provider)) {
64
+ // …
65
+ }
66
+ ```
67
+
68
+ ## Errors
69
+
70
+ `GenerationStopError` carries a `stop` for hosts that prefer throw/catch over
71
+ stream events. `isGenerationStopError` narrows it.
72
+
73
+ ## Exported API
74
+
75
+ | Export | Kind | Description |
76
+ | --- | --- | --- |
77
+ | `TurnStop` / `TurnStopKind` | type | Normalized stop on `done` |
78
+ | `TurnContinueFrom` | type | Partial state for resume |
79
+ | `ProfileResumeSpec` | type | `outputs.resume` policy |
80
+ | `CONTINUE_INSTRUCTION` | const | Fixed continue system text |
81
+ | `DEFAULT_AUTO_CONTINUE` | const | Default one-shot auto-continue kinds |
82
+ | `AUTO_CONTINUE_DELAY_MS` | const | Suggested pause before auto-continue |
83
+ | `isResumeableStop` / `shouldAutoContinue` / `isUserCancelledStop` | function | Host policy helpers |
84
+ | `turnStopFromOpenRouter` / `turnStopFromInteractionStatus` / `turnStopFromClientStreamEnd` | function | Provider / client mappers |
85
+ | `GenerationStopError` / `isGenerationStopError` | class / function | Optional throw path |
package/esm/mod.d.ts CHANGED
@@ -41,6 +41,8 @@ export { describeError, isAbortError, publicError, TheorumError, throwIfAborted,
41
41
  export type { QuotaSlotStatus } from './src/guardrails/quota.js';
42
42
  export { clientIp, quotaMessage, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
43
43
  export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
44
+ export type { CompactionSplit, CompactionTokens } from './src/kernel/engine/compaction.js';
45
+ export { compactionMeter, compactionNeeded, estimateHistoryTokens, HISTORY_MEDIA_TOKENS, HISTORY_TEXT_ENCODING, resolveCompactionTokens, resolveHistoryTokens, shouldCompact, splitForCompaction, } from './src/kernel/engine/compaction.js';
44
46
  export { runTurn } from './src/kernel/engine/runner.js';
45
47
  export { CATALOG, clampThinkingLevel, clampThinkingLevelForApiId, geminiKindForMime, getTool, listBuiltinIds, mimeAllowed, mimeEssence, modelEntryByApiId, registerTools, requireModelSpec, resetTools, } from './src/kernel/registry/catalog.js';
46
48
  export type { ProfileDefinition } from './src/kernel/registry/profiles.js';
@@ -48,8 +50,10 @@ export { clearProfiles, defineProfile, getProfile, hasProfile, listProfiles, reg
48
50
  export { projectProfile, resolveTurn } from './src/kernel/registry/resolve.js';
49
51
  export { getStructured, registerStructured } from './src/kernel/registry/schemas.js';
50
52
  export { executeTool } from './src/kernel/registry/tools.js';
53
+ export type { ProfileResumeSpec, TurnContinueFrom, TurnStop, TurnStopKind, } from './src/kernel/stop.js';
54
+ export { AUTO_CONTINUE_DELAY_MS, CONTINUE_INSTRUCTION, DEFAULT_AUTO_CONTINUE, GenerationStopError, isGenerationStopError, isResumeableStop, isUserCancelledStop, shouldAutoContinue, turnStopFromClientStreamEnd, turnStopFromInteractionStatus, turnStopFromOpenRouter, } from './src/kernel/stop.js';
51
55
  export type * from './src/kernel/types.js';
52
56
  export { jsonlSink, memorySink, noopSink, resolveTraceDir, sinkFromDir, writeTrace, } from './src/observability/trace.js';
53
57
  export type { TraceRecord } from './src/observability/trace-record.js';
54
- export type { CreateProviderOptions, GeminiTransport, GeminiVault } from './src/providers/mod.js';
55
- export { createProvider } from './src/providers/mod.js';
58
+ export type { CreateProviderOptions, GeminiTransport, GeminiVault, LocalProviderConfig, } from './src/providers/mod.js';
59
+ export { createLocalProvider, createProvider, DEFAULT_LOCAL_BASE_URL, } from './src/providers/mod.js';
package/esm/mod.js CHANGED
@@ -40,11 +40,13 @@ import "./_dnt.polyfills.js";
40
40
  export { describeError, isAbortError, publicError, TheorumError, throwIfAborted, toErrorEvent, } from './src/guardrails/error.js';
41
41
  export { clientIp, quotaMessage, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
42
42
  export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
43
+ export { compactionMeter, compactionNeeded, estimateHistoryTokens, HISTORY_MEDIA_TOKENS, HISTORY_TEXT_ENCODING, resolveCompactionTokens, resolveHistoryTokens, shouldCompact, splitForCompaction, } from './src/kernel/engine/compaction.js';
43
44
  export { runTurn } from './src/kernel/engine/runner.js';
44
45
  export { CATALOG, clampThinkingLevel, clampThinkingLevelForApiId, geminiKindForMime, getTool, listBuiltinIds, mimeAllowed, mimeEssence, modelEntryByApiId, registerTools, requireModelSpec, resetTools, } from './src/kernel/registry/catalog.js';
45
46
  export { clearProfiles, defineProfile, getProfile, hasProfile, listProfiles, registerProfile, registerProfiles, } from './src/kernel/registry/profiles.js';
46
47
  export { projectProfile, resolveTurn } from './src/kernel/registry/resolve.js';
47
48
  export { getStructured, registerStructured } from './src/kernel/registry/schemas.js';
48
49
  export { executeTool } from './src/kernel/registry/tools.js';
50
+ export { AUTO_CONTINUE_DELAY_MS, CONTINUE_INSTRUCTION, DEFAULT_AUTO_CONTINUE, GenerationStopError, isGenerationStopError, isResumeableStop, isUserCancelledStop, shouldAutoContinue, turnStopFromClientStreamEnd, turnStopFromInteractionStatus, turnStopFromOpenRouter, } from './src/kernel/stop.js';
49
51
  export { jsonlSink, memorySink, noopSink, resolveTraceDir, sinkFromDir, writeTrace, } from './src/observability/trace.js';
50
- export { createProvider } from './src/providers/mod.js';
52
+ export { createLocalProvider, createProvider, DEFAULT_LOCAL_BASE_URL, } from './src/providers/mod.js';
@@ -15,7 +15,7 @@
15
15
  import { sanitizeTurnRequest } from '../../guardrails/sanitize.js';
16
16
  import { bindCanary, eventHasCanary, mintCanary } from '../../kernel/engine/boundary.js';
17
17
  import { runTurn } from '../../kernel/engine/runner.js';
18
- import { clearProfiles, registerProfile, } from '../../kernel/registry/profiles.js';
18
+ import { clearProfiles, registerProfile } from '../../kernel/registry/profiles.js';
19
19
  import { pickSystemRole, resolveTurn } from '../../kernel/registry/resolve.js';
20
20
  import { buildRecord } from '../../observability/trace-record.js';
21
21
  const DEFAULT_CHUNKS = 200;
@@ -159,9 +159,7 @@ function aggregate(results) {
159
159
  const ttfe = results.map((r) => r.ttfe).sort((a, b) => a - b);
160
160
  const ttft = results.map((r) => r.ttft).sort((a, b) => a - b);
161
161
  const total = results.map((r) => r.totalMs).sort((a, b) => a - b);
162
- const tps = results
163
- .map((r) => (r.textEvents / r.totalMs) * MS_PER_SEC)
164
- .sort((a, b) => a - b);
162
+ const tps = results.map((r) => (r.textEvents / r.totalMs) * MS_PER_SEC).sort((a, b) => a - b);
165
163
  const mean = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
166
164
  return {
167
165
  ttfeMs: {