theorum 0.1.2 → 0.1.4
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 +83 -27
- package/deno.json +57 -0
- package/deno.lock +469 -0
- package/docs/AGENT_PROFILE_CONTRACT.md +45 -18
- package/docs/CLI_SPEC.md +2 -2
- package/docs/SECRETS.md +27 -22
- package/mod.ts +95 -0
- package/package.json +37 -29
- package/src/cli/commands/profile.ts +57 -0
- package/src/cli/commands/run.ts +66 -0
- package/src/cli/commands/test.ts +195 -0
- package/src/cli/index.ts +161 -0
- package/src/cli/matrix/fixtures.ts +96 -0
- package/src/cli/matrix/synthesizer.ts +240 -0
- package/src/guardrails/error.ts +142 -0
- package/src/guardrails/injection.ts +254 -0
- package/src/guardrails/mod.ts +40 -0
- package/src/guardrails/quota.ts +87 -0
- package/src/guardrails/sanitize.ts +165 -0
- package/src/guardrails/sensitive.ts +96 -0
- package/src/host/mint-trace.ts +54 -0
- package/src/host/mod.ts +20 -0
- package/src/host/reply.ts +32 -0
- package/src/kernel/engine/assert.ts +29 -0
- package/src/kernel/engine/boundary.ts +75 -0
- package/src/kernel/engine/delta.ts +440 -0
- package/src/kernel/engine/hash.ts +11 -0
- package/src/kernel/engine/record.ts +8 -0
- package/src/kernel/engine/repair.ts +56 -0
- package/src/kernel/engine/runner/gates.ts +338 -0
- package/src/kernel/engine/runner/mod.ts +135 -0
- package/src/kernel/engine/runner/state.ts +31 -0
- package/src/kernel/engine/runner/steps.ts +183 -0
- package/src/kernel/engine/runner/stream.ts +83 -0
- package/src/kernel/engine/runner/tokens.ts +47 -0
- package/src/kernel/engine/runner/tools.ts +266 -0
- package/src/kernel/engine/runner.ts +7 -0
- package/src/kernel/engine/tree.ts +18 -0
- package/src/kernel/mod.ts +39 -0
- package/src/kernel/registry/attachments.ts +195 -0
- package/src/kernel/registry/catalog.ts +181 -0
- package/src/kernel/registry/ingress.ts +223 -0
- package/src/kernel/registry/profiles.ts +165 -0
- package/src/kernel/registry/provider-request.ts +31 -0
- package/src/kernel/registry/resolve.ts +290 -0
- package/src/kernel/registry/schemas.ts +29 -0
- package/src/kernel/registry/tools.ts +45 -0
- package/src/kernel/registry/vault.ts +26 -0
- package/src/kernel/types.ts +576 -0
- package/{esm/src/observability/mod.js → src/observability/mod.ts} +11 -2
- package/src/observability/spans.ts +70 -0
- package/src/observability/trace-attach.ts +96 -0
- package/src/observability/trace-record.ts +254 -0
- package/src/observability/trace-usage.ts +36 -0
- package/src/observability/trace.ts +143 -0
- package/src/presets/google.ts +147 -0
- package/src/presets/mod.ts +24 -0
- package/src/providers/create-provider.ts +64 -0
- package/src/providers/gemini-tape.ts +59 -0
- package/src/providers/google-tap.ts +57 -0
- package/src/providers/interactions.ts +182 -0
- package/src/providers/keys.ts +182 -0
- package/src/providers/mod.ts +12 -0
- package/src/providers/openrouter-mod.ts +16 -0
- package/src/providers/openrouter-payload.ts +232 -0
- package/src/providers/openrouter.ts +758 -0
- package/src/providers/pcm.ts +37 -0
- package/src/providers/provider.ts +206 -0
- package/src/providers/speech.ts +180 -0
- package/src/providers/sse.ts +59 -0
- package/esm/_dnt.polyfills.d.ts +0 -11
- package/esm/_dnt.polyfills.js +0 -15
- package/esm/_dnt.shims.d.ts +0 -5
- package/esm/_dnt.shims.js +0 -61
- package/esm/mod.d.ts +0 -37
- package/esm/mod.js +0 -35
- package/esm/package.json +0 -3
- package/esm/src/guardrails/error.d.ts +0 -35
- package/esm/src/guardrails/error.js +0 -116
- package/esm/src/guardrails/injection.d.ts +0 -12
- package/esm/src/guardrails/injection.js +0 -220
- package/esm/src/guardrails/keys.d.ts +0 -12
- package/esm/src/guardrails/keys.js +0 -132
- package/esm/src/guardrails/mod.d.ts +0 -14
- package/esm/src/guardrails/mod.js +0 -14
- package/esm/src/guardrails/sanitize.d.ts +0 -22
- package/esm/src/guardrails/sanitize.js +0 -133
- package/esm/src/guardrails/sensitive.d.ts +0 -12
- package/esm/src/guardrails/sensitive.js +0 -88
- package/esm/src/kernel/engine/boundary.d.ts +0 -10
- package/esm/src/kernel/engine/boundary.js +0 -55
- package/esm/src/kernel/engine/delta.d.ts +0 -8
- package/esm/src/kernel/engine/delta.js +0 -362
- package/esm/src/kernel/engine/hash.d.ts +0 -1
- package/esm/src/kernel/engine/hash.js +0 -9
- package/esm/src/kernel/engine/record.d.ts +0 -2
- package/esm/src/kernel/engine/record.js +0 -7
- package/esm/src/kernel/engine/repair.d.ts +0 -9
- package/esm/src/kernel/engine/repair.js +0 -38
- package/esm/src/kernel/engine/runner.d.ts +0 -14
- package/esm/src/kernel/engine/runner.js +0 -731
- package/esm/src/kernel/engine/tree.d.ts +0 -2
- package/esm/src/kernel/engine/tree.js +0 -17
- package/esm/src/kernel/mod.d.ts +0 -16
- package/esm/src/kernel/mod.js +0 -15
- package/esm/src/kernel/registry/catalog.d.ts +0 -24
- package/esm/src/kernel/registry/catalog.js +0 -213
- package/esm/src/kernel/registry/profiles.d.ts +0 -36
- package/esm/src/kernel/registry/profiles.js +0 -111
- package/esm/src/kernel/registry/resolve.d.ts +0 -20
- package/esm/src/kernel/registry/resolve.js +0 -235
- package/esm/src/kernel/registry/schemas.d.ts +0 -14
- package/esm/src/kernel/registry/schemas.js +0 -23
- package/esm/src/kernel/registry/tools.d.ts +0 -12
- package/esm/src/kernel/registry/tools.js +0 -36
- package/esm/src/kernel/types.d.ts +0 -497
- package/esm/src/kernel/types.js +0 -10
- package/esm/src/observability/mod.d.ts +0 -12
- package/esm/src/observability/spans.d.ts +0 -16
- package/esm/src/observability/spans.js +0 -56
- package/esm/src/observability/trace-attach.d.ts +0 -16
- package/esm/src/observability/trace-attach.js +0 -81
- package/esm/src/observability/trace-record.d.ts +0 -112
- package/esm/src/observability/trace-record.js +0 -140
- package/esm/src/observability/trace-usage.d.ts +0 -3
- package/esm/src/observability/trace-usage.js +0 -32
- package/esm/src/observability/trace.d.ts +0 -23
- package/esm/src/observability/trace.js +0 -121
- package/esm/src/providers/attachments.d.ts +0 -17
- package/esm/src/providers/attachments.js +0 -156
- package/esm/src/providers/gemini-tape.d.ts +0 -3
- package/esm/src/providers/gemini-tape.js +0 -46
- package/esm/src/providers/google-tap.d.ts +0 -3
- package/esm/src/providers/google-tap.js +0 -48
- package/esm/src/providers/interactions.d.ts +0 -5
- package/esm/src/providers/interactions.js +0 -127
- package/esm/src/providers/media.d.ts +0 -5
- package/esm/src/providers/media.js +0 -125
- package/esm/src/providers/mod.d.ts +0 -15
- package/esm/src/providers/mod.js +0 -13
- package/esm/src/providers/openrouter-payload.d.ts +0 -24
- package/esm/src/providers/openrouter-payload.js +0 -177
- package/esm/src/providers/openrouter.d.ts +0 -17
- package/esm/src/providers/openrouter.js +0 -332
- package/esm/src/providers/provider.d.ts +0 -13
- package/esm/src/providers/provider.js +0 -123
- package/esm/src/providers/sse.d.ts +0 -7
- package/esm/src/providers/sse.js +0 -53
- package/esm/src/providers/tts.d.ts +0 -24
- package/esm/src/providers/tts.js +0 -144
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { providerCompleteRequest } from '../kernel/registry/provider-request.ts';
|
|
2
|
+
import type { ResolvedGeneration, TurnRequest } from '../kernel/types.ts';
|
|
3
|
+
import { tapeGemini } from '../providers/gemini-tape.ts';
|
|
4
|
+
import { toInteractionsBody } from '../providers/interactions.ts';
|
|
5
|
+
import type { TraceRecord } from './trace-record.ts';
|
|
6
|
+
import { httpStatus } from './trace-usage.ts';
|
|
7
|
+
|
|
8
|
+
function attachResolved(
|
|
9
|
+
record: TraceRecord,
|
|
10
|
+
args: {
|
|
11
|
+
safe: TurnRequest;
|
|
12
|
+
model?: string;
|
|
13
|
+
bucket?: string;
|
|
14
|
+
generation?: ResolvedGeneration;
|
|
15
|
+
},
|
|
16
|
+
): void {
|
|
17
|
+
const { safe, model, bucket, generation } = args;
|
|
18
|
+
if (safe.projectId) {
|
|
19
|
+
record.projectId = safe.projectId;
|
|
20
|
+
}
|
|
21
|
+
if (safe.select) {
|
|
22
|
+
record.select = safe.select;
|
|
23
|
+
}
|
|
24
|
+
if (safe.thinking !== undefined) {
|
|
25
|
+
record.thinking = safe.thinking;
|
|
26
|
+
}
|
|
27
|
+
if (safe.tools) {
|
|
28
|
+
record.tools = safe.tools;
|
|
29
|
+
}
|
|
30
|
+
if (safe.metadata) {
|
|
31
|
+
record.metadata = safe.metadata;
|
|
32
|
+
}
|
|
33
|
+
if (model) {
|
|
34
|
+
record.model = {
|
|
35
|
+
id: model,
|
|
36
|
+
apiId: generation?.apiId ?? model,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (bucket) {
|
|
40
|
+
record.bucket = bucket;
|
|
41
|
+
}
|
|
42
|
+
if (generation) {
|
|
43
|
+
record.generation = {
|
|
44
|
+
thinking: generation.thinking,
|
|
45
|
+
summaries: generation.summaries,
|
|
46
|
+
temperature: generation.temperature,
|
|
47
|
+
maxOutputTokens: generation.maxOutputTokens,
|
|
48
|
+
builtins: generation.builtins,
|
|
49
|
+
custom: generation.custom,
|
|
50
|
+
structured: generation.structured,
|
|
51
|
+
image: generation.image,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function attachTape(
|
|
57
|
+
record: TraceRecord,
|
|
58
|
+
args: {
|
|
59
|
+
gemini?: unknown;
|
|
60
|
+
canary?: string;
|
|
61
|
+
system?: string;
|
|
62
|
+
generation?: ResolvedGeneration;
|
|
63
|
+
},
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
const { gemini, canary, system, generation } = args;
|
|
66
|
+
if (gemini !== undefined) {
|
|
67
|
+
record.gemini = await tapeGemini(gemini, canary ?? '');
|
|
68
|
+
}
|
|
69
|
+
if (generation && system !== undefined) {
|
|
70
|
+
record.wire = await tapeGemini(
|
|
71
|
+
toInteractionsBody(providerCompleteRequest(generation, system)),
|
|
72
|
+
canary ?? '',
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function attachUsage(
|
|
78
|
+
record: TraceRecord,
|
|
79
|
+
gemini: unknown,
|
|
80
|
+
done: Record<string, unknown> | undefined,
|
|
81
|
+
): void {
|
|
82
|
+
if (done?.usage !== undefined) {
|
|
83
|
+
record.usage = done.usage;
|
|
84
|
+
}
|
|
85
|
+
const upStatus = httpStatus(gemini);
|
|
86
|
+
if (upStatus !== undefined || done) {
|
|
87
|
+
record.upstream = {
|
|
88
|
+
status: upStatus,
|
|
89
|
+
id: done?.id,
|
|
90
|
+
finish: done?.status,
|
|
91
|
+
serviceTier: done?.service_tier ?? done?.serviceTier,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { attachResolved, attachTape, attachUsage };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace record construction types.
|
|
3
|
+
*
|
|
4
|
+
* Trace records preserve useful execution evidence while hashing or omitting
|
|
5
|
+
* unsafe media bytes and canary-sensitive content.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { publicError } from '../guardrails/error.ts';
|
|
11
|
+
import { sanitizeText, sanitizeTurnRequest } from '../guardrails/sanitize.ts';
|
|
12
|
+
import { OMIT_CANARY } from '../kernel/engine/boundary.ts';
|
|
13
|
+
import { sha256 } from '../kernel/engine/hash.ts';
|
|
14
|
+
import type { ResolvedGeneration, TurnBlob, TurnEvent, TurnRequest } from '../kernel/types.ts';
|
|
15
|
+
import { attachResolved, attachTape, attachUsage } from './trace-attach.ts';
|
|
16
|
+
import { completedInteraction } from './trace-usage.ts';
|
|
17
|
+
|
|
18
|
+
const TRACE_VERSION = 2;
|
|
19
|
+
const TITLE_MAX = 80;
|
|
20
|
+
|
|
21
|
+
/** Hash-only image reference stored in trace records. */
|
|
22
|
+
export interface TraceImage {
|
|
23
|
+
mimeType: string;
|
|
24
|
+
sha256: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Trace-safe copy of a public turn event. */
|
|
28
|
+
export interface TraceEvent {
|
|
29
|
+
type: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
tool?: {
|
|
32
|
+
name: string;
|
|
33
|
+
arguments?: Record<string, unknown>;
|
|
34
|
+
result?: { status: string; finding?: string; data?: Record<string, unknown> };
|
|
35
|
+
};
|
|
36
|
+
structured?: unknown;
|
|
37
|
+
media?: TraceImage;
|
|
38
|
+
grounding?: TurnEvent['grounding'];
|
|
39
|
+
evidence?: TurnEvent['evidence'];
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Complete trace-safe record for one attempted turn. */
|
|
44
|
+
interface TraceRecord {
|
|
45
|
+
v: number;
|
|
46
|
+
id: string;
|
|
47
|
+
ts: number;
|
|
48
|
+
ms: number;
|
|
49
|
+
streamed: boolean;
|
|
50
|
+
cancelled: boolean;
|
|
51
|
+
previousInteractionId: string | null;
|
|
52
|
+
store: boolean | null;
|
|
53
|
+
profile: string;
|
|
54
|
+
title?: string;
|
|
55
|
+
projectId?: string;
|
|
56
|
+
select?: string;
|
|
57
|
+
thinking?: boolean;
|
|
58
|
+
tools?: TurnRequest['tools'];
|
|
59
|
+
metadata?: Record<string, unknown>;
|
|
60
|
+
model?: { id: string; apiId: string };
|
|
61
|
+
bucket?: string;
|
|
62
|
+
generation?: {
|
|
63
|
+
thinking: string;
|
|
64
|
+
summaries: string;
|
|
65
|
+
temperature: number;
|
|
66
|
+
maxOutputTokens: number;
|
|
67
|
+
builtins: string[];
|
|
68
|
+
custom: string[];
|
|
69
|
+
structured: string | null;
|
|
70
|
+
image: unknown;
|
|
71
|
+
};
|
|
72
|
+
input: {
|
|
73
|
+
text?: string;
|
|
74
|
+
role?: string;
|
|
75
|
+
slots?: Record<string, string>;
|
|
76
|
+
attachments: TraceImage[];
|
|
77
|
+
voice: TraceImage[];
|
|
78
|
+
images?: TraceImage[];
|
|
79
|
+
audio?: TraceImage[];
|
|
80
|
+
};
|
|
81
|
+
wire?: unknown;
|
|
82
|
+
events: TraceEvent[];
|
|
83
|
+
gemini?: unknown;
|
|
84
|
+
usage?: unknown;
|
|
85
|
+
upstream?: {
|
|
86
|
+
status?: unknown;
|
|
87
|
+
id?: unknown;
|
|
88
|
+
finish?: unknown;
|
|
89
|
+
serviceTier?: unknown;
|
|
90
|
+
};
|
|
91
|
+
ok: boolean;
|
|
92
|
+
error?: string;
|
|
93
|
+
errorInternal?: string;
|
|
94
|
+
app?: Record<string, unknown>;
|
|
95
|
+
cutout?: {
|
|
96
|
+
ok: boolean;
|
|
97
|
+
ms: number;
|
|
98
|
+
url?: string;
|
|
99
|
+
inSha256?: string;
|
|
100
|
+
outSha256?: string;
|
|
101
|
+
http?: unknown;
|
|
102
|
+
error?: string;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function hashBlobs(blobs: TurnBlob[] | undefined): Promise<TraceImage[]> {
|
|
107
|
+
if (!blobs) {
|
|
108
|
+
return Promise.resolve([]);
|
|
109
|
+
}
|
|
110
|
+
return Promise.all(
|
|
111
|
+
blobs.map(async (blob) => ({
|
|
112
|
+
mimeType: blob.mimeType,
|
|
113
|
+
sha256: await sha256(blob.data),
|
|
114
|
+
})),
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function snapshotEvent(event: TurnEvent): Promise<TraceEvent> {
|
|
119
|
+
const row: TraceEvent = { type: event.type };
|
|
120
|
+
if (event.text) {
|
|
121
|
+
row.text = sanitizeText(event.text);
|
|
122
|
+
}
|
|
123
|
+
if (event.error) {
|
|
124
|
+
row.error = event.error;
|
|
125
|
+
}
|
|
126
|
+
if (event.structured !== undefined) {
|
|
127
|
+
row.structured = event.structured;
|
|
128
|
+
}
|
|
129
|
+
if (event.tool) {
|
|
130
|
+
const { name, arguments: args, result } = event.tool;
|
|
131
|
+
row.tool = { name, arguments: args };
|
|
132
|
+
if (result) {
|
|
133
|
+
row.tool.result = {
|
|
134
|
+
status: result.status,
|
|
135
|
+
...(result.finding ? { finding: result.finding } : {}),
|
|
136
|
+
...(result.data ? { data: result.data } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (event.media) {
|
|
141
|
+
row.media = {
|
|
142
|
+
mimeType: event.media.mimeType,
|
|
143
|
+
sha256: await sha256(event.media.data),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (event.grounding) {
|
|
147
|
+
row.grounding = event.grounding;
|
|
148
|
+
}
|
|
149
|
+
if (event.evidence) {
|
|
150
|
+
row.evidence = event.evidence;
|
|
151
|
+
}
|
|
152
|
+
return row;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function requestForTrace(req: TurnRequest): TurnRequest {
|
|
156
|
+
try {
|
|
157
|
+
return sanitizeTurnRequest(req);
|
|
158
|
+
} catch {
|
|
159
|
+
return { profile: req.profile, input: {} };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function internalError(err: unknown): string | undefined {
|
|
164
|
+
if (typeof err === 'string') {
|
|
165
|
+
return sanitizeText(err);
|
|
166
|
+
}
|
|
167
|
+
if (err instanceof Error && err.message) {
|
|
168
|
+
return sanitizeText(err.message);
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function titleFrom(text: string | undefined): string | undefined {
|
|
174
|
+
if (!text) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const trimmed = text.trim().replaceAll(/\s+/g, ' ');
|
|
178
|
+
if (!trimmed) {
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
return trimmed.slice(0, TITLE_MAX);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function attachFailure(
|
|
185
|
+
record: TraceRecord,
|
|
186
|
+
thrown: unknown,
|
|
187
|
+
lastErr: TraceEvent | undefined,
|
|
188
|
+
canary: string | undefined,
|
|
189
|
+
): void {
|
|
190
|
+
if (!record.ok) {
|
|
191
|
+
record.error = publicError(thrown ?? lastErr?.error);
|
|
192
|
+
const inside = internalError(thrown) ?? lastErr?.error;
|
|
193
|
+
if (inside) {
|
|
194
|
+
record.errorInternal = inside;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (canary && JSON.stringify(record).includes(canary)) {
|
|
198
|
+
record.errorInternal = OMIT_CANARY;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function buildRecord(args: {
|
|
203
|
+
req: TurnRequest;
|
|
204
|
+
events: TurnEvent[];
|
|
205
|
+
started: number;
|
|
206
|
+
model?: string;
|
|
207
|
+
bucket?: string;
|
|
208
|
+
thrown?: unknown;
|
|
209
|
+
gemini?: unknown;
|
|
210
|
+
canary?: string;
|
|
211
|
+
system?: string;
|
|
212
|
+
generation?: ResolvedGeneration;
|
|
213
|
+
}): Promise<TraceRecord> {
|
|
214
|
+
const { req, events, started, model, bucket, thrown, gemini, canary, system, generation } = args;
|
|
215
|
+
const safe = requestForTrace(req);
|
|
216
|
+
const input = safe.input ?? {};
|
|
217
|
+
const snapped = await Promise.all(events.map((event) => snapshotEvent(event)));
|
|
218
|
+
const lastErr = [...snapped].reverse().find((row) => row.type === 'error');
|
|
219
|
+
const done = completedInteraction(gemini);
|
|
220
|
+
const status = done?.status;
|
|
221
|
+
const ok = !(thrown || lastErr) && status !== 'cancelled';
|
|
222
|
+
const record: TraceRecord = {
|
|
223
|
+
v: TRACE_VERSION,
|
|
224
|
+
id: crypto.randomUUID(),
|
|
225
|
+
ts: started,
|
|
226
|
+
ms: Date.now() - started,
|
|
227
|
+
streamed: true,
|
|
228
|
+
cancelled: status === 'cancelled',
|
|
229
|
+
previousInteractionId: safe.previousInteractionId ?? null,
|
|
230
|
+
store: safe.store ?? null,
|
|
231
|
+
profile: safe.profile,
|
|
232
|
+
input: {
|
|
233
|
+
text: input.text,
|
|
234
|
+
role: input.role,
|
|
235
|
+
slots: input.slots,
|
|
236
|
+
attachments: await hashBlobs(input.attachments),
|
|
237
|
+
voice: await hashBlobs(input.voice),
|
|
238
|
+
},
|
|
239
|
+
events: snapped,
|
|
240
|
+
ok,
|
|
241
|
+
};
|
|
242
|
+
const title = titleFrom(input.text);
|
|
243
|
+
if (title) {
|
|
244
|
+
record.title = title;
|
|
245
|
+
}
|
|
246
|
+
await attachTape(record, { gemini, canary, system, generation });
|
|
247
|
+
attachUsage(record, gemini, done);
|
|
248
|
+
attachResolved(record, { safe, model, bucket, generation });
|
|
249
|
+
attachFailure(record, thrown, lastErr, canary);
|
|
250
|
+
return record;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export type { TraceRecord };
|
|
254
|
+
export { buildRecord };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { asRecord } from '../kernel/engine/record.ts';
|
|
2
|
+
|
|
3
|
+
function eventName(row: Record<string, unknown>): string {
|
|
4
|
+
return String(row.event_type ?? row.eventType ?? '');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function completedInteraction(gemini: unknown): Record<string, unknown> | undefined {
|
|
8
|
+
if (!Array.isArray(gemini)) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
for (let i = gemini.length - 1; i >= 0; i -= 1) {
|
|
12
|
+
const row = asRecord(gemini[i]);
|
|
13
|
+
if (row) {
|
|
14
|
+
const name = eventName(row);
|
|
15
|
+
if (name === 'interaction.completed' || name === 'interaction.complete') {
|
|
16
|
+
return asRecord(row.interaction) ?? row;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function httpStatus(gemini: unknown): unknown {
|
|
24
|
+
if (!Array.isArray(gemini)) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
for (const item of gemini) {
|
|
28
|
+
const row = asRecord(item);
|
|
29
|
+
if (row && eventName(row) === 'http_response') {
|
|
30
|
+
return row.status;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { completedInteraction, httpStatus };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trace sink primitives for THEORUM.
|
|
3
|
+
*
|
|
4
|
+
* Tracing is host-injected: the kernel can write to a provided sink, a memory
|
|
5
|
+
* sink, a JSONL directory, or a noop sink. It does not read environment
|
|
6
|
+
* variables or own a database destination.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { TraceRecord } from './trace-record.ts';
|
|
12
|
+
|
|
13
|
+
const RETAIN_DAYS = 14;
|
|
14
|
+
const HOURS_PER_DAY = 24;
|
|
15
|
+
const MIN_PER_HOUR = 60;
|
|
16
|
+
const SEC_PER_MIN = 60;
|
|
17
|
+
const MS_PER_SEC = 1000;
|
|
18
|
+
const RETAIN_MS = RETAIN_DAYS * HOURS_PER_DAY * MIN_PER_HOUR * SEC_PER_MIN * MS_PER_SEC;
|
|
19
|
+
const KIB = 1024;
|
|
20
|
+
const MIB = KIB * KIB;
|
|
21
|
+
const ROTATE_MIB = 32;
|
|
22
|
+
const ROTATE_BYTES = ROTATE_MIB * MIB;
|
|
23
|
+
const FILE_DAY = /^turns-(\d{4}-\d{2}-\d{2})(?:-\d+)?\.jsonl$/;
|
|
24
|
+
|
|
25
|
+
/** Minimal async destination for completed turn trace records. */
|
|
26
|
+
interface TraceSink {
|
|
27
|
+
write: (record: TraceRecord) => Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Write a trace record without allowing trace failures to fail the turn. */
|
|
31
|
+
async function writeTrace(sink: TraceSink, record: Promise<TraceRecord>): Promise<void> {
|
|
32
|
+
try {
|
|
33
|
+
await sink.write(await record);
|
|
34
|
+
} catch {
|
|
35
|
+
// Tracing must not fail the turn.
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Trace sink that drops records. */
|
|
40
|
+
function noopSink(): TraceSink {
|
|
41
|
+
return { write: () => Promise.resolve() };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Trace sink that appends records to a caller-owned array. */
|
|
45
|
+
function memorySink(into: TraceRecord[]): TraceSink {
|
|
46
|
+
return {
|
|
47
|
+
write: (record) => {
|
|
48
|
+
into.push(record);
|
|
49
|
+
return Promise.resolve();
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dayStamp(ms: number): string {
|
|
55
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function fileDay(name: string): string | undefined {
|
|
59
|
+
return FILE_DAY.exec(name)?.[1];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function pruneTraces(dir: string, now: number): Promise<void> {
|
|
63
|
+
const cutoff = now - RETAIN_MS;
|
|
64
|
+
for await (const entry of Deno.readDir(dir)) {
|
|
65
|
+
const day = fileDay(entry.name);
|
|
66
|
+
if (day && Date.parse(`${day}T00:00:00.000Z`) < cutoff) {
|
|
67
|
+
await Deno.remove(`${dir}/${entry.name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function pickFile(dir: string, now: number): Promise<string> {
|
|
73
|
+
const day = dayStamp(now);
|
|
74
|
+
const base = `${dir}/turns-${day}.jsonl`;
|
|
75
|
+
try {
|
|
76
|
+
const info = await Deno.stat(base);
|
|
77
|
+
if ((info.size ?? 0) < ROTATE_BYTES) {
|
|
78
|
+
return base;
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
return base;
|
|
82
|
+
}
|
|
83
|
+
return `${dir}/turns-${day}-${now}.jsonl`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Trace sink that writes daily rotating JSONL files under a host-selected directory. */
|
|
87
|
+
function jsonlSink(dir: string, now: () => number = Date.now): TraceSink {
|
|
88
|
+
return {
|
|
89
|
+
write: async (record) => {
|
|
90
|
+
const at = now();
|
|
91
|
+
await Deno.mkdir(dir, { recursive: true });
|
|
92
|
+
await pruneTraces(dir, at);
|
|
93
|
+
const path = await pickFile(dir, at);
|
|
94
|
+
await Deno.writeTextFile(path, `${JSON.stringify(record)}\n`, { append: true });
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function insideDir(path: string, root: string): boolean {
|
|
100
|
+
let base = root;
|
|
101
|
+
if (root.endsWith('/')) {
|
|
102
|
+
base = root.slice(0, -1);
|
|
103
|
+
}
|
|
104
|
+
if (path === base) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return path.startsWith(`${base}/`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Resolve a trace directory while refusing relative paths or paths inside the clone. */
|
|
111
|
+
function resolveTraceDir(args: {
|
|
112
|
+
dir?: string;
|
|
113
|
+
fallbackDir?: string;
|
|
114
|
+
cwd?: string;
|
|
115
|
+
}): string | undefined {
|
|
116
|
+
const cwd = args.cwd ?? Deno.cwd();
|
|
117
|
+
if (args.dir === '') {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
let dir = args.dir?.trim() || args.fallbackDir;
|
|
121
|
+
if (!dir) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (!dir.startsWith('/') || insideDir(dir, cwd)) {
|
|
125
|
+
dir = args.fallbackDir;
|
|
126
|
+
}
|
|
127
|
+
if (!dir || insideDir(dir, cwd)) {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
return dir;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Build a JSONL sink from a host-supplied directory or return a noop sink. */
|
|
134
|
+
function sinkFromDir(dir?: string, fallbackDir?: string): TraceSink {
|
|
135
|
+
const resolved = resolveTraceDir({ dir, fallbackDir });
|
|
136
|
+
if (!resolved) {
|
|
137
|
+
return noopSink();
|
|
138
|
+
}
|
|
139
|
+
return jsonlSink(resolved);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export type { TraceSink };
|
|
143
|
+
export { jsonlSink, memorySink, noopSink, resolveTraceDir, sinkFromDir, writeTrace };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google / Gemini convenience preset.
|
|
3
|
+
*
|
|
4
|
+
* Kernel stays open (`string` pins). This pack owns Google vocabularies as
|
|
5
|
+
* real unions so host profiles stay typed when they opt into the preset.
|
|
6
|
+
*
|
|
7
|
+
* Call `registerGooglePreset()` at host startup for tools; use the exported
|
|
8
|
+
* types/constants when authoring image/speech-adjacent profile fields.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { registerTools } from '../kernel/registry/catalog.ts';
|
|
14
|
+
import type { ProfileImageSpec, ProfileSpeechSpec, ToolCatalogEntry } from '../kernel/types.ts';
|
|
15
|
+
|
|
16
|
+
/** Google Interactions / Gemini grounding builtins. */
|
|
17
|
+
const GOOGLE_BUILTIN_TOOLS: Record<string, ToolCatalogEntry> = {
|
|
18
|
+
googleSearch: {
|
|
19
|
+
kind: 'builtin',
|
|
20
|
+
ui: true,
|
|
21
|
+
interactionsType: 'google_search',
|
|
22
|
+
openRouterPlugin: 'web',
|
|
23
|
+
},
|
|
24
|
+
googleMaps: {
|
|
25
|
+
kind: 'builtin',
|
|
26
|
+
ui: true,
|
|
27
|
+
interactionsType: 'google_maps',
|
|
28
|
+
conflictsWith: ['googleSearch', 'urlContext'],
|
|
29
|
+
},
|
|
30
|
+
urlContext: {
|
|
31
|
+
kind: 'builtin',
|
|
32
|
+
ui: true,
|
|
33
|
+
interactionsType: 'url_context',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Common Gemini image input MIME allowlist. */
|
|
38
|
+
const GOOGLE_IMAGE_INPUT_MIMES = [
|
|
39
|
+
'image/png',
|
|
40
|
+
'image/jpeg',
|
|
41
|
+
'image/webp',
|
|
42
|
+
'image/heic',
|
|
43
|
+
'image/heif',
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
/** Common voice/audio input MIME allowlist for Gemini multimodal. */
|
|
47
|
+
const GOOGLE_VOICE_INPUT_MIMES = ['audio/webm', 'audio/wav', 'audio/mpeg', 'audio/mp4'] as const;
|
|
48
|
+
|
|
49
|
+
/** Common Gemini TTS voice names for `outputs.speech.voice`. */
|
|
50
|
+
const GOOGLE_SPEECH_VOICES = [
|
|
51
|
+
'Zephyr',
|
|
52
|
+
'Puck',
|
|
53
|
+
'Charon',
|
|
54
|
+
'Kore',
|
|
55
|
+
'Fenrir',
|
|
56
|
+
'Leda',
|
|
57
|
+
'Orus',
|
|
58
|
+
'Aoede',
|
|
59
|
+
'Callirrhoe',
|
|
60
|
+
'Autonoe',
|
|
61
|
+
'Enceladus',
|
|
62
|
+
'Iapetus',
|
|
63
|
+
'Umbriel',
|
|
64
|
+
'Algieba',
|
|
65
|
+
'Despina',
|
|
66
|
+
'Erinome',
|
|
67
|
+
'Algenib',
|
|
68
|
+
'Rasalgethi',
|
|
69
|
+
'Laomedeia',
|
|
70
|
+
'Achernar',
|
|
71
|
+
'Alnilam',
|
|
72
|
+
'Schedar',
|
|
73
|
+
'Gacrux',
|
|
74
|
+
'Pulcherrima',
|
|
75
|
+
'Achird',
|
|
76
|
+
'Zubenelgenubi',
|
|
77
|
+
'Vindemiatrix',
|
|
78
|
+
'Sadachbia',
|
|
79
|
+
'Sadaltager',
|
|
80
|
+
'Sulafat',
|
|
81
|
+
] as const;
|
|
82
|
+
|
|
83
|
+
type GoogleSpeechVoice = (typeof GOOGLE_SPEECH_VOICES)[number];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* `outputs.speech` pins narrowed to Google TTS vocabulary.
|
|
87
|
+
* Assignable to kernel `ProfileSpeechSpec`.
|
|
88
|
+
*/
|
|
89
|
+
type GoogleSpeechPins = Omit<ProfileSpeechSpec, 'voice'> & {
|
|
90
|
+
voice?: GoogleSpeechVoice;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Aspect ratios commonly accepted by Gemini image models. */
|
|
94
|
+
const GOOGLE_IMAGE_ASPECT_RATIOS = [
|
|
95
|
+
'1:1',
|
|
96
|
+
'3:2',
|
|
97
|
+
'2:3',
|
|
98
|
+
'3:4',
|
|
99
|
+
'4:3',
|
|
100
|
+
'4:5',
|
|
101
|
+
'5:4',
|
|
102
|
+
'9:16',
|
|
103
|
+
'16:9',
|
|
104
|
+
'21:9',
|
|
105
|
+
] as const;
|
|
106
|
+
|
|
107
|
+
/** Image sizes commonly accepted by Gemini Flash Lite image. */
|
|
108
|
+
const GOOGLE_IMAGE_SIZES = ['1K'] as const;
|
|
109
|
+
|
|
110
|
+
type GoogleImageInputMime = (typeof GOOGLE_IMAGE_INPUT_MIMES)[number];
|
|
111
|
+
type GoogleVoiceInputMime = (typeof GOOGLE_VOICE_INPUT_MIMES)[number];
|
|
112
|
+
type GoogleImageAspectRatio = (typeof GOOGLE_IMAGE_ASPECT_RATIOS)[number];
|
|
113
|
+
type GoogleImageSize = (typeof GOOGLE_IMAGE_SIZES)[number];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* `outputs.image` pins narrowed to Google image vocabulary.
|
|
117
|
+
* Assignable to kernel `ProfileImageSpec`.
|
|
118
|
+
*/
|
|
119
|
+
type GoogleImagePins = Omit<ProfileImageSpec, 'aspectRatio' | 'size' | 'mimeType'> & {
|
|
120
|
+
aspectRatio?: GoogleImageAspectRatio;
|
|
121
|
+
size?: GoogleImageSize;
|
|
122
|
+
mimeType?: GoogleImageInputMime | 'image/jpeg';
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** Register Google provider builtins into the process-local tool catalog. */
|
|
126
|
+
function registerGooglePreset(): void {
|
|
127
|
+
registerTools(GOOGLE_BUILTIN_TOOLS);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export type {
|
|
131
|
+
GoogleImageAspectRatio,
|
|
132
|
+
GoogleImageInputMime,
|
|
133
|
+
GoogleImagePins,
|
|
134
|
+
GoogleImageSize,
|
|
135
|
+
GoogleSpeechPins,
|
|
136
|
+
GoogleSpeechVoice,
|
|
137
|
+
GoogleVoiceInputMime,
|
|
138
|
+
};
|
|
139
|
+
export {
|
|
140
|
+
GOOGLE_BUILTIN_TOOLS,
|
|
141
|
+
GOOGLE_IMAGE_ASPECT_RATIOS,
|
|
142
|
+
GOOGLE_IMAGE_INPUT_MIMES,
|
|
143
|
+
GOOGLE_IMAGE_SIZES,
|
|
144
|
+
GOOGLE_SPEECH_VOICES,
|
|
145
|
+
GOOGLE_VOICE_INPUT_MIMES,
|
|
146
|
+
registerGooglePreset,
|
|
147
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional THEORUM presets.
|
|
3
|
+
*
|
|
4
|
+
* Presets register host-convenience catalogs (provider builtins, media
|
|
5
|
+
* vocabularies, later model packs) without baking product opinions into the kernel.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
GoogleImageAspectRatio,
|
|
12
|
+
GoogleImageInputMime,
|
|
13
|
+
GoogleImagePins,
|
|
14
|
+
GoogleImageSize,
|
|
15
|
+
GoogleVoiceInputMime,
|
|
16
|
+
} from './google.ts';
|
|
17
|
+
export {
|
|
18
|
+
GOOGLE_BUILTIN_TOOLS,
|
|
19
|
+
GOOGLE_IMAGE_ASPECT_RATIOS,
|
|
20
|
+
GOOGLE_IMAGE_INPUT_MIMES,
|
|
21
|
+
GOOGLE_IMAGE_SIZES,
|
|
22
|
+
GOOGLE_VOICE_INPUT_MIMES,
|
|
23
|
+
registerGooglePreset,
|
|
24
|
+
} from './google.ts';
|