theorum 0.1.4 → 0.1.6
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/docs/AGENT_PROFILE_CONTRACT.md +2 -2
- package/esm/_dnt.polyfills.d.ts +110 -0
- package/esm/_dnt.polyfills.js +144 -0
- package/esm/_dnt.shims.d.ts +5 -0
- package/esm/_dnt.shims.js +61 -0
- package/{mod.ts → esm/mod.d.ts} +17 -57
- package/esm/mod.js +50 -0
- package/esm/package.json +3 -0
- 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 +45 -0
- package/esm/src/guardrails/error.js +149 -0
- package/esm/src/guardrails/injection.d.ts +12 -0
- package/esm/src/guardrails/injection.js +220 -0
- package/esm/src/guardrails/mod.d.ts +16 -0
- package/esm/src/guardrails/mod.js +15 -0
- package/esm/src/guardrails/quota.d.ts +10 -0
- package/esm/src/guardrails/quota.js +64 -0
- package/esm/src/guardrails/sanitize.d.ts +22 -0
- package/esm/src/guardrails/sanitize.js +133 -0
- package/esm/src/guardrails/sensitive.d.ts +12 -0
- package/esm/src/guardrails/sensitive.js +88 -0
- 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/{src/host/mod.ts → esm/src/host/mod.js} +3 -11
- package/esm/src/host/reply.d.ts +14 -0
- package/esm/src/host/reply.js +27 -0
- package/esm/src/kernel/engine/boundary.d.ts +10 -0
- package/esm/src/kernel/engine/boundary.js +55 -0
- package/esm/src/kernel/engine/delta.d.ts +8 -0
- package/esm/src/kernel/engine/delta.js +389 -0
- package/esm/src/kernel/engine/hash.d.ts +1 -0
- package/esm/src/kernel/engine/hash.js +9 -0
- package/esm/src/kernel/engine/record.d.ts +2 -0
- package/esm/src/kernel/engine/record.js +7 -0
- package/esm/src/kernel/engine/repair.d.ts +7 -0
- package/esm/src/kernel/engine/repair.js +38 -0
- package/esm/src/kernel/engine/runner/gates.d.ts +4 -0
- package/esm/src/kernel/engine/runner/gates.js +208 -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/schema-validation.d.ts +22 -0
- package/esm/src/kernel/engine/runner/schema-validation.js +113 -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 +127 -0
- package/esm/src/kernel/engine/runner/stream.d.ts +11 -0
- package/esm/src/kernel/engine/runner/stream.js +70 -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 +192 -0
- package/{src/kernel/engine/runner.ts → esm/src/kernel/engine/runner.d.ts} +1 -2
- package/esm/src/kernel/engine/runner.js +6 -0
- package/esm/src/kernel/engine/tree.d.ts +2 -0
- package/esm/src/kernel/engine/tree.js +17 -0
- package/esm/src/kernel/mod.d.ts +18 -0
- package/esm/src/kernel/mod.js +16 -0
- package/esm/src/kernel/registry/attachments.d.ts +16 -0
- package/esm/src/kernel/registry/attachments.js +156 -0
- package/esm/src/kernel/registry/catalog.d.ts +31 -0
- package/esm/src/kernel/registry/catalog.js +135 -0
- 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 +34 -0
- package/esm/src/kernel/registry/profiles.js +120 -0
- 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 +19 -0
- package/esm/src/kernel/registry/resolve.js +217 -0
- package/esm/src/kernel/registry/schemas.d.ts +14 -0
- package/esm/src/kernel/registry/schemas.js +23 -0
- package/esm/src/kernel/registry/tools.d.ts +12 -0
- package/esm/src/kernel/registry/tools.js +36 -0
- package/{src/kernel/registry/vault.ts → esm/src/kernel/registry/vault.d.ts} +2 -17
- package/esm/src/kernel/registry/vault.js +18 -0
- package/esm/src/kernel/types.d.ts +528 -0
- package/esm/src/kernel/types.js +10 -0
- package/esm/src/observability/mod.d.ts +12 -0
- package/{src/observability/mod.ts → esm/src/observability/mod.js} +2 -11
- package/esm/src/observability/spans.d.ts +16 -0
- package/esm/src/observability/spans.js +56 -0
- package/esm/src/observability/trace-attach.d.ts +16 -0
- package/esm/src/observability/trace-attach.js +67 -0
- package/esm/src/observability/trace-record.d.ts +113 -0
- package/esm/src/observability/trace-record.js +143 -0
- package/esm/src/observability/trace-usage.d.ts +3 -0
- package/esm/src/observability/trace-usage.js +32 -0
- package/esm/src/observability/trace.d.ts +23 -0
- package/esm/src/observability/trace.js +121 -0
- 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 +2 -0
- package/esm/src/providers/gemini-tape.js +46 -0
- package/esm/src/providers/google-tap.d.ts +3 -0
- package/esm/src/providers/google-tap.js +48 -0
- package/esm/src/providers/interactions.d.ts +5 -0
- package/esm/src/providers/interactions.js +153 -0
- package/esm/src/providers/keys.d.ts +19 -0
- package/esm/src/providers/keys.js +129 -0
- package/esm/src/providers/mod.d.ts +12 -0
- package/{src/providers/mod.ts → esm/src/providers/mod.js} +2 -4
- package/esm/src/providers/openrouter-mod.d.ts +13 -0
- package/{src/providers/openrouter-mod.ts → esm/src/providers/openrouter-mod.js} +3 -7
- package/esm/src/providers/openrouter-payload.d.ts +32 -0
- package/esm/src/providers/openrouter-payload.js +179 -0
- package/esm/src/providers/openrouter.d.ts +15 -0
- package/esm/src/providers/openrouter.js +589 -0
- package/esm/src/providers/pcm.d.ts +7 -0
- package/esm/src/providers/pcm.js +35 -0
- package/esm/src/providers/provider.d.ts +15 -0
- package/esm/src/providers/provider.js +160 -0
- package/esm/src/providers/speech.d.ts +23 -0
- package/esm/src/providers/speech.js +125 -0
- package/esm/src/providers/sse.d.ts +7 -0
- package/esm/src/providers/sse.js +53 -0
- package/package.json +45 -36
- package/deno.json +0 -57
- package/deno.lock +0 -469
- package/src/cli/commands/profile.ts +0 -57
- package/src/cli/commands/run.ts +0 -66
- package/src/cli/commands/test.ts +0 -195
- package/src/cli/index.ts +0 -161
- package/src/cli/matrix/fixtures.ts +0 -96
- package/src/cli/matrix/synthesizer.ts +0 -240
- package/src/guardrails/error.ts +0 -142
- package/src/guardrails/injection.ts +0 -254
- package/src/guardrails/mod.ts +0 -40
- package/src/guardrails/quota.ts +0 -87
- package/src/guardrails/sanitize.ts +0 -165
- package/src/guardrails/sensitive.ts +0 -96
- package/src/host/mint-trace.ts +0 -54
- package/src/host/reply.ts +0 -32
- package/src/kernel/engine/assert.ts +0 -29
- package/src/kernel/engine/boundary.ts +0 -75
- package/src/kernel/engine/delta.ts +0 -440
- package/src/kernel/engine/hash.ts +0 -11
- package/src/kernel/engine/record.ts +0 -8
- package/src/kernel/engine/repair.ts +0 -56
- package/src/kernel/engine/runner/gates.ts +0 -338
- package/src/kernel/engine/runner/mod.ts +0 -135
- package/src/kernel/engine/runner/state.ts +0 -31
- package/src/kernel/engine/runner/steps.ts +0 -183
- package/src/kernel/engine/runner/stream.ts +0 -83
- package/src/kernel/engine/runner/tokens.ts +0 -47
- package/src/kernel/engine/runner/tools.ts +0 -266
- package/src/kernel/engine/tree.ts +0 -18
- package/src/kernel/mod.ts +0 -39
- package/src/kernel/registry/attachments.ts +0 -195
- package/src/kernel/registry/catalog.ts +0 -181
- package/src/kernel/registry/ingress.ts +0 -223
- package/src/kernel/registry/profiles.ts +0 -165
- package/src/kernel/registry/provider-request.ts +0 -31
- package/src/kernel/registry/resolve.ts +0 -290
- package/src/kernel/registry/schemas.ts +0 -29
- package/src/kernel/registry/tools.ts +0 -45
- package/src/kernel/types.ts +0 -576
- package/src/observability/spans.ts +0 -70
- package/src/observability/trace-attach.ts +0 -96
- package/src/observability/trace-record.ts +0 -254
- package/src/observability/trace-usage.ts +0 -36
- package/src/observability/trace.ts +0 -143
- package/src/presets/google.ts +0 -147
- package/src/presets/mod.ts +0 -24
- package/src/providers/create-provider.ts +0 -64
- package/src/providers/gemini-tape.ts +0 -59
- package/src/providers/google-tap.ts +0 -57
- package/src/providers/interactions.ts +0 -182
- package/src/providers/keys.ts +0 -182
- package/src/providers/openrouter-payload.ts +0 -232
- package/src/providers/openrouter.ts +0 -758
- package/src/providers/pcm.ts +0 -37
- package/src/providers/provider.ts +0 -206
- package/src/providers/speech.ts +0 -180
- package/src/providers/sse.ts +0 -59
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { TheorumError } from '../../guardrails/error.js';
|
|
2
|
+
import { injectionSpans } from '../../guardrails/injection.js';
|
|
3
|
+
import { sensitiveSpans } from '../../guardrails/sensitive.js';
|
|
4
|
+
import { applySpans } from '../../observability/spans.js';
|
|
5
|
+
import { getProfile } from './profiles.js';
|
|
6
|
+
const B64_PAD = 2;
|
|
7
|
+
const B64_WORD = 4;
|
|
8
|
+
const B64_TRIPLET = 3;
|
|
9
|
+
const CSV_FORMULA = /(^|,)(\s*)("?)(?:([=@])|([+-])(?![0-9."]))/gm;
|
|
10
|
+
const B64_BODY = /^[A-Za-z0-9+/]*={0,2}$/;
|
|
11
|
+
const TEXT_MIMES = new Set(['text/csv', 'text/plain', 'text/markdown']);
|
|
12
|
+
const BYTES_PER_KIB = 1024;
|
|
13
|
+
function formatMb(bytes) {
|
|
14
|
+
const mb = bytes / (BYTES_PER_KIB * BYTES_PER_KIB);
|
|
15
|
+
return Number.isInteger(mb) ? `${String(mb)} MB` : `${mb.toFixed(1)} MB`;
|
|
16
|
+
}
|
|
17
|
+
function tooManyFilesMessage(maxFiles) {
|
|
18
|
+
return maxFiles === 1
|
|
19
|
+
? 'Only 1 file per message.'
|
|
20
|
+
: `Only ${String(maxFiles)} files per message.`;
|
|
21
|
+
}
|
|
22
|
+
function fileTooLargeMessage(maxBytes) {
|
|
23
|
+
return `Each file must be ${formatMb(maxBytes)} or smaller.`;
|
|
24
|
+
}
|
|
25
|
+
function turnTooLargeMessage(maxTurnBytes) {
|
|
26
|
+
return `Those files together are too large for one message (${formatMb(maxTurnBytes)} max).`;
|
|
27
|
+
}
|
|
28
|
+
function mediaLimits(inputs) {
|
|
29
|
+
const { maxFiles, maxBytes, maxTurnBytes, limitsByMime } = inputs;
|
|
30
|
+
if (maxFiles && maxBytes && maxTurnBytes) {
|
|
31
|
+
return { maxFiles, maxBytes, maxTurnBytes, limitsByMime };
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
function maxBytesForMime(mimeType, limits) {
|
|
36
|
+
if (limits.limitsByMime) {
|
|
37
|
+
const cleanMime = mimeType.split(';')[0]?.trim().toLowerCase() ?? '';
|
|
38
|
+
if (limits.limitsByMime[cleanMime]) {
|
|
39
|
+
return limits.limitsByMime[cleanMime];
|
|
40
|
+
}
|
|
41
|
+
const [category] = cleanMime.split('/');
|
|
42
|
+
const wildCard = `${category}/*`;
|
|
43
|
+
if (limits.limitsByMime[wildCard]) {
|
|
44
|
+
return limits.limitsByMime[wildCard];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return limits.maxBytes;
|
|
48
|
+
}
|
|
49
|
+
function requireMediaLimits(profile) {
|
|
50
|
+
const limits = mediaLimits(profile.inputs);
|
|
51
|
+
if (!limits) {
|
|
52
|
+
throw new TheorumError(`Profile ${profile.id} must set maxFiles, maxBytes, and maxTurnBytes`);
|
|
53
|
+
}
|
|
54
|
+
return limits;
|
|
55
|
+
}
|
|
56
|
+
function b64DecodedLen(data) {
|
|
57
|
+
let pad = 0;
|
|
58
|
+
if (data.endsWith('==')) {
|
|
59
|
+
pad = B64_PAD;
|
|
60
|
+
}
|
|
61
|
+
else if (data.endsWith('=')) {
|
|
62
|
+
pad = 1;
|
|
63
|
+
}
|
|
64
|
+
return Math.floor((data.length * B64_TRIPLET) / B64_WORD) - pad;
|
|
65
|
+
}
|
|
66
|
+
function decodeB64(data) {
|
|
67
|
+
const binary = atob(data);
|
|
68
|
+
const bytes = new Uint8Array(binary.length);
|
|
69
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
70
|
+
bytes[i] = binary.charCodeAt(i);
|
|
71
|
+
}
|
|
72
|
+
return bytes;
|
|
73
|
+
}
|
|
74
|
+
function encodeB64(bytes) {
|
|
75
|
+
let binary = '';
|
|
76
|
+
for (const byte of bytes) {
|
|
77
|
+
binary += String.fromCharCode(byte);
|
|
78
|
+
}
|
|
79
|
+
return btoa(binary);
|
|
80
|
+
}
|
|
81
|
+
function decodeText(bytes) {
|
|
82
|
+
try {
|
|
83
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return new TextDecoder('latin1').decode(bytes);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function sanitizeCsvText(text) {
|
|
90
|
+
return text.replace(CSV_FORMULA, (_full, ...groups) => {
|
|
91
|
+
const [a, b, c, d, e] = groups;
|
|
92
|
+
return `${a}${b}${c}'${d ?? ''}${e ?? ''}`;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function sanitizeTextBytes(mime, bytes) {
|
|
96
|
+
let text = decodeText(bytes);
|
|
97
|
+
if (mime === 'text/csv') {
|
|
98
|
+
text = sanitizeCsvText(text);
|
|
99
|
+
}
|
|
100
|
+
return new TextEncoder().encode(applySpans(text, [...injectionSpans(text), ...sensitiveSpans(text)]));
|
|
101
|
+
}
|
|
102
|
+
function assertAttachmentLimits(blobs, limits) {
|
|
103
|
+
if (blobs.length > limits.maxFiles) {
|
|
104
|
+
throw new TheorumError(tooManyFilesMessage(limits.maxFiles));
|
|
105
|
+
}
|
|
106
|
+
let total = 0;
|
|
107
|
+
for (const blob of blobs) {
|
|
108
|
+
const { data, mimeType } = blob;
|
|
109
|
+
if (!B64_BODY.test(data)) {
|
|
110
|
+
throw new TheorumError('attachment data must be base64');
|
|
111
|
+
}
|
|
112
|
+
const size = b64DecodedLen(data);
|
|
113
|
+
const maxAllowed = maxBytesForMime(mimeType, limits);
|
|
114
|
+
if (size > maxAllowed) {
|
|
115
|
+
throw new TheorumError(fileTooLargeMessage(maxAllowed));
|
|
116
|
+
}
|
|
117
|
+
total += size;
|
|
118
|
+
}
|
|
119
|
+
if (total > limits.maxTurnBytes) {
|
|
120
|
+
throw new TheorumError(turnTooLargeMessage(limits.maxTurnBytes));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function sanitizeAttachment(blob) {
|
|
124
|
+
const { mimeType, data } = blob;
|
|
125
|
+
if (!TEXT_MIMES.has(mimeType.split(';')[0]?.trim().toLowerCase() ?? '')) {
|
|
126
|
+
return blob;
|
|
127
|
+
}
|
|
128
|
+
const bytes = sanitizeTextBytes(mimeType, decodeB64(data));
|
|
129
|
+
return { mimeType, data: encodeB64(bytes) };
|
|
130
|
+
}
|
|
131
|
+
function hasTurnBlobs(attachments, voice) {
|
|
132
|
+
return (attachments?.length ?? 0) > 0 || (voice?.length ?? 0) > 0;
|
|
133
|
+
}
|
|
134
|
+
function sanitizeTurnBlobs(attachments, voice, limits) {
|
|
135
|
+
if (!hasTurnBlobs(attachments, voice)) {
|
|
136
|
+
return { attachments, voice };
|
|
137
|
+
}
|
|
138
|
+
const files = attachments ?? [];
|
|
139
|
+
const clips = voice ?? [];
|
|
140
|
+
if (!limits) {
|
|
141
|
+
throw new TheorumError('This profile does not accept files.');
|
|
142
|
+
}
|
|
143
|
+
assertAttachmentLimits([...files, ...clips], limits);
|
|
144
|
+
return {
|
|
145
|
+
attachments: files.length > 0 ? files.map(sanitizeAttachment) : attachments,
|
|
146
|
+
voice: clips.length > 0 ? clips.map(sanitizeAttachment) : voice,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function sanitizeTurnBlobsForProfile(profileId, attachments, voice) {
|
|
150
|
+
if (!hasTurnBlobs(attachments, voice)) {
|
|
151
|
+
return { attachments, voice };
|
|
152
|
+
}
|
|
153
|
+
const limits = requireMediaLimits(getProfile(profileId));
|
|
154
|
+
return sanitizeTurnBlobs(attachments, voice, limits);
|
|
155
|
+
}
|
|
156
|
+
export { assertAttachmentLimits, fileTooLargeMessage, requireMediaLimits, sanitizeCsvText, sanitizeTurnBlobs, sanitizeTurnBlobsForProfile, tooManyFilesMessage, turnTooLargeMessage, };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool catalog and MIME helpers.
|
|
3
|
+
*
|
|
4
|
+
* Model wire metadata is host-owned on `profile.model.config`. This module only
|
|
5
|
+
* keeps builtin/custom tool descriptors and shared MIME utilities.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { BuiltinToolId, Catalog, GeminiInputKind, ModelId, ModelSpec, Profile, ThinkingLevel, ToolCatalogEntry, ToolId } from '../types.js';
|
|
10
|
+
/** Live tool catalog. Starts with harness tools; presets/hosts register more. */
|
|
11
|
+
declare const CATALOG: Catalog;
|
|
12
|
+
/** Register or replace tool descriptors (idempotent per id). */
|
|
13
|
+
declare function registerTools(entries: Record<string, ToolCatalogEntry>): void;
|
|
14
|
+
/** Look up one registered tool descriptor. */
|
|
15
|
+
declare function getTool(id: ToolId): ToolCatalogEntry | undefined;
|
|
16
|
+
/** Ids of all registered provider builtins. */
|
|
17
|
+
declare function listBuiltinIds(): BuiltinToolId[];
|
|
18
|
+
/** Restore harness-only tools (tests / host reloads). */
|
|
19
|
+
declare function resetTools(): void;
|
|
20
|
+
declare function mimeEssence(mime: string): string;
|
|
21
|
+
declare function mimeAllowed(accept: string[], mime: string): boolean;
|
|
22
|
+
declare function geminiKindForMime(mime: string): GeminiInputKind | undefined;
|
|
23
|
+
/** Require a host-declared model spec for an allowed profile model id. */
|
|
24
|
+
declare function requireModelSpec(profile: Profile, modelId: ModelId): ModelSpec;
|
|
25
|
+
/** Clamp a requested thinking level to what the model spec accepts. */
|
|
26
|
+
declare function clampThinkingLevel(spec: ModelSpec, level: ThinkingLevel): ThinkingLevel;
|
|
27
|
+
/** Look up a model spec by provider-native API id within a host specs map. */
|
|
28
|
+
declare function modelEntryByApiId(specs: Record<string, ModelSpec>, apiId: string): ModelSpec | undefined;
|
|
29
|
+
/** Clamp thinking level using a provider-native API id within a host specs map. */
|
|
30
|
+
declare function clampThinkingLevelForApiId(specs: Record<string, ModelSpec>, apiId: string, level: ThinkingLevel): ThinkingLevel;
|
|
31
|
+
export { CATALOG, clampThinkingLevel, clampThinkingLevelForApiId, geminiKindForMime, getTool, listBuiltinIds, mimeAllowed, mimeEssence, modelEntryByApiId, registerTools, requireModelSpec, resetTools, };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool catalog and MIME helpers.
|
|
3
|
+
*
|
|
4
|
+
* Model wire metadata is host-owned on `profile.model.config`. This module only
|
|
5
|
+
* keeps builtin/custom tool descriptors and shared MIME utilities.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { TheorumError } from '../../guardrails/error.js';
|
|
10
|
+
const ASK_USER_SCHEMA = {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
kind: { type: 'string', enum: ['confirm', 'choice', 'text'] },
|
|
14
|
+
prompt: { type: 'string' },
|
|
15
|
+
options: { type: 'array', items: { type: 'string' } },
|
|
16
|
+
},
|
|
17
|
+
required: ['kind', 'prompt'],
|
|
18
|
+
};
|
|
19
|
+
/** Harness tools that always ship with THEORUM. */
|
|
20
|
+
const HARNESS_TOOLS = {
|
|
21
|
+
askUser: { kind: 'custom', ui: true, schema: ASK_USER_SCHEMA },
|
|
22
|
+
};
|
|
23
|
+
/** Live tool catalog. Starts with harness tools; presets/hosts register more. */
|
|
24
|
+
const CATALOG = {
|
|
25
|
+
tools: { ...HARNESS_TOOLS },
|
|
26
|
+
};
|
|
27
|
+
/** Register or replace tool descriptors (idempotent per id). */
|
|
28
|
+
function registerTools(entries) {
|
|
29
|
+
Object.assign(CATALOG.tools, entries);
|
|
30
|
+
}
|
|
31
|
+
/** Look up one registered tool descriptor. */
|
|
32
|
+
function getTool(id) {
|
|
33
|
+
return CATALOG.tools[id];
|
|
34
|
+
}
|
|
35
|
+
/** Ids of all registered provider builtins. */
|
|
36
|
+
function listBuiltinIds() {
|
|
37
|
+
return Object.entries(CATALOG.tools)
|
|
38
|
+
.filter(([, entry]) => entry.kind === 'builtin')
|
|
39
|
+
.map(([id]) => id);
|
|
40
|
+
}
|
|
41
|
+
/** Restore harness-only tools (tests / host reloads). */
|
|
42
|
+
function resetTools() {
|
|
43
|
+
for (const id of Object.keys(CATALOG.tools)) {
|
|
44
|
+
delete CATALOG.tools[id];
|
|
45
|
+
}
|
|
46
|
+
Object.assign(CATALOG.tools, HARNESS_TOOLS);
|
|
47
|
+
}
|
|
48
|
+
/** Gemini Interactions inline MIME → part type (adapter wire map). */
|
|
49
|
+
const GEMINI_INPUT_KINDS = {
|
|
50
|
+
'image/png': 'image',
|
|
51
|
+
'image/jpeg': 'image',
|
|
52
|
+
'image/jpg': 'image',
|
|
53
|
+
'image/webp': 'image',
|
|
54
|
+
'image/heic': 'image',
|
|
55
|
+
'image/heif': 'image',
|
|
56
|
+
'audio/wav': 'audio',
|
|
57
|
+
'audio/x-wav': 'audio',
|
|
58
|
+
'audio/mpeg': 'audio',
|
|
59
|
+
'audio/mp3': 'audio',
|
|
60
|
+
'audio/aiff': 'audio',
|
|
61
|
+
'audio/aac': 'audio',
|
|
62
|
+
'audio/ogg': 'audio',
|
|
63
|
+
'audio/flac': 'audio',
|
|
64
|
+
'audio/webm': 'audio',
|
|
65
|
+
'audio/mp4': 'audio',
|
|
66
|
+
'audio/pcm': 'audio',
|
|
67
|
+
'video/mp4': 'video',
|
|
68
|
+
'video/mpeg': 'video',
|
|
69
|
+
'video/quicktime': 'video',
|
|
70
|
+
'video/x-msvideo': 'video',
|
|
71
|
+
'video/x-flv': 'video',
|
|
72
|
+
'video/mpg': 'video',
|
|
73
|
+
'video/webm': 'video',
|
|
74
|
+
'video/wmv': 'video',
|
|
75
|
+
'video/x-ms-wmv': 'video',
|
|
76
|
+
'video/3gpp': 'video',
|
|
77
|
+
'application/pdf': 'document',
|
|
78
|
+
'text/plain': 'document',
|
|
79
|
+
'text/csv': 'document',
|
|
80
|
+
'text/markdown': 'document',
|
|
81
|
+
'text/html': 'document',
|
|
82
|
+
'application/json': 'document',
|
|
83
|
+
};
|
|
84
|
+
function mimeEssence(mime) {
|
|
85
|
+
const [base] = mime.split(';');
|
|
86
|
+
return (base ?? '').trim().toLowerCase();
|
|
87
|
+
}
|
|
88
|
+
function mimeAllowed(accept, mime) {
|
|
89
|
+
const actual = mimeEssence(mime);
|
|
90
|
+
return accept.some((rule) => {
|
|
91
|
+
const allowed = mimeEssence(rule);
|
|
92
|
+
if (allowed.endsWith('/*')) {
|
|
93
|
+
return actual.startsWith(allowed.slice(0, -1));
|
|
94
|
+
}
|
|
95
|
+
return allowed === actual;
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function geminiKindForMime(mime) {
|
|
99
|
+
return GEMINI_INPUT_KINDS[mimeEssence(mime)];
|
|
100
|
+
}
|
|
101
|
+
/** Require a host-declared model spec for an allowed profile model id. */
|
|
102
|
+
function requireModelSpec(profile, modelId) {
|
|
103
|
+
const spec = profile.model.config[modelId];
|
|
104
|
+
if (!spec) {
|
|
105
|
+
throw new TheorumError(`Profile ${profile.id} has no model spec for '${modelId}'`);
|
|
106
|
+
}
|
|
107
|
+
return spec;
|
|
108
|
+
}
|
|
109
|
+
function clampLevels(entry, level) {
|
|
110
|
+
if (!entry?.thinkingLevels || entry.thinkingLevels.length === 0) {
|
|
111
|
+
return level;
|
|
112
|
+
}
|
|
113
|
+
if (entry.thinkingLevels.includes(level)) {
|
|
114
|
+
return level;
|
|
115
|
+
}
|
|
116
|
+
const fallback = entry.thinking.off;
|
|
117
|
+
if (entry.thinkingLevels.includes(fallback)) {
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
120
|
+
const first = entry.thinkingLevels[0];
|
|
121
|
+
return first ?? level;
|
|
122
|
+
}
|
|
123
|
+
/** Clamp a requested thinking level to what the model spec accepts. */
|
|
124
|
+
function clampThinkingLevel(spec, level) {
|
|
125
|
+
return clampLevels(spec, level);
|
|
126
|
+
}
|
|
127
|
+
/** Look up a model spec by provider-native API id within a host specs map. */
|
|
128
|
+
function modelEntryByApiId(specs, apiId) {
|
|
129
|
+
return Object.values(specs).find((m) => m.apiId === apiId);
|
|
130
|
+
}
|
|
131
|
+
/** Clamp thinking level using a provider-native API id within a host specs map. */
|
|
132
|
+
function clampThinkingLevelForApiId(specs, apiId, level) {
|
|
133
|
+
return clampLevels(modelEntryByApiId(specs, apiId), level);
|
|
134
|
+
}
|
|
135
|
+
export { CATALOG, clampThinkingLevel, clampThinkingLevelForApiId, geminiKindForMime, getTool, listBuiltinIds, mimeAllowed, mimeEssence, modelEntryByApiId, registerTools, requireModelSpec, resetTools, };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel ingress: input parts, image pins, and speech-role checks.
|
|
3
|
+
*
|
|
4
|
+
* Owned by the kernel so `resolveTurn` does not import provider adapters.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { BuiltinToolId, ImageResponseFormat, InteractionPart, ModelId, Profile, TurnRequest } from '../types.js';
|
|
9
|
+
declare function assertSpeechRole(profile: Profile): void;
|
|
10
|
+
declare function resolveImageFormat(profile: Profile, _model: ModelId, slots?: Record<string, string>): ImageResponseFormat | null;
|
|
11
|
+
declare function resolveInputParts(profile: Profile, model: ModelId, req: TurnRequest): InteractionPart[];
|
|
12
|
+
declare function assertImageGrounding(profile: Profile, model: ModelId, builtins: BuiltinToolId[]): void;
|
|
13
|
+
export { assertImageGrounding, assertSpeechRole, resolveImageFormat, resolveInputParts };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel ingress: input parts, image pins, and speech-role checks.
|
|
3
|
+
*
|
|
4
|
+
* Owned by the kernel so `resolveTurn` does not import provider adapters.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { TheorumError } from '../../guardrails/error.js';
|
|
9
|
+
import { wrapUserData } from '../engine/boundary.js';
|
|
10
|
+
import { synthesizeRepairPrompt } from '../engine/repair.js';
|
|
11
|
+
import { assertAttachmentLimits, requireMediaLimits } from './attachments.js';
|
|
12
|
+
import { geminiKindForMime, mimeAllowed, mimeEssence } from './catalog.js';
|
|
13
|
+
function listedValue(allowed, value) {
|
|
14
|
+
if (!value) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
if (!allowed || allowed.length === 0) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (allowed.includes(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
function resolveSlotOrPin(profileId, label, slotValue, pin, allow) {
|
|
26
|
+
if (slotValue !== undefined) {
|
|
27
|
+
const fromSlot = listedValue(allow, slotValue);
|
|
28
|
+
if (!fromSlot) {
|
|
29
|
+
throw new TheorumError(`Unknown image ${label} for ${profileId}`);
|
|
30
|
+
}
|
|
31
|
+
return fromSlot;
|
|
32
|
+
}
|
|
33
|
+
if (pin === undefined) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const fromPin = listedValue(allow, pin);
|
|
37
|
+
if (!fromPin) {
|
|
38
|
+
throw new TheorumError(`Unknown image ${label} for ${profileId}`);
|
|
39
|
+
}
|
|
40
|
+
return fromPin;
|
|
41
|
+
}
|
|
42
|
+
function assertImageRole(profile) {
|
|
43
|
+
const pins = profile.outputs.image;
|
|
44
|
+
if (!pins) {
|
|
45
|
+
throw new TheorumError(`Profile ${profile.id} requests image output but does not set outputs.image`);
|
|
46
|
+
}
|
|
47
|
+
assertExclusiveNativeOutput(profile, 'image');
|
|
48
|
+
return pins;
|
|
49
|
+
}
|
|
50
|
+
function assertSpeechRole(profile) {
|
|
51
|
+
if (!profile.outputs.speech) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
assertExclusiveNativeOutput(profile, 'speech');
|
|
55
|
+
if (profile.outputs.speech.format === 'mp3' && profile.model.protocol === 'geminiInteractions') {
|
|
56
|
+
throw new TheorumError(`Profile ${profile.id}: outputs.speech.format 'mp3' requires protocol 'openAi' ` +
|
|
57
|
+
`(geminiInteractions speech returns PCM and emits WAV)`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function assertExclusiveNativeOutput(profile, kind) {
|
|
61
|
+
const other = kind === 'image' ? 'speech' : 'image';
|
|
62
|
+
if (profile.outputs[other]) {
|
|
63
|
+
throw new TheorumError(`Profile ${profile.id} cannot mix outputs.${kind} with outputs.${other}`);
|
|
64
|
+
}
|
|
65
|
+
if (profile.outputs.structured !== null && profile.outputs.structured !== undefined) {
|
|
66
|
+
throw new TheorumError(`Profile ${profile.id} cannot mix structured JSON with native ${kind} output`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function resolveImageFormat(profile, _model, slots) {
|
|
70
|
+
if (!profile.outputs.image) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const pins = assertImageRole(profile);
|
|
74
|
+
const aspectRatio = resolveSlotOrPin(profile.id, 'aspect', slots?.aspectRatio, pins.aspectRatio, profile.inputs.slots?.aspectRatio);
|
|
75
|
+
const size = resolveSlotOrPin(profile.id, 'size', slots?.size, pins.size, profile.inputs.slots?.size);
|
|
76
|
+
if (!(aspectRatio && size)) {
|
|
77
|
+
throw new TheorumError(`Unknown image aspect or size for ${profile.id}`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
type: 'image',
|
|
81
|
+
mimeType: pins.mimeType ?? 'image/jpeg',
|
|
82
|
+
aspectRatio,
|
|
83
|
+
size,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function assertGeminiMime(mime) {
|
|
87
|
+
const kind = geminiKindForMime(mime);
|
|
88
|
+
if (!kind) {
|
|
89
|
+
throw new TheorumError(`MIME '${mime}' is not a Gemini input type`);
|
|
90
|
+
}
|
|
91
|
+
return kind;
|
|
92
|
+
}
|
|
93
|
+
function mediaParts(profile, model, blobs, channel) {
|
|
94
|
+
const accept = channel === 'voice' ? profile.inputs.voice?.accept : profile.inputs.attachments?.accept;
|
|
95
|
+
if (!accept) {
|
|
96
|
+
throw new TheorumError(`Profile ${profile.id} does not accept ${channel}`);
|
|
97
|
+
}
|
|
98
|
+
const maxInputImages = profile.outputs.image?.maxInputImages;
|
|
99
|
+
const imageCount = blobs.filter((blob) => geminiKindForMime(blob.mimeType) === 'image').length;
|
|
100
|
+
if (maxInputImages !== undefined && imageCount > maxInputImages) {
|
|
101
|
+
throw new TheorumError(`At most ${maxInputImages} reference images on ${model}`);
|
|
102
|
+
}
|
|
103
|
+
return blobs.map((blob) => {
|
|
104
|
+
const kind = assertGeminiMime(blob.mimeType);
|
|
105
|
+
if (!mimeAllowed(accept, blob.mimeType)) {
|
|
106
|
+
throw new TheorumError(`MIME '${blob.mimeType}' is not accepted on ${profile.id}`);
|
|
107
|
+
}
|
|
108
|
+
const essence = mimeEssence(blob.mimeType);
|
|
109
|
+
return {
|
|
110
|
+
type: kind,
|
|
111
|
+
mimeType: essence === 'image/jpg' ? 'image/jpeg' : essence,
|
|
112
|
+
data: blob.data,
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function extractTextPart(profile, req) {
|
|
117
|
+
const { text, repair, history } = req.input ?? {};
|
|
118
|
+
if (profile.inputs.text === false) {
|
|
119
|
+
if (text) {
|
|
120
|
+
throw new TheorumError(`Profile ${profile.id} does not accept text input`);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
let promptText = text;
|
|
125
|
+
if (repair) {
|
|
126
|
+
promptText = synthesizeRepairPrompt({ profile, repair, history });
|
|
127
|
+
}
|
|
128
|
+
if (!promptText) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return { type: 'text', text: wrapUserData(promptText) };
|
|
132
|
+
}
|
|
133
|
+
function extractMediaParts(profile, model, req) {
|
|
134
|
+
const { attachments, voice } = req.input ?? {};
|
|
135
|
+
const files = attachments ?? [];
|
|
136
|
+
const clips = voice ?? [];
|
|
137
|
+
if (files.length + clips.length > 0) {
|
|
138
|
+
assertAttachmentLimits([...files, ...clips], requireMediaLimits(profile));
|
|
139
|
+
}
|
|
140
|
+
const parts = [];
|
|
141
|
+
if (files.length > 0) {
|
|
142
|
+
parts.push(...mediaParts(profile, model, files, 'attachments'));
|
|
143
|
+
}
|
|
144
|
+
if (clips.length > 0) {
|
|
145
|
+
parts.push(...mediaParts(profile, model, clips, 'voice'));
|
|
146
|
+
}
|
|
147
|
+
return parts;
|
|
148
|
+
}
|
|
149
|
+
function resolveInputParts(profile, model, req) {
|
|
150
|
+
const parts = [];
|
|
151
|
+
const textPart = extractTextPart(profile, req);
|
|
152
|
+
if (textPart) {
|
|
153
|
+
parts.push(textPart);
|
|
154
|
+
}
|
|
155
|
+
parts.push(...extractMediaParts(profile, model, req));
|
|
156
|
+
return parts;
|
|
157
|
+
}
|
|
158
|
+
function assertImageGrounding(profile, model, builtins) {
|
|
159
|
+
if (profile.outputs.image?.allowsGrounding === false && builtins.length > 0) {
|
|
160
|
+
throw new TheorumError(`Grounding tools are not valid on ${model}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export { assertImageGrounding, assertSpeechRole, resolveImageFormat, resolveInputParts };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime profile registry for host-owned THEORUM profiles.
|
|
3
|
+
*
|
|
4
|
+
* THEORUM ships profile types and defaults, not application profiles. Host apps
|
|
5
|
+
* register their profiles at process startup or test setup.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { Profile } from '../types.js';
|
|
10
|
+
/** Host-authored profile definition, with defaults applied to omitted sections. */
|
|
11
|
+
export type ProfileDefinition = {
|
|
12
|
+
id: Profile['id'];
|
|
13
|
+
identity?: Partial<Profile['identity']>;
|
|
14
|
+
model: Partial<Profile['model']> & Pick<Profile['model'], 'allow' | 'config'>;
|
|
15
|
+
tools?: Partial<Profile['tools']>;
|
|
16
|
+
inputs?: Partial<Profile['inputs']>;
|
|
17
|
+
outputs?: Partial<Profile['outputs']>;
|
|
18
|
+
guardrails?: Partial<Profile['guardrails']>;
|
|
19
|
+
};
|
|
20
|
+
/** Define a typed profile with stable defaults for optional properties. */
|
|
21
|
+
declare function defineProfile(input: ProfileDefinition): Profile;
|
|
22
|
+
/** Register one host-owned profile in the process-local registry. */
|
|
23
|
+
declare function registerProfile(profileInput: Profile | ProfileDefinition): void;
|
|
24
|
+
/** Register several host-owned profiles in order. */
|
|
25
|
+
declare function registerProfiles(profilesList: Array<Profile | ProfileDefinition>): void;
|
|
26
|
+
/** Return whether a profile id is currently registered. */
|
|
27
|
+
declare function hasProfile(id: string): boolean;
|
|
28
|
+
/** List all currently registered profiles. */
|
|
29
|
+
declare function listProfiles(): Profile[];
|
|
30
|
+
/** Clear the process-local registry; intended for tests and host reloads. */
|
|
31
|
+
declare function clearProfiles(): void;
|
|
32
|
+
/** Fetch a registered profile or throw a `TheorumError`. */
|
|
33
|
+
declare function getProfile(id: string): Profile;
|
|
34
|
+
export { clearProfiles, defineProfile, getProfile, hasProfile, listProfiles, registerProfile, registerProfiles, };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime profile registry for host-owned THEORUM profiles.
|
|
3
|
+
*
|
|
4
|
+
* THEORUM ships profile types and defaults, not application profiles. Host apps
|
|
5
|
+
* register their profiles at process startup or test setup.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { TheorumError } from '../../guardrails/error.js';
|
|
10
|
+
const profiles = new Map();
|
|
11
|
+
function assertModelSpecs(profileId, allow, config) {
|
|
12
|
+
for (const id of allow) {
|
|
13
|
+
if (!config[id]) {
|
|
14
|
+
throw new TheorumError(`Profile ${profileId} allowlists '${id}' without a model spec`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function buildDefaultIdentity(id, identity) {
|
|
19
|
+
return {
|
|
20
|
+
handle: identity?.handle ?? id,
|
|
21
|
+
chat: identity?.chat,
|
|
22
|
+
system: identity?.system,
|
|
23
|
+
systemByRole: identity?.systemByRole,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function buildDefaultModel(profileId, model) {
|
|
27
|
+
assertModelSpecs(profileId, model.allow, model.config);
|
|
28
|
+
return {
|
|
29
|
+
protocol: model.protocol ?? 'geminiInteractions',
|
|
30
|
+
provider: model.provider ?? 'google',
|
|
31
|
+
allow: model.allow,
|
|
32
|
+
config: model.config,
|
|
33
|
+
thinking: model.thinking ?? 'minimal',
|
|
34
|
+
controls: model.controls ?? [],
|
|
35
|
+
maxSteps: model.maxSteps ?? 1,
|
|
36
|
+
key: model.key ?? 'freeA',
|
|
37
|
+
select: model.select,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildDefaultInputs(inputs) {
|
|
41
|
+
return {
|
|
42
|
+
text: inputs?.text ?? true,
|
|
43
|
+
attachments: inputs?.attachments,
|
|
44
|
+
voice: inputs?.voice,
|
|
45
|
+
maxFiles: inputs?.maxFiles,
|
|
46
|
+
maxBytes: inputs?.maxBytes,
|
|
47
|
+
maxTurnBytes: inputs?.maxTurnBytes,
|
|
48
|
+
limitsByMime: inputs?.limitsByMime,
|
|
49
|
+
slots: inputs?.slots,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function buildDefaultOutputs(outputs) {
|
|
53
|
+
return {
|
|
54
|
+
structured: outputs?.structured ?? null,
|
|
55
|
+
image: outputs?.image,
|
|
56
|
+
speech: outputs?.speech,
|
|
57
|
+
validation: outputs?.validation,
|
|
58
|
+
streaming: outputs?.streaming,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function buildDefaultGuardrails(guardrails) {
|
|
62
|
+
return {
|
|
63
|
+
quota: guardrails?.quota,
|
|
64
|
+
canary: guardrails?.canary ?? true,
|
|
65
|
+
sanitizeInput: guardrails?.sanitizeInput ?? true,
|
|
66
|
+
redactSensitive: guardrails?.redactSensitive ?? true,
|
|
67
|
+
egress: guardrails?.egress,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Define a typed profile with stable defaults for optional properties. */
|
|
71
|
+
function defineProfile(input) {
|
|
72
|
+
return {
|
|
73
|
+
id: input.id,
|
|
74
|
+
identity: buildDefaultIdentity(input.id, input.identity),
|
|
75
|
+
model: buildDefaultModel(input.id, input.model),
|
|
76
|
+
tools: { allow: input.tools?.allow ?? [] },
|
|
77
|
+
inputs: buildDefaultInputs(input.inputs),
|
|
78
|
+
outputs: buildDefaultOutputs(input.outputs),
|
|
79
|
+
guardrails: buildDefaultGuardrails(input.guardrails),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Register one host-owned profile in the process-local registry. */
|
|
83
|
+
function registerProfile(profileInput) {
|
|
84
|
+
const profile = defineProfile(profileInput);
|
|
85
|
+
assertModelSpecs(profile.id, profile.model.allow, profile.model.config);
|
|
86
|
+
const { attachments, voice, maxFiles, maxBytes, maxTurnBytes } = profile.inputs;
|
|
87
|
+
if (attachments || voice) {
|
|
88
|
+
if (!(maxFiles && maxBytes && maxTurnBytes)) {
|
|
89
|
+
throw new TheorumError(`Profile ${profile.id} must set maxFiles, maxBytes, and maxTurnBytes`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
profiles.set(profile.id, profile);
|
|
93
|
+
}
|
|
94
|
+
/** Register several host-owned profiles in order. */
|
|
95
|
+
function registerProfiles(profilesList) {
|
|
96
|
+
for (const p of profilesList) {
|
|
97
|
+
registerProfile(p);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Return whether a profile id is currently registered. */
|
|
101
|
+
function hasProfile(id) {
|
|
102
|
+
return profiles.has(id);
|
|
103
|
+
}
|
|
104
|
+
/** List all currently registered profiles. */
|
|
105
|
+
function listProfiles() {
|
|
106
|
+
return Array.from(profiles.values());
|
|
107
|
+
}
|
|
108
|
+
/** Clear the process-local registry; intended for tests and host reloads. */
|
|
109
|
+
function clearProfiles() {
|
|
110
|
+
profiles.clear();
|
|
111
|
+
}
|
|
112
|
+
/** Fetch a registered profile or throw a `TheorumError`. */
|
|
113
|
+
function getProfile(id) {
|
|
114
|
+
const profile = profiles.get(id);
|
|
115
|
+
if (!profile) {
|
|
116
|
+
throw new TheorumError(`Unknown profile '${id}'`);
|
|
117
|
+
}
|
|
118
|
+
return profile;
|
|
119
|
+
}
|
|
120
|
+
export { clearProfiles, defineProfile, getProfile, hasProfile, listProfiles, registerProfile, registerProfiles, };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderCompleteRequest, ResolvedGeneration } from '../types.js';
|
|
2
|
+
/** Build the provider request projection shared by execution and tracing. */
|
|
3
|
+
declare function providerCompleteRequest(generation: ResolvedGeneration, system: string): ProviderCompleteRequest;
|
|
4
|
+
export { providerCompleteRequest };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Build the provider request projection shared by execution and tracing. */
|
|
2
|
+
function providerCompleteRequest(generation, system) {
|
|
3
|
+
return {
|
|
4
|
+
model: generation.model,
|
|
5
|
+
apiId: generation.apiId,
|
|
6
|
+
openRouterId: generation.openRouterId,
|
|
7
|
+
previousInteractionId: generation.previousInteractionId,
|
|
8
|
+
store: generation.store,
|
|
9
|
+
thinking: generation.thinking,
|
|
10
|
+
summaries: generation.summaries,
|
|
11
|
+
maxOutputTokens: generation.maxOutputTokens,
|
|
12
|
+
temperature: generation.temperature,
|
|
13
|
+
builtins: generation.builtins,
|
|
14
|
+
system,
|
|
15
|
+
input: generation.input,
|
|
16
|
+
history: generation.history,
|
|
17
|
+
dynamicTools: generation.dynamicTools,
|
|
18
|
+
dynamicToolLoader: generation.dynamicToolLoader,
|
|
19
|
+
structured: generation.structured,
|
|
20
|
+
image: generation.image,
|
|
21
|
+
speech: generation.speech,
|
|
22
|
+
geminiBucket: generation.geminiBucket,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export { providerCompleteRequest };
|