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.
Files changed (99) hide show
  1. package/README.md +62 -27
  2. package/docs/AGENT_PROFILE_CONTRACT.md +45 -18
  3. package/docs/CLI_SPEC.md +2 -2
  4. package/docs/SECRETS.md +27 -22
  5. package/esm/_dnt.polyfills.d.ts +99 -0
  6. package/esm/_dnt.polyfills.js +130 -1
  7. package/esm/mod.d.ts +19 -3
  8. package/esm/mod.js +17 -3
  9. package/esm/src/cli/commands/profile.d.ts +2 -0
  10. package/esm/src/cli/commands/profile.js +53 -0
  11. package/esm/src/cli/commands/run.d.ts +10 -0
  12. package/esm/src/cli/commands/run.js +55 -0
  13. package/esm/src/cli/commands/test.d.ts +15 -0
  14. package/esm/src/cli/commands/test.js +140 -0
  15. package/esm/src/cli/index.d.ts +4 -0
  16. package/esm/src/cli/index.js +148 -0
  17. package/esm/src/cli/matrix/fixtures.d.ts +13 -0
  18. package/esm/src/cli/matrix/fixtures.js +77 -0
  19. package/esm/src/cli/matrix/synthesizer.d.ts +29 -0
  20. package/esm/src/cli/matrix/synthesizer.js +187 -0
  21. package/esm/src/guardrails/error.d.ts +1 -3
  22. package/esm/src/guardrails/error.js +4 -6
  23. package/esm/src/guardrails/mod.d.ts +1 -1
  24. package/esm/src/guardrails/mod.js +1 -1
  25. package/esm/src/guardrails/sanitize.js +1 -1
  26. package/esm/src/host/mint-trace.d.ts +27 -0
  27. package/esm/src/host/mint-trace.js +33 -0
  28. package/esm/src/host/mod.d.ts +13 -0
  29. package/esm/src/host/mod.js +12 -0
  30. package/esm/src/host/reply.d.ts +14 -0
  31. package/esm/src/host/reply.js +27 -0
  32. package/esm/src/kernel/engine/delta.js +31 -4
  33. package/esm/src/kernel/engine/repair.d.ts +1 -3
  34. package/esm/src/kernel/engine/repair.js +1 -1
  35. package/esm/src/kernel/engine/runner/gates.d.ts +4 -0
  36. package/esm/src/kernel/engine/runner/gates.js +206 -0
  37. package/esm/src/kernel/engine/runner/mod.d.ts +14 -0
  38. package/esm/src/kernel/engine/runner/mod.js +103 -0
  39. package/esm/src/kernel/engine/runner/state.d.ts +16 -0
  40. package/esm/src/kernel/engine/runner/state.js +8 -0
  41. package/esm/src/kernel/engine/runner/steps.d.ts +15 -0
  42. package/esm/src/kernel/engine/runner/steps.js +119 -0
  43. package/esm/src/kernel/engine/runner/stream.d.ts +11 -0
  44. package/esm/src/kernel/engine/runner/stream.js +65 -0
  45. package/esm/src/kernel/engine/runner/tokens.d.ts +3 -0
  46. package/esm/src/kernel/engine/runner/tokens.js +38 -0
  47. package/esm/src/kernel/engine/runner/tools.d.ts +13 -0
  48. package/esm/src/kernel/engine/runner/tools.js +197 -0
  49. package/esm/src/kernel/engine/runner.d.ts +1 -9
  50. package/esm/src/kernel/engine/runner.js +1 -726
  51. package/esm/src/kernel/mod.d.ts +3 -1
  52. package/esm/src/kernel/mod.js +2 -1
  53. package/esm/src/{providers → kernel/registry}/attachments.d.ts +2 -3
  54. package/esm/src/{providers → kernel/registry}/attachments.js +6 -6
  55. package/esm/src/kernel/registry/catalog.d.ts +28 -21
  56. package/esm/src/kernel/registry/catalog.js +55 -133
  57. package/esm/src/kernel/registry/ingress.d.ts +13 -0
  58. package/esm/src/kernel/registry/ingress.js +163 -0
  59. package/esm/src/kernel/registry/profiles.d.ts +2 -4
  60. package/esm/src/kernel/registry/profiles.js +14 -5
  61. package/esm/src/kernel/registry/provider-request.d.ts +4 -0
  62. package/esm/src/kernel/registry/provider-request.js +25 -0
  63. package/esm/src/kernel/registry/resolve.d.ts +0 -1
  64. package/esm/src/kernel/registry/resolve.js +35 -53
  65. package/esm/src/kernel/registry/tools.js +2 -2
  66. package/esm/src/kernel/registry/vault.d.ts +11 -0
  67. package/esm/src/kernel/registry/vault.js +18 -0
  68. package/esm/src/kernel/types.d.ts +102 -78
  69. package/esm/src/observability/trace-attach.js +6 -20
  70. package/esm/src/presets/google.d.ts +50 -0
  71. package/esm/src/presets/google.js +96 -0
  72. package/esm/src/presets/mod.d.ts +11 -0
  73. package/esm/src/presets/mod.js +10 -0
  74. package/esm/src/providers/create-provider.d.ts +29 -0
  75. package/esm/src/providers/create-provider.js +38 -0
  76. package/esm/src/providers/gemini-tape.d.ts +1 -2
  77. package/esm/src/providers/gemini-tape.js +1 -1
  78. package/esm/src/providers/interactions.js +41 -16
  79. package/esm/src/{guardrails → providers}/keys.d.ts +10 -3
  80. package/esm/src/{guardrails → providers}/keys.js +10 -13
  81. package/esm/src/providers/mod.d.ts +4 -7
  82. package/esm/src/providers/mod.js +2 -5
  83. package/esm/src/providers/openrouter-mod.d.ts +13 -0
  84. package/esm/src/providers/openrouter-mod.js +12 -0
  85. package/esm/src/providers/openrouter-payload.d.ts +11 -3
  86. package/esm/src/providers/openrouter-payload.js +28 -26
  87. package/esm/src/providers/openrouter.d.ts +5 -7
  88. package/esm/src/providers/openrouter.js +488 -231
  89. package/esm/src/providers/pcm.d.ts +7 -0
  90. package/esm/src/providers/pcm.js +35 -0
  91. package/esm/src/providers/provider.d.ts +3 -1
  92. package/esm/src/providers/provider.js +45 -2
  93. package/esm/src/providers/speech.d.ts +23 -0
  94. package/esm/src/providers/speech.js +125 -0
  95. package/package.json +20 -3
  96. package/esm/src/providers/media.d.ts +0 -5
  97. package/esm/src/providers/media.js +0 -125
  98. package/esm/src/providers/tts.d.ts +0 -24
  99. package/esm/src/providers/tts.js +0 -144
