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,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure protocol framing & serialization for Google Gemini Live WebSocket API (`BidiGenerateContent`).
|
|
3
|
+
*
|
|
4
|
+
* All functions are pure data transformations with no network I/O.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { TheorumError } from '../../../guardrails/error.js';
|
|
9
|
+
import { groundingFromEvent } from '../../../kernel/engine/delta.js';
|
|
10
|
+
import { isMediaRefPart } from '../../../kernel/interaction-parts.js';
|
|
11
|
+
import { getTool } from '../../../kernel/tools/registry.js';
|
|
12
|
+
import { base64ToBytes, bytesToBase64, wrapPcmAsWav } from '../../shared/pcm.js';
|
|
13
|
+
import { parseToolArgumentsObject } from '../../shared/tool-args.js';
|
|
14
|
+
import { GEMINI_LIVE_WS_URL } from '../urls.js';
|
|
15
|
+
import { toGeminiOpenApiSchema } from './openapi-schema.js';
|
|
16
|
+
/** Construct authenticated WebSocket URL for Gemini Live API. */
|
|
17
|
+
export function buildGeminiLiveWebSocketUrl(apiKey) {
|
|
18
|
+
return `${GEMINI_LIVE_WS_URL}?key=${encodeURIComponent(apiKey)}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Live tools are always declared non-blocking: the kernel session already runs
|
|
22
|
+
* tool execution asynchronously and honours `toolCallCancellation`, so the model
|
|
23
|
+
* is free to keep speaking while a call is in flight.
|
|
24
|
+
*/
|
|
25
|
+
const LIVE_FUNCTION_BEHAVIOR = 'NON_BLOCKING';
|
|
26
|
+
export function wireFunctionDeclaration(decl) {
|
|
27
|
+
const parameters = toGeminiOpenApiSchema(decl.parameters);
|
|
28
|
+
return {
|
|
29
|
+
name: decl.name,
|
|
30
|
+
description: decl.description,
|
|
31
|
+
behavior: LIVE_FUNCTION_BEHAVIOR,
|
|
32
|
+
parameters: parameters && typeof parameters === 'object'
|
|
33
|
+
? parameters
|
|
34
|
+
: { type: 'OBJECT', properties: {} },
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function wireLiveTools(req) {
|
|
38
|
+
const functionDeclarations = [];
|
|
39
|
+
for (const id of req.builtins) {
|
|
40
|
+
const entry = getTool(id);
|
|
41
|
+
if (entry?.type === 'builtin' && entry.wire.live) {
|
|
42
|
+
functionDeclarations.push({
|
|
43
|
+
name: id,
|
|
44
|
+
description: entry.description,
|
|
45
|
+
behavior: LIVE_FUNCTION_BEHAVIOR,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (const decl of req.wireTools ?? []) {
|
|
50
|
+
functionDeclarations.push(wireFunctionDeclaration(decl));
|
|
51
|
+
}
|
|
52
|
+
if (functionDeclarations.length === 0) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return [{ functionDeclarations }];
|
|
56
|
+
}
|
|
57
|
+
function buildLiveGenerationConfig(req) {
|
|
58
|
+
const generationConfig = {
|
|
59
|
+
responseModalities: ['AUDIO'],
|
|
60
|
+
temperature: req.temperature,
|
|
61
|
+
maxOutputTokens: req.maxOutputTokens,
|
|
62
|
+
};
|
|
63
|
+
if (req.live?.voice) {
|
|
64
|
+
generationConfig.speechConfig = {
|
|
65
|
+
voiceConfig: {
|
|
66
|
+
prebuiltVoiceConfig: {
|
|
67
|
+
voiceName: req.live.voice,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (req.thinking && req.thinking !== 'none') {
|
|
73
|
+
generationConfig.thinkingConfig = {
|
|
74
|
+
thinkingLevel: req.thinking,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return generationConfig;
|
|
78
|
+
}
|
|
79
|
+
function normalizeStartSensitivity(val) {
|
|
80
|
+
return val?.includes('HIGH') ? 'START_SENSITIVITY_HIGH' : 'START_SENSITIVITY_LOW';
|
|
81
|
+
}
|
|
82
|
+
function normalizeEndSensitivity(val) {
|
|
83
|
+
return val?.includes('HIGH') ? 'END_SENSITIVITY_HIGH' : 'END_SENSITIVITY_LOW';
|
|
84
|
+
}
|
|
85
|
+
function buildLiveRealtimeInputConfig(vad) {
|
|
86
|
+
const automaticActivityDetection = {};
|
|
87
|
+
if (vad.startSensitivity !== undefined) {
|
|
88
|
+
automaticActivityDetection.startOfSpeechSensitivity = normalizeStartSensitivity(vad.startSensitivity);
|
|
89
|
+
}
|
|
90
|
+
if (vad.endSensitivity !== undefined) {
|
|
91
|
+
automaticActivityDetection.endOfSpeechSensitivity = normalizeEndSensitivity(vad.endSensitivity);
|
|
92
|
+
}
|
|
93
|
+
if (vad.prefixPaddingMs !== undefined) {
|
|
94
|
+
automaticActivityDetection.prefixPaddingMs = vad.prefixPaddingMs;
|
|
95
|
+
}
|
|
96
|
+
if (vad.silenceDurationMs !== undefined) {
|
|
97
|
+
automaticActivityDetection.silenceDurationMs = vad.silenceDurationMs;
|
|
98
|
+
}
|
|
99
|
+
const config = {};
|
|
100
|
+
if (vad.activityHandling !== undefined) {
|
|
101
|
+
config.activityHandling = vad.activityHandling;
|
|
102
|
+
}
|
|
103
|
+
if (Object.keys(automaticActivityDetection).length > 0) {
|
|
104
|
+
config.automaticActivityDetection = automaticActivityDetection;
|
|
105
|
+
}
|
|
106
|
+
return Object.keys(config).length > 0 ? config : undefined;
|
|
107
|
+
}
|
|
108
|
+
function buildLiveSessionResumption(req) {
|
|
109
|
+
if (req.sessionResumptionHandle) {
|
|
110
|
+
return { handle: req.sessionResumptionHandle };
|
|
111
|
+
}
|
|
112
|
+
if (req.live?.sessionResumption) {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
function liveModelName(apiId) {
|
|
118
|
+
return apiId.startsWith('models/') ? apiId : `models/${apiId}`;
|
|
119
|
+
}
|
|
120
|
+
function applyLiveOptionalFeatures(live, setup) {
|
|
121
|
+
if (live?.transcription?.input) {
|
|
122
|
+
setup.inputAudioTranscription = {};
|
|
123
|
+
}
|
|
124
|
+
if (live?.transcription?.output) {
|
|
125
|
+
setup.outputAudioTranscription = {};
|
|
126
|
+
}
|
|
127
|
+
if (live?.proactiveAudio === true) {
|
|
128
|
+
setup.proactivity = { proactiveAudio: true };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** Build the initial `setup` message sent once immediately after WebSocket open. */
|
|
132
|
+
export function buildGeminiLiveSetupMessage(req) {
|
|
133
|
+
const live = req.live;
|
|
134
|
+
const realtimeInputConfig = live?.vad ? buildLiveRealtimeInputConfig(live.vad) : undefined;
|
|
135
|
+
const tools = wireLiveTools(req);
|
|
136
|
+
const sessionResumption = buildLiveSessionResumption(req);
|
|
137
|
+
// Only opt into initial-history gating when we actually have history to seed.
|
|
138
|
+
// With `initialHistoryInClientContent: true`, Gemini waits for clientContent
|
|
139
|
+
// after setupComplete and will not start realtime generation until that lands —
|
|
140
|
+
// empty sessions (e.g. Th30) would hang forever if this were always set.
|
|
141
|
+
const seedInitialHistory = Boolean(req.history && req.history.length > 0);
|
|
142
|
+
const setup = {
|
|
143
|
+
model: liveModelName(req.apiId),
|
|
144
|
+
generationConfig: buildLiveGenerationConfig(req),
|
|
145
|
+
systemInstruction: {
|
|
146
|
+
parts: [{ text: req.system }],
|
|
147
|
+
},
|
|
148
|
+
...(tools.length > 0 ? { tools } : {}),
|
|
149
|
+
...(sessionResumption ? { sessionResumption } : {}),
|
|
150
|
+
...(live?.contextCompression === 'slidingWindow'
|
|
151
|
+
? { contextWindowCompression: { slidingWindow: {} } }
|
|
152
|
+
: {}),
|
|
153
|
+
...(seedInitialHistory ? { historyConfig: { initialHistoryInClientContent: true } } : {}),
|
|
154
|
+
...(realtimeInputConfig ? { realtimeInputConfig } : {}),
|
|
155
|
+
};
|
|
156
|
+
applyLiveOptionalFeatures(live, setup);
|
|
157
|
+
return { setup };
|
|
158
|
+
}
|
|
159
|
+
/** Live carries inline bytes only — provider file references are rejected until support is verified. */
|
|
160
|
+
function inlineMediaPart(part) {
|
|
161
|
+
if (isMediaRefPart(part)) {
|
|
162
|
+
throw new TheorumError('media references are not supported on geminiLive');
|
|
163
|
+
}
|
|
164
|
+
return part;
|
|
165
|
+
}
|
|
166
|
+
function inlineData(part) {
|
|
167
|
+
const inline = inlineMediaPart(part);
|
|
168
|
+
return { mimeType: inline.mimeType, data: inline.data };
|
|
169
|
+
}
|
|
170
|
+
/** Format a single history message into a Google turn object. */
|
|
171
|
+
function historyTurnToGoogleTurn(msg) {
|
|
172
|
+
const role = msg.role === 'assistant' ? 'model' : 'user';
|
|
173
|
+
const parts = [];
|
|
174
|
+
if (msg.content) {
|
|
175
|
+
parts.push({ text: msg.content });
|
|
176
|
+
}
|
|
177
|
+
for (const part of msg.parts ?? []) {
|
|
178
|
+
if (part.type === 'text') {
|
|
179
|
+
parts.push({ text: part.text });
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
parts.push({ inlineData: inlineData(part) });
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return { role, parts };
|
|
186
|
+
}
|
|
187
|
+
/** Build the `clientContent` message used for seeding conversation history before realtime streaming. */
|
|
188
|
+
export function buildGeminiLiveClientContent(history) {
|
|
189
|
+
if (!history || history.length === 0) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
clientContent: {
|
|
194
|
+
turns: history.map(historyTurnToGoogleTurn),
|
|
195
|
+
turnComplete: true,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/** Build a `realtimeInput` message for streaming audio, video, or text chunks. */
|
|
200
|
+
export function buildGeminiLiveRealtimeInput(input) {
|
|
201
|
+
if (input.type === 'text') {
|
|
202
|
+
return {
|
|
203
|
+
realtimeInput: {
|
|
204
|
+
text: input.text,
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const part = inlineMediaPart(input);
|
|
209
|
+
if (part.type === 'audio') {
|
|
210
|
+
return {
|
|
211
|
+
realtimeInput: {
|
|
212
|
+
audio: {
|
|
213
|
+
mimeType: part.mimeType.includes('rate=') ? part.mimeType : 'audio/pcm;rate=16000',
|
|
214
|
+
data: part.data,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
// Image / video frame
|
|
220
|
+
return {
|
|
221
|
+
realtimeInput: {
|
|
222
|
+
video: {
|
|
223
|
+
mimeType: part.mimeType || 'image/jpeg',
|
|
224
|
+
data: part.data,
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
/** Build a `realtimeInput` message with text. */
|
|
230
|
+
export function buildGeminiLiveRealtimeText(text) {
|
|
231
|
+
return {
|
|
232
|
+
realtimeInput: {
|
|
233
|
+
text,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/** Build the Gemini Live `response` struct for a function result. */
|
|
238
|
+
function liveFunctionResponsePayload(output) {
|
|
239
|
+
if (typeof output === 'object' &&
|
|
240
|
+
output !== null &&
|
|
241
|
+
'error' in output &&
|
|
242
|
+
typeof output.error === 'string') {
|
|
243
|
+
return { error: output.error };
|
|
244
|
+
}
|
|
245
|
+
return { result: output };
|
|
246
|
+
}
|
|
247
|
+
/** Build a `toolResponse` message returning the execution result of a tool call. */
|
|
248
|
+
export function buildGeminiLiveToolResponse(id, name, output) {
|
|
249
|
+
return buildGeminiLiveToolResponses([{ id, name, output }]);
|
|
250
|
+
}
|
|
251
|
+
/** Build a batched `toolResponse` for one or more function results. */
|
|
252
|
+
export function buildGeminiLiveToolResponses(responses) {
|
|
253
|
+
return {
|
|
254
|
+
toolResponse: {
|
|
255
|
+
functionResponses: responses.map(({ id, name, output }) => ({
|
|
256
|
+
id,
|
|
257
|
+
name,
|
|
258
|
+
response: liveFunctionResponsePayload(output),
|
|
259
|
+
})),
|
|
260
|
+
},
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export function parseGeminiLiveMessage(raw) {
|
|
264
|
+
if (typeof raw === 'object' && raw !== null && !Array.isArray(raw)) {
|
|
265
|
+
if (raw instanceof ArrayBuffer || raw instanceof Uint8Array) {
|
|
266
|
+
const text = new TextDecoder().decode(raw);
|
|
267
|
+
return parseGeminiLiveMessage(text);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (typeof raw !== 'string' || !raw.trim()) {
|
|
271
|
+
return { ok: false, reason: 'empty' };
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
const parsed = JSON.parse(raw);
|
|
275
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
276
|
+
return { ok: true, value: parsed };
|
|
277
|
+
}
|
|
278
|
+
return { ok: false, reason: 'malformed' };
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return { ok: false, reason: 'malformed' };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
export function parseFunctionArguments(raw) {
|
|
285
|
+
return parseToolArgumentsObject(raw);
|
|
286
|
+
}
|
|
287
|
+
function readTokenCount(metadata, camelKey, snakeKey) {
|
|
288
|
+
const val = metadata[camelKey] ?? metadata[snakeKey];
|
|
289
|
+
return typeof val === 'number' ? val : 0;
|
|
290
|
+
}
|
|
291
|
+
export function extractLiveUsageTokens(metadata) {
|
|
292
|
+
const prompt = readTokenCount(metadata, 'promptTokenCount', 'prompt_token_count');
|
|
293
|
+
const output = readTokenCount(metadata, 'responseTokenCount', 'response_token_count');
|
|
294
|
+
const thinking = readTokenCount(metadata, 'thoughtsTokenCount', 'thoughts_token_count');
|
|
295
|
+
const rawTotal = metadata.totalTokenCount ?? metadata.total_token_count;
|
|
296
|
+
const total = typeof rawTotal === 'number' ? rawTotal : prompt + output;
|
|
297
|
+
if (prompt === 0 && output === 0 && total === 0) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
input: prompt,
|
|
302
|
+
output,
|
|
303
|
+
thinking: thinking > 0 ? thinking : undefined,
|
|
304
|
+
total,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function foldSessionUpdate(message, events) {
|
|
308
|
+
const sessionUpdate = message.sessionResumptionUpdate;
|
|
309
|
+
if (!sessionUpdate || typeof sessionUpdate !== 'object')
|
|
310
|
+
return;
|
|
311
|
+
const hasHandle = typeof sessionUpdate.newHandle === 'string' && sessionUpdate.newHandle.length > 0;
|
|
312
|
+
const hasResumable = typeof sessionUpdate.resumable === 'boolean';
|
|
313
|
+
if (!hasHandle && !hasResumable)
|
|
314
|
+
return;
|
|
315
|
+
events.push({
|
|
316
|
+
type: 'evidence',
|
|
317
|
+
...(hasHandle ? { sessionResumptionHandle: sessionUpdate.newHandle } : {}),
|
|
318
|
+
evidence: {
|
|
319
|
+
provider: 'google',
|
|
320
|
+
kind: 'session_resumption',
|
|
321
|
+
resumable: hasResumable ? sessionUpdate.resumable : hasHandle,
|
|
322
|
+
raw: sessionUpdate,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function foldToolCalls(message, events) {
|
|
327
|
+
const toolCall = message.toolCall;
|
|
328
|
+
if (!toolCall?.functionCalls || !Array.isArray(toolCall.functionCalls))
|
|
329
|
+
return;
|
|
330
|
+
for (const call of toolCall.functionCalls) {
|
|
331
|
+
if (!call.name)
|
|
332
|
+
continue;
|
|
333
|
+
const parsed = parseFunctionArguments(call.args);
|
|
334
|
+
if (!parsed.ok) {
|
|
335
|
+
events.push({
|
|
336
|
+
type: 'tool',
|
|
337
|
+
tool: {
|
|
338
|
+
id: call.id,
|
|
339
|
+
name: call.name,
|
|
340
|
+
arguments: {},
|
|
341
|
+
phase: 'error',
|
|
342
|
+
failure: {
|
|
343
|
+
code: 'malformed_arguments',
|
|
344
|
+
message: parsed.error,
|
|
345
|
+
details: { raw: parsed.raw },
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
events.push({
|
|
352
|
+
type: 'tool',
|
|
353
|
+
tool: {
|
|
354
|
+
id: call.id,
|
|
355
|
+
name: call.name,
|
|
356
|
+
arguments: parsed.value,
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function foldToolCancellations(message, events) {
|
|
362
|
+
const cancellation = message.toolCallCancellation;
|
|
363
|
+
const ids = cancellation?.ids;
|
|
364
|
+
if (!Array.isArray(ids))
|
|
365
|
+
return;
|
|
366
|
+
for (const id of ids) {
|
|
367
|
+
if (typeof id !== 'string' || id.length === 0)
|
|
368
|
+
continue;
|
|
369
|
+
events.push({
|
|
370
|
+
type: 'tool',
|
|
371
|
+
tool: {
|
|
372
|
+
id,
|
|
373
|
+
name: '',
|
|
374
|
+
phase: 'cancel',
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/** Parse Gemini goAway.timeLeft (seconds number, "10s", duration string) → ms when known. */
|
|
380
|
+
export function parseGoAwayTimeLeftMs(timeLeft) {
|
|
381
|
+
if (typeof timeLeft === 'number' && Number.isFinite(timeLeft) && timeLeft >= 0) {
|
|
382
|
+
return Math.round(timeLeft * 1000);
|
|
383
|
+
}
|
|
384
|
+
if (typeof timeLeft !== 'string')
|
|
385
|
+
return undefined;
|
|
386
|
+
const trimmed = timeLeft.trim();
|
|
387
|
+
if (!trimmed)
|
|
388
|
+
return undefined;
|
|
389
|
+
const seconds = Number(trimmed);
|
|
390
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
391
|
+
return Math.round(seconds * 1000);
|
|
392
|
+
}
|
|
393
|
+
const match = /^(\d+(?:\.\d+)?)\s*s$/i.exec(trimmed);
|
|
394
|
+
if (match?.[1]) {
|
|
395
|
+
const s = Number(match[1]);
|
|
396
|
+
if (Number.isFinite(s) && s >= 0)
|
|
397
|
+
return Math.round(s * 1000);
|
|
398
|
+
}
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
function foldGoAway(message, events) {
|
|
402
|
+
const goAway = message.goAway;
|
|
403
|
+
if (!goAway || typeof goAway !== 'object')
|
|
404
|
+
return;
|
|
405
|
+
const timeLeftMs = parseGoAwayTimeLeftMs(goAway.timeLeft);
|
|
406
|
+
events.push({
|
|
407
|
+
type: 'session',
|
|
408
|
+
session: {
|
|
409
|
+
kind: 'closing_soon',
|
|
410
|
+
...(timeLeftMs !== undefined ? { timeLeftMs } : {}),
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function foldModelPart(part, events) {
|
|
415
|
+
if (part.text) {
|
|
416
|
+
if (part.thought) {
|
|
417
|
+
events.push({ type: 'thought', text: part.text });
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
events.push({ type: 'text', text: part.text });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (part.inlineData?.data) {
|
|
424
|
+
const mime = part.inlineData.mimeType ?? 'audio/pcm;rate=24000';
|
|
425
|
+
if (mime.startsWith('audio/pcm') ||
|
|
426
|
+
mime.startsWith('audio/raw') ||
|
|
427
|
+
mime.startsWith('audio/l16')) {
|
|
428
|
+
const wav = wrapPcmAsWav(base64ToBytes(part.inlineData.data), 24000);
|
|
429
|
+
events.push({
|
|
430
|
+
type: 'media',
|
|
431
|
+
media: { mimeType: 'audio/wav', data: bytesToBase64(wav) },
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
events.push({
|
|
436
|
+
type: 'media',
|
|
437
|
+
media: { mimeType: mime, data: part.inlineData.data },
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function foldTranscription(text, kind, events, interim) {
|
|
443
|
+
if (!text)
|
|
444
|
+
return;
|
|
445
|
+
events.push({
|
|
446
|
+
type: 'evidence',
|
|
447
|
+
text,
|
|
448
|
+
evidence: {
|
|
449
|
+
provider: 'google',
|
|
450
|
+
kind,
|
|
451
|
+
...(interim ? { interim: true } : {}),
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function foldLiveGrounding(serverContent, events) {
|
|
456
|
+
const groundingEvent = groundingFromEvent({
|
|
457
|
+
groundingMetadata: serverContent.groundingMetadata ?? serverContent.grounding_metadata,
|
|
458
|
+
});
|
|
459
|
+
if (groundingEvent) {
|
|
460
|
+
events.push(groundingEvent);
|
|
461
|
+
}
|
|
462
|
+
const urlContext = serverContent.urlContextMetadata ?? serverContent.url_context_metadata;
|
|
463
|
+
if (urlContext && typeof urlContext === 'object') {
|
|
464
|
+
events.push({
|
|
465
|
+
type: 'evidence',
|
|
466
|
+
evidence: {
|
|
467
|
+
provider: 'google',
|
|
468
|
+
kind: 'url_context',
|
|
469
|
+
raw: urlContext,
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/** Read the server-side `interactionStatus` when the message carries one. */
|
|
475
|
+
export function readLiveInteractionStatus(message) {
|
|
476
|
+
const raw = message.interactionStatus ?? message.interaction_status;
|
|
477
|
+
if (raw === 'IN_PROGRESS' || raw === 'IDLE')
|
|
478
|
+
return raw;
|
|
479
|
+
return undefined;
|
|
480
|
+
}
|
|
481
|
+
function foldInteractionStatus(message, events) {
|
|
482
|
+
const status = readLiveInteractionStatus(message);
|
|
483
|
+
if (!status)
|
|
484
|
+
return;
|
|
485
|
+
events.push({
|
|
486
|
+
type: 'session',
|
|
487
|
+
session: { kind: status === 'IDLE' ? 'idle' : 'working' },
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
function foldServerContent(message, events) {
|
|
491
|
+
const serverContent = message.serverContent;
|
|
492
|
+
if (!serverContent || typeof serverContent !== 'object')
|
|
493
|
+
return;
|
|
494
|
+
if (serverContent.interrupted === true) {
|
|
495
|
+
events.push({
|
|
496
|
+
type: 'done',
|
|
497
|
+
interrupted: true,
|
|
498
|
+
stop: { kind: 'interrupted' },
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
if (serverContent.waitingForInput === true || serverContent.waiting_for_input === true) {
|
|
502
|
+
events.push({
|
|
503
|
+
type: 'session',
|
|
504
|
+
session: { kind: 'waiting_for_input' },
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
if (serverContent.generationComplete === true || serverContent.generation_complete === true) {
|
|
508
|
+
events.push({
|
|
509
|
+
type: 'done',
|
|
510
|
+
stop: { kind: 'generation_complete' },
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
const inputTranscription = serverContent.inputTranscription;
|
|
514
|
+
foldTranscription(inputTranscription?.text, 'input_transcription', events);
|
|
515
|
+
const interimInput = serverContent.interimInputTranscription;
|
|
516
|
+
foldTranscription(interimInput?.text, 'input_transcription', events, true);
|
|
517
|
+
const outputTranscription = serverContent.outputTranscription;
|
|
518
|
+
foldTranscription(outputTranscription?.text, 'output_transcription', events);
|
|
519
|
+
const modelTurn = serverContent.modelTurn;
|
|
520
|
+
for (const part of modelTurn?.parts ?? []) {
|
|
521
|
+
foldModelPart(part, events);
|
|
522
|
+
}
|
|
523
|
+
foldLiveGrounding(serverContent, events);
|
|
524
|
+
if (serverContent.turnComplete === true || serverContent.turn_complete === true) {
|
|
525
|
+
events.push({ type: 'session', session: { kind: 'turn_complete' } });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function foldUsageMetadata(message, events) {
|
|
529
|
+
const usageMetadata = message.usageMetadata;
|
|
530
|
+
if (usageMetadata) {
|
|
531
|
+
const tokens = extractLiveUsageTokens(usageMetadata);
|
|
532
|
+
if (tokens) {
|
|
533
|
+
events.push({ type: 'tokens', tokens });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Fold a raw `BidiGenerateContentServerMessage` into normalized `TurnEvent` items.
|
|
539
|
+
*/
|
|
540
|
+
export function foldGeminiLiveServerMessage(message) {
|
|
541
|
+
if (!message || typeof message !== 'object')
|
|
542
|
+
return [];
|
|
543
|
+
const events = [];
|
|
544
|
+
foldGoAway(message, events);
|
|
545
|
+
foldSessionUpdate(message, events);
|
|
546
|
+
foldToolCalls(message, events);
|
|
547
|
+
foldToolCancellations(message, events);
|
|
548
|
+
foldServerContent(message, events);
|
|
549
|
+
foldInteractionStatus(message, events);
|
|
550
|
+
foldUsageMetadata(message, events);
|
|
551
|
+
return events;
|
|
552
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Live / OpenAPI Schema 3.0 style type uppercasing for function parameters.
|
|
3
|
+
* JSON Schema uses lowercase `object`/`string`; Gemini Live expects `OBJECT`/`STRING`.
|
|
4
|
+
*/
|
|
5
|
+
/** Convert JSON-Schema-ish parameters to Gemini Live OpenAPI Schema shape. */
|
|
6
|
+
export declare function toGeminiOpenApiSchema(value: unknown): unknown;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini Live / OpenAPI Schema 3.0 style type uppercasing for function parameters.
|
|
3
|
+
* JSON Schema uses lowercase `object`/`string`; Gemini Live expects `OBJECT`/`STRING`.
|
|
4
|
+
*/
|
|
5
|
+
function convertGeminiSchemaType(value) {
|
|
6
|
+
if (typeof value === 'string')
|
|
7
|
+
return { type: value.toUpperCase() };
|
|
8
|
+
if (!Array.isArray(value))
|
|
9
|
+
return { type: toGeminiOpenApiSchema(value) };
|
|
10
|
+
const schemaTypes = value.filter((item) => typeof item === 'string');
|
|
11
|
+
const nonNullTypes = schemaTypes.filter((item) => item !== 'null');
|
|
12
|
+
const converted = {};
|
|
13
|
+
if (schemaTypes.includes('null'))
|
|
14
|
+
converted.nullable = true;
|
|
15
|
+
const [onlyType] = nonNullTypes;
|
|
16
|
+
if (onlyType !== undefined && nonNullTypes.length === 1)
|
|
17
|
+
converted.type = onlyType.toUpperCase();
|
|
18
|
+
if (nonNullTypes.length > 1) {
|
|
19
|
+
converted.anyOf = nonNullTypes.map((item) => ({ type: item.toUpperCase() }));
|
|
20
|
+
}
|
|
21
|
+
return converted;
|
|
22
|
+
}
|
|
23
|
+
function convertGeminiSchemaObject(obj) {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
26
|
+
if (key === 'type') {
|
|
27
|
+
Object.assign(result, convertGeminiSchemaType(value));
|
|
28
|
+
}
|
|
29
|
+
else if (key === 'enum' && Array.isArray(value)) {
|
|
30
|
+
result.enum = value.filter((item) => item !== null);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
result[key] = toGeminiOpenApiSchema(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/** Convert JSON-Schema-ish parameters to Gemini Live OpenAPI Schema shape. */
|
|
39
|
+
export function toGeminiOpenApiSchema(value) {
|
|
40
|
+
if (Array.isArray(value))
|
|
41
|
+
return value.map(toGeminiOpenApiSchema);
|
|
42
|
+
if (value !== null && typeof value === 'object') {
|
|
43
|
+
return convertGeminiSchemaObject(value);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Gemini Live session transport — long-lived BidiGenerateContent WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* `turnComplete` is a conversational turn boundary, not session teardown.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { ProviderCompleteRequest } from '../../../kernel/types.js';
|
|
9
|
+
import { type GeminiTransport } from '../keys.js';
|
|
10
|
+
import { type SessionQueueItem } from './stream.js';
|
|
11
|
+
export interface GoogleLiveConnection {
|
|
12
|
+
/** Send a framed JSON payload upstream. */
|
|
13
|
+
send(payload: string): void;
|
|
14
|
+
/** Drain session batches until the socket closes or errors. */
|
|
15
|
+
batches(): AsyncGenerator<SessionQueueItem>;
|
|
16
|
+
close(code?: number, reason?: string): void;
|
|
17
|
+
}
|
|
18
|
+
export type OpenLiveWebSocket = (url: string) => Promise<WebSocket>;
|
|
19
|
+
/**
|
|
20
|
+
* Open a long-lived Gemini Live WebSocket after setup handshake.
|
|
21
|
+
* Callers own send / batch drain / close — typically via `runSession`.
|
|
22
|
+
*
|
|
23
|
+
* @param openWebSocket Host override for Cloudflare fetch-upgrade (etc.).
|
|
24
|
+
*/
|
|
25
|
+
export declare function openGoogleLiveSession(req: ProviderCompleteRequest, transport: GeminiTransport, openWebSocket?: OpenLiveWebSocket): Promise<GoogleLiveConnection>;
|