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
|
@@ -2,22 +2,21 @@
|
|
|
2
2
|
* Trace sink primitives for THEORUM.
|
|
3
3
|
*
|
|
4
4
|
* Tracing is host-injected: the kernel can write to a provided sink, a memory
|
|
5
|
-
* sink, a JSONL directory, or a noop sink.
|
|
6
|
-
*
|
|
5
|
+
* sink, a JSONL directory, or a noop sink. Profiles declare policy via
|
|
6
|
+
* `observability`; hosts register named destinations. THEORUM does not read
|
|
7
|
+
* environment variables or own a database destination.
|
|
7
8
|
*
|
|
8
9
|
* @module
|
|
9
10
|
*/
|
|
10
11
|
import * as dntShim from "../../_dnt.shims.js";
|
|
11
|
-
const
|
|
12
|
+
const DEFAULT_RETAIN_DAYS = 14;
|
|
12
13
|
const HOURS_PER_DAY = 24;
|
|
13
14
|
const MIN_PER_HOUR = 60;
|
|
14
15
|
const SEC_PER_MIN = 60;
|
|
15
16
|
const MS_PER_SEC = 1000;
|
|
16
|
-
const RETAIN_MS = RETAIN_DAYS * HOURS_PER_DAY * MIN_PER_HOUR * SEC_PER_MIN * MS_PER_SEC;
|
|
17
17
|
const KIB = 1024;
|
|
18
18
|
const MIB = KIB * KIB;
|
|
19
|
-
const
|
|
20
|
-
const ROTATE_BYTES = ROTATE_MIB * MIB;
|
|
19
|
+
const DEFAULT_ROTATE_MIB = 32;
|
|
21
20
|
const FILE_DAY = /^turns-(\d{4}-\d{2}-\d{2})(?:-\d+)?\.jsonl$/;
|
|
22
21
|
/**
|
|
23
22
|
* Write a trace record without allowing trace failures to fail the turn.
|
|
@@ -55,8 +54,9 @@ function dayStamp(ms) {
|
|
|
55
54
|
function fileDay(name) {
|
|
56
55
|
return FILE_DAY.exec(name)?.[1];
|
|
57
56
|
}
|
|
58
|
-
async function pruneTraces(dir, now) {
|
|
59
|
-
const
|
|
57
|
+
async function pruneTraces(dir, now, retainForDays) {
|
|
58
|
+
const retainMs = retainForDays * HOURS_PER_DAY * MIN_PER_HOUR * SEC_PER_MIN * MS_PER_SEC;
|
|
59
|
+
const cutoff = now - retainMs;
|
|
60
60
|
for await (const entry of dntShim.Deno.readDir(dir)) {
|
|
61
61
|
const day = fileDay(entry.name);
|
|
62
62
|
if (day && Date.parse(`${day}T00:00:00.000Z`) < cutoff) {
|
|
@@ -64,12 +64,12 @@ async function pruneTraces(dir, now) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
async function pickFile(dir, now) {
|
|
67
|
+
async function pickFile(dir, now, rotateBytes) {
|
|
68
68
|
const day = dayStamp(now);
|
|
69
69
|
const base = `${dir}/turns-${day}.jsonl`;
|
|
70
70
|
try {
|
|
71
71
|
const info = await dntShim.Deno.stat(base);
|
|
72
|
-
if ((info.size ?? 0) <
|
|
72
|
+
if ((info.size ?? 0) < rotateBytes) {
|
|
73
73
|
return base;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -78,14 +78,23 @@ async function pickFile(dir, now) {
|
|
|
78
78
|
}
|
|
79
79
|
return `${dir}/turns-${day}-${now}.jsonl`;
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Trace sink that writes daily rotating JSONL files under a host-selected directory.
|
|
83
|
+
*
|
|
84
|
+
* @param dir - Absolute host-chosen directory
|
|
85
|
+
* @param optionsOrNow - Retention/rotate options, or a `now` clock (legacy)
|
|
86
|
+
*/
|
|
87
|
+
function jsonlSink(dir, optionsOrNow) {
|
|
88
|
+
const options = typeof optionsOrNow === 'function' ? { now: optionsOrNow } : (optionsOrNow ?? {});
|
|
89
|
+
const now = options.now ?? Date.now;
|
|
90
|
+
const retainForDays = options.retainForDays ?? DEFAULT_RETAIN_DAYS;
|
|
91
|
+
const rotateBytes = (options.rotateAfterMiB ?? DEFAULT_ROTATE_MIB) * MIB;
|
|
83
92
|
return {
|
|
84
93
|
write: async (record) => {
|
|
85
94
|
const at = now();
|
|
86
95
|
await dntShim.Deno.mkdir(dir, { recursive: true });
|
|
87
|
-
await pruneTraces(dir, at);
|
|
88
|
-
const path = await pickFile(dir, at);
|
|
96
|
+
await pruneTraces(dir, at, retainForDays);
|
|
97
|
+
const path = await pickFile(dir, at, rotateBytes);
|
|
89
98
|
await dntShim.Deno.writeTextFile(path, `${JSON.stringify(record)}\n`, { append: true });
|
|
90
99
|
},
|
|
91
100
|
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile observability vocabulary — destination, scrub, and include policy.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth for observability profile types. It
|
|
5
|
+
* must not import from `src/kernel/`: the kernel type-imports
|
|
6
|
+
* `ProfileObservabilitySpec` for `ProfileCommon.observability`, and that edge
|
|
7
|
+
* stays one-directional.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
import type { TraceSink } from './trace-sink.js';
|
|
12
|
+
/** Which payloads land in each TraceRecord. Omitted keys use resolved defaults. */
|
|
13
|
+
export interface TraceIncludeSpec {
|
|
14
|
+
/** Scrubbed provider HTTP/SSE rows. Default: true. */
|
|
15
|
+
upstreamLog?: boolean;
|
|
16
|
+
/** Scrubbed outbound request body. Default: false. */
|
|
17
|
+
outboundWire?: boolean;
|
|
18
|
+
/** Verbatim provider step JSON (`events[].evidence.raw`). Default: false. */
|
|
19
|
+
evidenceRaw?: boolean;
|
|
20
|
+
/** Token / usage fields. Default: true. */
|
|
21
|
+
usage?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Persist `{ type: 'guardrail' }` decisions into the TraceRecord. Default: true.
|
|
24
|
+
*/
|
|
25
|
+
guardrailDecisions?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Keep `GuardrailHit.match` (exact matched substring, capped) on guardrail
|
|
28
|
+
* events in the live stream and TraceRecord. Default: false — debugging only;
|
|
29
|
+
* treat like server logs when enabled.
|
|
30
|
+
*/
|
|
31
|
+
guardrailMatchPreview?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Scrubbing of what is written — independent of turn-path `profile.guardrails`.
|
|
35
|
+
* Defaults are safe for a host-confidential store.
|
|
36
|
+
*/
|
|
37
|
+
export interface TraceScrubSpec {
|
|
38
|
+
/** Strip credentials / PII spans in stored text. Default: true. */
|
|
39
|
+
sensitive?: boolean;
|
|
40
|
+
/** Strip injection spans in the stored request copy. Default: true. */
|
|
41
|
+
injection?: boolean;
|
|
42
|
+
/** Never persist the canary token. Default: true. */
|
|
43
|
+
canary?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Profile observability — what to record, and where.
|
|
47
|
+
*
|
|
48
|
+
* Omit the whole block → no tracing (noop). Prefer `writeTo: '<registered-id>'`
|
|
49
|
+
* in shared profiles; pass a TraceSink only for tests / custom exporters.
|
|
50
|
+
*/
|
|
51
|
+
export interface ProfileObservabilitySpec {
|
|
52
|
+
/**
|
|
53
|
+
* Destination for completed turn records.
|
|
54
|
+
*
|
|
55
|
+
* - `false` — explicitly off
|
|
56
|
+
* - `string` — host-registered destination id (`registerTraceDestination`)
|
|
57
|
+
* - `TraceSink` — inline writer (tests, OTEL bridge, etc.)
|
|
58
|
+
*
|
|
59
|
+
* `runTurn(..., sink)` still overrides this for one call.
|
|
60
|
+
*/
|
|
61
|
+
writeTo?: false | string | TraceSink;
|
|
62
|
+
/**
|
|
63
|
+
* Fraction of turns to record, 0–1 inclusive.
|
|
64
|
+
* Omitted → 1 (every turn). `0` means record none.
|
|
65
|
+
*/
|
|
66
|
+
sampleRate?: number;
|
|
67
|
+
/** Which payloads land in each TraceRecord. */
|
|
68
|
+
include?: TraceIncludeSpec;
|
|
69
|
+
/**
|
|
70
|
+
* Scrubbing of stored records — independent of turn-path guardrails.
|
|
71
|
+
* Defaults stay on even when `guardrails.redactSensitive` is false.
|
|
72
|
+
*/
|
|
73
|
+
scrub?: TraceScrubSpec;
|
|
74
|
+
/** JSONL retention days when the resolved destination is JSONL. Default: 14. */
|
|
75
|
+
retainForDays?: number;
|
|
76
|
+
/** JSONL rotate threshold in MiB when the resolved destination is JSONL. Default: 32. */
|
|
77
|
+
rotateAfterMiB?: number;
|
|
78
|
+
/**
|
|
79
|
+
* Called when record build or destination write fails.
|
|
80
|
+
* Must not throw; tracing never fails the turn.
|
|
81
|
+
*/
|
|
82
|
+
onWriteError?: (err: unknown) => void;
|
|
83
|
+
}
|
|
84
|
+
/** Resolved include flags after defaults. */
|
|
85
|
+
export interface ResolvedTraceInclude {
|
|
86
|
+
upstreamLog: boolean;
|
|
87
|
+
outboundWire: boolean;
|
|
88
|
+
evidenceRaw: boolean;
|
|
89
|
+
usage: boolean;
|
|
90
|
+
guardrailDecisions: boolean;
|
|
91
|
+
guardrailMatchPreview: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** Resolved scrub flags after defaults. */
|
|
94
|
+
export interface ResolvedTraceScrub {
|
|
95
|
+
sensitive: boolean;
|
|
96
|
+
injection: boolean;
|
|
97
|
+
canary: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Observability policy with defaults applied.
|
|
101
|
+
* Every path resolves through `resolveObservabilityPolicy`.
|
|
102
|
+
*/
|
|
103
|
+
export interface ResolvedObservabilityPolicy {
|
|
104
|
+
/** False when omitted, `writeTo: false`, or sampleRate drops the turn. */
|
|
105
|
+
record: boolean;
|
|
106
|
+
writeTo: false | string | TraceSink | undefined;
|
|
107
|
+
sampleRate: number;
|
|
108
|
+
include: ResolvedTraceInclude;
|
|
109
|
+
scrub: ResolvedTraceScrub;
|
|
110
|
+
retainForDays: number;
|
|
111
|
+
rotateAfterMiB: number;
|
|
112
|
+
onWriteError?: (err: unknown) => void;
|
|
113
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile observability vocabulary — destination, scrub, and include policy.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth for observability profile types. It
|
|
5
|
+
* must not import from `src/kernel/`: the kernel type-imports
|
|
6
|
+
* `ProfileObservabilitySpec` for `ProfileCommon.observability`, and that edge
|
|
7
|
+
* stays one-directional.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host provider factory — the single public door for binding a profile to a transport.
|
|
3
3
|
*
|
|
4
|
-
* Routes from `
|
|
5
|
-
* a speech or image role). Adapters under this folder are internal implementation.
|
|
4
|
+
* Routes from the selected model binding's `protocol` / `provider` (and whether the
|
|
5
|
+
* profile is a speech or image role). Adapters under this folder are internal implementation.
|
|
6
6
|
*
|
|
7
7
|
* Every adapter graph is loaded only when that transport's first `complete` runs —
|
|
8
8
|
* not when this module is imported.
|
|
9
9
|
*
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
|
-
import type { ModelProvider, Profile } from '../kernel/types.js';
|
|
12
|
+
import type { ModelId, ModelProvider, Profile } from '../kernel/types.js';
|
|
13
13
|
import type { GeminiTransport } from './google/keys.js';
|
|
14
14
|
import type { LocalProviderConfig, OpenAiGatewayConfig } from './types.js';
|
|
15
15
|
/** Credentials supplied by the host when creating a provider. */
|
|
@@ -32,5 +32,8 @@ export declare function isImageRole(profile: Profile): boolean;
|
|
|
32
32
|
/**
|
|
33
33
|
* Create a `ModelProvider` for a turn-based profile (text / image / speech).
|
|
34
34
|
* Live profiles use `runSession` — `createProvider` rejects geminiLive.
|
|
35
|
+
*
|
|
36
|
+
* When a profile declares multiple models, pass `modelId` to pick the binding used
|
|
37
|
+
* for adapter selection (defaults to `defaultModel` or the sole model key).
|
|
35
38
|
*/
|
|
36
|
-
export declare function createProvider(profile: Profile, options?: CreateProviderOptions): ModelProvider;
|
|
39
|
+
export declare function createProvider(profile: Profile, options?: CreateProviderOptions, modelId?: ModelId): ModelProvider;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host provider factory — the single public door for binding a profile to a transport.
|
|
3
3
|
*
|
|
4
|
-
* Routes from `
|
|
5
|
-
* a speech or image role). Adapters under this folder are internal implementation.
|
|
4
|
+
* Routes from the selected model binding's `protocol` / `provider` (and whether the
|
|
5
|
+
* profile is a speech or image role). Adapters under this folder are internal implementation.
|
|
6
6
|
*
|
|
7
7
|
* Every adapter graph is loaded only when that transport's first `complete` runs —
|
|
8
8
|
* not when this module is imported.
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
* @module
|
|
11
11
|
*/
|
|
12
12
|
import { TheorumError } from '../guardrails/error.js';
|
|
13
|
+
import { requireModelProfile } from '../kernel/registry/resolve.js';
|
|
14
|
+
import { soleModelId } from '../kernel/registry/sole-model.js';
|
|
13
15
|
import { isValidPair } from '../kernel/schema.js';
|
|
14
16
|
import { markModuleLoad } from './probe.js';
|
|
15
17
|
export function isSpeechRole(profile) {
|
|
@@ -18,6 +20,18 @@ export function isSpeechRole(profile) {
|
|
|
18
20
|
export function isImageRole(profile) {
|
|
19
21
|
return profile.type === 'image';
|
|
20
22
|
}
|
|
23
|
+
function bindingForProvider(input, modelId) {
|
|
24
|
+
const profile = requireModelProfile(input, 'createProvider');
|
|
25
|
+
const id = modelId ?? profile.defaultModel ?? soleModelId(profile.models);
|
|
26
|
+
if (!id) {
|
|
27
|
+
throw new TheorumError(`createProvider: profile '${profile.id}' must set defaultModel when multiple models are declared`);
|
|
28
|
+
}
|
|
29
|
+
const binding = profile.models[id];
|
|
30
|
+
if (!binding) {
|
|
31
|
+
throw new TheorumError(`createProvider: profile '${profile.id}' has no model '${id}'`);
|
|
32
|
+
}
|
|
33
|
+
return binding;
|
|
34
|
+
}
|
|
21
35
|
/**
|
|
22
36
|
* Lazy-load an adapter on first `complete`. When `THEORUM_IMPORT_PROBE=1`,
|
|
23
37
|
* emits `LOADED:<label>` exactly once at load time (import-isolation tests).
|
|
@@ -52,9 +66,12 @@ function lazyLocal(config) {
|
|
|
52
66
|
/**
|
|
53
67
|
* Create a `ModelProvider` for a turn-based profile (text / image / speech).
|
|
54
68
|
* Live profiles use `runSession` — `createProvider` rejects geminiLive.
|
|
69
|
+
*
|
|
70
|
+
* When a profile declares multiple models, pass `modelId` to pick the binding used
|
|
71
|
+
* for adapter selection (defaults to `defaultModel` or the sole model key).
|
|
55
72
|
*/
|
|
56
|
-
export function createProvider(profile, options = {}) {
|
|
57
|
-
const { protocol, provider } = profile
|
|
73
|
+
export function createProvider(profile, options = {}, modelId) {
|
|
74
|
+
const { protocol, provider } = bindingForProvider(profile, modelId);
|
|
58
75
|
if (!isValidPair(protocol, provider)) {
|
|
59
76
|
throw new TheorumError(`createProvider: unsupported protocol/provider pair '${protocol}'/'${provider}'`);
|
|
60
77
|
}
|
|
@@ -85,8 +102,5 @@ export function createProvider(profile, options = {}) {
|
|
|
85
102
|
}
|
|
86
103
|
return lazyLocal(options.local);
|
|
87
104
|
}
|
|
88
|
-
// Exhaustiveness guard: isValidPair above already rejects unknown pairs, so
|
|
89
|
-
// this throw is unreachable at runtime. It exists so TypeScript errors if a
|
|
90
|
-
// new protocol/provider is added to PROTOCOL_PROVIDERS without a branch here.
|
|
91
105
|
throw new TheorumError(`createProvider: unsupported protocol/provider pair '${protocol}'/'${provider}'`);
|
|
92
106
|
}
|
|
@@ -3,6 +3,14 @@ export declare function camelToSnake(key: string): string;
|
|
|
3
3
|
export declare function toGoogleValue(value: unknown): unknown;
|
|
4
4
|
export declare function wirePart(part: InteractionPart): Record<string, string>;
|
|
5
5
|
export declare function userInputStep(parts: InteractionPart[]): Record<string, unknown>;
|
|
6
|
+
/**
|
|
7
|
+
* Map one host history message to Interactions input step(s).
|
|
8
|
+
*
|
|
9
|
+
* OpenAI-shaped assistant `tool_calls` (often with no `content`) become
|
|
10
|
+
* `function_call` steps — never empty `model_output` text.
|
|
11
|
+
*/
|
|
12
|
+
export declare function historySteps(msg: TurnHistoryMessage): Record<string, unknown>[];
|
|
13
|
+
/** Single-step helper for simple messages (first of {@link historySteps}). */
|
|
6
14
|
export declare function historyStep(msg: TurnHistoryMessage): Record<string, unknown>;
|
|
7
15
|
export declare function systemHoldsUserInput(system: string, parts: InteractionPart[]): boolean;
|
|
8
16
|
export declare function jsonResponseFormat(schema: Record<string, unknown>): unknown[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TheorumError } from '../../../guardrails/error.js';
|
|
2
|
+
import { wireInteractionPart } from '../../../kernel/interaction-parts.js';
|
|
2
3
|
import { getStructured } from '../../../kernel/registry/schemas.js';
|
|
3
4
|
import { getTool } from '../../../kernel/tools/registry.js';
|
|
4
5
|
export function camelToSnake(key) {
|
|
@@ -24,35 +25,91 @@ export function toGoogleValue(value) {
|
|
|
24
25
|
return value;
|
|
25
26
|
}
|
|
26
27
|
export function wirePart(part) {
|
|
27
|
-
|
|
28
|
-
return { type: 'text', text: part.text };
|
|
29
|
-
}
|
|
30
|
-
return { type: part.type, mimeType: part.mimeType, data: part.data };
|
|
28
|
+
return wireInteractionPart(part);
|
|
31
29
|
}
|
|
32
30
|
const USER_INPUT = 'user_input';
|
|
33
31
|
export function userInputStep(parts) {
|
|
34
32
|
return { type: USER_INPUT, content: parts.map(wirePart) };
|
|
35
33
|
}
|
|
36
34
|
function functionResultStep(msg) {
|
|
35
|
+
const result = msg.parts && msg.parts.length > 0
|
|
36
|
+
? msg.parts.map(wirePart)
|
|
37
|
+
: [{ type: 'text', text: msg.content ?? '' }];
|
|
37
38
|
return {
|
|
38
39
|
type: 'function_result',
|
|
39
40
|
name: msg.name ?? '',
|
|
40
41
|
call_id: msg.tool_call_id ?? '',
|
|
41
|
-
result
|
|
42
|
+
result,
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
/** Parse OpenAI-style tool-call `arguments` JSON into an Interactions object. */
|
|
46
|
+
function functionCallArguments(raw) {
|
|
47
|
+
const trimmed = raw.trim();
|
|
48
|
+
if (!trimmed)
|
|
49
|
+
return {};
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(trimmed);
|
|
52
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
return { value: parsed };
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return { value: raw };
|
|
47
59
|
}
|
|
48
|
-
|
|
60
|
+
}
|
|
61
|
+
function functionCallStep(call) {
|
|
62
|
+
const step = {
|
|
63
|
+
type: 'function_call',
|
|
64
|
+
id: call.id,
|
|
65
|
+
name: call.function.name,
|
|
66
|
+
arguments: functionCallArguments(call.function.arguments),
|
|
67
|
+
};
|
|
68
|
+
if (call.thoughtSignature) {
|
|
69
|
+
step.thoughtSignature = call.thoughtSignature;
|
|
70
|
+
}
|
|
71
|
+
return step;
|
|
72
|
+
}
|
|
73
|
+
function textOrPartsStep(role, msg) {
|
|
49
74
|
// Google Interactions input steps: assistant history is `model_output` (not `model_turn`).
|
|
50
|
-
const type =
|
|
75
|
+
const type = role === 'assistant' ? 'model_output' : 'user_input';
|
|
51
76
|
if (msg.parts && msg.parts.length > 0) {
|
|
52
77
|
return { type, content: msg.parts.map(wirePart) };
|
|
53
78
|
}
|
|
54
79
|
return { type, content: [{ type: 'text', text: msg.content ?? '' }] };
|
|
55
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Map one host history message to Interactions input step(s).
|
|
83
|
+
*
|
|
84
|
+
* OpenAI-shaped assistant `tool_calls` (often with no `content`) become
|
|
85
|
+
* `function_call` steps — never empty `model_output` text.
|
|
86
|
+
*/
|
|
87
|
+
export function historySteps(msg) {
|
|
88
|
+
if (msg.role === 'tool') {
|
|
89
|
+
return [functionResultStep(msg)];
|
|
90
|
+
}
|
|
91
|
+
if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
|
|
92
|
+
const steps = [];
|
|
93
|
+
const hasParts = Boolean(msg.parts && msg.parts.length > 0);
|
|
94
|
+
const hasText = Boolean(msg.content?.trim());
|
|
95
|
+
if (hasParts || hasText) {
|
|
96
|
+
steps.push(textOrPartsStep('assistant', msg));
|
|
97
|
+
}
|
|
98
|
+
for (const call of msg.tool_calls) {
|
|
99
|
+
steps.push(functionCallStep(call));
|
|
100
|
+
}
|
|
101
|
+
return steps;
|
|
102
|
+
}
|
|
103
|
+
if (msg.role === 'assistant') {
|
|
104
|
+
return [textOrPartsStep('assistant', msg)];
|
|
105
|
+
}
|
|
106
|
+
return [textOrPartsStep('user', msg)];
|
|
107
|
+
}
|
|
108
|
+
/** Single-step helper for simple messages (first of {@link historySteps}). */
|
|
109
|
+
export function historyStep(msg) {
|
|
110
|
+
const steps = historySteps(msg);
|
|
111
|
+
return steps[0] ?? { type: 'user_input', content: [{ type: 'text', text: '' }] };
|
|
112
|
+
}
|
|
56
113
|
export function systemHoldsUserInput(system, parts) {
|
|
57
114
|
for (const part of parts) {
|
|
58
115
|
if (part.type === 'text' && part.text && system.includes(part.text)) {
|
|
@@ -154,7 +211,7 @@ export function inputStepsFromRequest(req) {
|
|
|
154
211
|
}
|
|
155
212
|
const inputSteps = [];
|
|
156
213
|
for (const h of req.history ?? []) {
|
|
157
|
-
inputSteps.push(
|
|
214
|
+
inputSteps.push(...historySteps(h));
|
|
158
215
|
}
|
|
159
216
|
if (req.input.length > 0 || inputSteps.length === 0) {
|
|
160
217
|
inputSteps.push(userInputStep(req.input));
|
|
@@ -189,8 +246,12 @@ export function baseInteractionsBody(req) {
|
|
|
189
246
|
attachSpeechConfig(req, generationConfig);
|
|
190
247
|
}
|
|
191
248
|
else {
|
|
192
|
-
|
|
193
|
-
|
|
249
|
+
if (req.thinking) {
|
|
250
|
+
generationConfig.thinkingLevel = req.thinking;
|
|
251
|
+
}
|
|
252
|
+
if (req.summaries) {
|
|
253
|
+
generationConfig.thinkingSummaries = req.summaries;
|
|
254
|
+
}
|
|
194
255
|
}
|
|
195
256
|
return {
|
|
196
257
|
model: req.apiId,
|
|
@@ -16,7 +16,7 @@ export declare function buildGeminiLiveSetupMessage(req: ProviderCompleteRequest
|
|
|
16
16
|
/** Build the `clientContent` message used for seeding conversation history before realtime streaming. */
|
|
17
17
|
export declare function buildGeminiLiveClientContent(history: TurnHistoryMessage[]): Record<string, unknown> | null;
|
|
18
18
|
/** Build a `realtimeInput` message for streaming audio, video, or text chunks. */
|
|
19
|
-
export declare function buildGeminiLiveRealtimeInput(
|
|
19
|
+
export declare function buildGeminiLiveRealtimeInput(input: InteractionPart): Record<string, unknown>;
|
|
20
20
|
/** Build a `realtimeInput` message with text. */
|
|
21
21
|
export declare function buildGeminiLiveRealtimeText(text: string): Record<string, unknown>;
|
|
22
22
|
/** Build a `toolResponse` message returning the execution result of a tool call. */
|
|
@@ -37,9 +37,12 @@ export type ParsedLiveMessage = {
|
|
|
37
37
|
};
|
|
38
38
|
export declare function parseGeminiLiveMessage(raw: unknown): ParsedLiveMessage;
|
|
39
39
|
export declare function parseFunctionArguments(raw: unknown): ReturnType<typeof parseToolArgumentsObject>;
|
|
40
|
-
export declare function
|
|
40
|
+
export declare function extractLiveUsageTokens(metadata: Record<string, unknown>): TurnTokens | undefined;
|
|
41
41
|
/** Parse Gemini goAway.timeLeft (seconds number, "10s", duration string) → ms when known. */
|
|
42
42
|
export declare function parseGoAwayTimeLeftMs(timeLeft: unknown): number | undefined;
|
|
43
|
+
export type LiveInteractionStatus = 'IN_PROGRESS' | 'IDLE';
|
|
44
|
+
/** Read the server-side `interactionStatus` when the message carries one. */
|
|
45
|
+
export declare function readLiveInteractionStatus(message: Record<string, unknown>): LiveInteractionStatus | undefined;
|
|
43
46
|
/**
|
|
44
47
|
* Fold a raw `BidiGenerateContentServerMessage` into normalized `TurnEvent` items.
|
|
45
48
|
*/
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { TheorumError } from '../../../guardrails/error.js';
|
|
9
9
|
import { groundingFromEvent } from '../../../kernel/engine/delta.js';
|
|
10
|
+
import { isMediaRefPart } from '../../../kernel/interaction-parts.js';
|
|
11
|
+
import { getTool } from '../../../kernel/tools/registry.js';
|
|
10
12
|
import { base64ToBytes, bytesToBase64, wrapPcmAsWav } from '../../shared/pcm.js';
|
|
11
13
|
import { parseToolArgumentsObject } from '../../shared/tool-args.js';
|
|
12
14
|
import { GEMINI_LIVE_WS_URL } from '../urls.js';
|
|
@@ -15,11 +17,18 @@ import { toGeminiOpenApiSchema } from './openapi-schema.js';
|
|
|
15
17
|
export function buildGeminiLiveWebSocketUrl(apiKey) {
|
|
16
18
|
return `${GEMINI_LIVE_WS_URL}?key=${encodeURIComponent(apiKey)}`;
|
|
17
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';
|
|
18
26
|
export function wireFunctionDeclaration(decl) {
|
|
19
27
|
const parameters = toGeminiOpenApiSchema(decl.parameters);
|
|
20
28
|
return {
|
|
21
29
|
name: decl.name,
|
|
22
30
|
description: decl.description,
|
|
31
|
+
behavior: LIVE_FUNCTION_BEHAVIOR,
|
|
23
32
|
parameters: parameters && typeof parameters === 'object'
|
|
24
33
|
? parameters
|
|
25
34
|
: { type: 'OBJECT', properties: {} },
|
|
@@ -33,6 +42,7 @@ export function wireLiveTools(req) {
|
|
|
33
42
|
functionDeclarations.push({
|
|
34
43
|
name: id,
|
|
35
44
|
description: entry.description,
|
|
45
|
+
behavior: LIVE_FUNCTION_BEHAVIOR,
|
|
36
46
|
});
|
|
37
47
|
}
|
|
38
48
|
}
|
|
@@ -146,6 +156,17 @@ export function buildGeminiLiveSetupMessage(req) {
|
|
|
146
156
|
applyLiveOptionalFeatures(live, setup);
|
|
147
157
|
return { setup };
|
|
148
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
|
+
}
|
|
149
170
|
/** Format a single history message into a Google turn object. */
|
|
150
171
|
function historyTurnToGoogleTurn(msg) {
|
|
151
172
|
const role = msg.role === 'assistant' ? 'model' : 'user';
|
|
@@ -158,12 +179,7 @@ function historyTurnToGoogleTurn(msg) {
|
|
|
158
179
|
parts.push({ text: part.text });
|
|
159
180
|
}
|
|
160
181
|
else {
|
|
161
|
-
parts.push({
|
|
162
|
-
inlineData: {
|
|
163
|
-
mimeType: part.mimeType,
|
|
164
|
-
data: part.data,
|
|
165
|
-
},
|
|
166
|
-
});
|
|
182
|
+
parts.push({ inlineData: inlineData(part) });
|
|
167
183
|
}
|
|
168
184
|
}
|
|
169
185
|
return { role, parts };
|
|
@@ -181,14 +197,15 @@ export function buildGeminiLiveClientContent(history) {
|
|
|
181
197
|
};
|
|
182
198
|
}
|
|
183
199
|
/** Build a `realtimeInput` message for streaming audio, video, or text chunks. */
|
|
184
|
-
export function buildGeminiLiveRealtimeInput(
|
|
185
|
-
if (
|
|
200
|
+
export function buildGeminiLiveRealtimeInput(input) {
|
|
201
|
+
if (input.type === 'text') {
|
|
186
202
|
return {
|
|
187
203
|
realtimeInput: {
|
|
188
|
-
text:
|
|
204
|
+
text: input.text,
|
|
189
205
|
},
|
|
190
206
|
};
|
|
191
207
|
}
|
|
208
|
+
const part = inlineMediaPart(input);
|
|
192
209
|
if (part.type === 'audio') {
|
|
193
210
|
return {
|
|
194
211
|
realtimeInput: {
|
|
@@ -271,7 +288,7 @@ function readTokenCount(metadata, camelKey, snakeKey) {
|
|
|
271
288
|
const val = metadata[camelKey] ?? metadata[snakeKey];
|
|
272
289
|
return typeof val === 'number' ? val : 0;
|
|
273
290
|
}
|
|
274
|
-
export function
|
|
291
|
+
export function extractLiveUsageTokens(metadata) {
|
|
275
292
|
const prompt = readTokenCount(metadata, 'promptTokenCount', 'prompt_token_count');
|
|
276
293
|
const output = readTokenCount(metadata, 'responseTokenCount', 'response_token_count');
|
|
277
294
|
const thinking = readTokenCount(metadata, 'thoughtsTokenCount', 'thoughts_token_count');
|
|
@@ -454,6 +471,22 @@ function foldLiveGrounding(serverContent, events) {
|
|
|
454
471
|
});
|
|
455
472
|
}
|
|
456
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
|
+
}
|
|
457
490
|
function foldServerContent(message, events) {
|
|
458
491
|
const serverContent = message.serverContent;
|
|
459
492
|
if (!serverContent || typeof serverContent !== 'object')
|
|
@@ -488,11 +521,14 @@ function foldServerContent(message, events) {
|
|
|
488
521
|
foldModelPart(part, events);
|
|
489
522
|
}
|
|
490
523
|
foldLiveGrounding(serverContent, events);
|
|
524
|
+
if (serverContent.turnComplete === true || serverContent.turn_complete === true) {
|
|
525
|
+
events.push({ type: 'session', session: { kind: 'turn_complete' } });
|
|
526
|
+
}
|
|
491
527
|
}
|
|
492
528
|
function foldUsageMetadata(message, events) {
|
|
493
529
|
const usageMetadata = message.usageMetadata;
|
|
494
530
|
if (usageMetadata) {
|
|
495
|
-
const tokens =
|
|
531
|
+
const tokens = extractLiveUsageTokens(usageMetadata);
|
|
496
532
|
if (tokens) {
|
|
497
533
|
events.push({ type: 'tokens', tokens });
|
|
498
534
|
}
|
|
@@ -510,6 +546,7 @@ export function foldGeminiLiveServerMessage(message) {
|
|
|
510
546
|
foldToolCalls(message, events);
|
|
511
547
|
foldToolCancellations(message, events);
|
|
512
548
|
foldServerContent(message, events);
|
|
549
|
+
foldInteractionStatus(message, events);
|
|
513
550
|
foldUsageMetadata(message, events);
|
|
514
551
|
return events;
|
|
515
552
|
}
|
|
@@ -12,8 +12,9 @@ function convertGeminiSchemaType(value) {
|
|
|
12
12
|
const converted = {};
|
|
13
13
|
if (schemaTypes.includes('null'))
|
|
14
14
|
converted.nullable = true;
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const [onlyType] = nonNullTypes;
|
|
16
|
+
if (onlyType !== undefined && nonNullTypes.length === 1)
|
|
17
|
+
converted.type = onlyType.toUpperCase();
|
|
17
18
|
if (nonNullTypes.length > 1) {
|
|
18
19
|
converted.anyOf = nonNullTypes.map((item) => ({ type: item.toUpperCase() }));
|
|
19
20
|
}
|
|
@@ -32,5 +32,14 @@ export interface LiveQueue {
|
|
|
32
32
|
isClosed: () => boolean;
|
|
33
33
|
}
|
|
34
34
|
export declare function createLiveQueue(): LiveQueue;
|
|
35
|
+
/**
|
|
36
|
+
* Conversational cycle boundary.
|
|
37
|
+
*
|
|
38
|
+
* When the server reports `interactionStatus`, that is authoritative: `IDLE`
|
|
39
|
+
* closes the cycle and `IN_PROGRESS` keeps it open even across `turnComplete`
|
|
40
|
+
* (background reasoning / async tools may still produce output). Without the
|
|
41
|
+
* field, `turnComplete` is the boundary as before.
|
|
42
|
+
*/
|
|
43
|
+
export declare function turnPhaseFromMessage(message: Record<string, unknown>, events: TurnEvent[]): LiveTurnPhase;
|
|
35
44
|
/** Attach handlers that keep the socket open across conversational turns. */
|
|
36
45
|
export declare function attachLiveSessionHandlers(ws: WebSocket, liveQueue: LiveQueue): void;
|