@@ -7,22 +7,17 @@
7
7
  * @module
8
8
  */
9
9
  import { TheorumError } from '../../guardrails/error.js';
10
- import { resolveGeminiBucket } from '../../guardrails/keys.js';
11
10
  import { sanitizeTurnRequest } from '../../guardrails/sanitize.js';
12
- import { assertImageGrounding, resolveImageFormat, resolveInputParts, } from '../../providers/media.js';
13
11
  import { mintCanary } from '../engine/boundary.js';
14
- import { CATALOG, clampThinkingLevel, modelEntry } from './catalog.js';
12
+ import { CATALOG, clampThinkingLevel, getTool, listBuiltinIds, requireModelSpec, } from './catalog.js';
13
+ import { assertImageGrounding, assertSpeechRole, resolveImageFormat, resolveInputParts, } from './ingress.js';
15
14
  import { getProfile } from './profiles.js';
16
- const BUILTINS = ['googleSearch', 'googleMaps', 'urlContext'];
15
+ import { resolveGeminiBucket } from './vault.js';
17
16
  function applyBuiltinMutualExclusions(requested) {
18
- const search = requested.includes('googleSearch');
19
- const maps = requested.includes('googleMaps');
20
- const urlContext = requested.includes('urlContext');
21
- // Google Interactions API: google_maps cannot be combined with google_search or url_context
22
- if (maps && (search || urlContext)) {
23
- return requested.filter((id) => id !== 'googleMaps');
24
- }
25
- return requested;
17
+ return requested.filter((id) => {
18
+ const conflicts = getTool(id)?.conflictsWith ?? [];
19
+ return !conflicts.some((other) => requested.includes(other));
20
+ });
26
21
  }
27
22
  function firstSelectKey(selectMap) {
28
23
  const [key] = Object.keys(selectMap);
@@ -60,12 +55,11 @@ function pickModel(profile, select) {
60
55
  }
61
56
  return only;
62
57
  }
