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
@@ -0,0 +1,96 @@
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 { registerTools } from '../kernel/registry/catalog.js';
14
+ /** Google Interactions / Gemini grounding builtins. */
15
+ const GOOGLE_BUILTIN_TOOLS = {
16
+ googleSearch: {
17
+ kind: 'builtin',
18
+ ui: true,
19
+ interactionsType: 'google_search',
20
+ openRouterPlugin: 'web',
21
+ },
22
+ googleMaps: {
23
+ kind: 'builtin',
24
+ ui: true,
25
+ interactionsType: 'google_maps',
26
+ conflictsWith: ['googleSearch', 'urlContext'],
27
+ },
28
+ urlContext: {
29
+ kind: 'builtin',
30
+ ui: true,
31
+ interactionsType: 'url_context',
32
+ },
33
+ };
34
+ /** Common Gemini image input MIME allowlist. */
35
+ const GOOGLE_IMAGE_INPUT_MIMES = [
36
+ 'image/png',
37
+ 'image/jpeg',
38
+ 'image/webp',
39
+ 'image/heic',
40
+ 'image/heif',
41
+ ];
42
+ /** Common voice/audio input MIME allowlist for Gemini multimodal. */
43
+ const GOOGLE_VOICE_INPUT_MIMES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4'];
44
+ /** Common Gemini TTS voice names for `outputs.speech.voice`. */
45
+ const GOOGLE_SPEECH_VOICES = [
46
+ 'Zephyr',
47
+ 'Puck',
48
+ 'Charon',
49
+ 'Kore',
50
+ 'Fenrir',
51
+ 'Leda',
52
+ 'Orus',
53
+ 'Aoede',
54
+ 'Callirrhoe',
55
+ 'Autonoe',
56
+ 'Enceladus',
57
+ 'Iapetus',
58
+ 'Umbriel',
59
+ 'Algieba',
60
+ 'Despina',
61
+ 'Erinome',
62
+ 'Algenib',
63
+ 'Rasalgethi',
64
+ 'Laomedeia',
65
+ 'Achernar',
66
+ 'Alnilam',
67
+ 'Schedar',
68
+ 'Gacrux',
69
+ 'Pulcherrima',
70
+ 'Achird',
71
+ 'Zubenelgenubi',
72
+ 'Vindemiatrix',
73
+ 'Sadachbia',
74
+ 'Sadaltager',
75
+ 'Sulafat',
76
+ ];
77
+ /** Aspect ratios commonly accepted by Gemini image models. */
78
+ const GOOGLE_IMAGE_ASPECT_RATIOS = [
79
+ '1:1',
80
+ '3:2',
81
+ '2:3',
82
+ '3:4',
83
+ '4:3',
84
+ '4:5',
85
+ '5:4',
86
+ '9:16',
87
+ '16:9',
88
+ '21:9',
89
+ ];
90
+ /** Image sizes commonly accepted by Gemini Flash Lite image. */
91
+ const GOOGLE_IMAGE_SIZES = ['1K'];
92
+ /** Register Google provider builtins into the process-local tool catalog. */
93
+ function registerGooglePreset() {
94
+ registerTools(GOOGLE_BUILTIN_TOOLS);
95
+ }
96
+ export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_SPEECH_VOICES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Optional THEORUM presets.
3
+ *
4
+ * Presets register host-convenience catalogs (provider builtins, media
5
+ * vocabularies, later model packs) without baking product opinions into the kernel.
6
+ *
7
+ * @module
8
+ */
9
+ import "../../_dnt.polyfills.js";
10
+ export type { GoogleImageAspectRatio, GoogleImageInputMime, GoogleImagePins, GoogleImageSize, GoogleVoiceInputMime, } from './google.js';
11
+ export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, } from './google.js';
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Optional THEORUM presets.
3
+ *
4
+ * Presets register host-convenience catalogs (provider builtins, media
5
+ * vocabularies, later model packs) without baking product opinions into the kernel.
6
+ *
7
+ * @module
8
+ */
9
+ import "../../_dnt.polyfills.js";
10
+ export { GOOGLE_BUILTIN_TOOLS, GOOGLE_IMAGE_ASPECT_RATIOS, GOOGLE_IMAGE_INPUT_MIMES, GOOGLE_IMAGE_SIZES, GOOGLE_VOICE_INPUT_MIMES, registerGooglePreset, } from './google.js';
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Host provider factory — the single public door for binding a profile to a transport.
3
+ *
4
+ * Routes from `profile.model.protocol` / `provider` (and whether the profile is
5
+ * a speech role). Adapters under this folder are internal implementation.
6
+ *
7
+ * @module
8
+ */
9
+ import type { ModelProvider, Profile } from '../kernel/types.js';
10
+ import type { GeminiTransport } from './keys.js';
11
+ import type { OpenRouterConfig } from './openrouter-payload.js';
12
+ /** Credentials supplied by the host when creating a provider. */
13
+ export interface CreateProviderOptions {
14
+ /** Google Interactions (chat, image, and speech when protocol is geminiInteractions). */
15
+ gemini?: GeminiTransport;
16
+ /**
17
+ * OpenRouter-compatible credentials for `openAi` profiles.
18
+ * Used for chat completions or `/audio/speech` when the profile is a speech role.
19
+ * Optional `voice` is a fallback when `outputs.speech.voice` is omitted.
20
+ */
21
+ openRouter?: OpenRouterConfig & {
22
+ voice?: string;
23
+ };
24
+ }
25
+ /**
26
+ * Create a `ModelProvider` for a profile.
27
+ * One call: protocol/provider (and speech role) pick the transport.
28
+ */
29
+ export declare function createProvider(profile: Profile, options?: CreateProviderOptions): ModelProvider;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Host provider factory — the single public door for binding a profile to a transport.
3
+ *
4
+ * Routes from `profile.model.protocol` / `provider` (and whether the profile is
5
+ * a speech role). Adapters under this folder are internal implementation.
6
+ *
7
+ * @module
8
+ */
9
+ import { TheorumError } from '../guardrails/error.js';
10
+ import { createOpenRouterProvider } from './openrouter.js';
11
+ import { createInteractionsProvider } from './provider.js';
12
+ import { createSpeechProvider } from './speech.js';
13
+ function isSpeechRole(profile) {
14
+ return profile.outputs.speech !== undefined;
15
+ }
16
+ /**
17
+ * Create a `ModelProvider` for a profile.
18
+ * One call: protocol/provider (and speech role) pick the transport.
19
+ */
20
+ export function createProvider(profile, options = {}) {
21
+ const { protocol, provider } = profile.model;
22
+ if (protocol === 'geminiInteractions' && provider === 'google') {
23
+ if (!options.gemini) {
24
+ throw new TheorumError('createProvider requires gemini transport for google Interactions');
25
+ }
26
+ return createInteractionsProvider(options.gemini);
27
+ }
28
+ if (protocol === 'openAi' && provider === 'openrouter') {
29
+ if (!options.openRouter) {
30
+ throw new TheorumError('createProvider requires openRouter config for openAi/openrouter');
31
+ }
32
+ if (isSpeechRole(profile)) {
33
+ return createSpeechProvider(options.openRouter);
34
+ }
35
+ return createOpenRouterProvider(options.openRouter);
36
+ }
37
+ throw new TheorumError(`createProvider: unsupported protocol/provider pair '${protocol}'/'${provider}'`);
38
+ }
@@ -1,3 +1,2 @@
1
- import { sha256 } from '../kernel/engine/hash.js';
2
1
  declare function tapeGemini(value: unknown, canary: string): Promise<unknown>;
