theorum 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -27
- package/docs/AGENT_PROFILE_CONTRACT.md +45 -18
- package/docs/CLI_SPEC.md +2 -2
- package/docs/SECRETS.md +27 -22
- package/esm/_dnt.polyfills.d.ts +99 -0
- package/esm/_dnt.polyfills.js +130 -1
- package/esm/mod.d.ts +19 -3
- package/esm/mod.js +17 -3
- package/esm/src/cli/commands/profile.d.ts +2 -0
- package/esm/src/cli/commands/profile.js +53 -0
- package/esm/src/cli/commands/run.d.ts +10 -0
- package/esm/src/cli/commands/run.js +55 -0
- package/esm/src/cli/commands/test.d.ts +15 -0
- package/esm/src/cli/commands/test.js +140 -0
- package/esm/src/cli/index.d.ts +4 -0
- package/esm/src/cli/index.js +148 -0
- package/esm/src/cli/matrix/fixtures.d.ts +13 -0
- package/esm/src/cli/matrix/fixtures.js +77 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +29 -0
- package/esm/src/cli/matrix/synthesizer.js +187 -0
- package/esm/src/guardrails/error.d.ts +1 -3
- package/esm/src/guardrails/error.js +4 -6
- package/esm/src/guardrails/mod.d.ts +1 -1
- package/esm/src/guardrails/mod.js +1 -1
- package/esm/src/guardrails/sanitize.js +1 -1
- package/esm/src/host/mint-trace.d.ts +27 -0
- package/esm/src/host/mint-trace.js +33 -0
- package/esm/src/host/mod.d.ts +13 -0
- package/esm/src/host/mod.js +12 -0
- package/esm/src/host/reply.d.ts +14 -0
- package/esm/src/host/reply.js +27 -0
- package/esm/src/kernel/engine/delta.js +31 -4
- package/esm/src/kernel/engine/repair.d.ts +1 -3
- package/esm/src/kernel/engine/repair.js +1 -1
- package/esm/src/kernel/engine/runner/gates.d.ts +4 -0
- package/esm/src/kernel/engine/runner/gates.js +206 -0
- package/esm/src/kernel/engine/runner/mod.d.ts +14 -0
- package/esm/src/kernel/engine/runner/mod.js +103 -0
- package/esm/src/kernel/engine/runner/state.d.ts +16 -0
- package/esm/src/kernel/engine/runner/state.js +8 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +15 -0
- package/esm/src/kernel/engine/runner/steps.js +119 -0
- package/esm/src/kernel/engine/runner/stream.d.ts +11 -0
- package/esm/src/kernel/engine/runner/stream.js +65 -0
- package/esm/src/kernel/engine/runner/tokens.d.ts +3 -0
- package/esm/src/kernel/engine/runner/tokens.js +38 -0
- package/esm/src/kernel/engine/runner/tools.d.ts +13 -0
- package/esm/src/kernel/engine/runner/tools.js +197 -0
- package/esm/src/kernel/engine/runner.d.ts +1 -9
- package/esm/src/kernel/engine/runner.js +1 -726
- package/esm/src/kernel/mod.d.ts +3 -1
- package/esm/src/kernel/mod.js +2 -1
- package/esm/src/{providers → kernel/registry}/attachments.d.ts +2 -3
- package/esm/src/{providers → kernel/registry}/attachments.js +6 -6
- package/esm/src/kernel/registry/catalog.d.ts +28 -21
- package/esm/src/kernel/registry/catalog.js +55 -133
- package/esm/src/kernel/registry/ingress.d.ts +13 -0
- package/esm/src/kernel/registry/ingress.js +163 -0
- package/esm/src/kernel/registry/profiles.d.ts +2 -4
- package/esm/src/kernel/registry/profiles.js +14 -5
- package/esm/src/kernel/registry/provider-request.d.ts +4 -0
- package/esm/src/kernel/registry/provider-request.js +25 -0
- package/esm/src/kernel/registry/resolve.d.ts +0 -1
- package/esm/src/kernel/registry/resolve.js +35 -53
- package/esm/src/kernel/registry/tools.js +2 -2
- package/esm/src/kernel/registry/vault.d.ts +11 -0
- package/esm/src/kernel/registry/vault.js +18 -0
- package/esm/src/kernel/types.d.ts +102 -78
- package/esm/src/observability/trace-attach.js +6 -20
- package/esm/src/presets/google.d.ts +50 -0
- package/esm/src/presets/google.js +96 -0
- package/esm/src/presets/mod.d.ts +11 -0
- package/esm/src/presets/mod.js +10 -0
- package/esm/src/providers/create-provider.d.ts +29 -0
- package/esm/src/providers/create-provider.js +38 -0
- package/esm/src/providers/gemini-tape.d.ts +1 -2
- package/esm/src/providers/gemini-tape.js +1 -1
- package/esm/src/providers/interactions.js +41 -16
- package/esm/src/{guardrails → providers}/keys.d.ts +10 -3
- package/esm/src/{guardrails → providers}/keys.js +10 -13
- package/esm/src/providers/mod.d.ts +4 -7
- package/esm/src/providers/mod.js +2 -5
- package/esm/src/providers/openrouter-mod.d.ts +13 -0
- package/esm/src/providers/openrouter-mod.js +12 -0
- package/esm/src/providers/openrouter-payload.d.ts +11 -3
- package/esm/src/providers/openrouter-payload.js +28 -26
- package/esm/src/providers/openrouter.d.ts +5 -7
- package/esm/src/providers/openrouter.js +488 -231
- package/esm/src/providers/pcm.d.ts +7 -0
- package/esm/src/providers/pcm.js +35 -0
- package/esm/src/providers/provider.d.ts +3 -1
- package/esm/src/providers/provider.js +45 -2
- package/esm/src/providers/speech.d.ts +23 -0
- package/esm/src/providers/speech.js +125 -0
- package/package.json +20 -3
- package/esm/src/providers/media.d.ts +0 -5
- package/esm/src/providers/media.js +0 -125
- package/esm/src/providers/tts.d.ts +0 -24
- package/esm/src/providers/tts.js +0 -144
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ The package is intentionally **not** an agent product. It ships no app profiles,
|
|
|
24
24
|
[kernel_contract]
|
|
25
25
|
profiles = "Host-owned declarations for model, inputs, outputs, tools, and guardrails"
|
|
26
26
|
runner = "Single deterministic execution path for one agent turn"
|
|
27
|
-
providers = "
|
|
27
|
+
providers = "createProvider routes protocol/provider; adapters stay internal"
|
|
28
28
|
tools = "Profile allowlist ceiling plus per-turn dynamic declarations"
|
|
29
29
|
egress = "Typed host hook for outbound disclosure checks and repair loops"
|
|
30
30
|
traces = "Host-injected sinks; no environment variables or bundled destinations"
|
|
@@ -62,9 +62,9 @@ flowchart TD
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
subgraph Providers["Provider adapters"]
|
|
65
|
-
OR["OpenRouter"]
|
|
65
|
+
OR["OpenRouter chat"]
|
|
66
66
|
GI["Google Interactions"]
|
|
67
|
-
|
|
67
|
+
Speech["Speech (Interactions or /audio/speech)"]
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
Profile --> Resolve
|
|
@@ -80,6 +80,8 @@ flowchart TD
|
|
|
80
80
|
Runner --> TraceSink
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
+
Hosts bind transports with `createProvider(profile, { gemini, openRouter })`. One door; protocol/provider (and speech role) pick the adapter.
|
|
84
|
+
|
|
83
85
|
### Turn Lifecycle
|
|
84
86
|
|
|
85
87
|
```mermaid
|
|
@@ -148,6 +150,17 @@ const profile = defineProfile({
|
|
|
148
150
|
protocol: "openAi",
|
|
149
151
|
provider: "openrouter",
|
|
150
152
|
allow: ["gemini35FlashLite"],
|
|
153
|
+
config: {
|
|
154
|
+
gemini35FlashLite: {
|
|
155
|
+
apiId: "gemini-3.5-flash-lite",
|
|
156
|
+
thinking: { on: "high", off: "minimal" },
|
|
157
|
+
thinkingLevels: ["minimal", "low", "medium", "high"],
|
|
158
|
+
summaries: { on: "auto", off: "none" },
|
|
159
|
+
maxOutputTokens: 8192,
|
|
160
|
+
temperature: 1,
|
|
161
|
+
keyBuiltins: [],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
151
164
|
thinking: "minimal",
|
|
152
165
|
maxSteps: 1,
|
|
153
166
|
},
|
|
@@ -221,7 +234,20 @@ Inbound and outbound safety are generic kernel hooks.
|
|
|
221
234
|
```ts
|
|
222
235
|
const guardedProfile = defineProfile({
|
|
223
236
|
id: "assistant.guarded",
|
|
224
|
-
model: {
|
|
237
|
+
model: {
|
|
238
|
+
allow: ["gemini35FlashLite"],
|
|
239
|
+
config: {
|
|
240
|
+
gemini35FlashLite: {
|
|
241
|
+
apiId: "gemini-3.5-flash-lite",
|
|
242
|
+
thinking: { on: "high", off: "minimal" },
|
|
243
|
+
thinkingLevels: ["minimal", "low", "medium", "high"],
|
|
244
|
+
summaries: { on: "auto", off: "none" },
|
|
245
|
+
maxOutputTokens: 8192,
|
|
246
|
+
temperature: 1,
|
|
247
|
+
keyBuiltins: [],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
225
251
|
guardrails: {
|
|
226
252
|
egress: {
|
|
227
253
|
onBlock: "reject_to_agent",
|
|
@@ -250,33 +276,30 @@ Quota is optional. If a profile omits `guardrails.quota`, the quota helper retur
|
|
|
250
276
|
|
|
251
277
|
## Provider Adapters
|
|
252
278
|
|
|
253
|
-
THEORUM includes provider adapters but does not own credentials.
|
|
279
|
+
THEORUM includes provider adapters but does not own credentials. Bind them with one door:
|
|
254
280
|
|
|
255
281
|
```ts
|
|
256
|
-
import {
|
|
282
|
+
import { createProvider, runTurn } from "jsr:@theorum/core";
|
|
257
283
|
|
|
258
|
-
const provider =
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
siteUrl: "https://example.com",
|
|
284
|
+
const provider = createProvider(profile, {
|
|
285
|
+
gemini: { vault: hostGeminiKeyVault, fetch },
|
|
286
|
+
openRouter: { apiKey: hostSecrets.openRouterApiKey },
|
|
262
287
|
});
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
```ts
|
|
266
|
-
import { createInteractionsProvider } from "jsr:@theorum/core/providers";
|
|
267
288
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
});
|
|
289
|
+
for await (const event of runTurn({ profile: profile.id, input: { text: "…" } }, provider)) {
|
|
290
|
+
// …
|
|
291
|
+
}
|
|
272
292
|
```
|
|
273
293
|
|
|
274
|
-
|
|
294
|
+
`createProvider` routes from `profile.model.protocol` / `provider`. Speech roles use the same call — Interactions when Google, `/audio/speech` when openAi/openrouter (same `openRouter` credentials).
|
|
275
295
|
|
|
276
|
-
|
|
|
277
|
-
| :--- | :--- |
|
|
278
|
-
|
|
|
279
|
-
|
|
|
296
|
+
| Profile | Transport |
|
|
297
|
+
| :--- | :--- |
|
|
298
|
+
| `geminiInteractions` + `google` | Google Interactions (chat, image, speech) |
|
|
299
|
+
| `openAi` + `openrouter` (chat) | OpenRouter chat completions |
|
|
300
|
+
| `openAi` + `openrouter` (speech role) | OpenRouter `/audio/speech` |
|
|
301
|
+
|
|
302
|
+
Advanced payload helpers live under `theorum/openrouter` (`toOpenRouterPayload`, …). Prefer `createProvider` for turns.
|
|
280
303
|
|
|
281
304
|
---
|
|
282
305
|
|
|
@@ -285,11 +308,15 @@ Provider support is intentionally split by wire protocol:
|
|
|
285
308
|
| Entrypoint | Purpose |
|
|
286
309
|
| :--- | :--- |
|
|
287
310
|
| `jsr:@theorum/core` / `theorum` | Main kernel API: profiles, schemas, runner, core types, provider constructors. |
|
|
288
|
-
| `jsr:@theorum/core/kernel` / `theorum/kernel` | Profile
|
|
289
|
-
| `jsr:@theorum/core/providers` / `theorum/providers` |
|
|
290
|
-
| `jsr:@theorum/core/openrouter` / `theorum/openrouter` | OpenRouter payload
|
|
311
|
+
| `jsr:@theorum/core/kernel` / `theorum/kernel` | Profile/turn types, tool catalog, `requireModelSpec`, thinking clamps over host model maps. |
|
|
312
|
+
| `jsr:@theorum/core/providers` / `theorum/providers` | `createProvider` + Gemini vault types. |
|
|
313
|
+
| `jsr:@theorum/core/openrouter` / `theorum/openrouter` | OpenRouter payload helpers (advanced). |
|
|
291
314
|
| `jsr:@theorum/core/guardrails` / `theorum/guardrails` | Sanitization, public error mapping, inbound injection/sensitive-data primitives. |
|
|
292
315
|
| `jsr:@theorum/core/observability` / `theorum/observability` | Trace sinks and trace record helpers. |
|
|
316
|
+
| `jsr:@theorum/core/host` / `theorum/host` | Optional Deno HTTP helpers (`json`, status mapping, cutout mint flush). |
|
|
317
|
+
| `jsr:@theorum/core/cli` / `theorum/cli` | Profile inspection and stress-test CLI (`theorum` binary on npm). |
|
|
318
|
+
| `jsr:@theorum/core/presets` / `theorum/presets` | Optional convenience packs (`registerGooglePreset`, …). |
|
|
319
|
+
| `jsr:@theorum/core/presets/google` / `theorum/presets/google` | Google builtins (search/maps/urlContext) + Interactions/OpenRouter wire metadata. |
|
|
293
320
|
|
|
294
321
|
Internal files remain present in source for maintainability, but package consumers should use the public entrypoints above.
|
|
295
322
|
|
|
@@ -304,7 +331,15 @@ npm run lint
|
|
|
304
331
|
deno publish --dry-run --allow-dirty
|
|
305
332
|
```
|
|
306
333
|
|
|
307
|
-
|
|
334
|
+
Run the packaged CLI locally:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
deno task theorum --help
|
|
338
|
+
# or after npm install -g / npx:
|
|
339
|
+
# npx theorum --help
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Build the npm package from the Deno source (publish only from `npm/`):
|
|
308
343
|
|
|
309
344
|
```bash
|
|
310
345
|
npm run build:npm
|
|
@@ -6,10 +6,12 @@ Every profile strictly namespaces its capabilities across 6 functional domains:
|
|
|
6
6
|
1. `identity` — Persona, display handle, and static base system prompts.
|
|
7
7
|
2. `model` — Protocol, provider backend, model whitelist, thinking level, and controls.
|
|
8
8
|
3. `tools` — Tool access ceiling.
|
|
9
|
-
4. `inputs` — Strict ingress constraints, file limits, and routing slots.
|
|
10
|
-
5. `outputs` — Structured schemas,
|
|
9
|
+
4. `inputs` — Strict ingress constraints (text, attachments, voice), file limits, and routing slots.
|
|
10
|
+
5. `outputs` — Structured schemas, image, speech, streaming, and validation/auto-repair.
|
|
11
11
|
6. `guardrails` — Rate limits, canary leak detection, content safety, and outbound disclosure policies.
|
|
12
12
|
|
|
13
|
+
**Vocabulary:** Ingress audio is `inputs.voice`. Generated audio is `outputs.speech` (TTS voice id is `outputs.speech.voice`). Wire container formats for speech (`pcm` / `mp3`) may live in the kernel; vendor voice catalogs and image aspect/size vocabularies live in presets/apps.
|
|
14
|
+
|
|
13
15
|
---
|
|
14
16
|
|
|
15
17
|
## 1. Authoring Shape
|
|
@@ -20,17 +22,30 @@ profiles. The minimum useful profile is:
|
|
|
20
22
|
```typescript
|
|
21
23
|
defineProfile({
|
|
22
24
|
id: 'host.agent',
|
|
23
|
-
model: {
|
|
25
|
+
model: {
|
|
26
|
+
allow: ['your-model-id'],
|
|
27
|
+
config: {
|
|
28
|
+
'your-model-id': {
|
|
29
|
+
apiId: 'provider-native-model-id',
|
|
30
|
+
thinking: { on: 'high', off: 'minimal' },
|
|
31
|
+
thinkingLevels: ['minimal', 'low', 'medium', 'high'],
|
|
32
|
+
summaries: { on: 'auto', off: 'none' },
|
|
33
|
+
maxOutputTokens: 8192,
|
|
34
|
+
temperature: 1,
|
|
35
|
+
keyBuiltins: [],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
24
39
|
});
|
|
25
40
|
```
|
|
26
41
|
|
|
27
|
-
Everything except `id` and `model.
|
|
42
|
+
Everything except `id`, `model.allow`, and `model.config` is optional at authoring time:
|
|
28
43
|
|
|
29
44
|
```typescript
|
|
30
45
|
export type ProfileDefinition = {
|
|
31
46
|
id: ProfileId;
|
|
32
47
|
identity?: Partial<Profile['identity']>;
|
|
33
|
-
model: Partial<Profile['model']> & Pick<Profile['model'], 'allow'>;
|
|
48
|
+
model: Partial<Profile['model']> & Pick<Profile['model'], 'allow' | 'config'>;
|
|
34
49
|
tools?: Partial<Profile['tools']>;
|
|
35
50
|
inputs?: Partial<Profile['inputs']>;
|
|
36
51
|
outputs?: Partial<Profile['outputs']>;
|
|
@@ -61,6 +76,8 @@ export interface Profile {
|
|
|
61
76
|
protocol: 'geminiInteractions' | 'openAi';
|
|
62
77
|
provider: 'google' | 'openrouter';
|
|
63
78
|
allow: ModelId[];
|
|
79
|
+
/** Host-owned wire config for every id in `allow`. */
|
|
80
|
+
config: Record<ModelId, ModelSpec>;
|
|
64
81
|
select?: Record<string, ModelId>;
|
|
65
82
|
thinking?: ThinkingLevel | Record<string, ThinkingLevel>;
|
|
66
83
|
controls?: ControlId[];
|
|
@@ -85,11 +102,13 @@ export interface Profile {
|
|
|
85
102
|
slots?: Record<string, string[]>;
|
|
86
103
|
};
|
|
87
104
|
|
|
88
|
-
/** 5. Outputs (Structured output,
|
|
105
|
+
/** 5. Outputs (Structured output, image, speech, validation, streaming) */
|
|
89
106
|
outputs: {
|
|
90
107
|
structured?: StructuredSchemaId | StructuredBySlot | null;
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
/** Pins for an image-role profile. Model id lives on `model`. */
|
|
109
|
+
image?: ProfileImageSpec;
|
|
110
|
+
/** Pins for a speech-role profile. Model id lives on `model`. */
|
|
111
|
+
speech?: ProfileSpeechSpec;
|
|
93
112
|
validation?: ProfileValidationSpec;
|
|
94
113
|
streaming?: ProfileStreamingSpec;
|
|
95
114
|
};
|
|
@@ -118,39 +137,47 @@ export interface Profile {
|
|
|
118
137
|
### `model`
|
|
119
138
|
- `model.protocol`: Wire framing protocol (`'geminiInteractions'` for Google Interactions API, `'openAi'` for OpenAI/OpenRouter compatible chat completions API).
|
|
120
139
|
- `model.provider`: Provider execution backend (`'google'` or `'openrouter'`).
|
|
121
|
-
- `model.allow`: Whitelist of
|
|
122
|
-
- `model.
|
|
123
|
-
- `model.
|
|
140
|
+
- `model.allow`: Whitelist of host-defined `ModelId`s for this profile. THEORUM does not ship model names.
|
|
141
|
+
- `model.config`: Host-owned `ModelSpec` map keyed by the same ids as `allow` / `select` (`apiId`, thinking levels, tokens, `keyBuiltins`, optional per-model `key`, etc.).
|
|
142
|
+
- `model.select`: Named model mappings (e.g. `{ fast: 'flash', deep: 'pro' }` — ids are host-defined).
|
|
143
|
+
- `model.thinking`: Pinned thinking level (`'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max'`) when not user-controllable. Each model’s accepted subset is declared on `ModelSpec.thinkingLevels`.
|
|
124
144
|
- `model.controls`: User-togglable controls on this profile (e.g. `['thinking']`).
|
|
125
145
|
- `model.maxSteps`: Step limit on autonomous tool loops (1 = one-shot; >1 = autonomous tool loop).
|
|
126
|
-
- `model.key`: Gemini
|
|
146
|
+
- `model.key`: Default Gemini vault slot for this profile (`'freeA' | 'freeB' | 'freeC'`). Overflow uses `'paid'`.
|
|
147
|
+
|
|
148
|
+
### `ModelSpec` (host-owned, per id in `config`)
|
|
149
|
+
- `apiId` / optional `openRouterId`: Provider-native wire ids.
|
|
150
|
+
- `keyBuiltins`: Builtins that may use `profile.model.key`. Any other enabled builtin selects the overflow vault slot. Host policy — THEORUM does not infer tool pricing.
|
|
151
|
+
- `key`: Optional vault slot override for this model (e.g. pin an image model to `'paid'`). When set, wins over profile key and builtin routing.
|
|
127
152
|
|
|
128
153
|
### `tools`
|
|
129
154
|
- `tools.allow`: Whitelist of tool IDs permitted to run under this profile. Calls to unlisted tools are blocked at the kernel boundary.
|
|
155
|
+
- Harness tools (`askUser`) ship with THEORUM and are always in the catalog.
|
|
156
|
+
- Provider builtins (e.g. Google search/maps/urlContext) are registered by optional presets such as `theorum/presets/google` via `registerGooglePreset()`.
|
|
130
157
|
|
|
131
158
|
### `inputs`
|
|
132
159
|
- `inputs.text`: Boolean flag accepting user text input.
|
|
133
160
|
- `inputs.attachments`: Allowed mime types for uploaded file attachments.
|
|
134
|
-
- `inputs.voice`: Allowed mime types for recorded audio clips.
|
|
161
|
+
- `inputs.voice`: Allowed mime types for recorded audio clips (ingress only).
|
|
135
162
|
- `inputs.maxFiles`: Maximum number of files permitted per message.
|
|
136
163
|
- `inputs.maxBytes`: Maximum byte size permitted per single file.
|
|
137
164
|
- `inputs.maxTurnBytes`: Maximum total byte size permitted across all files in one turn.
|
|
138
165
|
- `inputs.limitsByMime`: Granular per-MIME byte limits (e.g. `{ 'application/pdf': 50 * 1024 * 1024, 'video/*': 100 * 1024 * 1024 }`).
|
|
139
|
-
- `inputs.slots`: Allowed values for dynamic routing slots.
|
|
166
|
+
- `inputs.slots`: Allowed values for dynamic routing slots. Image-role overrides use `slots.aspectRatio` / `slots.size`.
|
|
140
167
|
|
|
141
168
|
### `outputs`
|
|
142
169
|
- `outputs.structured`: Structured JSON schema specification (or slot-based schema routing).
|
|
143
|
-
- `outputs.
|
|
144
|
-
- `outputs.
|
|
170
|
+
- `outputs.image`: Pins for an image-role profile (`aspectRatio`, `size`, `mimeType`, optional `allowsGrounding`, `maxInputImages`). The image model itself is selected via `model.allow` / `model.config`. Slot overrides use `slots.aspectRatio` / `slots.size` when the profile lists allowlists under `inputs.slots`. Adapters map `size` to provider wire keys (e.g. Google Interactions `imageSize`).
|
|
171
|
+
- `outputs.speech`: Pins for a speech-role profile (`voice`, optional `format: 'pcm' | 'mp3'`). The speech model itself is selected via `model.allow` / `model.config`. Bind with `createProvider(profile, …)` — same door as chat/image. `geminiInteractions` uses Interactions (`response_format: audio` + `speech_config`); `openAi`/`openrouter` speech roles use `/audio/speech` with the same `openRouter` credentials. `format: 'pcm'` (default) yields WAV media on both. `format: 'mp3'` is only valid on `openAi` speech — Interactions rejects it at resolve.
|
|
145
172
|
- `outputs.validation`: In-harness auto-correction validator (`validate`, optional `extract`, `maxRetries`, `repairGuidance`). If `extract` is omitted, the structured output itself is validated.
|
|
146
|
-
- `outputs.streaming`: SSE streaming behaviors (`streamThoughts`, `gateMedia`).
|
|
173
|
+
- `outputs.streaming`: SSE streaming behaviors (`streamThoughts`, `gateMedia`). `gateMedia` controls whether stream `media` events are held until validation/egress — unrelated to a profile output flag.
|
|
147
174
|
|
|
148
175
|
### `guardrails`
|
|
149
176
|
- `guardrails.quota.perDay`: Optional daily turn quota enforced per client IP. If omitted, quota enforcement is explicitly `not_configured`.
|
|
150
177
|
- `guardrails.canary`: Enable unique token canary leak interception (default `true`).
|
|
151
178
|
- `guardrails.sanitizeInput`: Run prompt injection / jailbreak redaction on ingress text (default `true`).
|
|
152
179
|
- `guardrails.redactSensitive`: Redact SSN, credit cards, IP addresses, API keys from inputs (default `true`).
|
|
153
|
-
- `guardrails.egress`: Generic outbound disclosure control engine (`enforce`, `onBlock: 'reject_to_agent' | 'refuse_to_user'`, `maxRetries`, `repairGuidance`). Runs deterministic auto-repair loops for chat or immediate in-character refusal
|
|
180
|
+
- `guardrails.egress`: Generic outbound disclosure control engine (`enforce`, `onBlock: 'reject_to_agent' | 'refuse_to_user'`, `maxRetries`, `repairGuidance`). Runs deterministic auto-repair loops for chat or immediate in-character refusal when blocking egress on voice-input turns.
|
|
154
181
|
|
|
155
182
|
### Per-turn Interactions state
|
|
156
183
|
- `TurnRequest.input`: Optional turn input object. If omitted, Theorum normalizes it to an empty input and still runs the profile/provider turn.
|
package/docs/CLI_SPEC.md
CHANGED
|
@@ -48,7 +48,7 @@ theorum test --all [--lite] [--concurrency 4]
|
|
|
48
48
|
| `--lite` | `boolean` | Minimal single-turn connectivity ping (fast mode, tools off, text only) |
|
|
49
49
|
| `--matrix` | `boolean` | Generate and execute all valid permutations for the profile |
|
|
50
50
|
| `--attachment` | `string[]` | Path(s) to custom attachment files (PNG, PDF, CSV, etc.) |
|
|
51
|
-
| `--voice` | `string` | Path to custom voice audio (WAV, PCM) |
|
|
51
|
+
| `--voice` | `string` | Path to custom voice input audio (WAV, PCM) |
|
|
52
52
|
| `--search` | `boolean` | Force Google Search tool on |
|
|
53
53
|
| `--map` | `boolean` | Force Google Maps tool on |
|
|
54
54
|
| `--mode` | `'fast' \| 'smart'` | Force reasoning / model speed tier |
|
|
@@ -112,7 +112,7 @@ interface SynthesizedTurn {
|
|
|
112
112
|
- If profile supports `smart` mode $\to$ select `smart` (highest pinned thinking tokens).
|
|
113
113
|
- Otherwise $\to$ use default profile model.
|
|
114
114
|
2. **Multimodal Fixtures**:
|
|
115
|
-
- If `inputs.voice
|
|
115
|
+
- If `inputs.voice` is set $\to$ inject synthetic 16kHz PCM/WAV voice-input fixture.
|
|
116
116
|
- If `inputs.attachments === true` $\to$ inject synthetic test document/image matching allowed MIME types within byte ceilings (`maxBytes`).
|
|
117
117
|
3. **Tool Resolution & Conflict Handling**:
|
|
118
118
|
- Turn on all allowed tools in `profile.tools.allow`.
|
package/docs/SECRETS.md
CHANGED
|
@@ -9,39 +9,44 @@ Host applications own credentials, runtime configuration, and secret storage. Th
|
|
|
9
9
|
- Do not create `.env` files in this repository.
|
|
10
10
|
- Do not commit key templates to this repository.
|
|
11
11
|
- Do not teach Theorum to discover keys from the shell or process environment.
|
|
12
|
-
- Business applications pass credentials into
|
|
12
|
+
- Business applications pass credentials into `createProvider`.
|
|
13
13
|
|
|
14
|
-
## 2.
|
|
14
|
+
## 2. Single door: `createProvider`
|
|
15
15
|
|
|
16
16
|
```ts
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
const provider =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
freeB: hostResolvedFreeBKey,
|
|
33
|
-
freeC: hostResolvedFreeCKey,
|
|
34
|
-
paid: hostResolvedPaidKey,
|
|
17
|
+
import { createProvider, runTurn } from 'theorum';
|
|
18
|
+
|
|
19
|
+
const provider = createProvider(profile, {
|
|
20
|
+
// geminiInteractions / google
|
|
21
|
+
gemini: {
|
|
22
|
+
vault: {
|
|
23
|
+
freeA: hostResolvedFreeAKey,
|
|
24
|
+
freeB: hostResolvedFreeBKey,
|
|
25
|
+
freeC: hostResolvedFreeCKey,
|
|
26
|
+
paid: hostResolvedPaidKey,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
// openAi / openrouter (chat or speech role — same credentials)
|
|
30
|
+
openRouter: {
|
|
31
|
+
apiKey: hostResolvedOpenRouterKey,
|
|
35
32
|
},
|
|
36
33
|
});
|
|
34
|
+
|
|
35
|
+
for await (const event of runTurn({ profile: profile.id, input: { text: '…' } }, provider)) {
|
|
36
|
+
// …
|
|
37
|
+
}
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
|
|
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.
|
|
41
|
+
|
|
42
|
+
## 3. Tracing
|
|
40
43
|
|
|
41
44
|
Tracing is silent by default. Hosts opt in by passing a sink to `runTurn`.
|
|
42
45
|
|
|
43
46
|
```ts
|
|
44
|
-
import { jsonlSink, runTurn } from 'theorum';
|
|
47
|
+
import { createProvider, jsonlSink, runTurn } from 'theorum';
|
|
48
|
+
|
|
49
|
+
const provider = createProvider(profile, { openRouter: { apiKey: hostResolvedOpenRouterKey } });
|
|
45
50
|
|
|
46
51
|
for await (const event of runTurn(request, provider, jsonlSink(hostTraceDir))) {
|
|
47
52
|
// stream events
|
package/esm/_dnt.polyfills.d.ts
CHANGED
|
@@ -9,3 +9,102 @@ declare global {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
export {};
|
|
12
|
+
/**
|
|
13
|
+
* Based on [import-meta-ponyfill](https://github.com/gaubee/import-meta-ponyfill),
|
|
14
|
+
* but instead of using npm to install additional dependencies,
|
|
15
|
+
* this approach manually consolidates cjs/mjs/d.ts into a single file.
|
|
16
|
+
*
|
|
17
|
+
* Note that this code might be imported multiple times
|
|
18
|
+
* (for example, both dnt.test.polyfills.ts and dnt.polyfills.ts contain this code;
|
|
19
|
+
* or Node.js might dynamically clear the cache and then force a require).
|
|
20
|
+
* Therefore, it's important to avoid redundant writes to global objects.
|
|
21
|
+
* Additionally, consider that commonjs is used alongside esm,
|
|
22
|
+
* so the two ponyfill functions are stored independently in two separate global objects.
|
|
23
|
+
*/
|
|
24
|
+
import { createRequire } from "node:module";
|
|
25
|
+
import { type URL } from "node:url";
|
|
26
|
+
declare global {
|
|
27
|
+
interface ImportMeta {
|
|
28
|
+
/** A string representation of the fully qualified module URL. When the
|
|
29
|
+
* module is loaded locally, the value will be a file URL (e.g.
|
|
30
|
+
* `file:///path/module.ts`).
|
|
31
|
+
*
|
|
32
|
+
* You can also parse the string as a URL to determine more information about
|
|
33
|
+
* how the current module was loaded. For example to determine if a module was
|
|
34
|
+
* local or not:
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* const url = new URL(import.meta.url);
|
|
38
|
+
* if (url.protocol === "file:") {
|
|
39
|
+
* console.log("this module was loaded locally");
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
url: string;
|
|
44
|
+
/**
|
|
45
|
+
* A function that returns resolved specifier as if it would be imported
|
|
46
|
+
* using `import(specifier)`.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* console.log(import.meta.resolve("./foo.js"));
|
|
50
|
+
* // file:///dev/foo.js
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @param specifier The module specifier to resolve relative to `parent`.
|
|
54
|
+
* @param parent The absolute parent module URL to resolve from.
|
|
55
|
+
* @returns The absolute (`file:`) URL string for the resolved module.
|
|
56
|
+
*/
|
|
57
|
+
resolve(specifier: string, parent?: string | URL | undefined): string;
|
|
58
|
+
/** A flag that indicates if the current module is the main module that was
|
|
59
|
+
* called when starting the program under Deno.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* if (import.meta.main) {
|
|
63
|
+
* // this was loaded as the main module, maybe do some bootstrapping
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
main: boolean;
|
|
68
|
+
/** The absolute path of the current module.
|
|
69
|
+
*
|
|
70
|
+
* This property is only provided for local modules (ie. using `file://` URLs).
|
|
71
|
+
*
|
|
72
|
+
* Example:
|
|
73
|
+
* ```
|
|
74
|
+
* // Unix
|
|
75
|
+
* console.log(import.meta.filename); // /home/alice/my_module.ts
|
|
76
|
+
*
|
|
77
|
+
* // Windows
|
|
78
|
+
* console.log(import.meta.filename); // C:\alice\my_module.ts
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
filename: string;
|
|
82
|
+
/** The absolute path of the directory containing the current module.
|
|
83
|
+
*
|
|
84
|
+
* This property is only provided for local modules (ie. using `file://` URLs).
|
|
85
|
+
*
|
|
86
|
+
* * Example:
|
|
87
|
+
* ```
|
|
88
|
+
* // Unix
|
|
89
|
+
* console.log(import.meta.dirname); // /home/alice
|
|
90
|
+
*
|
|
91
|
+
* // Windows
|
|
92
|
+
* console.log(import.meta.dirname); // C:\alice
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
dirname: string;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
type NodeRequest = ReturnType<typeof createRequire>;
|
|
99
|
+
type NodeModule = NonNullable<NodeRequest["main"]>;
|
|
100
|
+
interface ImportMetaPonyfillCommonjs {
|
|
101
|
+
(require: NodeRequest, module: NodeModule): ImportMeta;
|
|
102
|
+
}
|
|
103
|
+
interface ImportMetaPonyfillEsmodule {
|
|
104
|
+
(importMeta: ImportMeta): ImportMeta;
|
|
105
|
+
}
|
|
106
|
+
interface ImportMetaPonyfill extends ImportMetaPonyfillCommonjs, ImportMetaPonyfillEsmodule {
|
|
107
|
+
}
|
|
108
|
+
export declare let import_meta_ponyfill_commonjs: ImportMetaPonyfillCommonjs;
|
|
109
|
+
export declare let import_meta_ponyfill_esmodule: ImportMetaPonyfillEsmodule;
|
|
110
|
+
export declare let import_meta_ponyfill: ImportMetaPonyfill;
|
package/esm/_dnt.polyfills.js
CHANGED
|
@@ -12,4 +12,133 @@ if (!Object.hasOwn) {
|
|
|
12
12
|
writable: true,
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Based on [import-meta-ponyfill](https://github.com/gaubee/import-meta-ponyfill),
|
|
17
|
+
* but instead of using npm to install additional dependencies,
|
|
18
|
+
* this approach manually consolidates cjs/mjs/d.ts into a single file.
|
|
19
|
+
*
|
|
20
|
+
* Note that this code might be imported multiple times
|
|
21
|
+
* (for example, both dnt.test.polyfills.ts and dnt.polyfills.ts contain this code;
|
|
22
|
+
* or Node.js might dynamically clear the cache and then force a require).
|
|
23
|
+
* Therefore, it's important to avoid redundant writes to global objects.
|
|
24
|
+
* Additionally, consider that commonjs is used alongside esm,
|
|
25
|
+
* so the two ponyfill functions are stored independently in two separate global objects.
|
|
26
|
+
*/
|
|
27
|
+
//@ts-ignore
|
|
28
|
+
import { createRequire } from "node:module";
|
|
29
|
+
//@ts-ignore
|
|
30
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
31
|
+
//@ts-ignore
|
|
32
|
+
import { dirname } from "node:path";
|
|
33
|
+
const defineGlobalPonyfill = (symbolFor, fn) => {
|
|
34
|
+
if (!Reflect.has(globalThis, Symbol.for(symbolFor))) {
|
|
35
|
+
Object.defineProperty(globalThis, Symbol.for(symbolFor), {
|
|
36
|
+
configurable: true,
|
|
37
|
+
get() {
|
|
38
|
+
return fn;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export let import_meta_ponyfill_commonjs = (Reflect.get(globalThis, Symbol.for("import-meta-ponyfill-commonjs")) ??
|
|
44
|
+
(() => {
|
|
45
|
+
const moduleImportMetaWM = new WeakMap();
|
|
46
|
+
return (require, module) => {
|
|
47
|
+
let importMetaCache = moduleImportMetaWM.get(module);
|
|
48
|
+
if (importMetaCache == null) {
|
|
49
|
+
const importMeta = Object.assign(Object.create(null), {
|
|
50
|
+
url: pathToFileURL(module.filename).href,
|
|
51
|
+
main: require.main == module,
|
|
52
|
+
resolve: (specifier, parentURL = importMeta.url) => {
|
|
53
|
+
return pathToFileURL((importMeta.url === parentURL
|
|
54
|
+
? require
|
|
55
|
+
: createRequire(parentURL))
|
|
56
|
+
.resolve(specifier)).href;
|
|
57
|
+
},
|
|
58
|
+
filename: module.filename,
|
|
59
|
+
dirname: module.path,
|
|
60
|
+
});
|
|
61
|
+
moduleImportMetaWM.set(module, importMeta);
|
|
62
|
+
importMetaCache = importMeta;
|
|
63
|
+
}
|
|
64
|
+
return importMetaCache;
|
|
65
|
+
};
|
|
66
|
+
})());
|
|
67
|
+
defineGlobalPonyfill("import-meta-ponyfill-commonjs", import_meta_ponyfill_commonjs);
|
|
68
|
+
export let import_meta_ponyfill_esmodule = (Reflect.get(globalThis, Symbol.for("import-meta-ponyfill-esmodule")) ??
|
|
69
|
+
((importMeta) => {
|
|
70
|
+
const resolveFunStr = String(importMeta.resolve);
|
|
71
|
+
const shimWs = new WeakSet();
|
|
72
|
+
//@ts-ignore
|
|
73
|
+
const mainUrl = ("file:///" + process.argv[1].replace(/\\/g, "/"))
|
|
74
|
+
.replace(/\/{3,}/, "///");
|
|
75
|
+
const commonShim = (importMeta) => {
|
|
76
|
+
if (typeof importMeta.main !== "boolean") {
|
|
77
|
+
importMeta.main = importMeta.url === mainUrl;
|
|
78
|
+
}
|
|
79
|
+
if (typeof importMeta.filename !== "string") {
|
|
80
|
+
importMeta.filename = fileURLToPath(importMeta.url);
|
|
81
|
+
importMeta.dirname = dirname(importMeta.filename);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
if (
|
|
85
|
+
// v16.2.0+, v14.18.0+: Add support for WHATWG URL object to parentURL parameter.
|
|
86
|
+
resolveFunStr === "undefined" ||
|
|
87
|
+
// v20.0.0+, v18.19.0+"" This API now returns a string synchronously instead of a Promise.
|
|
88
|
+
resolveFunStr.startsWith("async")
|
|
89
|
+
// enable by --experimental-import-meta-resolve flag
|
|
90
|
+
) {
|
|
91
|
+
import_meta_ponyfill_esmodule = (importMeta) => {
|
|
92
|
+
if (!shimWs.has(importMeta)) {
|
|
93
|
+
shimWs.add(importMeta);
|
|
94
|
+
const importMetaUrlRequire = {
|
|
95
|
+
url: importMeta.url,
|
|
96
|
+
require: createRequire(importMeta.url),
|
|
97
|
+
};
|
|
98
|
+
// note: the parameter types must be explicitly annotated because
|
|
99
|
+
// `@types/node` also declares `ImportMeta.resolve`, which makes
|
|
100
|
+
// the contextual type an overload set that provides no inference
|
|
101
|
+
importMeta.resolve = function resolve(specifier, parentURL = importMeta.url) {
|
|
102
|
+
return pathToFileURL((importMetaUrlRequire.url === parentURL
|
|
103
|
+
? importMetaUrlRequire.require
|
|
104
|
+
: createRequire(parentURL)).resolve(specifier)).href;
|
|
105
|
+
};
|
|
106
|
+
commonShim(importMeta);
|
|
107
|
+
}
|
|
108
|
+
return importMeta;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
/// native support
|
|
113
|
+
import_meta_ponyfill_esmodule = (importMeta) => {
|
|
114
|
+
if (!shimWs.has(importMeta)) {
|
|
115
|
+
shimWs.add(importMeta);
|
|
116
|
+
commonShim(importMeta);
|
|
117
|
+
}
|
|
118
|
+
return importMeta;
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return import_meta_ponyfill_esmodule(importMeta);
|
|
122
|
+
}));
|
|
123
|
+
defineGlobalPonyfill("import-meta-ponyfill-esmodule", import_meta_ponyfill_esmodule);
|
|
124
|
+
export let import_meta_ponyfill = ((...args) => {
|
|
125
|
+
const _MODULE = (() => {
|
|
126
|
+
if (typeof require === "function" && typeof module === "object") {
|
|
127
|
+
return "commonjs";
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// eval("typeof import.meta");
|
|
131
|
+
return "esmodule";
|
|
132
|
+
}
|
|
133
|
+
})();
|
|
134
|
+
if (_MODULE === "commonjs") {
|
|
135
|
+
//@ts-ignore
|
|
136
|
+
import_meta_ponyfill = (r, m) => import_meta_ponyfill_commonjs(r, m);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
//@ts-ignore
|
|
140
|
+
import_meta_ponyfill = (im) => import_meta_ponyfill_esmodule(im);
|
|
141
|
+
}
|
|
142
|
+
//@ts-ignore
|
|
143
|
+
return import_meta_ponyfill(...args);
|
|
144
|
+
});
|
package/esm/mod.d.ts
CHANGED
|
@@ -11,7 +11,20 @@
|
|
|
11
11
|
*
|
|
12
12
|
* const profile = defineProfile({
|
|
13
13
|
* id: "assistant.basic",
|
|
14
|
-
* model: {
|
|
14
|
+
* model: {
|
|
15
|
+
* allow: ["gemini35FlashLite"],
|
|
16
|
+
* config: {
|
|
17
|
+
* gemini35FlashLite: {
|
|
18
|
+
* apiId: "gemini-3.5-flash-lite",
|
|
19
|
+
* thinking: { on: "high", off: "minimal" },
|
|
20
|
+
* thinkingLevels: ["minimal", "low", "medium", "high"],
|
|
21
|
+
* summaries: { on: "auto", off: "none" },
|
|
22
|
+
* maxOutputTokens: 8192,
|
|
23
|
+
* temperature: 1,
|
|
24
|
+
* keyBuiltins: [],
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* },
|
|
15
28
|
* tools: { allow: [] },
|
|
16
29
|
* inputs: { text: true },
|
|
17
30
|
* outputs: {},
|
|
@@ -27,11 +40,14 @@ import "./_dnt.polyfills.js";
|
|
|
27
40
|
export { publicError, TheorumError } from './src/guardrails/error.js';
|
|
28
41
|
export { PROJECT_ID_MAX, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, } from './src/guardrails/sanitize.js';
|
|
29
42
|
export { runTurn } from './src/kernel/engine/runner.js';
|
|
30
|
-
export {
|
|
43
|
+
export { CATALOG, clampThinkingLevel, clampThinkingLevelForApiId, getTool, listBuiltinIds, modelEntryByApiId, registerTools, requireModelSpec, resetTools, } from './src/kernel/registry/catalog.js';
|
|
44
|
+
export type { ProfileDefinition } from './src/kernel/registry/profiles.js';
|
|
45
|
+
export { clearProfiles, defineProfile, getProfile, hasProfile, listProfiles, registerProfile, registerProfiles, } from './src/kernel/registry/profiles.js';
|
|
31
46
|
export { projectProfile, resolveTurn } from './src/kernel/registry/resolve.js';
|
|
32
47
|
export { getStructured, registerStructured } from './src/kernel/registry/schemas.js';
|
|
33
48
|
export { executeTool } from './src/kernel/registry/tools.js';
|
|
34
49
|
export type * from './src/kernel/types.js';
|
|
35
50
|
export { jsonlSink, memorySink, noopSink, resolveTraceDir, sinkFromDir, writeTrace, } from './src/observability/trace.js';
|
|
36
51
|
export type { TraceRecord } from './src/observability/trace-record.js';
|
|
37
|
-
export {
|
|
52
|
+
export type { CreateProviderOptions, GeminiTransport, GeminiVault } from './src/providers/mod.js';
|
|
53
|
+
export { createProvider } from './src/providers/mod.js';
|