theorum 0.1.2 → 0.1.4

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 (150) hide show
  1. package/README.md +83 -27
  2. package/deno.json +57 -0
  3. package/deno.lock +469 -0
  4. package/docs/AGENT_PROFILE_CONTRACT.md +45 -18
  5. package/docs/CLI_SPEC.md +2 -2
  6. package/docs/SECRETS.md +27 -22
  7. package/mod.ts +95 -0
  8. package/package.json +37 -29
  9. package/src/cli/commands/profile.ts +57 -0
  10. package/src/cli/commands/run.ts +66 -0
  11. package/src/cli/commands/test.ts +195 -0
  12. package/src/cli/index.ts +161 -0
  13. package/src/cli/matrix/fixtures.ts +96 -0
  14. package/src/cli/matrix/synthesizer.ts +240 -0
  15. package/src/guardrails/error.ts +142 -0
  16. package/src/guardrails/injection.ts +254 -0
  17. package/src/guardrails/mod.ts +40 -0
  18. package/src/guardrails/quota.ts +87 -0
  19. package/src/guardrails/sanitize.ts +165 -0
  20. package/src/guardrails/sensitive.ts +96 -0
  21. package/src/host/mint-trace.ts +54 -0
  22. package/src/host/mod.ts +20 -0
  23. package/src/host/reply.ts +32 -0
  24. package/src/kernel/engine/assert.ts +29 -0
  25. package/src/kernel/engine/boundary.ts +75 -0
  26. package/src/kernel/engine/delta.ts +440 -0
  27. package/src/kernel/engine/hash.ts +11 -0
  28. package/src/kernel/engine/record.ts +8 -0
  29. package/src/kernel/engine/repair.ts +56 -0
  30. package/src/kernel/engine/runner/gates.ts +338 -0
  31. package/src/kernel/engine/runner/mod.ts +135 -0
  32. package/src/kernel/engine/runner/state.ts +31 -0
  33. package/src/kernel/engine/runner/steps.ts +183 -0
  34. package/src/kernel/engine/runner/stream.ts +83 -0
  35. package/src/kernel/engine/runner/tokens.ts +47 -0
  36. package/src/kernel/engine/runner/tools.ts +266 -0
  37. package/src/kernel/engine/runner.ts +7 -0
  38. package/src/kernel/engine/tree.ts +18 -0
  39. package/src/kernel/mod.ts +39 -0
  40. package/src/kernel/registry/attachments.ts +195 -0
  41. package/src/kernel/registry/catalog.ts +181 -0
  42. package/src/kernel/registry/ingress.ts +223 -0
  43. package/src/kernel/registry/profiles.ts +165 -0
  44. package/src/kernel/registry/provider-request.ts +31 -0
  45. package/src/kernel/registry/resolve.ts +290 -0
  46. package/src/kernel/registry/schemas.ts +29 -0
  47. package/src/kernel/registry/tools.ts +45 -0
  48. package/src/kernel/registry/vault.ts +26 -0
  49. package/src/kernel/types.ts +576 -0
  50. package/{esm/src/observability/mod.js → src/observability/mod.ts} +11 -2
  51. package/src/observability/spans.ts +70 -0
  52. package/src/observability/trace-attach.ts +96 -0
  53. package/src/observability/trace-record.ts +254 -0
  54. package/src/observability/trace-usage.ts +36 -0
  55. package/src/observability/trace.ts +143 -0
  56. package/src/presets/google.ts +147 -0
  57. package/src/presets/mod.ts +24 -0
  58. package/src/providers/create-provider.ts +64 -0
  59. package/src/providers/gemini-tape.ts +59 -0
  60. package/src/providers/google-tap.ts +57 -0
  61. package/src/providers/interactions.ts +182 -0
  62. package/src/providers/keys.ts +182 -0
  63. package/src/providers/mod.ts +12 -0
  64. package/src/providers/openrouter-mod.ts +16 -0
  65. package/src/providers/openrouter-payload.ts +232 -0
  66. package/src/providers/openrouter.ts +758 -0
  67. package/src/providers/pcm.ts +37 -0
  68. package/src/providers/provider.ts +206 -0
  69. package/src/providers/speech.ts +180 -0
  70. package/src/providers/sse.ts +59 -0
  71. package/esm/_dnt.polyfills.d.ts +0 -11
  72. package/esm/_dnt.polyfills.js +0 -15
  73. package/esm/_dnt.shims.d.ts +0 -5
  74. package/esm/_dnt.shims.js +0 -61
  75. package/esm/mod.d.ts +0 -37
  76. package/esm/mod.js +0 -35
  77. package/esm/package.json +0 -3
  78. package/esm/src/guardrails/error.d.ts +0 -35
  79. package/esm/src/guardrails/error.js +0 -116
  80. package/esm/src/guardrails/injection.d.ts +0 -12
  81. package/esm/src/guardrails/injection.js +0 -220
  82. package/esm/src/guardrails/keys.d.ts +0 -12
  83. package/esm/src/guardrails/keys.js +0 -132
  84. package/esm/src/guardrails/mod.d.ts +0 -14
  85. package/esm/src/guardrails/mod.js +0 -14
  86. package/esm/src/guardrails/sanitize.d.ts +0 -22
  87. package/esm/src/guardrails/sanitize.js +0 -133
  88. package/esm/src/guardrails/sensitive.d.ts +0 -12
  89. package/esm/src/guardrails/sensitive.js +0 -88
  90. package/esm/src/kernel/engine/boundary.d.ts +0 -10
  91. package/esm/src/kernel/engine/boundary.js +0 -55
  92. package/esm/src/kernel/engine/delta.d.ts +0 -8
  93. package/esm/src/kernel/engine/delta.js +0 -362
  94. package/esm/src/kernel/engine/hash.d.ts +0 -1
  95. package/esm/src/kernel/engine/hash.js +0 -9
  96. package/esm/src/kernel/engine/record.d.ts +0 -2
  97. package/esm/src/kernel/engine/record.js +0 -7
  98. package/esm/src/kernel/engine/repair.d.ts +0 -9
  99. package/esm/src/kernel/engine/repair.js +0 -38
  100. package/esm/src/kernel/engine/runner.d.ts +0 -14
  101. package/esm/src/kernel/engine/runner.js +0 -731
  102. package/esm/src/kernel/engine/tree.d.ts +0 -2
  103. package/esm/src/kernel/engine/tree.js +0 -17
  104. package/esm/src/kernel/mod.d.ts +0 -16
  105. package/esm/src/kernel/mod.js +0 -15
  106. package/esm/src/kernel/registry/catalog.d.ts +0 -24
  107. package/esm/src/kernel/registry/catalog.js +0 -213
  108. package/esm/src/kernel/registry/profiles.d.ts +0 -36
  109. package/esm/src/kernel/registry/profiles.js +0 -111
  110. package/esm/src/kernel/registry/resolve.d.ts +0 -20
  111. package/esm/src/kernel/registry/resolve.js +0 -235
  112. package/esm/src/kernel/registry/schemas.d.ts +0 -14
  113. package/esm/src/kernel/registry/schemas.js +0 -23
  114. package/esm/src/kernel/registry/tools.d.ts +0 -12
  115. package/esm/src/kernel/registry/tools.js +0 -36
  116. package/esm/src/kernel/types.d.ts +0 -497
  117. package/esm/src/kernel/types.js +0 -10
  118. package/esm/src/observability/mod.d.ts +0 -12
  119. package/esm/src/observability/spans.d.ts +0 -16
  120. package/esm/src/observability/spans.js +0 -56
  121. package/esm/src/observability/trace-attach.d.ts +0 -16
  122. package/esm/src/observability/trace-attach.js +0 -81
  123. package/esm/src/observability/trace-record.d.ts +0 -112
  124. package/esm/src/observability/trace-record.js +0 -140
  125. package/esm/src/observability/trace-usage.d.ts +0 -3
  126. package/esm/src/observability/trace-usage.js +0 -32
  127. package/esm/src/observability/trace.d.ts +0 -23
  128. package/esm/src/observability/trace.js +0 -121
  129. package/esm/src/providers/attachments.d.ts +0 -17
  130. package/esm/src/providers/attachments.js +0 -156
  131. package/esm/src/providers/gemini-tape.d.ts +0 -3
  132. package/esm/src/providers/gemini-tape.js +0 -46
  133. package/esm/src/providers/google-tap.d.ts +0 -3
  134. package/esm/src/providers/google-tap.js +0 -48
  135. package/esm/src/providers/interactions.d.ts +0 -5
  136. package/esm/src/providers/interactions.js +0 -127
  137. package/esm/src/providers/media.d.ts +0 -5
  138. package/esm/src/providers/media.js +0 -125
  139. package/esm/src/providers/mod.d.ts +0 -15
  140. package/esm/src/providers/mod.js +0 -13
  141. package/esm/src/providers/openrouter-payload.d.ts +0 -24
  142. package/esm/src/providers/openrouter-payload.js +0 -177
  143. package/esm/src/providers/openrouter.d.ts +0 -17
  144. package/esm/src/providers/openrouter.js +0 -332
  145. package/esm/src/providers/provider.d.ts +0 -13
  146. package/esm/src/providers/provider.js +0 -123
  147. package/esm/src/providers/sse.d.ts +0 -7
  148. package/esm/src/providers/sse.js +0 -53
  149. package/esm/src/providers/tts.d.ts +0 -24
  150. package/esm/src/providers/tts.js +0 -144
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Profile resolution for THEORUM turns.
3
+ *
4
+ * This module validates caller overrides, selects models and tools, normalizes
5
+ * input parts, and produces the provider request state consumed by `runTurn`.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { TheorumError } from '../../guardrails/error.ts';
11
+ import { sanitizeTurnRequest } from '../../guardrails/sanitize.ts';
12
+ import { mintCanary } from '../engine/boundary.ts';
13
+ import type {
14
+ BuiltinToolId,
15
+ CustomToolId,
16
+ ModelId,
17
+ ModelSpec,
18
+ Profile,
19
+ ProjectedProfile,
20
+ ResolvedGeneration,
21
+ StructuredSchemaId,
22
+ ThinkingLevel,
23
+ ToolId,
24
+ TurnRequest,
25
+ } from '../types.ts';
26
+ import {
27
+ CATALOG,
28
+ clampThinkingLevel,
29
+ getTool,
30
+ listBuiltinIds,
31
+ requireModelSpec,
32
+ } from './catalog.ts';
33
+ import {
34
+ assertImageGrounding,
35
+ assertSpeechRole,
36
+ resolveImageFormat,
37
+ resolveInputParts,
38
+ } from './ingress.ts';
39
+ import { getProfile } from './profiles.ts';
40
+ import { resolveGeminiBucket } from './vault.ts';
41
+
42
+ function applyBuiltinMutualExclusions(requested: BuiltinToolId[]): BuiltinToolId[] {
43
+ return requested.filter((id) => {
44
+ const conflicts = getTool(id)?.conflictsWith ?? [];
45
+ return !conflicts.some((other) => requested.includes(other));
46
+ });
47
+ }
48
+
49
+ function firstSelectKey(selectMap: Record<string, ModelId>): string | undefined {
50
+ const [key] = Object.keys(selectMap);
51
+ return key;
52
+ }
53
+
54
+ function lookupSelectId(profile: Profile, select?: string): ModelId | undefined {
55
+ const { select: selectMap } = profile.model;
56
+ if (!selectMap) {
57
+ return undefined;
58
+ }
59
+ let key = select;
60
+ if (!key) {
61
+ key = firstSelectKey(selectMap);
62
+ }
63
+ if (!key) {
64
+ return undefined;
65
+ }
66
+ return selectMap[key];
67
+ }
68
+
69
+ function pickModel(profile: Profile, select?: string): ModelId {
70
+ if (profile.model.select) {
71
+ const id = lookupSelectId(profile, select);
72
+ if (!(id && profile.model.allow.includes(id))) {
73
+ let label = '';
74
+ if (select) {
75
+ label = select;
76
+ }
77
+ throw new TheorumError(`Unknown model select '${label}' for ${profile.id}`);
78
+ }
79
+ return id;
80
+ }
81
+ const [only] = profile.model.allow;
82
+ if (!only) {
83
+ throw new TheorumError(`Profile ${profile.id} has no models`);
84
+ }
85
+ return only;
86
+ }
87
+
88
+ function thinkingFromControl(spec: ModelSpec, thinkingOn: boolean | undefined): ThinkingLevel {
89
+ if (thinkingOn) {
90
+ return spec.thinking.on;
91
+ }
92
+ return spec.thinking.off;
93
+ }
94
+
95
+ function pinnedLevel(
96
+ pinned: Record<string, ThinkingLevel>,
97
+ key: string | undefined,
98
+ ): ThinkingLevel | undefined {
99
+ if (!key) {
100
+ return undefined;
101
+ }
102
+ return pinned[key];
103
+ }
104
+
105
+ function thinkingFromPin(profile: Profile, select?: string): ThinkingLevel {
106
+ const pinned = profile.model.thinking;
107
+ if (typeof pinned === 'string') {
108
+ return pinned;
109
+ }
110
+ if (!pinned) {
111
+ throw new TheorumError(`Profile ${profile.id} must pin thinking or list it in controls`);
112
+ }
113
+ const fromSelect = pinnedLevel(pinned, select);
114
+ if (fromSelect) {
115
+ return fromSelect;
116
+ }
117
+ const fromFirst = pinnedLevel(pinned, firstSelectKey(profile.model.select ?? {}));
118
+ if (fromFirst) {
119
+ return fromFirst;
120
+ }
121
+ throw new TheorumError(`Profile ${profile.id} must pin thinking or list it in controls`);
122
+ }
123
+
124
+ function resolveThinking(
125
+ profile: Profile,
126
+ spec: ModelSpec,
127
+ thinkingOn: boolean | undefined,
128
+ select?: string,
129
+ ): ThinkingLevel {
130
+ const raw = profile.model.controls?.includes('thinking')
131
+ ? thinkingFromControl(spec, thinkingOn)
132
+ : thinkingFromPin(profile, select);
133
+ return clampThinkingLevel(spec, raw);
134
+ }
135
+
136
+ function resolveSummaries(
137
+ profile: Profile,
138
+ spec: ModelSpec,
139
+ thinkingOn: boolean | undefined,
140
+ ): 'auto' | 'none' {
141
+ if (profile.model.controls?.includes('thinking')) {
142
+ if (thinkingOn) {
143
+ return spec.summaries.on;
144
+ }
145
+ return spec.summaries.off;
146
+ }
147
+ return spec.summaries.on;
148
+ }
149
+
150
+ function isGatedOn(requested: Partial<Record<ToolId, boolean>> | undefined, id: ToolId): boolean {
151
+ if (!requested) {
152
+ return false;
153
+ }
154
+ return requested[id] === true;
155
+ }
156
+
157
+ function resolveBuiltins(
158
+ profile: Profile,
159
+ requested?: Partial<Record<ToolId, boolean>>,
160
+ ): BuiltinToolId[] {
161
+ const allowed = profile.tools.allow.filter(
162
+ (id): id is BuiltinToolId => CATALOG.tools[id]?.kind === 'builtin',
163
+ );
164
+ const picked = listBuiltinIds().filter((id) => allowed.includes(id) && isGatedOn(requested, id));
165
+ return applyBuiltinMutualExclusions(picked);
166
+ }
167
+
168
+ function resolveCustom(
169
+ profile: Profile,
170
+ requested?: Partial<Record<ToolId, boolean>>,
171
+ ): CustomToolId[] {
172
+ return profile.tools.allow.filter(
173
+ (id): id is CustomToolId => CATALOG.tools[id]?.kind === 'custom' && isGatedOn(requested, id),
174
+ );
175
+ }
176
+
177
+ function assertToolAllowed(profile: Profile, name: ToolId): void {
178
+ if (!profile.tools.allow.includes(name)) {
179
+ throw new TheorumError(`Tool '${name}' is not allowed on ${profile.id}`);
180
+ }
181
+ }
182
+
183
+ function resolveStructured(
184
+ profile: Profile,
185
+ slots?: Record<string, string>,
186
+ ): StructuredSchemaId | null {
187
+ const { structured } = profile.outputs;
188
+ if (!structured) {
189
+ return null;
190
+ }
191
+ if (typeof structured === 'string') {
192
+ return structured;
193
+ }
194
+ const value = slots?.[structured.by];
195
+ if (value) {
196
+ const mapped = structured.map[value];
197
+ if (mapped) {
198
+ return mapped;
199
+ }
200
+ }
201
+ return structured.fallback;
202
+ }
203
+
204
+ /** Resolve a host `TurnRequest` into provider-ready generation state. */
205
+ function resolveTurn(req: TurnRequest): {
206
+ profile: Profile;
207
+ generation: ResolvedGeneration;
208
+ } {
209
+ const safe = sanitizeTurnRequest(req);
210
+ const input = safe.input ?? {};
211
+ const profile = getProfile(safe.profile);
212
+ const model = pickModel(profile, safe.select);
213
+ const spec = requireModelSpec(profile, model);
214
+ const thinkingOn = safe.thinking === true;
215
+ const builtins = resolveBuiltins(profile, safe.tools);
216
+ assertImageGrounding(profile, model, builtins);
217
+ assertSpeechRole(profile);
218
+ const geminiBucket =
219
+ profile.model.provider === 'google'
220
+ ? resolveGeminiBucket(profile.model.key ?? 'freeA', spec, builtins)
221
+ : undefined;
222
+ return {
223
+ profile,
224
+ generation: {
225
+ model,
226
+ apiId: spec.apiId,
227
+ openRouterId: spec.openRouterId,
228
+ previousInteractionId: safe.previousInteractionId,
229
+ store: safe.store,
230
+ thinking: resolveThinking(profile, spec, thinkingOn, safe.select),
231
+ summaries: resolveSummaries(profile, spec, thinkingOn),
232
+ maxOutputTokens: spec.maxOutputTokens,
233
+ temperature: spec.temperature,
234
+ builtins,
235
+ custom: resolveCustom(profile, safe.tools),
236
+ dynamicTools: safe.dynamicTools,
237
+ dynamicToolLoader: safe.dynamicToolLoader,
238
+ sessionPermissions: safe.sessionPermissions,
239
+ history: input.history,
240
+ maxSteps: profile.model.maxSteps ?? 1,
241
+ structured: resolveStructured(profile, input.slots),
242
+ image: resolveImageFormat(profile, model, input.slots),
243
+ speech: profile.outputs.speech,
244
+ input: resolveInputParts(profile, model, safe),
245
+ geminiBucket,
246
+ canary: profile.guardrails.canary !== false ? mintCanary() : '',
247
+ },
248
+ };
249
+ }
250
+
251
+ function primaryImageSpec(profile: Profile) {
252
+ return profile.outputs.image;
253
+ }
254
+
255
+ /** Project a registered profile into a safe host/UI inspection object. */
256
+ function projectProfile(id: Profile['id']): ProjectedProfile {
257
+ const profile = getProfile(id);
258
+ const { model, identity, tools, inputs, outputs } = profile;
259
+ const { select, allow, maxSteps, controls } = model;
260
+ const { handle, chat } = identity;
261
+ const { slots } = inputs;
262
+ return {
263
+ id: profile.id,
264
+ handle,
265
+ chat: chat !== false,
266
+ maxSteps: maxSteps ?? 1,
267
+ models: allow,
268
+ select: select ?? null,
269
+ controls: controls ?? [],
270
+ tools: tools.allow.map((name) => ({
271
+ name,
272
+ ...(CATALOG.tools[name] ?? { kind: 'custom' as const, ui: true }),
273
+ })),
274
+ inputs,
275
+ slots: slots ?? {},
276
+ outputs,
277
+ image: primaryImageSpec(profile),
278
+ };
279
+ }
280
+
281
+ function pickSystemRole(profile: Profile, requested?: string): string {
282
+ const { identity } = profile;
283
+ const { handle, systemByRole } = identity;
284
+ if (requested && systemByRole && Object.hasOwn(systemByRole, requested)) {
285
+ return requested;
286
+ }
287
+ return handle;
288
+ }
289
+
290
+ export { assertToolAllowed, pickSystemRole, projectProfile, resolveTurn };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Runtime structured-output schema registry.
3
+ *
4
+ * Host apps register schemas by id, then reference those ids from profile output
5
+ * declarations.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { TheorumError } from '../../guardrails/error.ts';
11
+ import type { StructuredSpec } from '../types.ts';
12
+
13
+ const schemas = new Map<string, StructuredSpec>();
14
+
15
+ /** Register a host-owned structured output schema. */
16
+ function registerStructured(id: string, spec: StructuredSpec): void {
17
+ schemas.set(id, spec);
18
+ }
19
+
20
+ /** Fetch a registered structured output schema or throw a `TheorumError`. */
21
+ function getStructured(id: string): StructuredSpec {
22
+ const spec = schemas.get(id);
23
+ if (!spec) {
24
+ throw new TheorumError(`Unknown structured schema '${id}'`);
25
+ }
26
+ return spec;
27
+ }
28
+
29
+ export { getStructured, registerStructured };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Generic built-in custom tool executor.
3
+ *
4
+ * Static custom tools are intentionally minimal. Application-specific tools
5
+ * should be supplied as dynamic declarations with host-owned handlers.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import type { CustomToolId, Profile, ToolEnvelope } from '../types.ts';
11
+ import { getTool } from './catalog.ts';
12
+ import { assertToolAllowed } from './resolve.ts';
13
+
14
+ function executeAskUser(args: Record<string, unknown>): ToolEnvelope {
15
+ const { kind, prompt } = args;
16
+ if (kind !== 'confirm' && kind !== 'choice' && kind !== 'text') {
17
+ return { status: 'error', finding: 'askUser.kind must be confirm, choice, or text' };
18
+ }
19
+ if (typeof prompt !== 'string' || !prompt.trim()) {
20
+ return { status: 'error', finding: 'askUser.prompt is required' };
21
+ }
22
+ return {
23
+ status: 'pause',
24
+ finding: prompt,
25
+ data: { ...args, schema: getTool('askUser')?.schema },
26
+ };
27
+ }
28
+
29
+ /** Execute a static tool after enforcing the profile allowlist. */
30
+ function executeTool(
31
+ profile: Profile,
32
+ name: CustomToolId,
33
+ args: Record<string, unknown>,
34
+ ): ToolEnvelope {
35
+ assertToolAllowed(profile, name);
36
+ if (name === 'askUser') {
37
+ return executeAskUser(args);
38
+ }
39
+ return {
40
+ status: 'error',
41
+ finding: `Tool '${name}' has no kernel executor; pass a dynamic tool handler instead.`,
42
+ };
43
+ }
44
+
45
+ export { executeTool };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Gemini vault slot selection for Google Interactions transport.
3
+ *
4
+ * Host-owned policy over `ModelSpec.key` / `keyBuiltins`. Pure; no network.
5
+ *
6
+ * @module
7
+ */
8
+
9
+ import type { BuiltinToolId, GeminiBucket, GeminiFreeBucket, ModelSpec } from '../types.ts';
10
+
11
+ /** Pick the vault slot for a turn from profile key, model pin, and builtins. */
12
+ function resolveGeminiBucket(
13
+ profileKey: GeminiFreeBucket,
14
+ spec: ModelSpec,
15
+ builtins: BuiltinToolId[],
16
+ ): GeminiBucket {
17
+ if (spec.key) {
18
+ return spec.key;
19
+ }
20
+ if (builtins.some((id) => !spec.keyBuiltins.includes(id))) {
21
+ return 'paid';
22
+ }
23
+ return profileKey;
24
+ }
25
+
26
+ export { resolveGeminiBucket };