3
- export { sha256, tapeGemini };
2
+ export { tapeGemini };
@@ -43,4 +43,4 @@ function redactCanaryInTree(value, canary) {
43
43
  async function tapeGemini(value, canary) {
44
44
  return redactCanaryInTree(await scrubGemini(value), canary);
45
45
  }
46
- export { sha256, tapeGemini };
46
+ export { tapeGemini };
@@ -1,11 +1,6 @@
1
1
  import { TheorumError } from '../guardrails/error.js';
2
- import { modelEntry } from '../kernel/registry/catalog.js';
2
+ import { getTool } from '../kernel/registry/catalog.js';
3
3
  import { getStructured } from '../kernel/registry/schemas.js';
4
- const BUILTIN_API = {
5
- googleSearch: 'google_search',
6
- googleMaps: 'google_maps',
7
- urlContext: 'url_context',
8
- };
9
4
  function camelToSnake(key) {
10
5
  return key.replaceAll(/[A-Z]/g, (ch) => `_${ch.toLowerCase()}`);
11
6
  }
@@ -58,12 +53,22 @@ function jsonResponseFormat(schema) {
58
53
  return [{ type: 'text', mimeType: 'application/json', schema }];
59
54
  }
60
55
  function attachResponseFormat(req, camel) {
56
+ if (req.speech) {
57
+ if (req.image) {
58
+ throw new TheorumError('cannot mix speech and image response formats');
59
+ }
60
+ if (req.structured) {
61
+ throw new TheorumError('cannot mix speech and structured response formats');
62
+ }
63
+ camel.responseFormat = { type: 'audio' };
64
+ return;
65
+ }
61
66
  if (req.image) {
62
67
  camel.responseFormat = {
63
68
  type: 'image',
64
69
  mimeType: req.image.mimeType,
65
70
  aspectRatio: req.image.aspectRatio,
66
- imageSize: req.image.imageSize,
71
+ imageSize: req.image.size,
67
72
  };
68
73
  return;
69
74
  }
@@ -76,6 +81,14 @@ function attachResponseFormat(req, camel) {
76
81
  }
77
82
  camel.responseFormat = jsonResponseFormat(spec.jsonSchema);
78
83
  }
84
+ function attachSpeechConfig(req, generationConfig) {
85
+ if (!req.speech) {
86
+ return;
87
+ }
88
+ if (req.speech.voice) {
89
+ generationConfig.speechConfig = [{ voice: req.speech.voice }];
90
+ }
91
+ }
79
92
  function inputStepsFromRequest(req) {
80
93
  const inputSteps = [];
81
94
  for (const h of req.history ?? []) {
@@ -97,21 +110,33 @@ function applyOptionalRequestFields(req, camel) {
97
110
  camel.systemInstruction = req.system;
98
111
  }
99
112
  if (req.builtins.length > 0) {
100
- camel.tools = req.builtins.map((id) => ({ type: BUILTIN_API[id] }));
113
+ camel.tools = req.builtins.map((id) => {
114
+ const type = getTool(id)?.interactionsType;
115
+ if (!type) {
116
+ throw new TheorumError(`Builtin '${id}' has no Interactions wire type`);
117
+ }
118
+ return { type };
119
+ });
101
120
  }
102
121
  }
103
122
  function baseInteractionsBody(req) {
104
- const catalog = modelEntry(req.model);
123
+ const generationConfig = {
124
+ temperature: req.temperature,
125
+ maxOutputTokens: req.maxOutputTokens,
126
+ };
127
+ if (req.speech) {
128
+ // TTS models reject chat thinking knobs; voice lives under speech_config.
129
+ attachSpeechConfig(req, generationConfig);
130
+ }
131
+ else {
132
+ generationConfig.thinkingLevel = req.thinking;
133
+ generationConfig.thinkingSummaries = req.summaries;
134
+ }
105
135
  return {
106
- model: catalog.apiId,
136
+ model: req.apiId,
107
137
  stream: true,
108
138
  input: inputStepsFromRequest(req),
109
- generationConfig: {
110
- temperature: req.temperature,
111
- maxOutputTokens: req.maxOutputTokens,
112
- thinkingLevel: req.thinking,
113
- thinkingSummaries: req.summaries,
114
- },
139
+ generationConfig,
115
140
  };
116
141
  }
117
142
  /** Interactions REST body for one complete() call (Google snake_case keys). */
@@ -1,12 +1,19 @@
1
- import type { BuiltinToolId, GeminiBucket, GeminiFreeBucket, ModelId } from '../kernel/types.js';
1
+ /**
2
+ * Gemini key vault selection, quota overflow, and fetch retries.
3
+ *
4
+ * Host applications supply vault credentials through `GeminiTransport`.
5
+ * THEORUM does not read environment variables for these keys.
6
+ *
7
+ * @module
8
+ */
9
+ import type { GeminiBucket } from '../kernel/types.js';
2
10
  type GeminiVault = Record<GeminiBucket, string | undefined>;
3
11
  interface GeminiTransport {
4
12
  vault: GeminiVault;
5
13
  wait?: (ms: number) => Promise<void>;
6
14
  fetch?: typeof fetch;
7
15
  }
8
- declare function resolveGeminiBucket(free: GeminiFreeBucket, model: ModelId, builtins: BuiltinToolId[]): GeminiBucket;
9
16
  declare function withGeminiKey<T>(bucket: GeminiBucket, run: (apiKey: string) => Promise<T>, transport: GeminiTransport): Promise<T>;
10
17
  declare function fetchGemini(url: string, init: RequestInit, bucket: GeminiBucket, transport: GeminiTransport): Promise<Response>;
11
18
  export type { GeminiTransport, GeminiVault };
12
- export { fetchGemini, resolveGeminiBucket, withGeminiKey };
19
+ export { fetchGemini, withGeminiKey };
@@ -1,5 +1,12 @@
1
- import { modelEntry } from '../kernel/registry/catalog.js';
2
- import { TheorumError, UPSTREAM_FAILED } from './error.js';
1
+ /**
2
+ * Gemini key vault selection, quota overflow, and fetch retries.
3
+ *
4
+ * Host applications supply vault credentials through `GeminiTransport`.
5
+ * THEORUM does not read environment variables for these keys.
6
+ *
7
+ * @module
8
+ */
9
+ import { TheorumError, UPSTREAM_FAILED } from '../guardrails/error.js';
3
10
  const ATTEMPTS = 3;
4
11
  const LAST_ATTEMPT = ATTEMPTS - 1;
5
12
  const BACKOFF_FIRST_MS = 1000;
@@ -14,16 +21,6 @@ const HTTP_UNAVAILABLE = 503;
14
21
  const HTTP_GATEWAY_TIMEOUT = 504;
15
22
  const QUOTA_RE = /quota/i;
16
23
  const TRANSIENT_THROWN_RE = /name resolution|dns|econnreset|econnrefused|etimedout|network|fetch failed|temporarily unavailable|socket|503|502|504/i;
17
- function resolveGeminiBucket(free, model, builtins) {
18
- const entry = modelEntry(model);
19
- if (entry.image) {
20
- return 'paid';
21
- }
22
- if (builtins.some((id) => !entry.freeBuiltins.includes(id))) {
23
- return 'paid';
24
- }
25
- return free;
26
- }
27
24
  function waitDefault(ms) {
28
25
  return new Promise((resolve) => {
29
26
  setTimeout(resolve, ms);
@@ -129,4 +126,4 @@ async function fetchGemini(url, init, bucket, transport) {
129
126
  }
130
127
  return last;
131
128
  }
132
- export { fetchGemini, resolveGeminiBucket, withGeminiKey };
129
+ export { fetchGemini, withGeminiKey };
@@ -1,15 +1,12 @@
1
1
  /**
2
2
  * Provider adapters for THEORUM's `ModelProvider` contract.
3
3
  *
4
- * Use this entrypoint when a host app wants to bind THEORUM to Google
5
- * Interactions, OpenRouter-compatible chat completions, or OpenRouter TTS.
4
+ * Host apps should use `createProvider(profile, options)` the single door.
6
5
  * Credentials are always supplied by the host application.
7
6
  *
8
7
  * @module
9
8
  */
10
9
  import "../../_dnt.polyfills.js";
11
- export type { OpenRouterConfig } from './openrouter.js';
12
- export { createOpenRouterProvider, resolveOpenRouterApiKey, resolveOpenRouterModel, toOpenRouterPayload, } from './openrouter.js';
13
- export { createInteractionsProvider } from './provider.js';
14
- export type { OpenRouterTtsConfig } from './tts.js';
15
- export { createOpenRouterTtsProvider, streamOpenRouterTts, wrapPcmAsWav, } from './tts.js';
10
+ export type { CreateProviderOptions } from './create-provider.js';
11
+ export { createProvider } from './create-provider.js';
12
+ export type { GeminiTransport, GeminiVault } from './keys.js';
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * Provider adapters for THEORUM's `ModelProvider` contract.
3
3
  *
4
- * Use this entrypoint when a host app wants to bind THEORUM to Google
5
- * Interactions, OpenRouter-compatible chat completions, or OpenRouter TTS.
4
+ * Host apps should use `createProvider(profile, options)` the single door.
6
5
  * Credentials are always supplied by the host application.
7
6
  *
8
7
  * @module
9
8
  */
10
9
  import "../../_dnt.polyfills.js";
11
- export { createOpenRouterProvider, resolveOpenRouterApiKey, resolveOpenRouterModel, toOpenRouterPayload, } from './openrouter.js';
12
- export { createInteractionsProvider } from './provider.js';
13
- export { createOpenRouterTtsProvider, streamOpenRouterTts, wrapPcmAsWav, } from './tts.js';
10
+ export { createProvider } from './create-provider.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * OpenRouter provider adapter and payload helpers.
3
+ *
4
+ * Prefer `createProvider(profile, { openRouter })` from `theorum` / `theorum/providers`
5
+ * for turn execution. This entrypoint is for hosts that build OpenRouter payloads
6
+ * outside the runner.
7
+ *
8
+ * @module
9
+ */
10
+ import "../../_dnt.polyfills.js";
11
+ export { createOpenRouterProvider } from './openrouter.js';
12
+ export type { OpenRouterConfig } from './openrouter-payload.js';
13
+ export { resolveOpenRouterModel, toOpenRouterPayload, } from './openrouter-payload.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * OpenRouter provider adapter and payload helpers.
3
+ *
4
+ * Prefer `createProvider(profile, { openRouter })` from `theorum` / `theorum/providers`
5
+ * for turn execution. This entrypoint is for hosts that build OpenRouter payloads
6
+ * outside the runner.
7
+ *
8
+ * @module
9
+ */
10
+ import "../../_dnt.polyfills.js";
11
+ export { createOpenRouterProvider } from './openrouter.js';
12
+ export { resolveOpenRouterModel, toOpenRouterPayload, } from './openrouter-payload.js';
@@ -16,9 +16,17 @@ interface OpenRouterConfig {
16
16
  fetch?: typeof fetch;
17
17
  modelMap?: Record<string, string>;
18
18
  }
19
- /** Resolve a THEORUM model id to an OpenRouter model string. */
20
- declare function resolveOpenRouterModel(modelId: ModelId | string, customMap?: Record<string, string>): string;
19
+ /** Host-owned wire ids used when resolving an OpenRouter model string. */
20
+ interface OpenRouterWireIds {
21
+ apiId?: string;
22
+ openRouterId?: string;
23
+ }
24
+ /**
25
+ * Resolve a THEORUM model id to an OpenRouter model string.
26
+ * Prefer `openRouterId` / `apiId` from the profile model spec on the request.
27
+ */
28
+ declare function resolveOpenRouterModel(modelId: ModelId | string, customMap?: Record<string, string>, wire?: OpenRouterWireIds): string;
21
29
  /** Convert a provider-neutral request into an OpenRouter chat completion payload. */
22
30
  declare function toOpenRouterPayload(req: ProviderCompleteRequest, config: OpenRouterConfig): Record<string, unknown>;
23
- export type { OpenRouterConfig };
31
+ export type { OpenRouterConfig, OpenRouterWireIds };
24
32
  export { resolveOpenRouterModel, toOpenRouterPayload };
@@ -6,36 +6,33 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { CATALOG } from '../kernel/registry/catalog.js';
9
+ import { getTool } from '../kernel/registry/catalog.js';
10
10
  import { getStructured } from '../kernel/registry/schemas.js';
11
- /** Resolve a THEORUM model id to an OpenRouter model string. */
12
- function resolveOpenRouterModel(modelId, customMap) {
11
+ /**
12
+ * Resolve a THEORUM model id to an OpenRouter model string.
13
+ * Prefer `openRouterId` / `apiId` from the profile model spec on the request.
14
+ */
15
+ function resolveOpenRouterModel(modelId, customMap, wire) {
13
16
  if (customMap?.[modelId]) {
14
17
  return customMap[modelId];
15
18
  }
16
- const catalogEntry = CATALOG.models[modelId];
17
- if (catalogEntry?.openRouterId) {
18
- return catalogEntry.openRouterId;
19
+ if (wire?.openRouterId) {
20
+ return wire.openRouterId;
19
21
  }
20
- if (catalogEntry?.apiId.includes('/')) {
21
- return catalogEntry.apiId;
22
+ if (wire?.apiId?.includes('/')) {
23
+ return wire.apiId;
22
24
  }
23
- if (catalogEntry?.apiId) {
24
- return `google/${catalogEntry.apiId}`;
25
+ if (wire?.apiId) {
26
+ return `google/${wire.apiId}`;
27
+ }
28
+ if (String(modelId).includes('/')) {
29
+ return String(modelId);
25
30
  }
26
31
  return String(modelId);
27
32
  }
33
+ /** Map THEORUM thinking level to OpenRouter/OpenAI `reasoning.effort`. */
28
34
  function mapThinkingEffort(thinking) {
29
- if (thinking === 'low') {
30
- return 'low';
31
- }
32
- if (thinking === 'medium') {
33
- return 'medium';
34
- }
35
- if (thinking === 'high') {
36
- return 'high';
37
- }
38
- return null;
35
+ return thinking;
39
36
  }
40
37
  function wireAudioPart(part) {
41
38
  let format = 'mp3';
@@ -148,7 +145,10 @@ function resolveResponseFormat(structured) {
148
145
  }
149
146
  /** Convert a provider-neutral request into an OpenRouter chat completion payload. */
150
147
  function toOpenRouterPayload(req, config) {
151
- const model = resolveOpenRouterModel(req.model, config.modelMap);
148
+ const model = resolveOpenRouterModel(req.model, config.modelMap, {
149
+ apiId: req.apiId,
150
+ openRouterId: req.openRouterId,
151
+ });
152
152
  const messages = buildMessages(req);
153
153
  const payload = {
154
154
  model,
@@ -158,9 +158,7 @@ function toOpenRouterPayload(req, config) {
158
158
  max_tokens: req.maxOutputTokens,
159
159
  };
160
160
  const effort = mapThinkingEffort(req.thinking);
161
- if (effort) {
162
- payload.reasoning = { effort };
163
- }
161
+ payload.reasoning = { effort };
164
162
  const responseFormat = resolveResponseFormat(req.structured);
165
163
  if (responseFormat) {
166
164
  payload.response_format = responseFormat;
@@ -169,8 +167,12 @@ function toOpenRouterPayload(req, config) {
169
167
  if (tools.length > 0) {
170
168
  payload.tools = tools;
171
169
  }
172
- if (req.builtins.includes('googleSearch')) {
173
- payload.plugins = [{ id: 'web' }];
170
+ const plugins = req.builtins
171
+ .map((id) => getTool(id)?.openRouterPlugin)
172
+ .filter((id) => Boolean(id))
173
+ .map((id) => ({ id }));
174
+ if (plugins.length > 0) {
175
+ payload.plugins = plugins;
174
176
  }
175
177
  return payload;
176
178
  }
@@ -1,17 +1,15 @@
1
1
  /**
2
- * OpenRouter-compatible streaming provider adapter.
2
+ * OpenRouter provider adapter powered by Vercel AI SDK Core.
3
3
  *
4
- * This module maps THEORUM requests to OpenAI-style chat completions, including
5
- * reasoning deltas, tool calls, citations, structured output, and token usage.
4
+ * THEORUM keeps the public `ModelProvider` and `TurnEvent` contract; AI SDK
5
+ * owns the OpenRouter call, stream parsing, provider compatibility, and tool
6
+ * call normalization.
6
7
  *
7
8
  * @module
8
9
  */
9
- import "../../_dnt.polyfills.js";
10
10
  import type { ModelProvider } from '../kernel/types.js';
11
11
  import { type OpenRouterConfig, resolveOpenRouterModel, toOpenRouterPayload } from './openrouter-payload.js';
12
- /** Resolve an OpenRouter API key supplied explicitly by the host application. */
13
- export declare function resolveOpenRouterApiKey(explicitKey?: string): string | undefined;
14
- /** Create a `ModelProvider` backed by OpenRouter-compatible chat completions. */
12
+ /** Create a `ModelProvider` backed by OpenRouter through AI SDK Core. */
15
13
  declare function createOpenRouterProvider(config?: OpenRouterConfig): ModelProvider;
16
14
  export type { OpenRouterConfig };
17
15
  export { createOpenRouterProvider, resolveOpenRouterModel, toOpenRouterPayload };