63
- function thinkingFromControl(modelId, thinkingOn) {
64
- const catalog = modelEntry(modelId);
58
+ function thinkingFromControl(spec, thinkingOn) {
65
59
  if (thinkingOn) {
66
- return catalog.thinking.on;
60
+ return spec.thinking.on;
67
61
  }
68
- return catalog.thinking.off;
62
+ return spec.thinking.off;
69
63
  }
70
64
  function pinnedLevel(pinned, key) {
71
65
  if (!key) {
@@ -91,25 +85,20 @@ function thinkingFromPin(profile, select) {
91
85
  }
92
86
  throw new TheorumError(`Profile ${profile.id} must pin thinking or list it in controls`);
93
87
  }
94
- function resolveThinking(profile, modelId, thinkingOn, select) {
88
+ function resolveThinking(profile, spec, thinkingOn, select) {
95
89
  const raw = profile.model.controls?.includes('thinking')
96
- ? thinkingFromControl(modelId, thinkingOn)
90
+ ? thinkingFromControl(spec, thinkingOn)
97
91
  : thinkingFromPin(profile, select);
98
- return clampThinkingLevel(modelId, raw);
92
+ return clampThinkingLevel(spec, raw);
99
93
  }
100
- function resolveSummaries(profile, modelId, thinkingOn) {
101
- const override = profile.model.override?.[modelId]?.summaries;
102
- if (override) {
103
- return override;
104
- }
105
- const entry = modelEntry(modelId);
94
+ function resolveSummaries(profile, spec, thinkingOn) {
106
95
  if (profile.model.controls?.includes('thinking')) {
107
96
  if (thinkingOn) {
108
- return entry.summaries.on;
97
+ return spec.summaries.on;
109
98
  }
110
- return entry.summaries.off;
99
+ return spec.summaries.off;
111
100
  }
112
- return entry.summaries.on;
101
+ return spec.summaries.on;
113
102
  }
114
103
  function isGatedOn(requested, id) {
115
104
  if (!requested) {
@@ -119,7 +108,7 @@ function isGatedOn(requested, id) {
119
108
  }
120
109
  function resolveBuiltins(profile, requested) {
121
110
  const allowed = profile.tools.allow.filter((id) => CATALOG.tools[id]?.kind === 'builtin');
122
- const picked = BUILTINS.filter((id) => allowed.includes(id) && isGatedOn(requested, id));
111
+ const picked = listBuiltinIds().filter((id) => allowed.includes(id) && isGatedOn(requested, id));
123
112
  return applyBuiltinMutualExclusions(picked);
124
113
  }
125
114
  function resolveCustom(profile, requested) {
@@ -147,34 +136,32 @@ function resolveStructured(profile, slots) {
147
136
  }
148
137
  return structured.fallback;
149
138
  }
150
- function generationLimits(profile, model) {
151
- const catalog = modelEntry(model);
152
- const ov = profile.model.override?.[model];
153
- return {
154
- maxOutputTokens: ov?.maxOutputTokens ?? catalog.maxOutputTokens,
155
- temperature: ov?.temperature ?? catalog.temperature,
156
- };
157
- }
158
139
  /** Resolve a host `TurnRequest` into provider-ready generation state. */
159
140
  function resolveTurn(req) {
160
141
  const safe = sanitizeTurnRequest(req);
161
142
  const input = safe.input ?? {};
162
143
  const profile = getProfile(safe.profile);
163
144
  const model = pickModel(profile, safe.select);
145
+ const spec = requireModelSpec(profile, model);
164
146
  const thinkingOn = safe.thinking === true;
165
- const limits = generationLimits(profile, model);
166
147
  const builtins = resolveBuiltins(profile, safe.tools);
167
- assertImageGrounding(model, builtins);
148
+ assertImageGrounding(profile, model, builtins);
149
+ assertSpeechRole(profile);
150
+ const geminiBucket = profile.model.provider === 'google'
151
+ ? resolveGeminiBucket(profile.model.key ?? 'freeA', spec, builtins)
152
+ : undefined;
168
153
  return {
169
154
  profile,
170
155
  generation: {
171
156
  model,
157
+ apiId: spec.apiId,
158
+ openRouterId: spec.openRouterId,
172
159
  previousInteractionId: safe.previousInteractionId,
173
160
  store: safe.store,
174
- thinking: resolveThinking(profile, model, thinkingOn, safe.select),
175
- summaries: resolveSummaries(profile, model, thinkingOn),
176
- maxOutputTokens: limits.maxOutputTokens,
177
- temperature: limits.temperature,
161
+ thinking: resolveThinking(profile, spec, thinkingOn, safe.select),
162
+ summaries: resolveSummaries(profile, spec, thinkingOn),
163
+ maxOutputTokens: spec.maxOutputTokens,
164
+ temperature: spec.temperature,
178
165
  builtins,
179
166
  custom: resolveCustom(profile, safe.tools),
180
167
  dynamicTools: safe.dynamicTools,
@@ -184,21 +171,16 @@ function resolveTurn(req) {
184
171
  maxSteps: profile.model.maxSteps ?? 1,
185
172
  structured: resolveStructured(profile, input.slots),
186
173
  image: resolveImageFormat(profile, model, input.slots),
187
- voice: profile.outputs.voice,
174
+ speech: profile.outputs.speech,
188
175
  input: resolveInputParts(profile, model, safe),
189
- geminiBucket: resolveGeminiBucket(profile.model.key ?? 'freeA', model, builtins),
176
+ geminiBucket,
190
177
  canary: profile.guardrails.canary !== false ? mintCanary() : '',
191
178
  },
192
179
  };
193
180
  }
194
- function primaryImageSpec(allow) {
195
- const [primary] = allow;
196
- if (!primary) {
197
- return undefined;
198
- }
199
- return modelEntry(primary).image;
181
+ function primaryImageSpec(profile) {
182
+ return profile.outputs.image;
200
183
  }
201
- /** UI projection: catalog ∩ profile. Swatches are not included. */
202
184
  /** Project a registered profile into a safe host/UI inspection object. */
203
185
  function projectProfile(id) {
204
186
  const profile = getProfile(id);
@@ -221,7 +203,7 @@ function projectProfile(id) {
221
203
  inputs,
222
204
  slots: slots ?? {},
223
205
  outputs,
224
- image: primaryImageSpec(allow),
206
+ image: primaryImageSpec(profile),
225
207
  };
226
208
  }
227
209
  function pickSystemRole(profile, requested) {
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { CATALOG } from './catalog.js';
9
+ import { getTool } from './catalog.js';
10
10
  import { assertToolAllowed } from './resolve.js';
11
11
  function executeAskUser(args) {
12
12
  const { kind, prompt } = args;
@@ -19,7 +19,7 @@ function executeAskUser(args) {
19
19
  return {
20
20
  status: 'pause',
21
21
  finding: prompt,
22
- data: { ...args, schema: CATALOG.tools.askUser.schema },
22
+ data: { ...args, schema: getTool('askUser')?.schema },
23
23
  };
24
24
  }
25
25
  /** Execute a static tool after enforcing the profile allowlist. */
@@ -0,0 +1,11 @@
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
+ import type { BuiltinToolId, GeminiBucket, GeminiFreeBucket, ModelSpec } from '../types.js';
9
+ /** Pick the vault slot for a turn from profile key, model pin, and builtins. */
10
+ declare function resolveGeminiBucket(profileKey: GeminiFreeBucket, spec: ModelSpec, builtins: BuiltinToolId[]): GeminiBucket;
11
+ export { resolveGeminiBucket };
@@ -0,0 +1,18 @@
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
+ /** Pick the vault slot for a turn from profile key, model pin, and builtins. */
9
+ function resolveGeminiBucket(profileKey, spec, builtins) {
10
+ if (spec.key) {
11
+ return spec.key;
12
+ }
13
+ if (builtins.some((id) => !spec.keyBuiltins.includes(id))) {
14
+ return 'paid';
15
+ }
16
+ return profileKey;
17
+ }
18
+ export { resolveGeminiBucket };
@@ -8,17 +8,15 @@
8
8
  * @module
9
9
  */
10
10
  /** Model reasoning effort level normalized across provider adapters. */
11
- export type ThinkingLevel = 'minimal' | 'low' | 'medium' | 'high';
12
- /** Built-in model aliases included in THEORUM's generic model catalog. */
13
- export type StandardModelId = 'gemini31FlashLite' | 'gemini31ProPreview' | 'gemini35FlashLite' | 'gemini31FlashLiteImage' | 'gemini31FlashTts' | 'sonar';
14
- /** Any model id a host profile may allow, including app-provided custom ids. */
15
- export type ModelId = StandardModelId | (string & {});
16
- /** Provider-native tools THEORUM can project into supported provider payloads. */
17
- export type BuiltinToolId = 'googleSearch' | 'googleMaps' | 'urlContext';
18
- /** Minimal built-in custom tool available to host apps for user interaction pauses. */
19
- export type StandardCustomToolId = 'askUser';
20
- /** Host-owned custom tool id. */
21
- export type CustomToolId = StandardCustomToolId | (string & {});
11
+ export type ThinkingLevel = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
12
+ /** Any host-declared model id. */
13
+ export type ModelId = string;
14
+ /** Provider-projected builtin tool id (registered by presets/adapters). */
15
+ export type BuiltinToolId = string;
16
+ /** Harness custom tool that ships with THEORUM. */
17
+ export type HarnessToolId = 'askUser';
18
+ /** Host-owned or harness custom tool id. */
19
+ export type CustomToolId = HarnessToolId | (string & {});
22
20
  /** Any tool id accepted by profile allowlists and per-turn gates. */
23
21
  export type ToolId = BuiltinToolId | CustomToolId;
24
22
  /** Id of a host-registered structured output schema. */
@@ -35,10 +33,23 @@ export type GeminiFreeBucket = Exclude<GeminiBucket, 'paid'>;
35
33
  export type ChatRole = 'system' | 'user' | 'assistant';
36
34
  /** Profile-level control a caller may toggle at turn time. */
37
35
  export type ControlId = 'thinking';
38
- /** Native image size supported by the generic image response spec. */
39
- export type ImageSize = '1K';
40
- /** Native image aspect ratio accepted by image-capable model specs. */
41
- export type ImageAspectRatio = '1:1' | '3:2' | '2:3' | '3:4' | '4:3' | '4:5' | '5:4' | '9:16' | '16:9' | '21:9';
36
+ /**
37
+ * Image-role output pins owned by the host profile.
38
+ * The image model itself lives in `model.allow` / `model.config`.
39
+ * Aspect/size/mime values are host strings (presets/apps own the vocabularies).
40
+ */
41
+ export interface ProfileImageSpec {
42
+ /** Default aspect ratio when the turn does not set `slots.aspectRatio`. */
43
+ aspectRatio?: string;
44
+ /** Default size / resolution when the turn does not set `slots.size`. */
45
+ size?: string;
46
+ /** Output MIME for generated images. */
47
+ mimeType?: string;
48
+ /** When false, grounding builtins are rejected on this profile. */
49
+ allowsGrounding?: boolean;
50
+ /** Cap on reference images in one turn. */
51
+ maxInputImages?: number;
52
+ }
42
53
  /** Public event types emitted by `runTurn` and provider adapters. */
43
54
  export type TurnEventType = 'thought' | 'text' | 'tool' | 'structured' | 'media' | 'grounding' | 'evidence' | 'tokens' | 'done' | 'error';
44
55
  /** Provider thinking levels used when a boolean thinking control is on or off. */
@@ -51,44 +62,48 @@ export interface SummaryMap {
51
62
  on: 'auto' | 'none';
52
63
  off: 'auto' | 'none';
53
64
  }
54
- /** Native image generation on Interactions (`response_format.type = image`). */
55
- export interface ImageModelSpec {
56
- maxInputImages: number;
57
- inputMimes: string[];
58
- sizes: ImageSize[];
59
- aspectRatios: ImageAspectRatio[];
60
- outputMime: string;
61
- allowsGrounding: boolean;
62
- }
63
- /** Static metadata THEORUM needs to safely call a model id. */
64
- export interface ModelCatalogEntry {
65
+ /** Host-declared metadata THEORUM needs to call a model safely. */
66
+ export interface ModelSpec {
65
67
  apiId: string;
66
68
  /** Provider-native id for OpenRouter-compatible gateways. Defaults to `google/${apiId}`. */
67
69
  openRouterId?: string;
68
70
  thinking: ThinkingMap;
69
- /** Levels this model accepts on Interactions. Illegal values 400. */
71
+ /** Levels this model accepts. Illegal values are clamped via `thinkingLevels`. */
70
72
  thinkingLevels: ThinkingLevel[];
71
73
  summaries: SummaryMap;
72
74
  maxOutputTokens: number;
73
75
  temperature: number;
74
- /** Builtins that may run on the profile's free key. Anything else is paid. */
75
- freeBuiltins: BuiltinToolId[];
76
- image?: ImageModelSpec;
77
- }
78
- /** Static metadata for built-in and generic custom tools. */
76
+ /**
77
+ * Builtins that may use `profile.model.key`.
78
+ * Any other enabled builtin selects the overflow vault slot (`paid`).
79
+ * Host-owned policy — THEORUM does not infer tool pricing.
80
+ */
81
+ keyBuiltins: BuiltinToolId[];
82
+ /**
83
+ * Optional vault slot for this model. When set, overrides `profile.model.key`
84
+ * (and builtin routing). Host-owned — e.g. pin image models to `paid`.
85
+ */
86
+ key?: GeminiBucket;
87
+ }
88
+ /** Static metadata for harness, preset, and host-registered tools. */
79
89
  export interface ToolCatalogEntry {
80
90
  kind: 'builtin' | 'custom';
81
91
  ui: boolean;
82
92
  schema?: Record<string, unknown>;
93
+ /** Interactions API `tools[].type` when this builtin is projected. */
94
+ interactionsType?: string;
95
+ /** OpenRouter plugin id enabled when this builtin is on. */
96
+ openRouterPlugin?: string;
97
+ /** Drop this builtin when any listed sibling builtin is also requested. */
98
+ conflictsWith?: ToolId[];
83
99
  }
84
100
  /** Host-registered structured output schema and enforcement mode. */
85
101
  export interface StructuredSpec {
86
102
  enforced: 'responseFormat' | 'prompt';
87
103
  jsonSchema?: Record<string, unknown>;
88
104
  }
89
- /** In-memory model and tool catalog shape. */
105
+ /** In-memory tool catalog shape. */
90
106
  export interface Catalog {
91
- models: Record<ModelId, ModelCatalogEntry>;
92
107
  tools: Record<ToolId, ToolCatalogEntry>;
93
108
  }
94
109
  /** Per-turn file, byte, and MIME-specific input limits. */
@@ -114,16 +129,6 @@ export interface StructuredBySlot {
114
129
  map: Record<string, string>;
115
130
  fallback: string;
116
131
  }
117
- /** Legacy-compatible model selection block used by profile builders. */
118
- export interface ProfileModels {
119
- allow: ModelId[];
120
- select?: Record<string, ModelId>;
121
- /** Pinned level when `thinking` is not in `controls`. */
122
- thinking?: ThinkingLevel | Record<string, ThinkingLevel>;
123
- override?: Partial<Record<ModelId, Partial<Pick<ModelCatalogEntry, 'maxOutputTokens' | 'temperature'> & {
124
- summaries?: 'auto' | 'none';
125
- }>>>;
126
- }
127
132
  /** Result returned by a profile output validator. */
128
133
  export interface ValidationResult {
129
134
  isValid: boolean;
@@ -140,14 +145,26 @@ export interface ProfileValidationSpec {
140
145
  maxRetries?: number;
141
146
  repairGuidance?: string;
142
147
  }
143
- /** Audio container emitted by the OpenRouter TTS adapter. */
144
- export type OpenRouterAudioFormat = 'pcm' | 'mp3';
145
- /** Voice name passed through to OpenRouter-compatible TTS models. */
146
- export type OpenRouterTtsVoice = 'Zephyr' | 'Puck' | 'Charon' | 'Kore' | 'Fenrir' | 'Leda' | 'Orus' | 'Aoede' | 'Callirrhoe' | 'Autonoe' | 'Enceladus' | 'Iapetus' | 'Umbriel' | 'Algieba' | 'Despina' | 'Erinome' | 'Algenib' | 'Rasalgethi' | 'Laomedeia' | 'Achernar' | 'Alnilam' | 'Schedar' | 'Gacrux' | 'Pulcherrima' | 'Achird' | 'Zubenelgenubi' | 'Vindemiatrix' | 'Sadachbia' | 'Sadaltager' | 'Sulafat' | (string & {});
147
- /** Voice output configuration owned by the host profile. */
148
- export interface ProfileVoiceSpec {
149
- voice?: OpenRouterTtsVoice;
150
- responseFormat?: OpenRouterAudioFormat;
148
+ /**
149
+ * Audio container for speech generation output.
150
+ * - `openAi` speech (`/audio/speech`): sent as wire `response_format`.
151
+ * - `geminiInteractions`: only `pcm` (or omit). Google returns PCM; THEORUM emits WAV.
152
+ * `mp3` is rejected at resolve.
153
+ */
154
+ export type SpeechAudioFormat = 'pcm' | 'mp3';
155
+ /**
156
+ * Speech-role output pins owned by the host profile.
157
+ * The speech model itself lives in `model.allow` / `model.config`.
158
+ * Namespaced under `outputs.speech` so `voice` here is the TTS voice id,
159
+ * not ingress audio (`inputs.voice`).
160
+ */
161
+ export interface ProfileSpeechSpec {
162
+ voice?: string;
163
+ /**
164
+ * Output container. `pcm` (default) → WAV media on both transports.
165
+ * `mp3` requires `protocol: 'openAi'` speech; rejected on Interactions.
166
+ */
167
+ format?: SpeechAudioFormat;
151
168
  }
152
169
  /** Stream delivery controls enforced by the kernel. */
153
170
  export interface ProfileStreamingSpec {
@@ -194,17 +211,15 @@ export interface ProfileGuardrailsSpec {
194
211
  export interface ProfileModelSpec {
195
212
  protocol: 'geminiInteractions' | 'openAi';
196
213
  provider: 'google' | 'openrouter';
214
+ /** Ids this profile may select. Each id must exist in `config`. */
197
215
  allow: ModelId[];
216
+ /** Host-owned wire config keyed by the same ids used in `allow` / `select`. */
217
+ config: Record<ModelId, ModelSpec>;
198
218
  select?: Record<string, ModelId>;
199
219
  thinking?: ThinkingLevel | Record<string, ThinkingLevel>;
200
220
  controls?: ControlId[];
201
221
  maxSteps?: number;
202
222
  key?: GeminiFreeBucket;
203
- override?: Record<string, {
204
- maxOutputTokens?: number;
205
- temperature?: number;
206
- summaries?: 'auto' | 'none';
207
- }>;
208
223
  }
209
224
  /** Text, attachment, voice, slot, and size rules for a profile. */
210
225
  export interface ProfileInputsSpec {
@@ -221,11 +236,13 @@ export interface ProfileInputsSpec {
221
236
  limitsByMime?: Record<string, number>;
222
237
  slots?: Record<string, string[]>;
223
238
  }
224
- /** Output schema, media, voice, validation, and stream rules for a profile. */
239
+ /** Output schema, image, speech, validation, and stream rules for a profile. */
225
240
  export interface ProfileOutputsSpec {
226
241
  structured?: StructuredSchemaId | StructuredBySlot | null;
227
- media?: boolean;
228
- voice?: ProfileVoiceSpec;
242
+ /** Pins for an image-role profile. Model id is on `model`. */
243
+ image?: ProfileImageSpec;
244
+ /** Pins for a speech-role profile (`voice` / `format`). Model id is on `model`. */
245
+ speech?: ProfileSpeechSpec;
229
246
  validation?: ProfileValidationSpec;
230
247
  streaming?: ProfileStreamingSpec;
231
248
  }
@@ -263,8 +280,9 @@ export type InteractionPart = InteractionTextPart | InteractionMediaPart;
263
280
  export interface ImageResponseFormat {
264
281
  type: 'image';
265
282
  mimeType: string;
266
- aspectRatio: ImageAspectRatio;
267
- imageSize: ImageSize;
283
+ aspectRatio: string;
284
+ /** Authoring / kernel name; adapters map to provider wire keys (e.g. Google `imageSize`). */
285
+ size: string;
268
286
  }
269
287
  /** Base64-encoded blob supplied by a host turn request. */
270
288
  export interface TurnBlob {
@@ -386,11 +404,14 @@ export interface ProjectedProfile {
386
404
  inputs: Profile['inputs'];
387
405
  slots: Record<string, string[]>;
388
406
  outputs: Profile['outputs'];
389
- image?: ImageModelSpec | null;
407
+ image?: ProfileImageSpec | null;
390
408
  }
391
- /** Fully-resolved provider request state created from a `TurnRequest`. */
392
- export interface ResolvedGeneration {
409
+ /** Provider selection and generation knobs shared before and after resolution. */
410
+ export interface ProviderGenerationConfig {
393
411
  model: ModelId;
412
+ /** Provider-native model id taken from the profile model spec. */
413
+ apiId: string;
414
+ openRouterId?: string;
394
415
  previousInteractionId?: string;
395
416
  store?: boolean;
396
417
  thinking: ThinkingLevel;
@@ -398,6 +419,9 @@ export interface ResolvedGeneration {
398
419
  maxOutputTokens: number;
399
420
  temperature: number;
400
421
  builtins: BuiltinToolId[];
422
+ }
423
+ /** Fully-resolved provider request state created from a `TurnRequest`. */
424
+ export interface ResolvedGeneration extends ProviderGenerationConfig {
401
425
  custom: CustomToolId[];
402
426
  dynamicTools?: DynamicToolDeclaration[];
403
427
  dynamicToolLoader?: DynamicToolLoader;
@@ -406,9 +430,13 @@ export interface ResolvedGeneration {
406
430
  maxSteps: number;
407
431
  structured: StructuredSchemaId | null;
408
432
  image: ImageResponseFormat | null;
409
- voice?: ProfileVoiceSpec;
433
+ speech?: ProfileSpeechSpec;
410
434
  input: InteractionPart[];
411
- geminiBucket: GeminiBucket;
435
+ /**
436
+ * Gemini vault slot for Google Interactions transport only.
437
+ * Omitted for non-Google providers; never sent on the wire.
438
+ */
439
+ geminiBucket?: GeminiBucket;
412
440
  canary: string;
413
441
  }
414
442
  /** Tool execution status returned to the model and stream. */
@@ -470,15 +498,7 @@ export interface TurnEvent {
470
498
  error?: string;
471
499
  }
472
500
  /** Provider-neutral request object sent from the kernel to a model adapter. */
473
- export interface ProviderCompleteRequest {
474
- model: ModelId;
475
- previousInteractionId?: string;
476
- store?: boolean;
477
- thinking: ThinkingLevel;
478
- summaries: 'auto' | 'none';
479
- maxOutputTokens: number;
480
- temperature: number;
481
- builtins: BuiltinToolId[];
501
+ export interface ProviderCompleteRequest extends ProviderGenerationConfig {
482
502
  system: string;
483
503
  input: InteractionPart[];
484
504
  history?: TurnHistoryMessage[];
@@ -486,8 +506,12 @@ export interface ProviderCompleteRequest {
486
506
  dynamicToolLoader?: DynamicToolLoader;
487
507
  structured: StructuredSchemaId | null;
488
508
  image: ImageResponseFormat | null;
489
- voice?: ProfileVoiceSpec;
490
- geminiBucket: GeminiBucket;
509
+ speech?: ProfileSpeechSpec;
510
+ /**
511
+ * Gemini vault slot for Google Interactions transport only.
512
+ * Required when completing via Google Interactions.
513
+ */
514
+ geminiBucket?: GeminiBucket;
491
515
  /** Scrubbed SSE / HTTP rows for traces. */
492
516
  tapGemini?: (row: Record<string, unknown>) => void;
493
517
  }
@@ -1,24 +1,7 @@
1
- import { modelEntry } from '../kernel/registry/catalog.js';
1
+ import { providerCompleteRequest } from '../kernel/registry/provider-request.js';
2
2
  import { tapeGemini } from '../providers/gemini-tape.js';
3
3
  import { toInteractionsBody } from '../providers/interactions.js';
4
4
  import { httpStatus } from './trace-usage.js';
5
- function completeRequest(generation, system) {
6
- return {
7
- model: generation.model,
8
- previousInteractionId: generation.previousInteractionId,
9
- store: generation.store,
10
- thinking: generation.thinking,
11
- summaries: generation.summaries,
12
- maxOutputTokens: generation.maxOutputTokens,
13
- temperature: generation.temperature,
14
- builtins: generation.builtins,
15
- system,
16
- input: generation.input,
17
- structured: generation.structured,
18
- image: generation.image,
19
- geminiBucket: generation.geminiBucket,
20
- };
21
- }
22
5
  function attachResolved(record, args) {
23
6
  const { safe, model, bucket, generation } = args;
24
7
  if (safe.projectId) {
@@ -37,7 +20,10 @@ function attachResolved(record, args) {
37
20
  record.metadata = safe.metadata;
38
21
  }
39
22
  if (model) {
40
- record.model = { id: model, apiId: modelEntry(model).apiId };
23
+ record.model = {
24
+ id: model,
25
+ apiId: generation?.apiId ?? model,
26
+ };
41
27
  }
42
28
  if (bucket) {
43
29
  record.bucket = bucket;
@@ -61,7 +47,7 @@ async function attachTape(record, args) {
61
47
  record.gemini = await tapeGemini(gemini, canary ?? '');
62
48
  }
63
49
  if (generation && system !== undefined) {
64
- record.wire = await tapeGemini(toInteractionsBody(completeRequest(generation, system)), canary ?? '');
50
+ record.wire = await tapeGemini(toInteractionsBody(providerCompleteRequest(generation, system)), canary ?? '');
65
51
  }
66
52
  }
67
53
  function attachUsage(record, gemini, done) {
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Google / Gemini convenience preset.
3
+ *
4
+ * Kernel stays open (`string` pins). This pack owns Google vocabularies as
5
+ * real unions so host profiles stay typed when they opt into the preset.
6
+ *
7
+ * Call `registerGooglePreset()` at host startup for tools; use the exported
8
+ * types/constants when authoring image/speech-adjacent profile fields.
9
+ *
10
+ * @module
11
+ */
12
+ import "../../_dnt.polyfills.js";
13
+ import type { ProfileImageSpec, ProfileSpeechSpec, ToolCatalogEntry } from '../kernel/types.js';
14
+ /** Google Interactions / Gemini grounding builtins. */
15
+ declare const GOOGLE_BUILTIN_TOOLS: Record<string, ToolCatalogEntry>;
16
+ /** Common Gemini image input MIME allowlist. */
17
+ declare const GOOGLE_IMAGE_INPUT_MIMES: readonly ["image/png", "image/jpeg", "image/webp", "image/heic", "image/heif"];
18
+ /** Common voice/audio input MIME allowlist for Gemini multimodal. */
19
+ declare const GOOGLE_VOICE_INPUT_MIMES: readonly ["audio/webm", "audio/wav", "audio/mpeg", "audio/mp4"];
20
+ /** Common Gemini TTS voice names for `outputs.speech.voice`. */
21
+ declare const GOOGLE_SPEECH_VOICES: readonly ["Zephyr", "Puck", "Charon", "Kore", "Fenrir", "Leda", "Orus", "Aoede", "Callirrhoe", "Autonoe", "Enceladus", "Iapetus", "Umbriel", "Algieba", "Despina", "Erinome", "Algenib", "Rasalgethi", "Laomedeia", "Achernar", "Alnilam", "Schedar", "Gacrux", "Pulcherrima", "Achird", "Zubenelgenubi", "Vindemiatrix", "Sadachbia", "Sadaltager", "Sulafat"];
22
+ type GoogleSpeechVoice = (typeof GOOGLE_SPEECH_VOICES)[number];
23
+ /**
24
+ * `outputs.speech` pins narrowed to Google TTS vocabulary.
25
+ * Assignable to kernel `ProfileSpeechSpec`.
26
+ */
27
+ type GoogleSpeechPins = Omit<ProfileSpeechSpec, 'voice'> & {
28
+ voice?: GoogleSpeechVoice;
29
+ };
30
+ /** Aspect ratios commonly accepted by Gemini image models. */
31
+ declare const GOOGLE_IMAGE_ASPECT_RATIOS: readonly ["1:1", "3:2", "2:3", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
32
+ /** Image sizes commonly accepted by Gemini Flash Lite image. */
33
+ declare const GOOGLE_IMAGE_SIZES: readonly ["1K"];
34
+ type GoogleImageInputMime = (typeof GOOGLE_IMAGE_INPUT_MIMES)[number];
35
+ type GoogleVoiceInputMime = (typeof GOOGLE_VOICE_INPUT_MIMES)[number];
36
+ type GoogleImageAspectRatio = (typeof GOOGLE_IMAGE_ASPECT_RATIOS)[number];
37
+ type GoogleImageSize = (typeof GOOGLE_IMAGE_SIZES)[number];
38
+ /**
39
+ * `outputs.image` pins narrowed to Google image vocabulary.
40
+ * Assignable to kernel `ProfileImageSpec`.
41
+ */
42
+ type GoogleImagePins = Omit<ProfileImageSpec, 'aspectRatio' | 'size' | 'mimeType'> & {
43
+ aspectRatio?: GoogleImageAspectRatio;
44
+ size?: GoogleImageSize;
45
+ mimeType?: GoogleImageInputMime | 'image/jpeg';
46
+ };
47
+ /** Register Google provider builtins into the process-local tool catalog. */
48
+ declare function registerGooglePreset(): void;
49
+ export type { GoogleImageAspectRatio, GoogleImageInputMime, GoogleImagePins, GoogleImageSize, GoogleSpeechPins, GoogleSpeechVoice, GoogleVoiceInputMime, };
50
+ export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_SPEECH_VOICES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, };