theorum 0.1.15 → 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 +241 -98
- package/esm/mod.d.ts +57 -28
- package/esm/mod.js +43 -23
- package/esm/src/cli/commands/bench.js +18 -18
- package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
- package/esm/src/cli/commands/fuzz-canary.js +191 -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/guardrails-eval.d.ts +14 -0
- package/esm/src/cli/commands/guardrails-eval.js +15 -0
- package/esm/src/cli/commands/profile.js +35 -15
- package/esm/src/cli/commands/run.d.ts +3 -0
- package/esm/src/cli/commands/run.js +23 -32
- package/esm/src/cli/commands/test.d.ts +10 -1
- package/esm/src/cli/commands/test.js +34 -34
- 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 +57 -11
- package/esm/src/cli/matrix/synthesizer.d.ts +10 -12
- package/esm/src/cli/matrix/synthesizer.js +45 -118
- package/esm/src/guardrails/canary-gate.d.ts +21 -0
- package/esm/src/guardrails/canary-gate.js +32 -0
- package/esm/src/guardrails/canary.d.ts +34 -0
- package/esm/src/guardrails/canary.js +150 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +17 -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 +213 -0
- package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
- package/esm/src/guardrails/corpus/inbound-payloads.js +125 -0
- package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
- package/esm/src/guardrails/corpus/live-attacks.js +231 -0
- package/esm/src/guardrails/corpus/mod.d.ts +14 -0
- package/esm/src/guardrails/corpus/mod.js +11 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +17 -0
- package/esm/src/guardrails/corpus/secrets.js +17 -0
- package/esm/src/guardrails/corpus/strings.d.ts +28 -0
- package/esm/src/guardrails/corpus/strings.js +34 -0
- package/esm/src/guardrails/corpus/types.d.ts +38 -0
- package/esm/src/guardrails/corpus/types.js +6 -0
- package/esm/src/guardrails/egress.d.ts +32 -0
- package/esm/src/guardrails/egress.js +87 -0
- package/esm/src/guardrails/error.d.ts +14 -23
- package/esm/src/guardrails/error.js +87 -76
- 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/injection.js +28 -5
- 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 +41 -0
- package/esm/src/guardrails/live-outbound-gate.js +222 -0
- package/esm/src/guardrails/mod.d.ts +30 -6
- package/esm/src/guardrails/mod.js +20 -5
- 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 +45 -19
- package/esm/src/guardrails/sanitize.js +177 -94
- package/esm/src/guardrails/sensitive.js +2 -1
- package/esm/src/guardrails/serialize.d.ts +35 -0
- package/esm/src/guardrails/serialize.js +58 -0
- package/esm/src/guardrails/testing.d.ts +17 -0
- package/esm/src/guardrails/testing.js +13 -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.d.ts +19 -0
- package/esm/src/host/client-turn.js +36 -0
- package/esm/src/host/mint-trace.d.ts +1 -1
- package/esm/src/host/mod.d.ts +5 -3
- package/esm/src/host/mod.js +4 -3
- 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.d.ts +24 -2
- package/esm/src/kernel/engine/delta.js +478 -39
- package/esm/src/kernel/engine/live-inbound.d.ts +21 -0
- package/esm/src/kernel/engine/live-inbound.js +31 -0
- 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.d.ts +1 -1
- package/esm/src/kernel/engine/runner/gates.js +130 -43
- package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
- package/esm/src/kernel/engine/runner/mod.js +192 -53
- 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 +31 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
- package/esm/src/kernel/engine/runner/steps.js +244 -43
- package/esm/src/kernel/engine/runner/stream.d.ts +9 -3
- package/esm/src/kernel/engine/runner/stream.js +140 -44
- package/esm/src/kernel/engine/session/mod.d.ts +25 -0
- package/esm/src/kernel/engine/session/mod.js +557 -0
- 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 +21 -10
- package/esm/src/kernel/mod.js +11 -8
- 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 +12 -10
- package/esm/src/kernel/registry/attachments.js +33 -27
- package/esm/src/kernel/registry/catalog.d.ts +25 -24
- package/esm/src/kernel/registry/catalog.js +60 -101
- package/esm/src/kernel/registry/ingress.d.ts +9 -4
- package/esm/src/kernel/registry/ingress.js +97 -75
- 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 +55 -12
- package/esm/src/kernel/registry/profiles.js +413 -73
- package/esm/src/kernel/registry/provider-request.js +13 -7
- package/esm/src/kernel/registry/resolve.d.ts +8 -8
- package/esm/src/kernel/registry/resolve.js +169 -154
- 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 +12 -7
- package/esm/src/kernel/registry/vault.js +32 -10
- package/esm/src/kernel/schema.d.ts +231 -0
- package/esm/src/kernel/schema.js +607 -0
- package/esm/src/kernel/stages.d.ts +175 -0
- package/esm/src/kernel/stages.js +476 -0
- package/esm/src/kernel/stop.d.ts +78 -19
- package/esm/src/kernel/stop.js +51 -16
- 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 +84 -0
- package/esm/src/kernel/tools/execute.js +614 -0
- package/esm/src/kernel/tools/harness.d.ts +8 -0
- package/esm/src/kernel/tools/harness.js +46 -0
- package/esm/src/kernel/tools/invoke.d.ts +10 -0
- package/esm/src/kernel/tools/invoke.js +101 -0
- package/esm/src/kernel/tools/mod.d.ts +13 -0
- package/esm/src/kernel/tools/mod.js +11 -0
- 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.d.ts +12 -0
- package/esm/src/kernel/tools/project.js +36 -0
- package/esm/src/kernel/tools/registry.d.ts +23 -0
- package/esm/src/kernel/tools/registry.js +81 -0
- 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 +39 -0
- package/esm/src/kernel/tools/resolve.js +283 -0
- package/esm/src/kernel/tools/schema.d.ts +15 -0
- package/esm/src/kernel/tools/schema.js +176 -0
- 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 +394 -0
- package/esm/src/kernel/tools/types.js +9 -0
- package/esm/src/kernel/types.d.ts +540 -256
- 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 +8 -4
- package/esm/src/observability/trace-attach.js +50 -29
- package/esm/src/observability/trace-record.d.ts +23 -13
- package/esm/src/observability/trace-record.js +96 -39
- package/esm/src/observability/trace-sink.d.ts +19 -0
- package/esm/src/observability/trace-sink.js +10 -0
- 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 +18 -7
- package/esm/src/observability/trace.js +34 -17
- package/esm/src/observability/types.d.ts +113 -0
- package/esm/src/observability/types.js +11 -0
- 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 +20 -17
- package/esm/src/providers/create-provider.js +72 -26
- package/esm/src/providers/google/interactions/framing.d.ts +23 -0
- package/esm/src/providers/google/interactions/framing.js +269 -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 +49 -0
- package/esm/src/providers/google/live/framing.js +552 -0
- package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
- package/esm/src/providers/google/live/openapi-schema.js +46 -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 +45 -0
- package/esm/src/providers/google/live/stream.js +214 -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/cache-control.d.ts +24 -0
- package/esm/src/providers/openrouter/cache-control.js +23 -0
- package/esm/src/providers/openrouter/chat.d.ts +107 -0
- package/esm/src/providers/{openrouter.js → openrouter/chat.js} +117 -231
- package/esm/src/providers/openrouter/image.d.ts +34 -0
- package/esm/src/providers/openrouter/image.js +275 -0
- package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
- package/esm/src/providers/openrouter/openai/chat-payload.js +82 -0
- package/esm/src/providers/openrouter/openai/compat.d.ts +53 -0
- package/esm/src/providers/openrouter/openai/compat.js +213 -0
- package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
- package/esm/src/providers/openrouter/openai/image-payload.js +90 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.js +122 -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 -202
- 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 -202
- 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
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible image generation transport (internal).
|
|
3
|
+
*
|
|
4
|
+
* Hosts use `createProvider(profile, { openAiGateway })` — this module is selected
|
|
5
|
+
* when the profile is an openAi image role on OpenRouter.
|
|
6
|
+
*
|
|
7
|
+
* Image-only turns POST `/images`. When `image.includeText` is set, chat
|
|
8
|
+
* completions carry an OpenRouter image-generation server tool so the model may
|
|
9
|
+
* return interleaved assistant text and images.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { toErrorEvent } from '../../guardrails/error.js';
|
|
14
|
+
import { extractUsageTokens } from '../../kernel/engine/delta.js';
|
|
15
|
+
import { bytesToBase64 } from '../shared/pcm.js';
|
|
16
|
+
import { buildChatMessages, openAiGatewayHeaders } from './openai/compat.js';
|
|
17
|
+
import { buildImagesPayload, extractPromptText, imageToolParameters, } from './openai/image-payload.js';
|
|
18
|
+
import { resolveOpenAiGatewayApiKey } from './resolve-api-key.js';
|
|
19
|
+
const HTTP_OK = 200;
|
|
20
|
+
/** OpenRouter chat server tool for inline image generation. */
|
|
21
|
+
export const OPENROUTER_IMAGE_TOOL = 'openrouter:image_generation';
|
|
22
|
+
export function buildImageHeaders(apiKey, config) {
|
|
23
|
+
const headers = {
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
};
|
|
27
|
+
const gateway = openAiGatewayHeaders(config);
|
|
28
|
+
if (gateway) {
|
|
29
|
+
Object.assign(headers, gateway);
|
|
30
|
+
}
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
function baseUrl(config) {
|
|
34
|
+
return config.baseUrl?.replace(/\/+$/, '') ?? 'https://openrouter.ai/api/v1';
|
|
35
|
+
}
|
|
36
|
+
function* yieldUsage(raw) {
|
|
37
|
+
const tokens = extractUsageTokens(raw);
|
|
38
|
+
if (!tokens) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
yield { type: 'tokens', tokens };
|
|
42
|
+
}
|
|
43
|
+
export function mediaFromImagesResponse(body, fallbackMime) {
|
|
44
|
+
const data = body.data;
|
|
45
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const first = data[0];
|
|
49
|
+
if (!first || typeof first !== 'object') {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const entry = first;
|
|
53
|
+
const b64 = typeof entry.b64_json === 'string' ? entry.b64_json : '';
|
|
54
|
+
if (!b64) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const mimeType = typeof entry.media_type === 'string' && entry.media_type.length > 0
|
|
58
|
+
? entry.media_type
|
|
59
|
+
: fallbackMime;
|
|
60
|
+
if (!mimeType) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
return { mimeType, data: b64 };
|
|
64
|
+
}
|
|
65
|
+
export async function fetchImageAsBase64(url, fetchFn, signal) {
|
|
66
|
+
const res = await fetchFn(url, { signal });
|
|
67
|
+
if (res.status !== HTTP_OK) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const mimeType = res.headers.get('content-type')?.split(';')[0]?.trim() || 'image/png';
|
|
71
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
72
|
+
if (bytes.length === 0) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return { mimeType, data: bytesToBase64(bytes) };
|
|
76
|
+
}
|
|
77
|
+
export function markdownImageUrls(content) {
|
|
78
|
+
const urls = [];
|
|
79
|
+
const pattern = /!\[[^\]]*]\((https?:\/\/[^)\s]+)\)/g;
|
|
80
|
+
for (const match of content.matchAll(pattern)) {
|
|
81
|
+
const url = match[1];
|
|
82
|
+
if (url) {
|
|
83
|
+
urls.push(url);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return urls;
|
|
87
|
+
}
|
|
88
|
+
export function plainTextFromContent(content) {
|
|
89
|
+
if (typeof content === 'string') {
|
|
90
|
+
return content.replace(/!\[[^\]]*]\([^)]+\)/g, '').trim();
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(content)) {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
return content
|
|
96
|
+
.map((part) => {
|
|
97
|
+
if (!part || typeof part !== 'object') {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
const entry = part;
|
|
101
|
+
if (entry.type === 'text' && typeof entry.text === 'string') {
|
|
102
|
+
return entry.text;
|
|
103
|
+
}
|
|
104
|
+
return '';
|
|
105
|
+
})
|
|
106
|
+
.join('\n')
|
|
107
|
+
.replace(/!\[[^\]]*]\([^)]+\)/g, '')
|
|
108
|
+
.trim();
|
|
109
|
+
}
|
|
110
|
+
function httpImageUrlFromPart(entry) {
|
|
111
|
+
if (entry.type !== 'image_url') {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
const imageUrl = entry.image_url;
|
|
115
|
+
if (!imageUrl || typeof imageUrl !== 'object') {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const url = imageUrl.url;
|
|
119
|
+
if (typeof url === 'string' && /^https?:\/\//.test(url)) {
|
|
120
|
+
return url;
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
function inlineImageUrls(content) {
|
|
125
|
+
if (!Array.isArray(content)) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const urls = [];
|
|
129
|
+
for (const part of content) {
|
|
130
|
+
if (!part || typeof part !== 'object') {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const url = httpImageUrlFromPart(part);
|
|
134
|
+
if (url) {
|
|
135
|
+
urls.push(url);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return urls;
|
|
139
|
+
}
|
|
140
|
+
async function postJson(config, apiKey, path, body, signal) {
|
|
141
|
+
const fetchFn = config.fetch ?? fetch;
|
|
142
|
+
return await fetchFn(`${baseUrl(config)}${path}`, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: buildImageHeaders(apiKey, config),
|
|
145
|
+
body: JSON.stringify(body),
|
|
146
|
+
signal,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function imageHttpError(res, label) {
|
|
150
|
+
if (res.status !== HTTP_OK) {
|
|
151
|
+
return toErrorEvent(`${label} HTTP ${String(res.status)}`);
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
async function requestImages(req, config, apiKey) {
|
|
156
|
+
return await postJson(config, apiKey, '/images', buildImagesPayload(req), req.signal);
|
|
157
|
+
}
|
|
158
|
+
export function buildInterleavedChatPayload(req) {
|
|
159
|
+
if (!req.image) {
|
|
160
|
+
throw new Error('buildInterleavedChatPayload requires req.image');
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
model: req.apiId,
|
|
164
|
+
stream: false,
|
|
165
|
+
messages: buildChatMessages(req),
|
|
166
|
+
temperature: req.temperature,
|
|
167
|
+
max_tokens: req.maxOutputTokens,
|
|
168
|
+
...(req.thinking && req.thinking !== 'none' ? { reasoning: { effort: req.thinking } } : {}),
|
|
169
|
+
tools: [
|
|
170
|
+
{
|
|
171
|
+
type: OPENROUTER_IMAGE_TOOL,
|
|
172
|
+
parameters: imageToolParameters(req.image),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
async function requestInterleavedChat(req, config, apiKey) {
|
|
178
|
+
return await postJson(config, apiKey, '/chat/completions', buildInterleavedChatPayload(req), req.signal);
|
|
179
|
+
}
|
|
180
|
+
export async function* yieldInterleavedChat(req, config, apiKey) {
|
|
181
|
+
const res = await requestInterleavedChat(req, config, apiKey);
|
|
182
|
+
const httpErr = imageHttpError(res, 'Image chat');
|
|
183
|
+
if (httpErr) {
|
|
184
|
+
yield httpErr;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const body = (await res.json());
|
|
188
|
+
const choices = body.choices;
|
|
189
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
190
|
+
yield toErrorEvent('no chat choices returned for image generation');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const first = choices[0];
|
|
194
|
+
if (!first || typeof first !== 'object') {
|
|
195
|
+
yield toErrorEvent('invalid chat choice for image generation');
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const message = first.message;
|
|
199
|
+
if (!message || typeof message !== 'object') {
|
|
200
|
+
yield toErrorEvent('no assistant message returned for image generation');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const content = message.content;
|
|
204
|
+
const text = plainTextFromContent(content);
|
|
205
|
+
if (text) {
|
|
206
|
+
yield { type: 'text', text };
|
|
207
|
+
}
|
|
208
|
+
const fetchFn = config.fetch ?? fetch;
|
|
209
|
+
const urls = [
|
|
210
|
+
...inlineImageUrls(content),
|
|
211
|
+
...(typeof content === 'string' ? markdownImageUrls(content) : []),
|
|
212
|
+
];
|
|
213
|
+
let emittedMedia = false;
|
|
214
|
+
for (const url of urls) {
|
|
215
|
+
const media = await fetchImageAsBase64(url, fetchFn, req.signal);
|
|
216
|
+
if (!media) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
emittedMedia = true;
|
|
220
|
+
yield { type: 'media', media };
|
|
221
|
+
}
|
|
222
|
+
if (!emittedMedia) {
|
|
223
|
+
yield toErrorEvent('no image returned from chat image generation');
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
yield* yieldUsage(body.usage);
|
|
227
|
+
yield { type: 'done' };
|
|
228
|
+
}
|
|
229
|
+
export async function* yieldImagesEndpoint(req, config, apiKey) {
|
|
230
|
+
const res = await requestImages(req, config, apiKey);
|
|
231
|
+
const httpErr = imageHttpError(res, 'Image');
|
|
232
|
+
if (httpErr) {
|
|
233
|
+
yield httpErr;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const body = (await res.json());
|
|
237
|
+
const media = mediaFromImagesResponse(body, req.image?.mimeType);
|
|
238
|
+
if (!media) {
|
|
239
|
+
yield toErrorEvent('no image returned from image generation');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
yield { type: 'media', media };
|
|
243
|
+
yield* yieldUsage(body.usage);
|
|
244
|
+
yield { type: 'done' };
|
|
245
|
+
}
|
|
246
|
+
export async function* streamImage(req, config = {}) {
|
|
247
|
+
let apiKey;
|
|
248
|
+
try {
|
|
249
|
+
apiKey = resolveOpenAiGatewayApiKey(config, req.keySlot);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
yield toErrorEvent(err);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!req.image) {
|
|
256
|
+
yield toErrorEvent('missing image response format');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const prompt = extractPromptText(req.input);
|
|
260
|
+
if (!prompt) {
|
|
261
|
+
yield toErrorEvent('empty text for image generation');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (req.image.includeText) {
|
|
265
|
+
yield* yieldInterleavedChat(req, config, apiKey);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
yield* yieldImagesEndpoint(req, config, apiKey);
|
|
269
|
+
}
|
|
270
|
+
/** Internal ModelProvider for openAi image roles on OpenRouter. */
|
|
271
|
+
export function createImageProvider(config = {}) {
|
|
272
|
+
return {
|
|
273
|
+
complete: (req) => streamImage(req, config),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible chat payload builder.
|
|
3
|
+
*
|
|
4
|
+
* Converts THEORUM provider requests into OpenAI-compatible chat completion
|
|
5
|
+
* payloads for OpenRouter and compatible gateways.
|
|
6
|
+
*
|
|
7
|
+
* Wire-format helpers (messages, tools, response format) are delegated to the
|
|
8
|
+
* shared `openai/compat` module. This file owns OpenRouter-specific concerns:
|
|
9
|
+
* plugins, web search, and the top-level payload shape.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { ProviderCompleteRequest } from '../../../kernel/types.js';
|
|
14
|
+
/** Convert a provider-neutral request into an OpenAI chat completion payload. */
|
|
15
|
+
declare function toOpenAiChatPayload(req: ProviderCompleteRequest): Record<string, unknown>;
|
|
16
|
+
interface ResolvedPlugins {
|
|
17
|
+
plugins: Array<{
|
|
18
|
+
id: string;
|
|
19
|
+
}>;
|
|
20
|
+
webSearch: boolean;
|
|
21
|
+
}
|
|
22
|
+
declare function resolveOpenRouterPlugins(builtins: readonly string[]): ResolvedPlugins;
|
|
23
|
+
export type { ResolvedPlugins };
|
|
24
|
+
export { resolveOpenRouterPlugins, toOpenAiChatPayload };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible chat payload builder.
|
|
3
|
+
*
|
|
4
|
+
* Converts THEORUM provider requests into OpenAI-compatible chat completion
|
|
5
|
+
* payloads for OpenRouter and compatible gateways.
|
|
6
|
+
*
|
|
7
|
+
* Wire-format helpers (messages, tools, response format) are delegated to the
|
|
8
|
+
* shared `openai/compat` module. This file owns OpenRouter-specific concerns:
|
|
9
|
+
* plugins, web search, and the top-level payload shape.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { getTool } from '../../../kernel/tools/registry.js';
|
|
14
|
+
import { cacheControlFromSpec } from '../cache-control.js';
|
|
15
|
+
import { buildChatMessages, resolveResponseFormat, wireTools } from './compat.js';
|
|
16
|
+
/** Convert a provider-neutral request into an OpenAI chat completion payload. */
|
|
17
|
+
function toOpenAiChatPayload(req) {
|
|
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
|
+
}
|
|
32
|
+
const payload = {
|
|
33
|
+
model: req.apiId,
|
|
34
|
+
stream: true,
|
|
35
|
+
messages,
|
|
36
|
+
temperature: req.temperature,
|
|
37
|
+
max_tokens: req.maxOutputTokens,
|
|
38
|
+
};
|
|
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
|
+
}
|
|
48
|
+
const responseFormat = resolveResponseFormat(req.structured);
|
|
49
|
+
if (responseFormat) {
|
|
50
|
+
payload.response_format = responseFormat;
|
|
51
|
+
}
|
|
52
|
+
const tools = wireTools(req.wireTools);
|
|
53
|
+
if (tools) {
|
|
54
|
+
payload.tools = tools;
|
|
55
|
+
}
|
|
56
|
+
const resolved = resolveOpenRouterPlugins(req.builtins);
|
|
57
|
+
if (resolved.webSearch) {
|
|
58
|
+
payload.web_search_options = {};
|
|
59
|
+
}
|
|
60
|
+
if (resolved.plugins.length > 0) {
|
|
61
|
+
payload.plugins = resolved.plugins;
|
|
62
|
+
}
|
|
63
|
+
return payload;
|
|
64
|
+
}
|
|
65
|
+
function resolveOpenRouterPlugins(builtins) {
|
|
66
|
+
let webSearch = false;
|
|
67
|
+
const plugins = [];
|
|
68
|
+
for (const id of builtins) {
|
|
69
|
+
const entry = getTool(id);
|
|
70
|
+
const pluginId = entry?.type === 'builtin' ? entry.wire.openRouter : undefined;
|
|
71
|
+
if (!pluginId)
|
|
72
|
+
continue;
|
|
73
|
+
if (pluginId === 'web') {
|
|
74
|
+
webSearch = true;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
plugins.push({ id: pluginId });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { plugins, webSearch };
|
|
81
|
+
}
|
|
82
|
+
export { resolveOpenRouterPlugins, toOpenAiChatPayload };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for building OpenAI-compatible chat completion payloads.
|
|
3
|
+
*
|
|
4
|
+
* Used by local.ts (raw fetch), openai/chat-payload.ts (REST payload), and
|
|
5
|
+
* Used by openrouter/chat.ts (headers + response format). Message bodies for the AI SDK
|
|
6
|
+
* path are built by `openai/sdk-messages.ts`, which shares tool helpers here.
|
|
7
|
+
* Single source of truth for message wire format, tool declarations,
|
|
8
|
+
* structured response format, and gateway headers.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import type { InteractionPart, ProviderCompleteRequest, StructuredSchemaId, WireFunctionTool } from '../../../kernel/types.js';
|
|
13
|
+
/** Subset of provider config used for OpenAI-gateway HTTP headers. */
|
|
14
|
+
interface GatewayHeaderConfig {
|
|
15
|
+
siteUrl?: string;
|
|
16
|
+
siteName?: string;
|
|
17
|
+
}
|
|
18
|
+
declare function stringDefault(value: string | undefined, fallback: string): string;
|
|
19
|
+
declare function fallbackToolCallId(name?: string): string;
|
|
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;
|
|
27
|
+
/**
|
|
28
|
+
* Build the complete `messages` array for an OpenAI chat completion request.
|
|
29
|
+
* Assembles: system → history → user input.
|
|
30
|
+
*
|
|
31
|
+
* Set `includeSystem: false` when the caller passes system text separately
|
|
32
|
+
* (e.g. AI SDK `instructions`).
|
|
33
|
+
*/
|
|
34
|
+
declare function buildChatMessages(req: ProviderCompleteRequest, options?: {
|
|
35
|
+
includeSystem?: boolean;
|
|
36
|
+
}): Record<string, unknown>[];
|
|
37
|
+
/**
|
|
38
|
+
* Map wire function tools to OpenAI-compat function tool format.
|
|
39
|
+
* Returns undefined when there are no tools.
|
|
40
|
+
*/
|
|
41
|
+
declare function wireTools(wireTools?: WireFunctionTool[]): Record<string, unknown>[] | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a StructuredSchemaId to an OpenAI `response_format` object.
|
|
44
|
+
* Returns undefined when the schema has no JSON schema.
|
|
45
|
+
*/
|
|
46
|
+
declare function resolveResponseFormat(structured: StructuredSchemaId | null): Record<string, unknown> | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* Build optional HTTP-Referer / X-Title headers for OpenAI-compat gateways.
|
|
49
|
+
* Used by OpenRouter (chat + speech) and local providers.
|
|
50
|
+
*/
|
|
51
|
+
declare function openAiGatewayHeaders(config: GatewayHeaderConfig): Record<string, string> | undefined;
|
|
52
|
+
export type { GatewayHeaderConfig };
|
|
53
|
+
export { buildChatMessages, fallbackToolCallId, openAiGatewayHeaders, parseToolInput, resolveResponseFormat, stringDefault, wireTools, };
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for building OpenAI-compatible chat completion payloads.
|
|
3
|
+
*
|
|
4
|
+
* Used by local.ts (raw fetch), openai/chat-payload.ts (REST payload), and
|
|
5
|
+
* Used by openrouter/chat.ts (headers + response format). Message bodies for the AI SDK
|
|
6
|
+
* path are built by `openai/sdk-messages.ts`, which shares tool helpers here.
|
|
7
|
+
* Single source of truth for message wire format, tool declarations,
|
|
8
|
+
* structured response format, and gateway headers.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
import { TheorumError } from '../../../guardrails/error.js';
|
|
13
|
+
import { isMediaRefPart } from '../../../kernel/interaction-parts.js';
|
|
14
|
+
import { getStructured } from '../../../kernel/registry/schemas.js';
|
|
15
|
+
import { parseToolArgumentsObject } from '../../shared/tool-args.js';
|
|
16
|
+
// ── shared tool helpers ─────────────────────────────
|
|
17
|
+
function stringDefault(value, fallback) {
|
|
18
|
+
return value === undefined ? fallback : value;
|
|
19
|
+
}
|
|
20
|
+
function fallbackToolCallId(name) {
|
|
21
|
+
return `call_${stringDefault(name, 'tool')}`;
|
|
22
|
+
}
|
|
23
|
+
function parseToolInput(raw) {
|
|
24
|
+
const parsed = parseToolArgumentsObject(raw);
|
|
25
|
+
if (!parsed.ok) {
|
|
26
|
+
throw new TheorumError(parsed.error);
|
|
27
|
+
}
|
|
28
|
+
return parsed.value;
|
|
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
|
+
}
|
|
36
|
+
function wireAudioPart(part) {
|
|
37
|
+
let format = 'mp3';
|
|
38
|
+
if (part.mimeType.includes('wav')) {
|
|
39
|
+
format = 'wav';
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
type: 'input_audio',
|
|
43
|
+
input_audio: {
|
|
44
|
+
data: part.data,
|
|
45
|
+
format,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Map InteractionPart[] to OpenAI-compat message content.
|
|
51
|
+
* Text-only inputs are joined as a plain string; mixed inputs produce a
|
|
52
|
+
* content-part array (text, image_url, input_audio, file).
|
|
53
|
+
*/
|
|
54
|
+
export function wireMessageContent(parts) {
|
|
55
|
+
const isAllText = parts.every((p) => p.type === 'text');
|
|
56
|
+
if (isAllText) {
|
|
57
|
+
return parts
|
|
58
|
+
.map((p) => {
|
|
59
|
+
if (p.type === 'text') {
|
|
60
|
+
return p.text;
|
|
61
|
+
}
|
|
62
|
+
return '';
|
|
63
|
+
})
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.join('\n');
|
|
66
|
+
}
|
|
67
|
+
return parts.map((part) => {
|
|
68
|
+
if (part.type === 'text') {
|
|
69
|
+
return { type: 'text', text: part.text };
|
|
70
|
+
}
|
|
71
|
+
rejectMediaRef(part);
|
|
72
|
+
if (part.type === 'image') {
|
|
73
|
+
return {
|
|
74
|
+
type: 'image_url',
|
|
75
|
+
image_url: { url: `data:${part.mimeType};base64,${part.data}` },
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (part.type === 'audio') {
|
|
79
|
+
return wireAudioPart(part);
|
|
80
|
+
}
|
|
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
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// ── history message wire format ─────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Map a single TurnHistoryMessage to an OpenAI-compat wire message.
|
|
94
|
+
* Tool messages receive a fallback tool_call_id when the source omits one.
|
|
95
|
+
* Assistant tool_calls are mapped to strip non-standard fields.
|
|
96
|
+
*/
|
|
97
|
+
function wireHistoryMessage(msg) {
|
|
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
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
role: 'tool',
|
|
106
|
+
tool_call_id: msg.tool_call_id ?? fallbackToolCallId(msg.name),
|
|
107
|
+
name: msg.name,
|
|
108
|
+
content,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
let content = msg.content ?? '';
|
|
112
|
+
if (msg.parts && msg.parts.length > 0) {
|
|
113
|
+
content = wireMessageContent(msg.parts);
|
|
114
|
+
}
|
|
115
|
+
const wired = {
|
|
116
|
+
role: msg.role,
|
|
117
|
+
content,
|
|
118
|
+
};
|
|
119
|
+
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
120
|
+
wired.tool_calls = msg.tool_calls.map((tc) => ({
|
|
121
|
+
id: tc.id,
|
|
122
|
+
type: 'function',
|
|
123
|
+
function: { name: tc.function.name, arguments: tc.function.arguments },
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
if (msg.name) {
|
|
127
|
+
wired.name = msg.name;
|
|
128
|
+
}
|
|
129
|
+
return wired;
|
|
130
|
+
}
|
|
131
|
+
// ── full message array builder ──────────────────────
|
|
132
|
+
/**
|
|
133
|
+
* Build the complete `messages` array for an OpenAI chat completion request.
|
|
134
|
+
* Assembles: system → history → user input.
|
|
135
|
+
*
|
|
136
|
+
* Set `includeSystem: false` when the caller passes system text separately
|
|
137
|
+
* (e.g. AI SDK `instructions`).
|
|
138
|
+
*/
|
|
139
|
+
function buildChatMessages(req, options) {
|
|
140
|
+
const includeSystem = options?.includeSystem !== false;
|
|
141
|
+
const messages = [];
|
|
142
|
+
if (includeSystem && req.system) {
|
|
143
|
+
messages.push({ role: 'system', content: req.system });
|
|
144
|
+
}
|
|
145
|
+
if (req.history && req.history.length > 0) {
|
|
146
|
+
for (const h of req.history) {
|
|
147
|
+
messages.push(wireHistoryMessage(h));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (req.input.length > 0) {
|
|
151
|
+
messages.push({
|
|
152
|
+
role: 'user',
|
|
153
|
+
content: wireMessageContent(req.input),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return messages;
|
|
157
|
+
}
|
|
158
|
+
// ── tool declarations ───────────────────────────────
|
|
159
|
+
/**
|
|
160
|
+
* Map wire function tools to OpenAI-compat function tool format.
|
|
161
|
+
* Returns undefined when there are no tools.
|
|
162
|
+
*/
|
|
163
|
+
function wireTools(wireTools) {
|
|
164
|
+
if (!wireTools || wireTools.length === 0) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
return wireTools.map((t) => ({
|
|
168
|
+
type: 'function',
|
|
169
|
+
function: {
|
|
170
|
+
name: t.name,
|
|
171
|
+
description: t.description,
|
|
172
|
+
parameters: t.parameters,
|
|
173
|
+
},
|
|
174
|
+
}));
|
|
175
|
+
}
|
|
176
|
+
// ── structured response format ──────────────────────
|
|
177
|
+
/**
|
|
178
|
+
* Resolve a StructuredSchemaId to an OpenAI `response_format` object.
|
|
179
|
+
* Returns undefined when the schema has no JSON schema.
|
|
180
|
+
*/
|
|
181
|
+
function resolveResponseFormat(structured) {
|
|
182
|
+
if (!structured) {
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
const spec = getStructured(structured);
|
|
186
|
+
if (!spec.jsonSchema) {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
type: 'json_schema',
|
|
191
|
+
json_schema: {
|
|
192
|
+
name: String(structured),
|
|
193
|
+
strict: true,
|
|
194
|
+
schema: spec.jsonSchema,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// ── gateway headers ─────────────────────────────────
|
|
199
|
+
/**
|
|
200
|
+
* Build optional HTTP-Referer / X-Title headers for OpenAI-compat gateways.
|
|
201
|
+
* Used by OpenRouter (chat + speech) and local providers.
|
|
202
|
+
*/
|
|
203
|
+
function openAiGatewayHeaders(config) {
|
|
204
|
+
const headers = {};
|
|
205
|
+
if (config.siteUrl) {
|
|
206
|
+
headers['HTTP-Referer'] = config.siteUrl;
|
|
207
|
+
}
|
|
208
|
+
if (config.siteName) {
|
|
209
|
+
headers['X-Title'] = config.siteName;
|
|
210
|
+
}
|
|
211
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
212
|
+
}
|
|
213
|
+
export { buildChatMessages, fallbackToolCallId, openAiGatewayHeaders, parseToolInput, resolveResponseFormat, stringDefault, wireTools, };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible image generation payloads.
|
|
3
|
+
*
|
|
4
|
+
* Maps kernel `ImageResponseFormat` pins to the `/images` REST body shared by
|
|
5
|
+
* OpenRouter and other OpenAI-compat gateways.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import type { ImageResponseFormat, InteractionMediaPart, InteractionPart, ProviderCompleteRequest } from '../../../kernel/types.js';
|
|
10
|
+
export declare function extractPromptText(input: InteractionPart[]): string;
|
|
11
|
+
export declare function outputFormatFromMime(mimeType: string): string;
|
|
12
|
+
export declare function wireInputReference(part: InteractionMediaPart): Record<string, unknown>;
|
|
13
|
+
export declare function wireInputReferences(input: InteractionPart[]): Record<string, unknown>[];
|
|
14
|
+
export declare function attachImagePins(payload: Record<string, unknown>, image: ImageResponseFormat): void;
|
|
15
|
+
/** Build a POST `/images` body for native image-generation models. */
|
|
16
|
+
export declare function buildImagesPayload(req: ProviderCompleteRequest): Record<string, unknown>;
|
|
17
|
+
/** Tool parameters for gateways that generate images inside chat completions. */
|
|
18
|
+
export declare function imageToolParameters(image: ImageResponseFormat): Record<string, unknown>;
|