theorum 0.1.14 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -89
- package/esm/mod.d.ts +21 -11
- package/esm/mod.js +15 -8
- package/esm/src/cli/commands/bench.js +6 -3
- package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
- package/esm/src/cli/commands/fuzz-canary.js +195 -0
- package/esm/src/cli/commands/fuzz-guardrails.d.ts +3 -5
- package/esm/src/cli/commands/fuzz-guardrails.js +4 -581
- package/esm/src/cli/commands/profile.js +25 -14
- package/esm/src/cli/commands/run.d.ts +3 -0
- package/esm/src/cli/commands/run.js +22 -30
- package/esm/src/cli/commands/test.d.ts +10 -1
- package/esm/src/cli/commands/test.js +31 -31
- package/esm/src/cli/event-log.d.ts +19 -0
- package/esm/src/cli/event-log.js +147 -0
- package/esm/src/cli/index.js +45 -11
- package/esm/src/cli/matrix/synthesizer.d.ts +7 -9
- package/esm/src/cli/matrix/synthesizer.js +32 -106
- package/esm/src/guardrails/canary-gate.d.ts +20 -0
- package/esm/src/guardrails/canary-gate.js +31 -0
- package/esm/src/guardrails/canary.d.ts +26 -0
- package/esm/src/guardrails/canary.js +139 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +16 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +216 -0
- package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
- package/esm/src/guardrails/corpus/inbound-payloads.js +124 -0
- package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
- package/esm/src/guardrails/corpus/live-attacks.js +232 -0
- package/esm/src/guardrails/corpus/mod.d.ts +13 -0
- package/esm/src/guardrails/corpus/mod.js +11 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +16 -0
- package/esm/src/guardrails/corpus/secrets.js +16 -0
- package/esm/src/guardrails/corpus/strings.d.ts +27 -0
- package/esm/src/guardrails/corpus/strings.js +33 -0
- package/esm/src/guardrails/corpus/types.d.ts +37 -0
- package/esm/src/guardrails/corpus/types.js +6 -0
- package/esm/src/guardrails/egress.d.ts +9 -0
- package/esm/src/guardrails/egress.js +36 -0
- package/esm/src/guardrails/error.js +6 -4
- package/esm/src/guardrails/injection.js +28 -5
- package/esm/src/guardrails/live-outbound-gate.d.ts +39 -0
- package/esm/src/guardrails/live-outbound-gate.js +180 -0
- package/esm/src/guardrails/mod.d.ts +13 -4
- package/esm/src/guardrails/mod.js +10 -4
- package/esm/src/guardrails/quota.js +1 -1
- package/esm/src/guardrails/sanitize.d.ts +11 -15
- package/esm/src/guardrails/sanitize.js +40 -49
- package/esm/src/guardrails/sensitive.js +2 -1
- package/esm/src/guardrails/testing.d.ts +10 -0
- package/esm/src/guardrails/testing.js +9 -0
- package/esm/src/host/client-turn.d.ts +19 -0
- package/esm/src/host/client-turn.js +32 -0
- package/esm/src/host/mod.d.ts +5 -3
- package/esm/src/host/mod.js +4 -3
- package/esm/src/interface/blocks.d.ts +20 -0
- package/esm/src/interface/blocks.js +180 -0
- package/esm/src/interface/from-profile.d.ts +14 -0
- package/esm/src/interface/from-profile.js +104 -0
- package/esm/src/interface/inputs.d.ts +8 -0
- package/esm/src/interface/inputs.js +127 -0
- package/esm/src/interface/mod.d.ts +10 -0
- package/esm/src/interface/mod.js +9 -0
- package/esm/src/interface/types.d.ts +153 -0
- package/esm/src/interface/types.js +13 -0
- package/esm/src/kernel/engine/delta.d.ts +24 -2
- package/esm/src/kernel/engine/delta.js +519 -29
- package/esm/src/kernel/engine/live-inbound.d.ts +9 -0
- package/esm/src/kernel/engine/live-inbound.js +17 -0
- package/esm/src/kernel/engine/repair.js +1 -1
- package/esm/src/kernel/engine/runner/gates.d.ts +1 -1
- package/esm/src/kernel/engine/runner/gates.js +7 -7
- package/esm/src/kernel/engine/runner/mod.js +13 -13
- package/esm/src/kernel/engine/runner/state.d.ts +7 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
- package/esm/src/kernel/engine/runner/steps.js +171 -37
- package/esm/src/kernel/engine/runner/stream.d.ts +1 -2
- package/esm/src/kernel/engine/runner/stream.js +66 -29
- package/esm/src/kernel/engine/session/mod.d.ts +23 -0
- package/esm/src/kernel/engine/session/mod.js +205 -0
- package/esm/src/kernel/mod.d.ts +9 -10
- package/esm/src/kernel/mod.js +6 -8
- package/esm/src/kernel/registry/attachments.d.ts +4 -2
- package/esm/src/kernel/registry/attachments.js +6 -3
- package/esm/src/kernel/registry/catalog.d.ts +3 -16
- package/esm/src/kernel/registry/catalog.js +5 -81
- package/esm/src/kernel/registry/ingress.d.ts +9 -4
- package/esm/src/kernel/registry/ingress.js +73 -63
- package/esm/src/kernel/registry/profiles.d.ts +35 -12
- package/esm/src/kernel/registry/profiles.js +159 -66
- package/esm/src/kernel/registry/provider-request.js +11 -7
- package/esm/src/kernel/registry/resolve.d.ts +5 -6
- package/esm/src/kernel/registry/resolve.js +100 -70
- package/esm/src/kernel/registry/vault.d.ts +12 -7
- package/esm/src/kernel/registry/vault.js +30 -8
- package/esm/src/kernel/schema.d.ts +145 -0
- package/esm/src/kernel/schema.js +462 -0
- package/esm/src/kernel/stop.d.ts +15 -10
- package/esm/src/kernel/stop.js +5 -5
- package/esm/src/kernel/tools/execute.d.ts +45 -0
- package/esm/src/kernel/tools/execute.js +363 -0
- package/esm/src/kernel/tools/harness.d.ts +8 -0
- package/esm/src/kernel/tools/harness.js +44 -0
- package/esm/src/kernel/tools/invoke.d.ts +10 -0
- package/esm/src/kernel/tools/invoke.js +83 -0
- package/esm/src/kernel/tools/mod.d.ts +11 -0
- package/esm/src/kernel/tools/mod.js +10 -0
- package/esm/src/kernel/tools/project.d.ts +12 -0
- package/esm/src/kernel/tools/project.js +34 -0
- package/esm/src/kernel/tools/registry.d.ts +23 -0
- package/esm/src/kernel/tools/registry.js +71 -0
- package/esm/src/kernel/tools/resolve.d.ts +29 -0
- package/esm/src/kernel/tools/resolve.js +262 -0
- package/esm/src/kernel/tools/schema.d.ts +13 -0
- package/esm/src/kernel/tools/schema.js +159 -0
- package/esm/src/kernel/tools/types.d.ts +216 -0
- package/esm/src/kernel/tools/types.js +9 -0
- package/esm/src/kernel/types.d.ts +338 -193
- package/esm/src/observability/trace-attach.d.ts +6 -4
- package/esm/src/observability/trace-attach.js +39 -25
- package/esm/src/observability/trace-record.d.ts +11 -9
- package/esm/src/observability/trace-record.js +36 -23
- package/esm/src/observability/trace-usage.d.ts +10 -3
- package/esm/src/observability/trace-usage.js +70 -17
- package/esm/src/observability/trace.d.ts +9 -1
- package/esm/src/observability/trace.js +11 -3
- package/esm/src/presets/google/speech-voices.d.ts +11 -0
- package/esm/src/presets/google/speech-voices.js +41 -0
- package/esm/src/presets/google.d.ts +36 -24
- package/esm/src/presets/google.js +50 -63
- package/esm/src/presets/mod.d.ts +2 -2
- package/esm/src/presets/mod.js +1 -1
- package/esm/src/providers/create-provider.d.ts +14 -14
- package/esm/src/providers/create-provider.js +55 -23
- package/esm/src/providers/google/interactions/framing.d.ts +15 -0
- package/esm/src/providers/google/interactions/framing.js +208 -0
- package/esm/src/providers/google/interactions/mod.d.ts +7 -0
- package/esm/src/providers/google/interactions/mod.js +7 -0
- package/esm/src/providers/google/interactions/stream.d.ts +83 -0
- package/esm/src/providers/google/interactions/stream.js +588 -0
- package/esm/src/providers/google/keys.d.ts +26 -0
- package/esm/src/providers/{keys.js → google/keys.js} +19 -31
- package/esm/src/providers/google/live/framing.d.ts +46 -0
- package/esm/src/providers/google/live/framing.js +515 -0
- package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
- package/esm/src/providers/google/live/openapi-schema.js +45 -0
- package/esm/src/providers/google/live/session.d.ts +25 -0
- package/esm/src/providers/google/live/session.js +134 -0
- package/esm/src/providers/google/live/stream.d.ts +36 -0
- package/esm/src/providers/google/live/stream.js +201 -0
- package/esm/src/providers/google/urls.d.ts +6 -0
- package/esm/src/providers/google/urls.js +6 -0
- package/esm/src/providers/local/local.d.ts +30 -0
- package/esm/src/providers/{local.js → local/local.js} +66 -126
- package/esm/src/providers/local/mod.d.ts +9 -0
- package/esm/src/providers/local/mod.js +9 -0
- package/esm/src/providers/mod.d.ts +6 -3
- package/esm/src/providers/mod.js +3 -1
- package/esm/src/providers/openrouter/chat.d.ts +87 -0
- package/esm/src/providers/{openrouter.js → openrouter/chat.js} +70 -229
- package/esm/src/providers/openrouter/image.d.ts +34 -0
- package/esm/src/providers/openrouter/image.js +286 -0
- package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
- package/esm/src/providers/openrouter/openai/chat-payload.js +60 -0
- package/esm/src/providers/openrouter/openai/compat.d.ts +47 -0
- package/esm/src/providers/openrouter/openai/compat.js +194 -0
- package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
- package/esm/src/providers/openrouter/openai/image-payload.js +84 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.js +95 -0
- package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
- package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
- package/esm/src/providers/openrouter/speech.d.ts +23 -0
- package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
- package/esm/src/providers/probe.d.ts +1 -0
- package/esm/src/providers/probe.js +22 -0
- package/esm/src/providers/shared/pcm.d.ts +12 -0
- package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
- package/esm/src/providers/shared/sse.d.ts +18 -0
- package/esm/src/providers/shared/sse.js +87 -0
- package/esm/src/providers/shared/tool-args.d.ts +17 -0
- package/esm/src/providers/shared/tool-args.js +45 -0
- package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
- package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
- package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
- package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
- package/esm/src/providers/types.d.ts +27 -0
- package/esm/src/providers/types.js +1 -0
- package/package.json +11 -7
- package/docs/cli.md +0 -97
- package/docs/guardrails.md +0 -178
- package/docs/host.md +0 -97
- package/docs/kernel.md +0 -404
- package/docs/observability.md +0 -105
- package/docs/openrouter.md +0 -125
- package/docs/presets-google.md +0 -91
- package/docs/presets.md +0 -88
- package/docs/providers.md +0 -201
- package/docs/streaming.md +0 -96
- package/esm/src/kernel/engine/boundary.d.ts +0 -10
- package/esm/src/kernel/engine/boundary.js +0 -55
- package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
- package/esm/src/kernel/engine/runner/tools.js +0 -198
- package/esm/src/kernel/registry/tools.d.ts +0 -12
- package/esm/src/kernel/registry/tools.js +0 -36
- package/esm/src/providers/expose-for-tests.d.ts +0 -1
- package/esm/src/providers/expose-for-tests.js +0 -25
- package/esm/src/providers/gemini-tape.d.ts +0 -2
- package/esm/src/providers/google-tap.d.ts +0 -3
- package/esm/src/providers/interactions.d.ts +0 -5
- package/esm/src/providers/interactions.js +0 -169
- package/esm/src/providers/keys.d.ts +0 -19
- package/esm/src/providers/local.d.ts +0 -29
- package/esm/src/providers/openrouter-mod.d.ts +0 -13
- package/esm/src/providers/openrouter-mod.js +0 -12
- package/esm/src/providers/openrouter-payload.d.ts +0 -39
- package/esm/src/providers/openrouter-payload.js +0 -195
- package/esm/src/providers/openrouter.d.ts +0 -15
- package/esm/src/providers/pcm.d.ts +0 -7
- package/esm/src/providers/provider.d.ts +0 -15
- package/esm/src/providers/provider.js +0 -176
- package/esm/src/providers/speech.d.ts +0 -23
- package/esm/src/providers/sse.d.ts +0 -7
- package/esm/src/providers/sse.js +0 -55
- package/esm/src/streaming/mod.d.ts +0 -9
- package/esm/src/streaming/mod.js +0 -8
- /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
- /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
|
@@ -5,82 +5,72 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
|
+
import { wrapUserData } from '../../guardrails/canary.js';
|
|
8
9
|
import { TheorumError } from '../../guardrails/error.js';
|
|
9
|
-
import { wrapUserData } from '../engine/boundary.js';
|
|
10
10
|
import { synthesizeRepairPrompt } from '../engine/repair.js';
|
|
11
11
|
import { assertAttachmentLimits, requireMediaLimits } from './attachments.js';
|
|
12
12
|
import { mediaKindForMime, mimeAllowed, mimeEssence } from './catalog.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!allowed || allowed.length === 0) {
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
if (allowed.includes(value)) {
|
|
21
|
-
return value;
|
|
13
|
+
import { getStructured } from './schemas.js';
|
|
14
|
+
function usesStructuredResponseFormat(structuredId) {
|
|
15
|
+
if (!structuredId) {
|
|
16
|
+
return false;
|
|
22
17
|
}
|
|
23
|
-
|
|
18
|
+
const spec = getStructured(structuredId);
|
|
19
|
+
return spec.enforced === 'responseFormat' && spec.jsonSchema != null;
|
|
24
20
|
}
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
throw new TheorumError(`Unknown image ${label} for ${profileId}`);
|
|
30
|
-
}
|
|
31
|
-
return fromSlot;
|
|
21
|
+
function activePrimaryOutputModes(profile, structuredId) {
|
|
22
|
+
const modes = [];
|
|
23
|
+
if (usesStructuredResponseFormat(structuredId)) {
|
|
24
|
+
modes.push('structured');
|
|
32
25
|
}
|
|
33
|
-
if (
|
|
34
|
-
|
|
26
|
+
if (profile.type === 'image') {
|
|
27
|
+
modes.push('image');
|
|
28
|
+
}
|
|
29
|
+
if (profile.type === 'speech') {
|
|
30
|
+
modes.push('speech');
|
|
35
31
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
return modes;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Provider wire formats (JSON schema, image, speech) are mutually exclusive.
|
|
36
|
+
* Typed profiles make illegal mixes unrepresentable; this remains a safety net
|
|
37
|
+
* for responseFormat structured on text vs accidental dual modes.
|
|
38
|
+
*/
|
|
39
|
+
function assertOutputMode(profile, structuredId) {
|
|
40
|
+
const active = activePrimaryOutputModes(profile, structuredId);
|
|
41
|
+
if (active.length <= 1) {
|
|
42
|
+
return;
|
|
39
43
|
}
|
|
40
|
-
|
|
44
|
+
throw new TheorumError(`Profile ${profile.id} declares multiple output wire formats (${active.join(', ')}). ` +
|
|
45
|
+
`Only one of responseFormat JSON schema (outputs.structured with enforced ` +
|
|
46
|
+
`'responseFormat'), image, or speech may be active.`);
|
|
41
47
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
throw new TheorumError(`Profile ${profile.id} requests image output but does not set outputs.image`);
|
|
48
|
+
function assertImagePins(profile) {
|
|
49
|
+
if (profile.type !== 'image') {
|
|
50
|
+
throw new TheorumError(`Profile ${profile.id} is not type 'image'`);
|
|
46
51
|
}
|
|
47
|
-
|
|
48
|
-
return pins;
|
|
52
|
+
return profile.image;
|
|
49
53
|
}
|
|
50
54
|
function assertSpeechRole(profile) {
|
|
51
|
-
if (
|
|
55
|
+
if (profile.type !== 'speech') {
|
|
52
56
|
return;
|
|
53
57
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
throw new TheorumError(`Profile ${profile.id}: outputs.speech.format 'mp3' requires protocol 'openAi' ` +
|
|
58
|
+
if (profile.speech.format === 'mp3' && profile.model.protocol === 'geminiInteractions') {
|
|
59
|
+
throw new TheorumError(`Profile ${profile.id}: speech.format 'mp3' requires protocol 'openAi' ` +
|
|
57
60
|
`(geminiInteractions speech returns PCM and emits WAV)`);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
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) {
|
|
63
|
+
function resolveImageFormat(profile) {
|
|
64
|
+
if (profile.type !== 'image') {
|
|
71
65
|
return null;
|
|
72
66
|
}
|
|
73
|
-
const pins =
|
|
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
|
-
}
|
|
67
|
+
const pins = assertImagePins(profile);
|
|
79
68
|
return {
|
|
80
69
|
type: 'image',
|
|
81
|
-
mimeType: pins.mimeType
|
|
82
|
-
aspectRatio,
|
|
83
|
-
size,
|
|
70
|
+
mimeType: pins.mimeType,
|
|
71
|
+
aspectRatio: pins.aspectRatio,
|
|
72
|
+
size: pins.size,
|
|
73
|
+
includeText: pins.includeText === true,
|
|
84
74
|
};
|
|
85
75
|
}
|
|
86
76
|
function assertMediaMime(mime) {
|
|
@@ -90,12 +80,19 @@ function assertMediaMime(mime) {
|
|
|
90
80
|
}
|
|
91
81
|
return kind;
|
|
92
82
|
}
|
|
83
|
+
function profileInputs(profile) {
|
|
84
|
+
if (profile.type === 'speech') {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return profile.inputs;
|
|
88
|
+
}
|
|
93
89
|
function mediaParts(profile, model, blobs, channel) {
|
|
94
|
-
const
|
|
90
|
+
const inputs = profileInputs(profile);
|
|
91
|
+
const accept = channel === 'voice' ? inputs?.voice?.accept : inputs?.attachments?.accept;
|
|
95
92
|
if (!accept) {
|
|
96
93
|
throw new TheorumError(`Profile ${profile.id} does not accept ${channel}`);
|
|
97
94
|
}
|
|
98
|
-
const maxInputImages = profile.
|
|
95
|
+
const maxInputImages = profile.type === 'image' ? profile.image.maxInputImages : undefined;
|
|
99
96
|
const imageCount = blobs.filter((blob) => mediaKindForMime(blob.mimeType) === 'image').length;
|
|
100
97
|
if (maxInputImages !== undefined && imageCount > maxInputImages) {
|
|
101
98
|
throw new TheorumError(`At most ${maxInputImages} reference images on ${model}`);
|
|
@@ -115,7 +112,18 @@ function mediaParts(profile, model, blobs, channel) {
|
|
|
115
112
|
}
|
|
116
113
|
function extractTextPart(profile, req) {
|
|
117
114
|
const { text, repair, history } = req.input ?? {};
|
|
118
|
-
if (profile.
|
|
115
|
+
if (profile.type === 'speech') {
|
|
116
|
+
if (!text?.trim()) {
|
|
117
|
+
throw new TheorumError(`Profile ${profile.id} (speech) requires text input`);
|
|
118
|
+
}
|
|
119
|
+
let promptText = text;
|
|
120
|
+
if (repair) {
|
|
121
|
+
promptText = synthesizeRepairPrompt({ profile, repair, history });
|
|
122
|
+
}
|
|
123
|
+
return { type: 'text', text: wrapUserData(promptText) };
|
|
124
|
+
}
|
|
125
|
+
const inputs = profileInputs(profile);
|
|
126
|
+
if (inputs?.text === false) {
|
|
119
127
|
if (text) {
|
|
120
128
|
throw new TheorumError(`Profile ${profile.id} does not accept text input`);
|
|
121
129
|
}
|
|
@@ -131,6 +139,13 @@ function extractTextPart(profile, req) {
|
|
|
131
139
|
return { type: 'text', text: wrapUserData(promptText) };
|
|
132
140
|
}
|
|
133
141
|
function extractMediaParts(profile, model, req) {
|
|
142
|
+
if (profile.type === 'speech') {
|
|
143
|
+
const { attachments, voice } = req.input ?? {};
|
|
144
|
+
if ((attachments?.length ?? 0) + (voice?.length ?? 0) > 0) {
|
|
145
|
+
throw new TheorumError(`Profile ${profile.id} (speech) does not accept media input`);
|
|
146
|
+
}
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
134
149
|
const { attachments, voice } = req.input ?? {};
|
|
135
150
|
const files = attachments ?? [];
|
|
136
151
|
const clips = voice ?? [];
|
|
@@ -155,9 +170,4 @@ function resolveInputParts(profile, model, req) {
|
|
|
155
170
|
parts.push(...extractMediaParts(profile, model, req));
|
|
156
171
|
return parts;
|
|
157
172
|
}
|
|
158
|
-
|
|
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 };
|
|
173
|
+
export { assertOutputMode, assertSpeechRole, resolveImageFormat, resolveInputParts };
|
|
@@ -1,23 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime profile registry for host-owned THEORUM profiles.
|
|
3
3
|
*
|
|
4
|
-
* THEORUM ships profile types
|
|
5
|
-
*
|
|
4
|
+
* THEORUM ships profile types — not application profiles and not invented defaults.
|
|
5
|
+
* Hosts must pass required fields explicitly (`type`, `model.protocol`, `model.provider`, …).
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import type { Profile } from '../types.js';
|
|
10
|
-
|
|
11
|
-
export type ProfileDefinition = {
|
|
9
|
+
import type { ImageProfile, LiveProfile, LiveProfileModelSpec, Profile, ProfileGuardrailsSpec, ProfileIdentity, ProfileInputsSpec, ProfileModelSpec, ProfileOutputsSpec, ProfileToolsSpec, ProfileTurnResumptionSpec, SpeechProfile, TurnProfileModelSpec } from '../types.js';
|
|
10
|
+
export type ProfileDefinitionBase<P extends ProfileModelSpec = ProfileModelSpec> = {
|
|
12
11
|
id: Profile['id'];
|
|
13
|
-
identity
|
|
14
|
-
model:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
outputs?: Partial<Profile['outputs']>;
|
|
18
|
-
guardrails?: Partial<Profile['guardrails']>;
|
|
12
|
+
identity: ProfileIdentity;
|
|
13
|
+
model: P;
|
|
14
|
+
outputs?: ProfileOutputsSpec;
|
|
15
|
+
guardrails?: ProfileGuardrailsSpec;
|
|
19
16
|
};
|
|
20
|
-
|
|
17
|
+
export type TextProfileDefinition = ProfileDefinitionBase<TurnProfileModelSpec> & {
|
|
18
|
+
type: 'text';
|
|
19
|
+
tools: ProfileToolsSpec;
|
|
20
|
+
inputs: ProfileInputsSpec;
|
|
21
|
+
turnResumption?: ProfileTurnResumptionSpec;
|
|
22
|
+
};
|
|
23
|
+
export type ImageProfileDefinition = ProfileDefinitionBase<TurnProfileModelSpec> & {
|
|
24
|
+
type: 'image';
|
|
25
|
+
image: NonNullable<ImageProfile['image']>;
|
|
26
|
+
tools: ProfileToolsSpec;
|
|
27
|
+
inputs: ProfileInputsSpec;
|
|
28
|
+
turnResumption?: ProfileTurnResumptionSpec;
|
|
29
|
+
};
|
|
30
|
+
export type SpeechProfileDefinition = ProfileDefinitionBase<TurnProfileModelSpec> & {
|
|
31
|
+
type: 'speech';
|
|
32
|
+
speech: NonNullable<SpeechProfile['speech']>;
|
|
33
|
+
turnResumption?: ProfileTurnResumptionSpec;
|
|
34
|
+
};
|
|
35
|
+
export type LiveProfileDefinition = ProfileDefinitionBase<LiveProfileModelSpec> & {
|
|
36
|
+
type: 'live';
|
|
37
|
+
live: NonNullable<LiveProfile['live']>;
|
|
38
|
+
tools: ProfileToolsSpec;
|
|
39
|
+
inputs?: ProfileInputsSpec;
|
|
40
|
+
};
|
|
41
|
+
/** Host-authored profile definition — discriminated on `type`. No THEORUM defaults. */
|
|
42
|
+
export type ProfileDefinition = TextProfileDefinition | ImageProfileDefinition | SpeechProfileDefinition | LiveProfileDefinition;
|
|
43
|
+
/** Define a typed profile. Required fields must be set explicitly; optional fields stay optional. */
|
|
21
44
|
declare function defineProfile(input: ProfileDefinition): Profile;
|
|
22
45
|
/** Register one host-owned profile in the process-local registry. */
|
|
23
46
|
declare function registerProfile(profileInput: Profile | ProfileDefinition): void;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime profile registry for host-owned THEORUM profiles.
|
|
3
3
|
*
|
|
4
|
-
* THEORUM ships profile types
|
|
5
|
-
*
|
|
4
|
+
* THEORUM ships profile types — not application profiles and not invented defaults.
|
|
5
|
+
* Hosts must pass required fields explicitly (`type`, `model.protocol`, `model.provider`, …).
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { TheorumError } from '../../guardrails/error.js';
|
|
10
|
+
import { isValidPair, isValidProfileProtocol, protocolsForProfileType } from '../schema.js';
|
|
11
|
+
import { getTool } from '../tools/registry.js';
|
|
10
12
|
const profiles = new Map();
|
|
11
13
|
function assertModelSpecs(profileId, allow, config) {
|
|
12
14
|
for (const id of allow) {
|
|
@@ -15,69 +17,96 @@ function assertModelSpecs(profileId, allow, config) {
|
|
|
15
17
|
}
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
};
|
|
20
|
+
function assertProtocolProvider(profileId, model) {
|
|
21
|
+
if (!model.protocol) {
|
|
22
|
+
throw new TheorumError(`Profile ${profileId} must set model.protocol`);
|
|
23
|
+
}
|
|
24
|
+
if (!model.provider) {
|
|
25
|
+
throw new TheorumError(`Profile ${profileId} must set model.provider`);
|
|
26
|
+
}
|
|
27
|
+
if (!isValidPair(model.protocol, model.provider)) {
|
|
28
|
+
throw new TheorumError(`Profile ${profileId}: protocol '${model.protocol}' is not valid for provider '${model.provider}'`);
|
|
29
|
+
}
|
|
60
30
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
egress: guardrails?.egress,
|
|
68
|
-
};
|
|
31
|
+
function assertTypeProtocol(profile) {
|
|
32
|
+
const protocol = profile.model.protocol;
|
|
33
|
+
if (isValidProfileProtocol(profile.type, protocol))
|
|
34
|
+
return;
|
|
35
|
+
const valid = protocolsForProfileType(profile.type).join(', ');
|
|
36
|
+
throw new TheorumError(`Profile ${profile.id}: type '${profile.type}' cannot use protocol '${protocol}'. Supported: ${valid}`);
|
|
69
37
|
}
|
|
70
|
-
/** Define a typed profile
|
|
38
|
+
/** Define a typed profile. Required fields must be set explicitly; optional fields stay optional. */
|
|
71
39
|
function defineProfile(input) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
outputs: buildDefaultOutputs(input.outputs),
|
|
79
|
-
guardrails: buildDefaultGuardrails(input.guardrails),
|
|
40
|
+
assertProtocolProvider(input.id, input.model);
|
|
41
|
+
assertModelSpecs(input.id, input.model.allow, input.model.config);
|
|
42
|
+
const identity = {
|
|
43
|
+
handle: input.identity.handle,
|
|
44
|
+
system: input.identity.system,
|
|
45
|
+
systemByRole: input.identity.systemByRole,
|
|
80
46
|
};
|
|
47
|
+
const outputs = input.outputs;
|
|
48
|
+
const guardrails = input.guardrails;
|
|
49
|
+
let profile;
|
|
50
|
+
switch (input.type) {
|
|
51
|
+
case 'text':
|
|
52
|
+
profile = {
|
|
53
|
+
type: 'text',
|
|
54
|
+
id: input.id,
|
|
55
|
+
identity,
|
|
56
|
+
model: input.model,
|
|
57
|
+
tools: input.tools,
|
|
58
|
+
inputs: input.inputs,
|
|
59
|
+
outputs,
|
|
60
|
+
turnResumption: input.turnResumption,
|
|
61
|
+
guardrails,
|
|
62
|
+
};
|
|
63
|
+
break;
|
|
64
|
+
case 'image':
|
|
65
|
+
profile = {
|
|
66
|
+
type: 'image',
|
|
67
|
+
id: input.id,
|
|
68
|
+
identity,
|
|
69
|
+
model: input.model,
|
|
70
|
+
image: input.image,
|
|
71
|
+
tools: input.tools,
|
|
72
|
+
inputs: input.inputs,
|
|
73
|
+
outputs,
|
|
74
|
+
turnResumption: input.turnResumption,
|
|
75
|
+
guardrails,
|
|
76
|
+
};
|
|
77
|
+
break;
|
|
78
|
+
case 'speech':
|
|
79
|
+
profile = {
|
|
80
|
+
type: 'speech',
|
|
81
|
+
id: input.id,
|
|
82
|
+
identity,
|
|
83
|
+
model: input.model,
|
|
84
|
+
speech: input.speech,
|
|
85
|
+
outputs,
|
|
86
|
+
turnResumption: input.turnResumption,
|
|
87
|
+
guardrails,
|
|
88
|
+
};
|
|
89
|
+
break;
|
|
90
|
+
case 'live':
|
|
91
|
+
profile = {
|
|
92
|
+
type: 'live',
|
|
93
|
+
id: input.id,
|
|
94
|
+
identity,
|
|
95
|
+
model: input.model,
|
|
96
|
+
live: input.live,
|
|
97
|
+
tools: input.tools,
|
|
98
|
+
inputs: input.inputs,
|
|
99
|
+
outputs,
|
|
100
|
+
guardrails,
|
|
101
|
+
};
|
|
102
|
+
break;
|
|
103
|
+
default: {
|
|
104
|
+
const _exhaustive = input;
|
|
105
|
+
throw new TheorumError(`Unknown profile type '${String(_exhaustive)}'`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
assertTypeProtocol(profile);
|
|
109
|
+
return profile;
|
|
81
110
|
}
|
|
82
111
|
function assertCompactionSpec(profileId, modelId, spec) {
|
|
83
112
|
const tag = `Profile ${profileId} model ${modelId} compaction`;
|
|
@@ -111,16 +140,80 @@ function assertCompactionRetain(tag, spec) {
|
|
|
111
140
|
throw new TheorumError(`${tag}: previousExchanges >= 1 must be an integer`);
|
|
112
141
|
}
|
|
113
142
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
143
|
+
function profileToolsAllow(profile) {
|
|
144
|
+
if (profile.type === 'speech') {
|
|
145
|
+
return [];
|
|
146
|
+
}
|
|
147
|
+
return profile.tools.allow;
|
|
148
|
+
}
|
|
149
|
+
function assertCustomToolsOnly(profile) {
|
|
150
|
+
for (const id of profileToolsAllow(profile)) {
|
|
151
|
+
const tool = getTool(id);
|
|
152
|
+
if (tool?.type === 'builtin') {
|
|
153
|
+
throw new TheorumError(`Profile ${profile.id} lists builtin '${id}' in tools.allow — declare it on model.config.*.builtInTools instead`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function assertProfileToolLoader(profile) {
|
|
158
|
+
if (profile.type === 'speech') {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const loaderId = profile.tools.t2Loader;
|
|
162
|
+
if (!loaderId) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (!profile.tools.allow.includes(loaderId)) {
|
|
166
|
+
throw new TheorumError(`Profile ${profile.id} tools.t2Loader '${loaderId}' must also be listed in tools.allow`);
|
|
167
|
+
}
|
|
168
|
+
const tool = getTool(loaderId);
|
|
169
|
+
if (tool?.type !== 'function') {
|
|
170
|
+
throw new TheorumError(`Profile ${profile.id} tools.t2Loader '${loaderId}' must be a registered type: 'function' tool`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function assertModelBuiltInTools(profile) {
|
|
174
|
+
for (const [modelId, spec] of Object.entries(profile.model.config)) {
|
|
175
|
+
for (const id of spec.builtInTools ?? []) {
|
|
176
|
+
const tool = getTool(id);
|
|
177
|
+
if (tool?.type !== 'builtin') {
|
|
178
|
+
throw new TheorumError(`Profile ${profile.id} model '${modelId}' lists '${id}' in builtInTools — not a registered builtin`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function assertCompactionOnlyOnText(profile) {
|
|
184
|
+
if (profile.type === 'text') {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const [modelId, spec] of Object.entries(profile.model.config)) {
|
|
188
|
+
if (spec.compaction) {
|
|
189
|
+
throw new TheorumError(`Profile ${profile.id} model '${modelId}': compaction is only valid on type 'text'`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function assertMediaLimits(profile) {
|
|
194
|
+
if (profile.type === 'speech') {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const inputs = profile.inputs;
|
|
198
|
+
if (!inputs) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const { attachments, voice, maxFiles, maxBytes, maxTurnBytes } = inputs;
|
|
119
202
|
if (attachments || voice) {
|
|
120
203
|
if (!(maxFiles && maxBytes && maxTurnBytes)) {
|
|
121
204
|
throw new TheorumError(`Profile ${profile.id} must set maxFiles, maxBytes, and maxTurnBytes`);
|
|
122
205
|
}
|
|
123
206
|
}
|
|
207
|
+
}
|
|
208
|
+
/** Register one host-owned profile in the process-local registry. */
|
|
209
|
+
function registerProfile(profileInput) {
|
|
210
|
+
const profile = defineProfile(profileInput);
|
|
211
|
+
assertModelSpecs(profile.id, profile.model.allow, profile.model.config);
|
|
212
|
+
assertCustomToolsOnly(profile);
|
|
213
|
+
assertProfileToolLoader(profile);
|
|
214
|
+
assertModelBuiltInTools(profile);
|
|
215
|
+
assertCompactionOnlyOnText(profile);
|
|
216
|
+
assertMediaLimits(profile);
|
|
124
217
|
for (const [modelId, spec] of Object.entries(profile.model.config)) {
|
|
125
218
|
if (spec.compaction) {
|
|
126
219
|
assertCompactionSpec(profile.id, modelId, spec.compaction);
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
/** Build the provider request projection shared by execution and tracing. */
|
|
2
2
|
function providerCompleteRequest(generation, system) {
|
|
3
|
+
const isInteractions = generation.transport === 'interactions';
|
|
3
4
|
return {
|
|
4
5
|
model: generation.model,
|
|
5
6
|
apiId: generation.apiId,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
previousInteractionId: isInteractions ? generation.previousInteractionId : undefined,
|
|
8
|
+
store: isInteractions ? generation.store : undefined,
|
|
9
|
+
stream: isInteractions ? generation.stream : undefined,
|
|
9
10
|
thinking: generation.thinking,
|
|
10
|
-
summaries: generation.summaries,
|
|
11
|
+
summaries: isInteractions ? generation.summaries : undefined,
|
|
11
12
|
maxOutputTokens: generation.maxOutputTokens,
|
|
12
13
|
temperature: generation.temperature,
|
|
13
14
|
builtins: generation.builtins,
|
|
15
|
+
googleMapsLocation: isInteractions ? generation.googleMapsLocation : undefined,
|
|
14
16
|
system,
|
|
15
17
|
input: generation.input,
|
|
16
18
|
history: generation.history,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
interactionOnlyInput: isInteractions ? generation.interactionOnlyInput : undefined,
|
|
20
|
+
wireTools: generation.tools.wire,
|
|
19
21
|
structured: generation.structured,
|
|
20
22
|
image: generation.image,
|
|
21
23
|
speech: generation.speech,
|
|
22
|
-
|
|
24
|
+
live: generation.live,
|
|
25
|
+
sessionResumptionHandle: generation.sessionResumptionHandle,
|
|
26
|
+
keySlot: generation.keySlot,
|
|
23
27
|
};
|
|
24
28
|
}
|
|
25
29
|
export { providerCompleteRequest };
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Profile resolution for THEORUM turns.
|
|
3
3
|
*
|
|
4
|
-
* This module validates caller overrides, selects models and tools, normalizes
|
|
5
|
-
* input parts, and produces the provider request state consumed by `runTurn`.
|
|
6
|
-
*
|
|
7
4
|
* @module
|
|
8
5
|
*/
|
|
9
|
-
import type { Profile, ProjectedProfile, ResolvedGeneration,
|
|
10
|
-
declare function
|
|
6
|
+
import type { ModelId, Profile, ProjectedProfile, ResolvedGeneration, TurnRequest } from '../types.js';
|
|
7
|
+
declare function pickModel(profile: Profile, select?: string): ModelId;
|
|
11
8
|
/** Resolve a host `TurnRequest` into provider-ready generation state. */
|
|
12
9
|
declare function resolveTurn(req: TurnRequest): {
|
|
13
10
|
profile: Profile;
|
|
14
11
|
generation: ResolvedGeneration;
|
|
15
12
|
};
|
|
13
|
+
/** Project a profile object into a safe host/UI inspection object. */
|
|
14
|
+
declare function projectProfileObject(profile: Profile): ProjectedProfile;
|
|
16
15
|
/** Project a registered profile into a safe host/UI inspection object. */
|
|
17
16
|
declare function projectProfile(id: Profile['id']): ProjectedProfile;
|
|
18
17
|
declare function pickSystemRole(profile: Profile, requested?: string): string;
|
|
19
|
-
export {
|
|
18
|
+
export { pickModel, pickSystemRole, projectProfile, projectProfileObject, resolveTurn };
|