melchizedek-agents 0.9.2 → 0.9.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/AGENT_SETUP.md +2 -2
- package/DOCUMENTATION.md +13 -22
- package/README.md +22 -20
- package/config/agents/examples/ares.yaml +0 -2
- package/config/agents/examples/augustin.yaml +0 -2
- package/config/agents/examples/cartographers.yaml +0 -3
- package/config/agents/examples/council.yaml +1 -1
- package/config/agents/examples/image_production.yaml +0 -1
- package/config/agents/examples/librarian.yaml +0 -2
- package/config/agents/examples/patient_advocate.yaml +0 -2
- package/config/agents/examples/scriptorium.yaml +0 -3
- package/config/agents/examples/style_council.yaml +1 -5
- package/config/agents/examples/syndicate.yaml +0 -3
- package/config/agents/examples/tutor.yaml +1 -1
- package/config/agents/syndicateSchema.yaml +4 -2
- package/dist/lib/compile.d.ts +64 -0
- package/dist/lib/compile.js +105 -0
- package/dist/lib/models/claudeLlm.js +25 -1
- package/dist/lib/models/gptLlm.js +5 -2
- package/dist/lib/models/ollamaLlm.d.ts +2 -0
- package/dist/lib/models/ollamaLlm.js +4 -0
- package/dist/lib/models/openAiCompatibleLlm.d.ts +2 -0
- package/dist/lib/models/openAiCompatibleLlm.js +14 -5
- package/dist/lib/models/schemaNormalize.d.ts +9 -0
- package/dist/lib/models/schemaNormalize.js +34 -0
- package/dist/lib/observability/embeddings.d.ts +21 -0
- package/dist/lib/observability/embeddings.js +47 -0
- package/dist/lib/observability/lineage.d.ts +35 -0
- package/dist/lib/observability/lineage.js +96 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +186 -18
- package/dist/lib/observability/supabaseSpanExporter.js +377 -80
- package/dist/lib/observability/tracer.d.ts +74 -0
- package/dist/lib/observability/tracer.js +282 -1
- package/dist/scripts/a2a_server.js +57 -72
- package/package.json +8 -4
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
import { BaseLlm } from '@google/adk';
|
|
31
31
|
import { traceLlmGeneration, setLlmSpanAttribute, } from "../observability/tracer.js";
|
|
32
32
|
import { wantsWebSearch, isWebSearchSentinel, } from "../tools/webSearchTool.js";
|
|
33
|
-
import { toLowercaseJsonSchema } from "./schemaNormalize.js";
|
|
33
|
+
import { toLowercaseJsonSchema, toStrictJsonSchema } from "./schemaNormalize.js";
|
|
34
34
|
/** Splits "<think>…</think>" scratchpad from the reply. */
|
|
35
35
|
export function splitThinkBlocks(text) {
|
|
36
36
|
const blocks = [];
|
|
@@ -50,6 +50,10 @@ export class OpenAiCompatibleLlm extends BaseLlm {
|
|
|
50
50
|
return this.model;
|
|
51
51
|
}
|
|
52
52
|
/** Provider-specific request body fields (merged last). */
|
|
53
|
+
/** Whether the endpoint accepts response_format json_schema (strict). */
|
|
54
|
+
supportsJsonSchemaFormat() {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
53
57
|
extraBodyFields(_llmRequest) {
|
|
54
58
|
return {};
|
|
55
59
|
}
|
|
@@ -115,10 +119,15 @@ export class OpenAiCompatibleLlm extends BaseLlm {
|
|
|
115
119
|
? { max_tokens: cfg.maxOutputTokens }
|
|
116
120
|
: {}),
|
|
117
121
|
...(openAiTools.length > 0 ? { tools: openAiTools } : {}),
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
// Structured output: an ADK outputSchema becomes a strict json_schema
|
|
123
|
+
// response_format where the endpoint supports it (xAI does; Ollama's
|
|
124
|
+
// OpenAI-compatible endpoint does not, see supportsJsonSchemaFormat),
|
|
125
|
+
// else plain JSON mode.
|
|
126
|
+
...(cfg.responseSchema && this.supportsJsonSchemaFormat()
|
|
127
|
+
? { response_format: { type: 'json_schema', json_schema: { name: 'response', strict: true, schema: toStrictJsonSchema(cfg.responseSchema) } } }
|
|
128
|
+
: cfg.responseMimeType === 'application/json' || cfg.responseSchema
|
|
129
|
+
? { response_format: { type: 'json_object' } }
|
|
130
|
+
: {}),
|
|
122
131
|
...this.extraBodyFields(llmRequest),
|
|
123
132
|
};
|
|
124
133
|
// web_search: enable the provider's native search, or omit with a warning.
|
|
@@ -20,3 +20,12 @@
|
|
|
20
20
|
* Never mutates the input — a Gemini agent may hold the same tool object.
|
|
21
21
|
*/
|
|
22
22
|
export declare function toLowercaseJsonSchema(schema: unknown): Record<string, unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* The lowercase schema in the shape OpenAI-style "strict" structured output
|
|
25
|
+
* demands: every object node carries `additionalProperties: false` and lists
|
|
26
|
+
* ALL of its properties as required. Optional fields are expressed by the
|
|
27
|
+
* model emitting a null/empty value, not by omission — that is the strict
|
|
28
|
+
* contract, and it is what makes a judge's rubric fields arrive under the
|
|
29
|
+
* names the harness expects rather than improvised ones.
|
|
30
|
+
*/
|
|
31
|
+
export declare function toStrictJsonSchema(schema: unknown): Record<string, unknown>;
|
|
@@ -56,3 +56,37 @@ function normalizeNode(node) {
|
|
|
56
56
|
}
|
|
57
57
|
return out;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* The lowercase schema in the shape OpenAI-style "strict" structured output
|
|
61
|
+
* demands: every object node carries `additionalProperties: false` and lists
|
|
62
|
+
* ALL of its properties as required. Optional fields are expressed by the
|
|
63
|
+
* model emitting a null/empty value, not by omission — that is the strict
|
|
64
|
+
* contract, and it is what makes a judge's rubric fields arrive under the
|
|
65
|
+
* names the harness expects rather than improvised ones.
|
|
66
|
+
*/
|
|
67
|
+
export function toStrictJsonSchema(schema) {
|
|
68
|
+
return strictNode(toLowercaseJsonSchema(schema));
|
|
69
|
+
}
|
|
70
|
+
function strictNode(node) {
|
|
71
|
+
if (Array.isArray(node))
|
|
72
|
+
return node.map(strictNode);
|
|
73
|
+
if (!node || typeof node !== 'object')
|
|
74
|
+
return node;
|
|
75
|
+
const out = { ...node };
|
|
76
|
+
const type = out.type;
|
|
77
|
+
const isObject = type === 'object' || (Array.isArray(type) && type.includes('object')) || isPlainObject(out.properties);
|
|
78
|
+
if (isObject && isPlainObject(out.properties)) {
|
|
79
|
+
const props = {};
|
|
80
|
+
for (const [k, v] of Object.entries(out.properties))
|
|
81
|
+
props[k] = strictNode(v);
|
|
82
|
+
out.properties = props;
|
|
83
|
+
out.required = Object.keys(props);
|
|
84
|
+
out.additionalProperties = false;
|
|
85
|
+
}
|
|
86
|
+
if (out.items !== undefined)
|
|
87
|
+
out.items = strictNode(out.items);
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function isPlainObject(v) {
|
|
91
|
+
return !!v && typeof v === 'object' && !Array.isArray(v);
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/observability/embeddings.ts — embeddings for ledger search.
|
|
3
|
+
*
|
|
4
|
+
* The same model and dimensionality as long-term memory (lib/config.ts), so
|
|
5
|
+
* a query embedded here is comparable to a turn embedded by
|
|
6
|
+
* `npm run telemetry:embed`, and the `match_turns` RPC (db/telemetry.sql)
|
|
7
|
+
* compares apples to apples. Kept out of the exporter on purpose: an API
|
|
8
|
+
* call per turn would put inference on the export path; a job embeds in
|
|
9
|
+
* batches after the fact.
|
|
10
|
+
*/
|
|
11
|
+
import { EMBEDDING_DIMENSIONS, EMBEDDING_MODEL } from '../config.ts';
|
|
12
|
+
export interface EmbedOptions {
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/** Characters kept per text (long answers are truncated, not skipped). */
|
|
15
|
+
maxChars?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Embeds each text; a failed text yields an empty vector rather than a throw. */
|
|
18
|
+
export declare function embedTexts(texts: string[], options?: EmbedOptions): Promise<number[][]>;
|
|
19
|
+
/** The text a turn is embedded by: the question and the answer, in that order. */
|
|
20
|
+
export declare function turnEmbeddingText(input: string | null | undefined, output: string | null | undefined): string;
|
|
21
|
+
export { EMBEDDING_DIMENSIONS, EMBEDDING_MODEL };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/observability/embeddings.ts — embeddings for ledger search.
|
|
3
|
+
*
|
|
4
|
+
* The same model and dimensionality as long-term memory (lib/config.ts), so
|
|
5
|
+
* a query embedded here is comparable to a turn embedded by
|
|
6
|
+
* `npm run telemetry:embed`, and the `match_turns` RPC (db/telemetry.sql)
|
|
7
|
+
* compares apples to apples. Kept out of the exporter on purpose: an API
|
|
8
|
+
* call per turn would put inference on the export path; a job embeds in
|
|
9
|
+
* batches after the fact.
|
|
10
|
+
*/
|
|
11
|
+
import { GoogleGenAI } from '@google/genai';
|
|
12
|
+
import { EMBEDDING_DIMENSIONS, EMBEDDING_MODEL } from "../config.js";
|
|
13
|
+
const MAX_CHARS = 8_000;
|
|
14
|
+
/** Embeds each text; a failed text yields an empty vector rather than a throw. */
|
|
15
|
+
export async function embedTexts(texts, options = {}) {
|
|
16
|
+
const apiKey = options.apiKey ?? process.env.GOOGLE_GENAI_API_KEY ?? process.env.GEMINI_API_KEY;
|
|
17
|
+
if (!apiKey)
|
|
18
|
+
throw new Error('GOOGLE_GENAI_API_KEY is required for embeddings');
|
|
19
|
+
const genai = new GoogleGenAI({ apiKey });
|
|
20
|
+
const limit = options.maxChars ?? MAX_CHARS;
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const text of texts) {
|
|
23
|
+
const clipped = (text ?? '').slice(0, limit);
|
|
24
|
+
if (!clipped.trim()) {
|
|
25
|
+
out.push([]);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const response = await genai.models.embedContent({
|
|
30
|
+
model: EMBEDDING_MODEL,
|
|
31
|
+
contents: clipped,
|
|
32
|
+
config: { outputDimensionality: EMBEDDING_DIMENSIONS },
|
|
33
|
+
});
|
|
34
|
+
out.push(response.embeddings?.[0]?.values ?? []);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.warn(`[embeddings] failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
38
|
+
out.push([]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/** The text a turn is embedded by: the question and the answer, in that order. */
|
|
44
|
+
export function turnEmbeddingText(input, output) {
|
|
45
|
+
return `${(input ?? '').trim()}\n\n${(output ?? '').trim()}`.trim();
|
|
46
|
+
}
|
|
47
|
+
export { EMBEDDING_DIMENSIONS, EMBEDDING_MODEL };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/observability/lineage.ts — provenance stamps for the ledger.
|
|
3
|
+
*
|
|
4
|
+
* A stored output is evidence only if you know exactly what produced it.
|
|
5
|
+
* Every turn row carries two stamps computed here:
|
|
6
|
+
*
|
|
7
|
+
* config_hash SHA-256 (first 16 hex) of the RESOLVED syndicate config
|
|
8
|
+
* with every nested `yaml_reference` inlined — the exact
|
|
9
|
+
* prompts, models, tools and generation settings that ran.
|
|
10
|
+
* Two deployments serving "the same" syndicate with a
|
|
11
|
+
* drifted registry row or a stale file produce different
|
|
12
|
+
* hashes, which is the first time that drift is visible in
|
|
13
|
+
* stored data. Eval runs hash the overridden config, so a
|
|
14
|
+
* variant's rows are distinguishable from production's.
|
|
15
|
+
* engine_version package version plus the git commit (or the platform's
|
|
16
|
+
* SOURCE_VERSION / HEROKU_SLUG_COMMIT), so a behaviour
|
|
17
|
+
* change can be pinned to a deploy.
|
|
18
|
+
*
|
|
19
|
+
* Both are deterministic and cheap; neither touches the network.
|
|
20
|
+
*/
|
|
21
|
+
import type { SyndicateYamlConfig } from '../loadSyndicate.ts';
|
|
22
|
+
/** Schema version stamped on every ledger row (adk_turns / adk_payloads). */
|
|
23
|
+
export declare const TELEMETRY_SCHEMA_VERSION = 2;
|
|
24
|
+
/** JSON with object keys sorted at every depth — stable across reloads. */
|
|
25
|
+
export declare function stableStringify(value: unknown): string;
|
|
26
|
+
/** The config with every `yaml_reference` replaced by the loaded syndicate. */
|
|
27
|
+
export declare function inlineReferences(config: SyndicateYamlConfig, loadNested: (ref: string) => SyndicateYamlConfig, seen?: Set<string>): Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Hash of the fully resolved config. `loadNested` is the same loader the
|
|
30
|
+
* caller compiles with (the server's plain loadSyndicate; the observatory's
|
|
31
|
+
* override-applying loader), so the hash describes what actually ran.
|
|
32
|
+
*/
|
|
33
|
+
export declare function configDigest(config: SyndicateYamlConfig, loadNested: (ref: string) => SyndicateYamlConfig): string;
|
|
34
|
+
/** "<package version>+<commit>" — commit from the platform env or git, else "local". */
|
|
35
|
+
export declare function engineVersion(): string;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/observability/lineage.ts — provenance stamps for the ledger.
|
|
3
|
+
*
|
|
4
|
+
* A stored output is evidence only if you know exactly what produced it.
|
|
5
|
+
* Every turn row carries two stamps computed here:
|
|
6
|
+
*
|
|
7
|
+
* config_hash SHA-256 (first 16 hex) of the RESOLVED syndicate config
|
|
8
|
+
* with every nested `yaml_reference` inlined — the exact
|
|
9
|
+
* prompts, models, tools and generation settings that ran.
|
|
10
|
+
* Two deployments serving "the same" syndicate with a
|
|
11
|
+
* drifted registry row or a stale file produce different
|
|
12
|
+
* hashes, which is the first time that drift is visible in
|
|
13
|
+
* stored data. Eval runs hash the overridden config, so a
|
|
14
|
+
* variant's rows are distinguishable from production's.
|
|
15
|
+
* engine_version package version plus the git commit (or the platform's
|
|
16
|
+
* SOURCE_VERSION / HEROKU_SLUG_COMMIT), so a behaviour
|
|
17
|
+
* change can be pinned to a deploy.
|
|
18
|
+
*
|
|
19
|
+
* Both are deterministic and cheap; neither touches the network.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
import { execSync } from 'node:child_process';
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
24
|
+
import { join } from 'node:path';
|
|
25
|
+
/** Schema version stamped on every ledger row (adk_turns / adk_payloads). */
|
|
26
|
+
export const TELEMETRY_SCHEMA_VERSION = 2;
|
|
27
|
+
/** JSON with object keys sorted at every depth — stable across reloads. */
|
|
28
|
+
export function stableStringify(value) {
|
|
29
|
+
if (value === null || typeof value !== 'object')
|
|
30
|
+
return JSON.stringify(value);
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
33
|
+
const obj = value;
|
|
34
|
+
const keys = Object.keys(obj)
|
|
35
|
+
.filter((k) => obj[k] !== undefined)
|
|
36
|
+
.sort();
|
|
37
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(',')}}`;
|
|
38
|
+
}
|
|
39
|
+
/** The config with every `yaml_reference` replaced by the loaded syndicate. */
|
|
40
|
+
export function inlineReferences(config, loadNested, seen = new Set()) {
|
|
41
|
+
return {
|
|
42
|
+
...config,
|
|
43
|
+
subagents: (config.subagents ?? []).map((sub) => {
|
|
44
|
+
if (!sub.yaml_reference || seen.has(sub.yaml_reference))
|
|
45
|
+
return sub;
|
|
46
|
+
const next = new Set(seen);
|
|
47
|
+
next.add(sub.yaml_reference);
|
|
48
|
+
let nested;
|
|
49
|
+
try {
|
|
50
|
+
nested = inlineReferences(loadNested(sub.yaml_reference), loadNested, next);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
nested = { unresolved: sub.yaml_reference, error: err instanceof Error ? err.message : String(err) };
|
|
54
|
+
}
|
|
55
|
+
return { ...sub, resolved_reference: nested };
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Hash of the fully resolved config. `loadNested` is the same loader the
|
|
61
|
+
* caller compiles with (the server's plain loadSyndicate; the observatory's
|
|
62
|
+
* override-applying loader), so the hash describes what actually ran.
|
|
63
|
+
*/
|
|
64
|
+
export function configDigest(config, loadNested) {
|
|
65
|
+
const resolved = inlineReferences(config, loadNested);
|
|
66
|
+
return createHash('sha256').update(stableStringify(resolved)).digest('hex').slice(0, 16);
|
|
67
|
+
}
|
|
68
|
+
let cachedEngineVersion;
|
|
69
|
+
/** "<package version>+<commit>" — commit from the platform env or git, else "local". */
|
|
70
|
+
export function engineVersion() {
|
|
71
|
+
if (cachedEngineVersion)
|
|
72
|
+
return cachedEngineVersion;
|
|
73
|
+
let pkgVersion = '0.0.0';
|
|
74
|
+
try {
|
|
75
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
76
|
+
if (existsSync(pkgPath)) {
|
|
77
|
+
pkgVersion = String(JSON.parse(readFileSync(pkgPath, 'utf-8')).version ?? pkgVersion);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* no package.json in cwd — version stays 0.0.0 */
|
|
82
|
+
}
|
|
83
|
+
let commit = process.env.SOURCE_VERSION ?? process.env.HEROKU_SLUG_COMMIT ?? process.env.GIT_COMMIT ?? '';
|
|
84
|
+
if (!commit) {
|
|
85
|
+
try {
|
|
86
|
+
commit = execSync('git rev-parse --short HEAD', { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
87
|
+
.toString()
|
|
88
|
+
.trim();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
commit = 'local';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
cachedEngineVersion = `${pkgVersion}+${commit.slice(0, 12)}`;
|
|
95
|
+
return cachedEngineVersion;
|
|
96
|
+
}
|
|
@@ -1,35 +1,203 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/observability/supabaseSpanExporter.ts —
|
|
3
|
-
* that persists model-request telemetry to Supabase.
|
|
2
|
+
* lib/observability/supabaseSpanExporter.ts — the observability ledger sink.
|
|
4
3
|
*
|
|
5
4
|
* WHY this file exists:
|
|
6
5
|
* The console exporter in tracer.ts prints every span as an
|
|
7
|
-
* [OTEL_SPAN_JSON] line — good for a terminal, gone when it scrolls.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* [OTEL_SPAN_JSON] line — good for a terminal, gone when it scrolls. With
|
|
7
|
+
* TELEMETRY_SUPABASE=true (and Supabase credentials) every turn and every
|
|
8
|
+
* model call is also written to Supabase, in three tiers with three
|
|
9
|
+
* lifetimes (schema: db/telemetry.sql, lockdown: db/hardening.sql):
|
|
10
|
+
*
|
|
11
|
+
* adk_turns one row per turn — the system of record: input, output,
|
|
12
|
+
* route, agent, errors, tokens, model-vs-tool time, the
|
|
13
|
+
* tool calls WITH their responses, the ids that join it to
|
|
14
|
+
* the session (session_id, invocation_id) and the task,
|
|
15
|
+
* and provenance (config_hash, engine_version).
|
|
16
|
+
* adk_telemetry one row per span — llm.request and root spans, raw.
|
|
17
|
+
* adk_payloads full request/response per model call, captured by
|
|
18
|
+
* POLICY and expiring. ADK's own call_llm spans carry the
|
|
19
|
+
* assembled prompt and raw response; they are 10-100x a
|
|
20
|
+
* turn row, so: errors and fallbacks always, a
|
|
21
|
+
* deterministic sample of the rest, 30-day TTL.
|
|
12
22
|
*
|
|
13
23
|
* DESIGN NOTES:
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
24
|
+
* - Never on the hot path: inserts are fire-and-forget behind a
|
|
25
|
+
* BatchSpanProcessor; a failure is logged once per table and the rows
|
|
26
|
+
* are spooled to a dead-letter file (OUTPUTS_DIR/telemetry-deadletter.ndjson)
|
|
27
|
+
* so a sink outage loses nothing silently. `npm run telemetry:stats`
|
|
28
|
+
* shows what landed; `telemetry:replay` re-sends the spool.
|
|
29
|
+
* - Payload decisions need the TURN's outcome (error? fallback?), which is
|
|
30
|
+
* only known when the root span ends — after its call_llm children. So
|
|
31
|
+
* call_llm spans wait in a per-trace buffer until their root arrives,
|
|
32
|
+
* then the decision is applied to all of them at once. Children that
|
|
33
|
+
* arrive after the root (a later batch) find the decision cached.
|
|
34
|
+
* - Sampling is deterministic in the trace id: a trace keeps all of its
|
|
35
|
+
* payloads or none, and a re-run of the same policy keeps the same set.
|
|
18
36
|
*/
|
|
19
37
|
import type { SpanExporter, ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
20
|
-
/**
|
|
38
|
+
/** Row shape for adk_telemetry (raw spans). */
|
|
39
|
+
export interface TelemetryRow {
|
|
40
|
+
ts: string;
|
|
41
|
+
trace_id: string;
|
|
42
|
+
span_id: string;
|
|
43
|
+
span_name: string;
|
|
44
|
+
syndicate: string | null;
|
|
45
|
+
agent: string | null;
|
|
46
|
+
provider: string | null;
|
|
47
|
+
model: string | null;
|
|
48
|
+
input_tokens: number | null;
|
|
49
|
+
output_tokens: number | null;
|
|
50
|
+
thinking_tokens: number | null;
|
|
51
|
+
latency_ms: number;
|
|
52
|
+
session_id: string | null;
|
|
53
|
+
user_id: string | null;
|
|
54
|
+
task_id: string | null;
|
|
55
|
+
invocation_id: string | null;
|
|
56
|
+
schema_version: number;
|
|
57
|
+
span: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
/** Row shape for adk_turns (the system of record). */
|
|
60
|
+
export interface TurnRow {
|
|
61
|
+
ts: string;
|
|
62
|
+
trace_id: string;
|
|
63
|
+
span_id: string;
|
|
64
|
+
session_id: string | null;
|
|
65
|
+
user_id: string | null;
|
|
66
|
+
task_id: string | null;
|
|
67
|
+
invocation_id: string | null;
|
|
68
|
+
syndicate: string;
|
|
69
|
+
stage: string;
|
|
70
|
+
agent: string | null;
|
|
71
|
+
route: string | null;
|
|
72
|
+
route_reason: string | null;
|
|
73
|
+
route_fell_back: boolean | null;
|
|
74
|
+
route_via_override: boolean | null;
|
|
75
|
+
relay_fallback: boolean;
|
|
76
|
+
input: string;
|
|
77
|
+
output: string;
|
|
78
|
+
error_code: string | null;
|
|
79
|
+
error_message: string | null;
|
|
80
|
+
input_tokens: number | null;
|
|
81
|
+
output_tokens: number | null;
|
|
82
|
+
thinking_tokens: number | null;
|
|
83
|
+
latency_ms: number;
|
|
84
|
+
model_ms: number | null;
|
|
85
|
+
tool_ms: number | null;
|
|
86
|
+
llm_calls: number | null;
|
|
87
|
+
tool_calls: number;
|
|
88
|
+
models: string[];
|
|
89
|
+
tool_events: Array<Record<string, unknown>>;
|
|
90
|
+
config_hash: string | null;
|
|
91
|
+
engine_version: string | null;
|
|
92
|
+
eval_run: string | null;
|
|
93
|
+
eval_suite: string | null;
|
|
94
|
+
eval_case: string | null;
|
|
95
|
+
eval_variant: string | null;
|
|
96
|
+
eval_trial: number | null;
|
|
97
|
+
attributes: Record<string, unknown>;
|
|
98
|
+
schema_version: number;
|
|
99
|
+
}
|
|
100
|
+
export type PayloadReason = 'error' | 'fallback' | 'sample' | 'all';
|
|
101
|
+
/** Row shape for adk_payloads. */
|
|
102
|
+
export interface PayloadRow {
|
|
103
|
+
ts: string;
|
|
104
|
+
trace_id: string;
|
|
105
|
+
span_id: string;
|
|
106
|
+
session_id: string | null;
|
|
107
|
+
invocation_id: string | null;
|
|
108
|
+
agent: string | null;
|
|
109
|
+
provider: string | null;
|
|
110
|
+
model: string | null;
|
|
111
|
+
reason: PayloadReason;
|
|
112
|
+
request: unknown;
|
|
113
|
+
response: unknown;
|
|
114
|
+
request_chars: number;
|
|
115
|
+
response_chars: number;
|
|
116
|
+
expires_at: string;
|
|
117
|
+
schema_version: number;
|
|
118
|
+
}
|
|
119
|
+
export type PayloadMode = 'off' | 'errors' | 'sample' | 'all';
|
|
120
|
+
export interface PayloadPolicy {
|
|
121
|
+
mode: PayloadMode;
|
|
122
|
+
/** Fraction of non-error, non-fallback traces kept in 'sample' mode. */
|
|
123
|
+
sampleRate: number;
|
|
124
|
+
ttlDays: number;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* TELEMETRY_PAYLOADS: off | errors | sample (default) | all
|
|
128
|
+
* TELEMETRY_PAYLOAD_SAMPLE: 0..1, default 0.10
|
|
129
|
+
* TELEMETRY_PAYLOAD_TTL_DAYS: default 30
|
|
130
|
+
*/
|
|
131
|
+
export declare function payloadPolicyFromEnv(env?: NodeJS.ProcessEnv): PayloadPolicy;
|
|
132
|
+
/** Deterministic 0..1 value from a trace id (first 8 hex chars). */
|
|
133
|
+
export declare function traceSampleValue(traceId: string): number;
|
|
134
|
+
export interface TurnFacts {
|
|
135
|
+
error: boolean;
|
|
136
|
+
fallback: boolean;
|
|
137
|
+
}
|
|
138
|
+
/** Which payloads of a turn to keep, given the policy and the turn's outcome. */
|
|
139
|
+
export declare function payloadDecision(policy: PayloadPolicy, traceId: string, facts: TurnFacts): PayloadReason | null;
|
|
140
|
+
export declare function isRootSpan(span: ReadableSpan): boolean;
|
|
141
|
+
export declare function isPayloadSpan(span: ReadableSpan): boolean;
|
|
142
|
+
/** `syndicate.input` is JSON.stringify of either a string or A2A parts. */
|
|
143
|
+
export declare function parseInputText(raw: unknown): string;
|
|
144
|
+
/** Flatten an llm.request or root span into one adk_telemetry row. */
|
|
145
|
+
export declare function toTelemetryRow(span: ReadableSpan): TelemetryRow;
|
|
146
|
+
/** Project a root span into the adk_turns system-of-record row. */
|
|
147
|
+
export declare function toTurnRow(span: ReadableSpan): TurnRow;
|
|
148
|
+
/** The facts a payload decision needs, read off a root span. */
|
|
149
|
+
export declare function turnFacts(span: ReadableSpan): TurnFacts;
|
|
150
|
+
/** Map an ADK call_llm span into an adk_payloads row. */
|
|
151
|
+
export declare function toPayloadRow(span: ReadableSpan, reason: PayloadReason, ttlDays: number, turn: {
|
|
152
|
+
sessionId: string | null;
|
|
153
|
+
invocationId: string | null;
|
|
154
|
+
} | undefined): PayloadRow;
|
|
155
|
+
/** True when the operator has opted into the Supabase ledger. */
|
|
21
156
|
export declare function telemetrySinkEnabled(): boolean;
|
|
157
|
+
export declare function deadLetterPath(): string;
|
|
158
|
+
export interface ExportStats {
|
|
159
|
+
inserted: Record<string, number>;
|
|
160
|
+
failed: Record<string, number>;
|
|
161
|
+
deadLettered: number;
|
|
162
|
+
payloadsDropped: number;
|
|
163
|
+
}
|
|
164
|
+
interface SupabaseLikeClient {
|
|
165
|
+
from(table: string): {
|
|
166
|
+
insert(rows: unknown[]): PromiseLike<{
|
|
167
|
+
error: {
|
|
168
|
+
message: string;
|
|
169
|
+
} | null;
|
|
170
|
+
}>;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export interface ExporterOptions {
|
|
174
|
+
/** Injected client (tests); default: lazily created supabase-js client. */
|
|
175
|
+
client?: SupabaseLikeClient;
|
|
176
|
+
policy?: PayloadPolicy;
|
|
177
|
+
/** Where failed batches are spooled; '' disables the spool. */
|
|
178
|
+
deadLetterFile?: string;
|
|
179
|
+
}
|
|
22
180
|
export declare class SupabaseSpanExporter implements SpanExporter {
|
|
23
|
-
/** Lazily created on first export so constructing the exporter is free
|
|
24
|
-
* and synchronous (NodeTracerProvider wants processors at construction). */
|
|
25
181
|
private clientPromise;
|
|
26
|
-
private
|
|
27
|
-
private
|
|
182
|
+
private readonly policy;
|
|
183
|
+
private readonly deadLetterFile;
|
|
184
|
+
private readonly failureLogged;
|
|
185
|
+
private readonly pendingInserts;
|
|
186
|
+
/** call_llm spans waiting for their root span's outcome, by trace id. */
|
|
187
|
+
private readonly pendingPayloads;
|
|
188
|
+
/** Decisions already taken for a trace, for children arriving late. */
|
|
189
|
+
private readonly decisions;
|
|
190
|
+
private pendingCount;
|
|
191
|
+
readonly stats: ExportStats;
|
|
192
|
+
constructor(options?: ExporterOptions);
|
|
28
193
|
private getClient;
|
|
29
194
|
export(spans: ReadableSpan[], resultCallback: (result: any) => void): void;
|
|
195
|
+
private pruneBuffers;
|
|
196
|
+
/** Fire-and-forget insert; failures are logged once per table and spooled. */
|
|
197
|
+
private insert;
|
|
198
|
+
private onFailure;
|
|
30
199
|
/** Waits for in-flight inserts so short-lived scripts don't drop rows. */
|
|
31
200
|
shutdown(): Promise<void>;
|
|
32
201
|
forceFlush(): Promise<void>;
|
|
33
|
-
/** Flatten a span into one adk_telemetry row. */
|
|
34
|
-
private toRow;
|
|
35
202
|
}
|
|
203
|
+
export {};
|