theorum 1.0.0 → 1.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.
- package/README.md +81 -25
- package/esm/mod.d.ts +48 -29
- package/esm/mod.js +37 -24
- package/esm/src/cli/commands/bench.js +13 -16
- package/esm/src/cli/commands/fuzz-canary.js +25 -29
- package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
- package/esm/src/cli/commands/guardrails-eval.js +15 -0
- package/esm/src/cli/commands/profile.js +12 -3
- package/esm/src/cli/commands/run.js +8 -9
- package/esm/src/cli/commands/test.js +8 -8
- package/esm/src/cli/event-log.d.ts +1 -1
- package/esm/src/cli/index.js +12 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +6 -6
- package/esm/src/cli/matrix/synthesizer.js +23 -22
- package/esm/src/guardrails/canary-gate.d.ts +2 -1
- package/esm/src/guardrails/canary-gate.js +2 -1
- package/esm/src/guardrails/canary.d.ts +9 -1
- package/esm/src/guardrails/canary.js +21 -10
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +1 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +12 -15
- package/esm/src/guardrails/corpus/inbound-payloads.js +1 -0
- package/esm/src/guardrails/corpus/live-attacks.js +0 -1
- package/esm/src/guardrails/corpus/mod.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.js +1 -0
- package/esm/src/guardrails/corpus/strings.d.ts +1 -0
- package/esm/src/guardrails/corpus/strings.js +1 -0
- package/esm/src/guardrails/corpus/types.d.ts +1 -0
- package/esm/src/guardrails/egress.d.ts +26 -3
- package/esm/src/guardrails/egress.js +69 -18
- package/esm/src/guardrails/error.d.ts +14 -23
- package/esm/src/guardrails/error.js +86 -77
- package/esm/src/guardrails/eval/corpus.d.ts +108 -0
- package/esm/src/guardrails/eval/corpus.js +978 -0
- package/esm/src/guardrails/eval/mod.d.ts +51 -0
- package/esm/src/guardrails/eval/mod.js +133 -0
- package/esm/src/guardrails/eval/score.d.ts +66 -0
- package/esm/src/guardrails/eval/score.js +114 -0
- package/esm/src/guardrails/events.d.ts +25 -0
- package/esm/src/guardrails/events.js +56 -0
- package/esm/src/guardrails/hits.d.ts +24 -0
- package/esm/src/guardrails/hits.js +45 -0
- package/esm/src/guardrails/lexicon.d.ts +39 -0
- package/esm/src/guardrails/lexicon.js +200 -0
- package/esm/src/guardrails/live-outbound-gate.d.ts +15 -13
- package/esm/src/guardrails/live-outbound-gate.js +131 -89
- package/esm/src/guardrails/mod.d.ts +19 -4
- package/esm/src/guardrails/mod.js +12 -3
- package/esm/src/guardrails/network.d.ts +19 -0
- package/esm/src/guardrails/network.js +234 -0
- package/esm/src/guardrails/policy.d.ts +35 -0
- package/esm/src/guardrails/policy.js +50 -0
- package/esm/src/guardrails/progressive-yield.d.ts +51 -0
- package/esm/src/guardrails/progressive-yield.js +98 -0
- package/esm/src/guardrails/quota.d.ts +17 -3
- package/esm/src/guardrails/quota.js +18 -4
- package/esm/src/guardrails/sanitize.d.ts +36 -6
- package/esm/src/guardrails/sanitize.js +152 -60
- package/esm/src/guardrails/serialize.d.ts +35 -0
- package/esm/src/guardrails/serialize.js +58 -0
- package/esm/src/guardrails/testing.d.ts +7 -0
- package/esm/src/guardrails/testing.js +4 -0
- package/esm/src/guardrails/theorum-error.d.ts +12 -0
- package/esm/src/guardrails/theorum-error.js +15 -0
- package/esm/src/guardrails/tool-directives.d.ts +48 -0
- package/esm/src/guardrails/tool-directives.js +124 -0
- package/esm/src/guardrails/tool-result.d.ts +93 -0
- package/esm/src/guardrails/tool-result.js +276 -0
- package/esm/src/guardrails/types.d.ts +291 -0
- package/esm/src/guardrails/types.js +72 -0
- package/esm/src/host/client-turn.js +4 -0
- package/esm/src/host/mint-trace.d.ts +1 -1
- package/esm/src/kernel/auth/crypto.d.ts +42 -0
- package/esm/src/kernel/auth/crypto.js +106 -0
- package/esm/src/kernel/auth/mod.d.ts +11 -0
- package/esm/src/kernel/auth/mod.js +11 -0
- package/esm/src/kernel/auth/oauth.d.ts +47 -0
- package/esm/src/kernel/auth/oauth.js +278 -0
- package/esm/src/kernel/auth/types.d.ts +133 -0
- package/esm/src/kernel/auth/types.js +13 -0
- package/esm/src/kernel/engine/delta.js +43 -2
- package/esm/src/kernel/engine/live-inbound.d.ts +15 -3
- package/esm/src/kernel/engine/live-inbound.js +22 -8
- package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
- package/esm/src/kernel/engine/live-ingress.js +47 -0
- package/esm/src/kernel/engine/repair.js +13 -12
- package/esm/src/kernel/engine/runner/gates.js +127 -40
- package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
- package/esm/src/kernel/engine/runner/mod.js +182 -43
- package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
- package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
- package/esm/src/kernel/engine/runner/stages.js +89 -0
- package/esm/src/kernel/engine/runner/state.d.ts +24 -0
- package/esm/src/kernel/engine/runner/steps.js +128 -61
- package/esm/src/kernel/engine/runner/stream.d.ts +9 -2
- package/esm/src/kernel/engine/runner/stream.js +114 -55
- package/esm/src/kernel/engine/session/mod.d.ts +3 -1
- package/esm/src/kernel/engine/session/mod.js +386 -34
- package/esm/src/kernel/interaction-parts.d.ts +14 -0
- package/esm/src/kernel/interaction-parts.js +23 -0
- package/esm/src/kernel/mod.d.ts +19 -7
- package/esm/src/kernel/mod.js +10 -5
- package/esm/src/kernel/profile-graph.d.ts +159 -0
- package/esm/src/kernel/profile-graph.js +156 -0
- package/esm/src/kernel/registry/attachments.d.ts +10 -10
- package/esm/src/kernel/registry/attachments.js +29 -26
- package/esm/src/kernel/registry/catalog.d.ts +25 -11
- package/esm/src/kernel/registry/catalog.js +59 -24
- package/esm/src/kernel/registry/ingress.js +34 -22
- package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
- package/esm/src/kernel/registry/profile-outputs.js +8 -0
- package/esm/src/kernel/registry/profiles.d.ts +34 -14
- package/esm/src/kernel/registry/profiles.js +306 -59
- package/esm/src/kernel/registry/provider-request.js +2 -0
- package/esm/src/kernel/registry/resolve.d.ts +7 -6
- package/esm/src/kernel/registry/resolve.js +102 -117
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/registry/sole-model.d.ts +8 -0
- package/esm/src/kernel/registry/sole-model.js +10 -0
- package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
- package/esm/src/kernel/registry/system-prompt.js +40 -0
- package/esm/src/kernel/registry/system-role.d.ts +8 -0
- package/esm/src/kernel/registry/system-role.js +14 -0
- package/esm/src/kernel/registry/vault.d.ts +2 -2
- package/esm/src/kernel/registry/vault.js +4 -4
- package/esm/src/kernel/schema.d.ts +97 -11
- package/esm/src/kernel/schema.js +256 -111
- package/esm/src/kernel/stages.d.ts +175 -0
- package/esm/src/kernel/stages.js +476 -0
- package/esm/src/kernel/stop.d.ts +65 -11
- package/esm/src/kernel/stop.js +46 -11
- package/esm/src/kernel/tools/events.d.ts +41 -0
- package/esm/src/kernel/tools/events.js +71 -0
- package/esm/src/kernel/tools/execute.d.ts +63 -24
- package/esm/src/kernel/tools/execute.js +475 -224
- package/esm/src/kernel/tools/harness.js +16 -14
- package/esm/src/kernel/tools/invoke.js +26 -8
- package/esm/src/kernel/tools/mod.d.ts +3 -1
- package/esm/src/kernel/tools/mod.js +2 -1
- package/esm/src/kernel/tools/permission.d.ts +15 -0
- package/esm/src/kernel/tools/permission.js +47 -0
- package/esm/src/kernel/tools/project.js +5 -3
- package/esm/src/kernel/tools/registry.js +20 -10
- package/esm/src/kernel/tools/remote.d.ts +94 -0
- package/esm/src/kernel/tools/remote.js +577 -0
- package/esm/src/kernel/tools/resolve.d.ts +17 -7
- package/esm/src/kernel/tools/resolve.js +38 -17
- package/esm/src/kernel/tools/schema.d.ts +2 -0
- package/esm/src/kernel/tools/schema.js +23 -6
- package/esm/src/kernel/tools/stage-run.d.ts +105 -0
- package/esm/src/kernel/tools/stage-run.js +155 -0
- package/esm/src/kernel/tools/types.d.ts +198 -20
- package/esm/src/kernel/types.d.ts +253 -114
- package/esm/src/kernel/util/find-last.d.ts +2 -0
- package/esm/src/kernel/util/find-last.js +10 -0
- package/esm/src/observability/destinations.d.ts +31 -0
- package/esm/src/observability/destinations.js +67 -0
- package/esm/src/observability/mod.d.ts +10 -3
- package/esm/src/observability/mod.js +6 -2
- package/esm/src/observability/policy.d.ts +27 -0
- package/esm/src/observability/policy.js +80 -0
- package/esm/src/observability/resolve-policy.d.ts +16 -0
- package/esm/src/observability/resolve-policy.js +64 -0
- package/esm/src/observability/trace-attach.d.ts +3 -1
- package/esm/src/observability/trace-attach.js +17 -10
- package/esm/src/observability/trace-record.d.ts +12 -4
- package/esm/src/observability/trace-record.js +63 -19
- package/esm/src/observability/trace-sink.d.ts +19 -0
- package/esm/src/observability/trace-sink.js +10 -0
- package/esm/src/observability/trace.d.ts +14 -11
- package/esm/src/observability/trace.js +23 -14
- package/esm/src/observability/types.d.ts +113 -0
- package/esm/src/observability/types.js +11 -0
- package/esm/src/providers/create-provider.d.ts +7 -4
- package/esm/src/providers/create-provider.js +21 -7
- package/esm/src/providers/google/interactions/framing.d.ts +8 -0
- package/esm/src/providers/google/interactions/framing.js +74 -13
- package/esm/src/providers/google/live/framing.d.ts +5 -2
- package/esm/src/providers/google/live/framing.js +49 -12
- package/esm/src/providers/google/live/openapi-schema.js +3 -2
- package/esm/src/providers/google/live/stream.d.ts +9 -0
- package/esm/src/providers/google/live/stream.js +16 -3
- package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
- package/esm/src/providers/openrouter/cache-control.js +23 -0
- package/esm/src/providers/openrouter/chat.d.ts +21 -1
- package/esm/src/providers/openrouter/chat.js +51 -6
- package/esm/src/providers/openrouter/image.js +8 -19
- package/esm/src/providers/openrouter/openai/chat-payload.js +23 -1
- package/esm/src/providers/openrouter/openai/compat.d.ts +7 -1
- package/esm/src/providers/openrouter/openai/compat.js +23 -4
- package/esm/src/providers/openrouter/openai/image-payload.js +8 -2
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +1 -1
- package/esm/src/providers/openrouter/openai/sdk-messages.js +32 -5
- package/package.json +1 -1
- package/esm/src/interface/blocks.d.ts +0 -20
- package/esm/src/interface/blocks.js +0 -180
- package/esm/src/interface/from-profile.d.ts +0 -14
- package/esm/src/interface/from-profile.js +0 -104
- package/esm/src/interface/inputs.d.ts +0 -8
- package/esm/src/interface/inputs.js +0 -127
- package/esm/src/interface/mod.d.ts +0 -10
- package/esm/src/interface/mod.js +0 -9
- package/esm/src/interface/types.d.ts +0 -153
- package/esm/src/interface/types.js +0 -13
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
9
|
import { TheorumError } from '../../../guardrails/error.js';
|
|
10
|
-
import { buildGeminiLiveClientContent, buildGeminiLiveRealtimeInput, buildGeminiLiveSetupMessage, foldGeminiLiveServerMessage, parseGeminiLiveMessage, } from './framing.js';
|
|
10
|
+
import { buildGeminiLiveClientContent, buildGeminiLiveRealtimeInput, buildGeminiLiveSetupMessage, foldGeminiLiveServerMessage, parseGeminiLiveMessage, readLiveInteractionStatus, } from './framing.js';
|
|
11
11
|
const SETUP_TIMEOUT_MS = 20_000;
|
|
12
12
|
export async function readMessageData(data) {
|
|
13
13
|
if (typeof data === 'string')
|
|
@@ -143,12 +143,25 @@ export function createLiveQueue() {
|
|
|
143
143
|
},
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
|
|
146
|
+
/**
|
|
147
|
+
* Conversational cycle boundary.
|
|
148
|
+
*
|
|
149
|
+
* When the server reports `interactionStatus`, that is authoritative: `IDLE`
|
|
150
|
+
* closes the cycle and `IN_PROGRESS` keeps it open even across `turnComplete`
|
|
151
|
+
* (background reasoning / async tools may still produce output). Without the
|
|
152
|
+
* field, `turnComplete` is the boundary as before.
|
|
153
|
+
*/
|
|
154
|
+
export function turnPhaseFromMessage(message, events) {
|
|
147
155
|
const interrupted = events.some((ev) => ev.type === 'done' && ev.interrupted === true);
|
|
148
156
|
if (interrupted)
|
|
149
157
|
return 'abort';
|
|
158
|
+
const status = readLiveInteractionStatus(message);
|
|
159
|
+
if (status === 'IDLE')
|
|
160
|
+
return 'complete';
|
|
161
|
+
if (status === 'IN_PROGRESS')
|
|
162
|
+
return 'streaming';
|
|
150
163
|
const serverContent = message.serverContent;
|
|
151
|
-
if (serverContent?.turnComplete)
|
|
164
|
+
if (serverContent?.turnComplete || serverContent?.turn_complete)
|
|
152
165
|
return 'complete';
|
|
153
166
|
return 'streaming';
|
|
154
167
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared OpenRouter `cache_control` directive from a profile CacheSpec.
|
|
3
|
+
*
|
|
4
|
+
* Used by AI SDK `providerOptionsFor` and REST `toOpenAiChatPayload` so both
|
|
5
|
+
* paths share one wire shape.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { CacheTtl } from '../../kernel/schema.js';
|
|
10
|
+
import type { CacheSpec } from '../../kernel/types.js';
|
|
11
|
+
/** Anthropic / OpenRouter ephemeral cache_control object. */
|
|
12
|
+
export interface CacheControlDirective {
|
|
13
|
+
type: 'ephemeral';
|
|
14
|
+
ttl?: CacheTtl;
|
|
15
|
+
}
|
|
16
|
+
/** Map a profile CacheSpec to the upstream cache_control body fragment. */
|
|
17
|
+
export declare function cacheControlFromSpec(spec: CacheSpec): CacheControlDirective;
|
|
18
|
+
/**
|
|
19
|
+
* Plain JSON object for AI SDK `providerOptions` (needs string index signature).
|
|
20
|
+
* Same values as `cacheControlFromSpec`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function cacheControlJson(spec: CacheSpec): {
|
|
23
|
+
[key: string]: string;
|
|
24
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared OpenRouter `cache_control` directive from a profile CacheSpec.
|
|
3
|
+
*
|
|
4
|
+
* Used by AI SDK `providerOptionsFor` and REST `toOpenAiChatPayload` so both
|
|
5
|
+
* paths share one wire shape.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** Map a profile CacheSpec to the upstream cache_control body fragment. */
|
|
10
|
+
export function cacheControlFromSpec(spec) {
|
|
11
|
+
if (spec.ttl) {
|
|
12
|
+
return { type: 'ephemeral', ttl: spec.ttl };
|
|
13
|
+
}
|
|
14
|
+
return { type: 'ephemeral' };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Plain JSON object for AI SDK `providerOptions` (needs string index signature).
|
|
18
|
+
* Same values as `cacheControlFromSpec`.
|
|
19
|
+
*/
|
|
20
|
+
export function cacheControlJson(spec) {
|
|
21
|
+
const directive = cacheControlFromSpec(spec);
|
|
22
|
+
return directive.ttl ? { type: directive.type, ttl: directive.ttl } : { type: directive.type };
|
|
23
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
|
-
import { type TextStreamPart, type ToolSet } from 'ai';
|
|
10
|
+
import { type ModelMessage, type TextStreamPart, type ToolSet } from 'ai';
|
|
11
11
|
import type { ModelProvider, ProviderCompleteRequest, TurnEvent, TurnTokens, WireFunctionTool } from '../../kernel/types.js';
|
|
12
12
|
import type { OpenAiGatewayConfig } from '../types.js';
|
|
13
13
|
export interface StreamAccumulator {
|
|
@@ -37,6 +37,11 @@ export declare function tokensFromUsage(usage: {
|
|
|
37
37
|
inputTokens?: number | null;
|
|
38
38
|
outputTokens?: number | null;
|
|
39
39
|
totalTokens?: number | null;
|
|
40
|
+
inputTokenDetails?: {
|
|
41
|
+
cacheReadTokens?: number | null;
|
|
42
|
+
cacheWriteTokens?: number | null;
|
|
43
|
+
} | null;
|
|
44
|
+
cachedInputTokens?: number | null;
|
|
40
45
|
}): TurnTokens | undefined;
|
|
41
46
|
export declare function rawRecord(value: unknown): Record<string, unknown> | undefined;
|
|
42
47
|
export declare function stringArray(value: unknown): string[] | undefined;
|
|
@@ -66,6 +71,11 @@ export declare function tokenEvent(part: {
|
|
|
66
71
|
inputTokens?: number | null;
|
|
67
72
|
outputTokens?: number | null;
|
|
68
73
|
totalTokens?: number | null;
|
|
74
|
+
inputTokenDetails?: {
|
|
75
|
+
cacheReadTokens?: number | null;
|
|
76
|
+
cacheWriteTokens?: number | null;
|
|
77
|
+
} | null;
|
|
78
|
+
cachedInputTokens?: number | null;
|
|
69
79
|
};
|
|
70
80
|
}): TurnEvent | undefined;
|
|
71
81
|
export declare function providerMetadataEvent(part: TextStreamPart<ToolSet>, acc: StreamAccumulator): TurnEvent | undefined;
|
|
@@ -77,9 +87,19 @@ export declare function finishEvent(part: {
|
|
|
77
87
|
inputTokens?: number | null;
|
|
78
88
|
outputTokens?: number | null;
|
|
79
89
|
totalTokens?: number | null;
|
|
90
|
+
inputTokenDetails?: {
|
|
91
|
+
cacheReadTokens?: number | null;
|
|
92
|
+
cacheWriteTokens?: number | null;
|
|
93
|
+
} | null;
|
|
94
|
+
cachedInputTokens?: number | null;
|
|
80
95
|
};
|
|
81
96
|
}, acc: StreamAccumulator): TurnEvent | undefined;
|
|
82
97
|
export declare function finalEvents(req: ProviderCompleteRequest, acc: StreamAccumulator): Generator<TurnEvent>;
|
|
98
|
+
/** System via instructions XOR a cache-marked system message — never both. */
|
|
99
|
+
export declare function systemDelivery(req: ProviderCompleteRequest): {
|
|
100
|
+
instructions?: string;
|
|
101
|
+
systemMessage?: ModelMessage;
|
|
102
|
+
};
|
|
83
103
|
export declare function missingOpenRouterKey(): TurnEvent;
|
|
84
104
|
export declare function providerOptionsFor(req: ProviderCompleteRequest): ProviderOptions | undefined;
|
|
85
105
|
/** Create a `ModelProvider` backed by OpenRouter through AI SDK Core. */
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
11
|
-
import { jsonSchema, streamText, tool } from 'ai';
|
|
11
|
+
import { jsonSchema, streamText, tool, } from 'ai';
|
|
12
12
|
import { isAbortError, TheorumError, toErrorEvent } from '../../guardrails/error.js';
|
|
13
|
-
import { parseStructuredOutput } from '../../kernel/engine/delta.js';
|
|
13
|
+
import { extractUsageTokens, parseStructuredOutput } from '../../kernel/engine/delta.js';
|
|
14
14
|
import { turnStopFromOpenAiFinishReason } from '../../kernel/stop.js';
|
|
15
|
+
import { cacheControlJson } from './cache-control.js';
|
|
15
16
|
import { resolveOpenRouterPlugins } from './openai/chat-payload.js';
|
|
16
17
|
import { openAiGatewayHeaders, resolveResponseFormat } from './openai/compat.js';
|
|
17
18
|
import { buildAiSdkMessages } from './openai/sdk-messages.js';
|
|
@@ -80,13 +81,21 @@ export function tokensFromUsage(usage) {
|
|
|
80
81
|
const input = usage.inputTokens ?? 0;
|
|
81
82
|
const output = usage.outputTokens ?? 0;
|
|
82
83
|
const total = usage.totalTokens ?? input + output;
|
|
83
|
-
|
|
84
|
+
const cached = usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens ?? undefined;
|
|
85
|
+
const cacheWrite = usage.inputTokenDetails?.cacheWriteTokens ?? undefined;
|
|
86
|
+
if (input === 0 &&
|
|
87
|
+
output === 0 &&
|
|
88
|
+
total === 0 &&
|
|
89
|
+
!(cached && cached > 0) &&
|
|
90
|
+
!(cacheWrite && cacheWrite > 0)) {
|
|
84
91
|
return undefined;
|
|
85
92
|
}
|
|
86
93
|
return {
|
|
87
94
|
input,
|
|
88
95
|
output,
|
|
89
96
|
total,
|
|
97
|
+
...(cached && cached > 0 ? { cached } : {}),
|
|
98
|
+
...(cacheWrite && cacheWrite > 0 ? { cacheWrite } : {}),
|
|
90
99
|
};
|
|
91
100
|
}
|
|
92
101
|
export function rawRecord(value) {
|
|
@@ -211,6 +220,13 @@ export function rawEvents(raw, acc) {
|
|
|
211
220
|
if (messageEvidence) {
|
|
212
221
|
events.push(messageEvidence);
|
|
213
222
|
}
|
|
223
|
+
if (!acc.emittedTokens) {
|
|
224
|
+
const usage = extractUsageTokens(record.usage);
|
|
225
|
+
if (usage) {
|
|
226
|
+
acc.emittedTokens = true;
|
|
227
|
+
events.push({ type: 'tokens', tokens: usage });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
214
230
|
const choices = Array.isArray(record.choices) ? record.choices : [];
|
|
215
231
|
for (const choice of choices) {
|
|
216
232
|
const row = rawRecord(choice);
|
|
@@ -338,11 +354,34 @@ function createStreamContext(req, config, apiKey) {
|
|
|
338
354
|
modelName: req.apiId,
|
|
339
355
|
};
|
|
340
356
|
}
|
|
357
|
+
/** System via instructions XOR a cache-marked system message — never both. */
|
|
358
|
+
export function systemDelivery(req) {
|
|
359
|
+
if (!req.system) {
|
|
360
|
+
return {};
|
|
361
|
+
}
|
|
362
|
+
if (req.cache?.mode === 'system') {
|
|
363
|
+
return {
|
|
364
|
+
systemMessage: {
|
|
365
|
+
role: 'system',
|
|
366
|
+
content: req.system,
|
|
367
|
+
providerOptions: {
|
|
368
|
+
openrouter: { cacheControl: cacheControlJson(req.cache) },
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return { instructions: req.system };
|
|
374
|
+
}
|
|
341
375
|
function streamTextOptions(req, context) {
|
|
376
|
+
const delivery = systemDelivery(req);
|
|
377
|
+
const messages = buildAiSdkMessages(req);
|
|
378
|
+
if (delivery.systemMessage) {
|
|
379
|
+
messages.unshift(delivery.systemMessage);
|
|
380
|
+
}
|
|
342
381
|
return {
|
|
343
382
|
model: context.openrouter.chat(context.modelName, openRouterSettings(req)),
|
|
344
|
-
instructions:
|
|
345
|
-
messages
|
|
383
|
+
instructions: delivery.instructions,
|
|
384
|
+
messages,
|
|
346
385
|
allowSystemInMessages: true,
|
|
347
386
|
temperature: req.temperature,
|
|
348
387
|
maxOutputTokens: req.maxOutputTokens,
|
|
@@ -395,13 +434,19 @@ async function* streamOpenRouter(req, config) {
|
|
|
395
434
|
}
|
|
396
435
|
export function providerOptionsFor(req) {
|
|
397
436
|
const openrouter = {};
|
|
398
|
-
if (req.thinking !== 'none') {
|
|
437
|
+
if (req.thinking && req.thinking !== 'none') {
|
|
399
438
|
openrouter.reasoning = { effort: req.thinking };
|
|
400
439
|
}
|
|
401
440
|
const responseFormat = resolveResponseFormat(req.structured);
|
|
402
441
|
if (responseFormat) {
|
|
403
442
|
openrouter.response_format = responseFormat;
|
|
404
443
|
}
|
|
444
|
+
if (req.cache?.mode === 'automatic') {
|
|
445
|
+
openrouter.cacheControl = cacheControlJson(req.cache);
|
|
446
|
+
}
|
|
447
|
+
if (req.sessionId) {
|
|
448
|
+
openrouter.session_id = req.sessionId;
|
|
449
|
+
}
|
|
405
450
|
if (Object.keys(openrouter).length === 0)
|
|
406
451
|
return undefined;
|
|
407
452
|
return { openrouter };
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import { toErrorEvent } from '../../guardrails/error.js';
|
|
14
|
+
import { extractUsageTokens } from '../../kernel/engine/delta.js';
|
|
14
15
|
import { bytesToBase64 } from '../shared/pcm.js';
|
|
15
16
|
import { buildChatMessages, openAiGatewayHeaders } from './openai/compat.js';
|
|
16
17
|
import { buildImagesPayload, extractPromptText, imageToolParameters, } from './openai/image-payload.js';
|
|
@@ -32,24 +33,12 @@ export function buildImageHeaders(apiKey, config) {
|
|
|
32
33
|
function baseUrl(config) {
|
|
33
34
|
return config.baseUrl?.replace(/\/+$/, '') ?? 'https://openrouter.ai/api/v1';
|
|
34
35
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
const usage = raw;
|
|
40
|
-
const input = typeof usage.prompt_tokens === 'number' ? usage.prompt_tokens : 0;
|
|
41
|
-
const output = typeof usage.completion_tokens === 'number' ? usage.completion_tokens : 0;
|
|
42
|
-
const total = typeof usage.total_tokens === 'number' ? usage.total_tokens : input + output;
|
|
43
|
-
if (input + output + total === 0) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
return { input, output, total };
|
|
47
|
-
}
|
|
48
|
-
function* yieldUsage(usage) {
|
|
49
|
-
if (!usage) {
|
|
36
|
+
function* yieldUsage(raw) {
|
|
37
|
+
const tokens = extractUsageTokens(raw);
|
|
38
|
+
if (!tokens) {
|
|
50
39
|
return;
|
|
51
40
|
}
|
|
52
|
-
yield { type: 'tokens', tokens
|
|
41
|
+
yield { type: 'tokens', tokens };
|
|
53
42
|
}
|
|
54
43
|
export function mediaFromImagesResponse(body, fallbackMime) {
|
|
55
44
|
const data = body.data;
|
|
@@ -176,7 +165,7 @@ export function buildInterleavedChatPayload(req) {
|
|
|
176
165
|
messages: buildChatMessages(req),
|
|
177
166
|
temperature: req.temperature,
|
|
178
167
|
max_tokens: req.maxOutputTokens,
|
|
179
|
-
reasoning: { effort: req.thinking },
|
|
168
|
+
...(req.thinking && req.thinking !== 'none' ? { reasoning: { effort: req.thinking } } : {}),
|
|
180
169
|
tools: [
|
|
181
170
|
{
|
|
182
171
|
type: OPENROUTER_IMAGE_TOOL,
|
|
@@ -234,7 +223,7 @@ export async function* yieldInterleavedChat(req, config, apiKey) {
|
|
|
234
223
|
yield toErrorEvent('no image returned from chat image generation');
|
|
235
224
|
return;
|
|
236
225
|
}
|
|
237
|
-
yield* yieldUsage(
|
|
226
|
+
yield* yieldUsage(body.usage);
|
|
238
227
|
yield { type: 'done' };
|
|
239
228
|
}
|
|
240
229
|
export async function* yieldImagesEndpoint(req, config, apiKey) {
|
|
@@ -251,7 +240,7 @@ export async function* yieldImagesEndpoint(req, config, apiKey) {
|
|
|
251
240
|
return;
|
|
252
241
|
}
|
|
253
242
|
yield { type: 'media', media };
|
|
254
|
-
yield* yieldUsage(
|
|
243
|
+
yield* yieldUsage(body.usage);
|
|
255
244
|
yield { type: 'done' };
|
|
256
245
|
}
|
|
257
246
|
export async function* streamImage(req, config = {}) {
|
|
@@ -11,10 +11,24 @@
|
|
|
11
11
|
* @module
|
|
12
12
|
*/
|
|
13
13
|
import { getTool } from '../../../kernel/tools/registry.js';
|
|
14
|
+
import { cacheControlFromSpec } from '../cache-control.js';
|
|
14
15
|
import { buildChatMessages, resolveResponseFormat, wireTools } from './compat.js';
|
|
15
16
|
/** Convert a provider-neutral request into an OpenAI chat completion payload. */
|
|
16
17
|
function toOpenAiChatPayload(req) {
|
|
17
18
|
const messages = buildChatMessages(req);
|
|
19
|
+
if (req.cache?.mode === 'system' && req.system) {
|
|
20
|
+
const directive = cacheControlFromSpec(req.cache);
|
|
21
|
+
const systemIdx = messages.findIndex((m) => m.role === 'system');
|
|
22
|
+
if (systemIdx >= 0) {
|
|
23
|
+
const text = typeof messages[systemIdx].content === 'string'
|
|
24
|
+
? messages[systemIdx].content
|
|
25
|
+
: '';
|
|
26
|
+
messages[systemIdx] = {
|
|
27
|
+
role: 'system',
|
|
28
|
+
content: [{ type: 'text', text, cache_control: directive }],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
18
32
|
const payload = {
|
|
19
33
|
model: req.apiId,
|
|
20
34
|
stream: true,
|
|
@@ -22,7 +36,15 @@ function toOpenAiChatPayload(req) {
|
|
|
22
36
|
temperature: req.temperature,
|
|
23
37
|
max_tokens: req.maxOutputTokens,
|
|
24
38
|
};
|
|
25
|
-
|
|
39
|
+
if (req.thinking && req.thinking !== 'none') {
|
|
40
|
+
payload.reasoning = { effort: req.thinking };
|
|
41
|
+
}
|
|
42
|
+
if (req.cache?.mode === 'automatic') {
|
|
43
|
+
payload.cache_control = cacheControlFromSpec(req.cache);
|
|
44
|
+
}
|
|
45
|
+
if (req.sessionId) {
|
|
46
|
+
payload.session_id = req.sessionId;
|
|
47
|
+
}
|
|
26
48
|
const responseFormat = resolveResponseFormat(req.structured);
|
|
27
49
|
if (responseFormat) {
|
|
28
50
|
payload.response_format = responseFormat;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
|
-
import type { ProviderCompleteRequest, StructuredSchemaId, WireFunctionTool } from '../../../kernel/types.js';
|
|
12
|
+
import type { InteractionPart, ProviderCompleteRequest, StructuredSchemaId, WireFunctionTool } from '../../../kernel/types.js';
|
|
13
13
|
/** Subset of provider config used for OpenAI-gateway HTTP headers. */
|
|
14
14
|
interface GatewayHeaderConfig {
|
|
15
15
|
siteUrl?: string;
|
|
@@ -18,6 +18,12 @@ interface GatewayHeaderConfig {
|
|
|
18
18
|
declare function stringDefault(value: string | undefined, fallback: string): string;
|
|
19
19
|
declare function fallbackToolCallId(name?: string): string;
|
|
20
20
|
declare function parseToolInput(raw: string): Record<string, unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Map InteractionPart[] to OpenAI-compat message content.
|
|
23
|
+
* Text-only inputs are joined as a plain string; mixed inputs produce a
|
|
24
|
+
* content-part array (text, image_url, input_audio, file).
|
|
25
|
+
*/
|
|
26
|
+
export declare function wireMessageContent(parts: InteractionPart[]): unknown;
|
|
21
27
|
/**
|
|
22
28
|
* Build the complete `messages` array for an OpenAI chat completion request.
|
|
23
29
|
* Assembles: system → history → user input.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
import { TheorumError } from '../../../guardrails/error.js';
|
|
13
|
+
import { isMediaRefPart } from '../../../kernel/interaction-parts.js';
|
|
13
14
|
import { getStructured } from '../../../kernel/registry/schemas.js';
|
|
14
15
|
import { parseToolArgumentsObject } from '../../shared/tool-args.js';
|
|
15
16
|
// ── shared tool helpers ─────────────────────────────
|
|
@@ -27,6 +28,11 @@ function parseToolInput(raw) {
|
|
|
27
28
|
return parsed.value;
|
|
28
29
|
}
|
|
29
30
|
// ── content wire format ─────────────────────────────
|
|
31
|
+
function rejectMediaRef(part) {
|
|
32
|
+
if (isMediaRefPart(part)) {
|
|
33
|
+
throw new TheorumError('media references are not supported on openAi');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
30
36
|
function wireAudioPart(part) {
|
|
31
37
|
let format = 'mp3';
|
|
32
38
|
if (part.mimeType.includes('wav')) {
|
|
@@ -43,9 +49,9 @@ function wireAudioPart(part) {
|
|
|
43
49
|
/**
|
|
44
50
|
* Map InteractionPart[] to OpenAI-compat message content.
|
|
45
51
|
* Text-only inputs are joined as a plain string; mixed inputs produce a
|
|
46
|
-
* content-part array (text, image_url, input_audio).
|
|
52
|
+
* content-part array (text, image_url, input_audio, file).
|
|
47
53
|
*/
|
|
48
|
-
function wireMessageContent(parts) {
|
|
54
|
+
export function wireMessageContent(parts) {
|
|
49
55
|
const isAllText = parts.every((p) => p.type === 'text');
|
|
50
56
|
if (isAllText) {
|
|
51
57
|
return parts
|
|
@@ -62,6 +68,7 @@ function wireMessageContent(parts) {
|
|
|
62
68
|
if (part.type === 'text') {
|
|
63
69
|
return { type: 'text', text: part.text };
|
|
64
70
|
}
|
|
71
|
+
rejectMediaRef(part);
|
|
65
72
|
if (part.type === 'image') {
|
|
66
73
|
return {
|
|
67
74
|
type: 'image_url',
|
|
@@ -71,7 +78,14 @@ function wireMessageContent(parts) {
|
|
|
71
78
|
if (part.type === 'audio') {
|
|
72
79
|
return wireAudioPart(part);
|
|
73
80
|
}
|
|
74
|
-
|
|
81
|
+
// video + document — file part (OpenAI-compat / OpenRouter)
|
|
82
|
+
return {
|
|
83
|
+
type: 'file',
|
|
84
|
+
file: {
|
|
85
|
+
filename: part.type === 'video' ? 'clip.bin' : 'document.bin',
|
|
86
|
+
file_data: `data:${part.mimeType};base64,${part.data}`,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
75
89
|
});
|
|
76
90
|
}
|
|
77
91
|
// ── history message wire format ─────────────────────
|
|
@@ -82,11 +96,16 @@ function wireMessageContent(parts) {
|
|
|
82
96
|
*/
|
|
83
97
|
function wireHistoryMessage(msg) {
|
|
84
98
|
if (msg.role === 'tool') {
|
|
99
|
+
let content = msg.content ?? '';
|
|
100
|
+
if (msg.parts && msg.parts.length > 0) {
|
|
101
|
+
// Prefer multimodal parts; if only text parts, wireMessageContent collapses to string.
|
|
102
|
+
content = wireMessageContent(msg.parts);
|
|
103
|
+
}
|
|
85
104
|
return {
|
|
86
105
|
role: 'tool',
|
|
87
106
|
tool_call_id: msg.tool_call_id ?? fallbackToolCallId(msg.name),
|
|
88
107
|
name: msg.name,
|
|
89
|
-
content
|
|
108
|
+
content,
|
|
90
109
|
};
|
|
91
110
|
}
|
|
92
111
|
let content = msg.content ?? '';
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { TheorumError } from '../../../guardrails/error.js';
|
|
10
|
+
import { isMediaRefPart } from '../../../kernel/interaction-parts.js';
|
|
9
11
|
export function extractPromptText(input) {
|
|
10
12
|
return input
|
|
11
13
|
.filter((part) => part.type === 'text')
|
|
@@ -32,9 +34,13 @@ export function wireInputReference(part) {
|
|
|
32
34
|
export function wireInputReferences(input) {
|
|
33
35
|
const references = [];
|
|
34
36
|
for (const part of input) {
|
|
35
|
-
if (part.type
|
|
36
|
-
|
|
37
|
+
if (part.type !== 'image') {
|
|
38
|
+
continue;
|
|
37
39
|
}
|
|
40
|
+
if (isMediaRefPart(part)) {
|
|
41
|
+
throw new TheorumError('media references are not supported on openAi');
|
|
42
|
+
}
|
|
43
|
+
references.push(wireInputReference(part));
|
|
38
44
|
}
|
|
39
45
|
return references;
|
|
40
46
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { ModelMessage } from 'ai';
|
|
10
10
|
import type { InteractionPart, ProviderCompleteRequest, TurnHistoryMessage } from '../../../kernel/types.js';
|
|
11
|
-
export declare function sdkPart(
|
|
11
|
+
export declare function sdkPart(input: InteractionPart): Record<string, unknown>;
|
|
12
12
|
export declare function sdkContentFromParts(parts: InteractionPart[]): string | Array<Record<string, unknown>>;
|
|
13
13
|
export declare function sdkContentFromOptionalParts(parts: InteractionPart[] | undefined, text: string | undefined): string | Array<Record<string, unknown>>;
|
|
14
14
|
export declare function toolResultMessage(msg: TurnHistoryMessage): ModelMessage;
|
|
@@ -6,14 +6,23 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
+
import { TheorumError } from '../../../guardrails/error.js';
|
|
10
|
+
import { isMediaRefPart } from '../../../kernel/interaction-parts.js';
|
|
9
11
|
import { fallbackToolCallId, parseToolInput } from './compat.js';
|
|
12
|
+
function inlineMediaPart(part) {
|
|
13
|
+
if (isMediaRefPart(part)) {
|
|
14
|
+
throw new TheorumError('media references are not supported on openAi');
|
|
15
|
+
}
|
|
16
|
+
return part;
|
|
17
|
+
}
|
|
10
18
|
function stringDefault(value, fallback) {
|
|
11
19
|
return value === undefined ? fallback : value;
|
|
12
20
|
}
|
|
13
|
-
export function sdkPart(
|
|
14
|
-
if (
|
|
15
|
-
return { type: 'text', text:
|
|
21
|
+
export function sdkPart(input) {
|
|
22
|
+
if (input.type === 'text') {
|
|
23
|
+
return { type: 'text', text: input.text };
|
|
16
24
|
}
|
|
25
|
+
const part = inlineMediaPart(input);
|
|
17
26
|
if (part.type === 'image') {
|
|
18
27
|
return {
|
|
19
28
|
type: 'image',
|
|
@@ -39,15 +48,33 @@ export function sdkContentFromOptionalParts(parts, text) {
|
|
|
39
48
|
}
|
|
40
49
|
export function toolResultMessage(msg) {
|
|
41
50
|
const toolName = stringDefault(msg.name, 'tool');
|
|
51
|
+
const toolCallId = stringDefault(msg.tool_call_id, fallbackToolCallId(msg.name));
|
|
52
|
+
const output = msg.parts && msg.parts.length > 0
|
|
53
|
+
? {
|
|
54
|
+
type: 'content',
|
|
55
|
+
value: msg.parts.map((input) => {
|
|
56
|
+
if (input.type === 'text') {
|
|
57
|
+
return { type: 'text', text: input.text };
|
|
58
|
+
}
|
|
59
|
+
// image / audio / video / document — AI SDK tool-result file parts
|
|
60
|
+
const part = inlineMediaPart(input);
|
|
61
|
+
return {
|
|
62
|
+
type: 'file',
|
|
63
|
+
mediaType: part.mimeType,
|
|
64
|
+
data: { type: 'data', data: part.data },
|
|
65
|
+
};
|
|
66
|
+
}),
|
|
67
|
+
}
|
|
68
|
+
: { type: 'text', value: stringDefault(msg.content, '') };
|
|
42
69
|
// AI SDK ToolModelMessage is a branded union; structural tool-result is correct at runtime.
|
|
43
70
|
return {
|
|
44
71
|
role: 'tool',
|
|
45
72
|
content: [
|
|
46
73
|
{
|
|
47
74
|
type: 'tool-result',
|
|
48
|
-
toolCallId
|
|
75
|
+
toolCallId,
|
|
49
76
|
toolName,
|
|
50
|
-
output
|
|
77
|
+
output,
|
|
51
78
|
},
|
|
52
79
|
],
|
|
53
80
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "theorum",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turn event folding — map kernel `TurnEvent` streams to transcript blocks.
|
|
3
|
-
*
|
|
4
|
-
* @module
|
|
5
|
-
*/
|
|
6
|
-
import type { TurnEvent } from '../kernel/types.js';
|
|
7
|
-
import type { FoldTurnEventsOptions, TranscriptBlock, UserTurnDraft } from './types.js';
|
|
8
|
-
declare function resetBlockIds(): void;
|
|
9
|
-
/** Build transcript blocks for a user-authored turn. */
|
|
10
|
-
declare function buildUserTurnBlocks(draft: UserTurnDraft, idPrefix?: string): TranscriptBlock[];
|
|
11
|
-
/**
|
|
12
|
-
* Fold a single assistant turn's `TurnEvent` stream into ordered transcript blocks.
|
|
13
|
-
*
|
|
14
|
-
* Merges consecutive `text` and `thought` deltas, upserts tool calls by id, and
|
|
15
|
-
* skips kernel bookkeeping events (`tokens`, `session`) unless folded into `turn-done`.
|
|
16
|
-
*/
|
|
17
|
-
declare function foldTurnEvents(events: readonly TurnEvent[], options?: FoldTurnEventsOptions): TranscriptBlock[];
|
|
18
|
-
/** User draft blocks followed by folded assistant turn events. */
|
|
19
|
-
declare function foldConversationTurn(draft: UserTurnDraft, assistantEvents: readonly TurnEvent[], options?: FoldTurnEventsOptions): TranscriptBlock[];
|
|
20
|
-
export { buildUserTurnBlocks, foldConversationTurn, foldTurnEvents, resetBlockIds };
|