melchizedek-agents 1.7.0 → 1.10.0
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/CHANGELOG.md +161 -1
- package/DOCUMENTATION.md +77 -10
- package/README.md +2 -0
- package/config/agents/syndicate.schema.json +69 -0
- package/config/agents/syndicateSchema.yaml +17 -0
- package/db/hardening.sql +3 -1
- package/db/migrations/0016_span_tree.sql +52 -0
- package/db/migrations/0017_cache_pricing.sql +146 -0
- package/db/migrations/0018_queue_view.sql +82 -0
- package/db/telemetry.sql +36 -8
- package/dist/lib/a2a/app.d.ts +26 -4
- package/dist/lib/a2a/app.js +41 -6
- package/dist/lib/a2a/executor.d.ts +28 -1
- package/dist/lib/a2a/executor.js +65 -2
- package/dist/lib/a2a/feedback.d.ts +121 -0
- package/dist/lib/a2a/feedback.js +219 -0
- package/dist/lib/a2a/policy.d.ts +55 -5
- package/dist/lib/a2a/policy.js +56 -12
- package/dist/lib/compile.d.ts +2 -0
- package/dist/lib/compile.js +2 -0
- package/dist/lib/compileNative.js +2 -0
- package/dist/lib/doctor.d.ts +10 -0
- package/dist/lib/doctor.js +33 -4
- package/dist/lib/index.d.ts +6 -2
- package/dist/lib/index.js +3 -0
- package/dist/lib/loadSyndicate.d.ts +14 -0
- package/dist/lib/loadSyndicate.js +22 -5
- package/dist/lib/memory/providers.js +7 -3
- package/dist/lib/observability/lineage.d.ts +19 -0
- package/dist/lib/observability/lineage.js +31 -0
- package/dist/lib/observability/metadata.d.ts +69 -0
- package/dist/lib/observability/metadata.js +153 -0
- package/dist/lib/observability/otlpFilter.js +1 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +31 -2
- package/dist/lib/observability/supabaseSpanExporter.js +45 -6
- package/dist/lib/observability/tracer.d.ts +17 -1
- package/dist/lib/observability/tracer.js +61 -3
- package/dist/lib/project.d.ts +116 -0
- package/dist/lib/project.js +274 -0
- package/dist/lib/runtime/native/compaction.js +7 -3
- package/dist/lib/runtime/native/request.d.ts +2 -0
- package/dist/lib/runtime/native/step.js +1 -1
- package/dist/lib/runtime/native/telemetry.d.ts +10 -3
- package/dist/lib/runtime/native/telemetry.js +49 -4
- package/dist/lib/runtime/syndicateTurn.js +5 -1
- package/dist/lib/storage/postgres/index.d.ts +10 -0
- package/dist/lib/storage/postgres/index.js +6 -1
- package/dist/lib/syndicateSchema.d.ts +5 -0
- package/dist/lib/syndicateSchema.js +13 -0
- package/dist/lib/tools/taskTools.d.ts +6 -0
- package/dist/lib/tools/taskTools.js +24 -5
- package/dist/lib/workflow/toolNode.d.ts +1 -0
- package/dist/lib/workflow/toolNode.js +1 -1
- package/dist/scripts/a2a_server.js +3 -3
- package/dist/scripts/doctor.js +2 -1
- package/dist/scripts/mcp_server.js +2 -1
- package/package.json +6 -4
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
* tool calls WITH their responses, the ids that join it to
|
|
14
14
|
* the session (session_id, invocation_id) and the task,
|
|
15
15
|
* and provenance (config_hash, engine_version).
|
|
16
|
-
* adk_telemetry one row per span —
|
|
16
|
+
* adk_telemetry one row per span the ledger keeps — the turn's root, each
|
|
17
|
+
* agent run, model step and provider call, tool call,
|
|
18
|
+
* script run and call, workflow walk and node run — each
|
|
19
|
+
* with its parent among them and its kind, so a turn's
|
|
20
|
+
* span tree is stored (ADR 0139).
|
|
17
21
|
* adk_payloads full request/response per model call, captured by
|
|
18
22
|
* POLICY and expiring. ADK's own call_llm spans carry the
|
|
19
23
|
* assembled prompt and raw response, and the native
|
|
@@ -41,7 +45,7 @@ import { join } from 'node:path';
|
|
|
41
45
|
import core from '@opentelemetry/core';
|
|
42
46
|
const { ExportResultCode } = core;
|
|
43
47
|
import { hasSupabaseCredentials } from '../persistence/supabaseProvider.js';
|
|
44
|
-
import { TELEMETRY_SCHEMA_VERSION, isModelCallSpan } from './lineage.js';
|
|
48
|
+
import { LEDGER_PARENT_ATTRIBUTE, TELEMETRY_SCHEMA_VERSION, isModelCallSpan, ledgerSpanKind } from './lineage.js';
|
|
45
49
|
import { redactRow, telemetryRedactor } from './redact.js';
|
|
46
50
|
import { outputsDir } from '../paths.js';
|
|
47
51
|
/**
|
|
@@ -81,8 +85,17 @@ export function payloadDecision(policy, traceId, facts) {
|
|
|
81
85
|
}
|
|
82
86
|
// ── Span → row mapping (pure; tested offline) ───────────────────────────────
|
|
83
87
|
const ROOT_PREFIX = 'Syndicate Execution: ';
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The spans that get an adk_telemetry row: every span ledgerSpanKind names
|
|
90
|
+
* (ADR 0139). A model step's row (`model.call`) leaves its payload to
|
|
91
|
+
* adk_payloads (spanJson drops `llm.payload.*`) and its tokens to its
|
|
92
|
+
* llm.request rows, so a trace's rows sum without counting a call twice. A
|
|
93
|
+
* script's own spans (ADR 0137 item 16) carry its run, its calls' kinds,
|
|
94
|
+
* names, sequence and status, never an argument or a result.
|
|
95
|
+
*/
|
|
96
|
+
export function isLedgerSpan(span) {
|
|
97
|
+
return ledgerSpanKind(span.name) !== null;
|
|
98
|
+
}
|
|
86
99
|
function attrsOf(span) {
|
|
87
100
|
return span.attributes;
|
|
88
101
|
}
|
|
@@ -169,6 +182,25 @@ function spanJson(span) {
|
|
|
169
182
|
status: span.status,
|
|
170
183
|
};
|
|
171
184
|
}
|
|
185
|
+
/** The span's parent in the ledger: the ancestor the tracer recorded at start, else its direct parent. */
|
|
186
|
+
export function ledgerParentOf(span) {
|
|
187
|
+
const recorded = attrsOf(span)[LEDGER_PARENT_ATTRIBUTE];
|
|
188
|
+
if (typeof recorded === 'string' && recorded)
|
|
189
|
+
return recorded;
|
|
190
|
+
const s = span;
|
|
191
|
+
const parent = s.parentSpanContext?.spanId ?? s.parentSpanId;
|
|
192
|
+
return typeof parent === 'string' && parent ? parent : null;
|
|
193
|
+
}
|
|
194
|
+
/** Whether a span failed: its status, an error code it records, or a tool's error response. */
|
|
195
|
+
export function spanFailed(span) {
|
|
196
|
+
const attrs = attrsOf(span);
|
|
197
|
+
return (span.status?.code === 2 || // SpanStatusCode.ERROR
|
|
198
|
+
!!str(attrs, 'llm.error_code') ||
|
|
199
|
+
!!str(attrs, 'syndicate.error.code') ||
|
|
200
|
+
attrs['tool.error'] === true ||
|
|
201
|
+
attrs['script.call.status'] === 'error' ||
|
|
202
|
+
attrs['adk.node.status'] === 'failed');
|
|
203
|
+
}
|
|
172
204
|
/** Span attributes minus the payload capture (`llm.payload.*`). */
|
|
173
205
|
export function withoutPayloadAttributes(attrs) {
|
|
174
206
|
const out = {};
|
|
@@ -177,7 +209,7 @@ export function withoutPayloadAttributes(attrs) {
|
|
|
177
209
|
out[k] = v;
|
|
178
210
|
return out;
|
|
179
211
|
}
|
|
180
|
-
/** Flatten
|
|
212
|
+
/** Flatten a span the ledger keeps (isLedgerSpan) into one adk_telemetry row. */
|
|
181
213
|
export function toTelemetryRow(span) {
|
|
182
214
|
const attrs = attrsOf(span);
|
|
183
215
|
return {
|
|
@@ -192,11 +224,16 @@ export function toTelemetryRow(span) {
|
|
|
192
224
|
input_tokens: num(attrs, 'llm.tokens.input') ?? num(attrs, 'syndicate.tokens.input'),
|
|
193
225
|
output_tokens: num(attrs, 'llm.tokens.output') ?? num(attrs, 'syndicate.tokens.output'),
|
|
194
226
|
thinking_tokens: num(attrs, 'llm.tokens.thinking') ?? num(attrs, 'syndicate.tokens.thinking'),
|
|
227
|
+
cache_read_tokens: num(attrs, 'llm.tokens.cache_read') ?? num(attrs, 'syndicate.tokens.cache_read'),
|
|
228
|
+
cache_write_tokens: num(attrs, 'llm.tokens.cache_write') ?? num(attrs, 'syndicate.tokens.cache_write'),
|
|
195
229
|
latency_ms: durationMs(span),
|
|
196
230
|
session_id: str(attrs, 'session.id'),
|
|
197
231
|
user_id: str(attrs, 'user.id'),
|
|
198
232
|
task_id: str(attrs, 'a2a.task_id'),
|
|
199
233
|
invocation_id: str(attrs, 'adk.invocation_id'),
|
|
234
|
+
parent_span_id: ledgerParentOf(span),
|
|
235
|
+
kind: ledgerSpanKind(span.name),
|
|
236
|
+
error: spanFailed(span),
|
|
200
237
|
schema_version: TELEMETRY_SCHEMA_VERSION,
|
|
201
238
|
span: spanJson(span),
|
|
202
239
|
};
|
|
@@ -241,6 +278,8 @@ export function toTurnRow(span) {
|
|
|
241
278
|
input_tokens: num(attrs, 'syndicate.tokens.input'),
|
|
242
279
|
output_tokens: num(attrs, 'syndicate.tokens.output'),
|
|
243
280
|
thinking_tokens: num(attrs, 'syndicate.tokens.thinking'),
|
|
281
|
+
cache_read_tokens: num(attrs, 'syndicate.tokens.cache_read'),
|
|
282
|
+
cache_write_tokens: num(attrs, 'syndicate.tokens.cache_write'),
|
|
244
283
|
latency_ms: durationMs(span),
|
|
245
284
|
model_ms: num(attrs, 'syndicate.latency.model_ms'),
|
|
246
285
|
tool_ms: num(attrs, 'syndicate.latency.tool_ms'),
|
|
@@ -371,7 +410,7 @@ export class SupabaseSpanExporter {
|
|
|
371
410
|
const turnRows = [];
|
|
372
411
|
const payloadRows = [];
|
|
373
412
|
for (const span of spans) {
|
|
374
|
-
if (
|
|
413
|
+
if (isLedgerSpan(span))
|
|
375
414
|
telemetryRows.push(toTelemetryRow(span));
|
|
376
415
|
if (isRootSpan(span)) {
|
|
377
416
|
const turn = toTurnRow(span);
|
|
@@ -2,7 +2,7 @@ import type { Context, Span, SpanContext, SpanOptions } from '@opentelemetry/api
|
|
|
2
2
|
import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
|
|
3
3
|
import type { LlmResponse } from '../models/genaiMapping.ts';
|
|
4
4
|
import type { TurnEvent as Event } from '../runtime/events.ts';
|
|
5
|
-
import type { ModelRequest } from '../models/contract.ts';
|
|
5
|
+
import type { ModelRequest, ModelResponse } from '../models/contract.ts';
|
|
6
6
|
import type { ExporterOptions, LedgerClient } from './supabaseSpanExporter.ts';
|
|
7
7
|
/**
|
|
8
8
|
* Optional OTLP export for a live trace viewer (Phoenix, Langfuse, Grafana
|
|
@@ -45,6 +45,9 @@ interface TraceStats {
|
|
|
45
45
|
toolMs: number;
|
|
46
46
|
llmCalls: number;
|
|
47
47
|
models: Set<string>;
|
|
48
|
+
/** Cache tokens summed over every call (`syndicate.tokens.cache_read` / `.cache_write`). */
|
|
49
|
+
cacheRead: number;
|
|
50
|
+
cacheWrite: number;
|
|
48
51
|
/** Each model's calls and tokens, input summed per call (what a provider bills), for `syndicate.usage` (ADR 0136). */
|
|
49
52
|
usage: Map<string, ModelUsage>;
|
|
50
53
|
startedAt: number;
|
|
@@ -52,9 +55,13 @@ interface TraceStats {
|
|
|
52
55
|
/** One model's share of a turn: what the ledger prices it from (model_pricing, ADR 0136). */
|
|
53
56
|
export interface ModelUsage {
|
|
54
57
|
calls: number;
|
|
58
|
+
/** Every input token, the cache's included (the contract's meaning, lib/models/contract.ts). */
|
|
55
59
|
input: number;
|
|
56
60
|
output: number;
|
|
57
61
|
thinking: number;
|
|
62
|
+
/** The parts of `input` read from and written to the provider's prompt cache, priced at their own rates (ADR 0139). */
|
|
63
|
+
cache_read: number;
|
|
64
|
+
cache_write: number;
|
|
58
65
|
}
|
|
59
66
|
/** Read-and-forget the stats for a finished trace. */
|
|
60
67
|
export declare function takeTraceStats(traceId: string): TraceStats | undefined;
|
|
@@ -171,6 +178,15 @@ export interface LlmCallMeta {
|
|
|
171
178
|
* for an adapter written before the model contract.
|
|
172
179
|
*/
|
|
173
180
|
llmRequest?: unknown;
|
|
181
|
+
/**
|
|
182
|
+
* The contract response a yielded LlmResponse was mapped from, when the
|
|
183
|
+
* caller holds it (the native step, the summarizer, the memory extractor).
|
|
184
|
+
* The span reads what an LlmResponse cannot carry from it: the cache
|
|
185
|
+
* write count and the contract's finish reason. Without it the span
|
|
186
|
+
* records the cache reads Gemini's usage names and no finish reason
|
|
187
|
+
* unless the call failed.
|
|
188
|
+
*/
|
|
189
|
+
source?: (response: LlmResponse) => ModelResponse | undefined;
|
|
174
190
|
}
|
|
175
191
|
/**
|
|
176
192
|
* Wraps one adapter `generateContentAsync` invocation in an `llm.request`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { trace, context, defaultTextMapGetter, ROOT_CONTEXT, INVALID_SPAN_CONTEXT } from '@opentelemetry/api';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import { ADK_SPAN_SCOPE, RUNTIME_SPAN_SCOPE, TELEMETRY_SCHEMA_VERSION, agentOfSpanName, engineVersion, isModelCallSpan, isToolSpanName } from './lineage.js';
|
|
3
|
+
import { ADK_SPAN_SCOPE, LEDGER_PARENT_ATTRIBUTE, RUNTIME_SPAN_SCOPE, TELEMETRY_SCHEMA_VERSION, agentOfSpanName, engineVersion, isModelCallSpan, isToolSpanName, ledgerSpanKind } from './lineage.js';
|
|
4
4
|
import { FilteringSpanExporter, otlpContentMode } from './otlpFilter.js';
|
|
5
5
|
import { telemetryRedactor } from './redact.js';
|
|
6
6
|
import { chargeLlmCall, chargeTokens, turnUntraced } from '../runtime/turnControl.js';
|
|
@@ -194,7 +194,7 @@ const TRACE_STATS_TTL_MS = 60 * 60 * 1000;
|
|
|
194
194
|
function statsFor(traceId) {
|
|
195
195
|
let s = traceStats.get(traceId);
|
|
196
196
|
if (!s) {
|
|
197
|
-
s = { modelMs: 0, toolMs: 0, llmCalls: 0, models: new Set(), usage: new Map(), startedAt: Date.now() };
|
|
197
|
+
s = { modelMs: 0, toolMs: 0, llmCalls: 0, models: new Set(), cacheRead: 0, cacheWrite: 0, usage: new Map(), startedAt: Date.now() };
|
|
198
198
|
traceStats.set(traceId, s);
|
|
199
199
|
if (traceStats.size > 2_000) {
|
|
200
200
|
const cutoff = Date.now() - TRACE_STATS_TTL_MS;
|
|
@@ -219,6 +219,15 @@ class SpanLineageProcessor {
|
|
|
219
219
|
const ctx = span.spanContext();
|
|
220
220
|
const parent = span.parentSpanContext?.spanId ?? span.parentSpanId ?? undefined;
|
|
221
221
|
spanLineage.set(ctx.spanId, { name: span.name, parent });
|
|
222
|
+
// The ledger's parent is the nearest ancestor it keeps a row for, read
|
|
223
|
+
// now: an ancestor's entry is gone once it ends, and the exporter sees
|
|
224
|
+
// the span only after that (ADR 0139). Recorded only when it differs
|
|
225
|
+
// from the direct parent, which the span carries itself.
|
|
226
|
+
if (ledgerSpanKind(span.name) !== null) {
|
|
227
|
+
const ledgerParent = ledgerAncestor(parent);
|
|
228
|
+
if (ledgerParent && ledgerParent !== parent)
|
|
229
|
+
span.setAttribute(LEDGER_PARENT_ATTRIBUTE, ledgerParent);
|
|
230
|
+
}
|
|
222
231
|
// The model-call span (the loop's model.call, ADK's call_llm before 1.0.0: the one
|
|
223
232
|
// carrying the full request/response payloads) learns its agent the same
|
|
224
233
|
// way llm.request does, so a payload row can be attributed without
|
|
@@ -250,13 +259,21 @@ class SpanLineageProcessor {
|
|
|
250
259
|
const v = span.attributes[key];
|
|
251
260
|
return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : 0;
|
|
252
261
|
};
|
|
253
|
-
const u = s.usage.get(model) ?? { calls: 0, input: 0, output: 0, thinking: 0 };
|
|
262
|
+
const u = s.usage.get(model) ?? { calls: 0, input: 0, output: 0, thinking: 0, cache_read: 0, cache_write: 0 };
|
|
254
263
|
u.calls += 1;
|
|
255
264
|
u.input += tokens('llm.tokens.input');
|
|
256
265
|
u.output += tokens('llm.tokens.output');
|
|
257
266
|
u.thinking += tokens('llm.tokens.thinking');
|
|
267
|
+
u.cache_read += tokens('llm.tokens.cache_read');
|
|
268
|
+
u.cache_write += tokens('llm.tokens.cache_write');
|
|
258
269
|
s.usage.set(model, u);
|
|
259
270
|
}
|
|
271
|
+
const cache = (key) => {
|
|
272
|
+
const v = span.attributes[key];
|
|
273
|
+
return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : 0;
|
|
274
|
+
};
|
|
275
|
+
s.cacheRead += cache('llm.tokens.cache_read');
|
|
276
|
+
s.cacheWrite += cache('llm.tokens.cache_write');
|
|
260
277
|
}
|
|
261
278
|
else if (isToolSpanName(span.name)) {
|
|
262
279
|
statsFor(traceId).toolMs += spanDurationMs(span);
|
|
@@ -265,6 +282,23 @@ class SpanLineageProcessor {
|
|
|
265
282
|
shutdown() { return Promise.resolve(); }
|
|
266
283
|
forceFlush() { return Promise.resolve(); }
|
|
267
284
|
}
|
|
285
|
+
/**
|
|
286
|
+
* The nearest of `spanId` and its ancestors that the ledger keeps a row for
|
|
287
|
+
* (ledgerSpanKind), or `spanId` itself when the chain leaves the lineage
|
|
288
|
+
* (a remote parent, an evicted entry) or holds no such span.
|
|
289
|
+
*/
|
|
290
|
+
function ledgerAncestor(spanId) {
|
|
291
|
+
let cursor = spanId;
|
|
292
|
+
for (let depth = 0; cursor && depth < 32; depth++) {
|
|
293
|
+
const entry = spanLineage.get(cursor);
|
|
294
|
+
if (!entry)
|
|
295
|
+
return depth === 0 ? spanId : cursor;
|
|
296
|
+
if (ledgerSpanKind(entry.name) !== null)
|
|
297
|
+
return cursor;
|
|
298
|
+
cursor = entry.parent;
|
|
299
|
+
}
|
|
300
|
+
return spanId;
|
|
301
|
+
}
|
|
268
302
|
/** Name of the agent of the nearest enclosing agent span (ADK's `invoke_agent`, the loop's `agent.invoke`), or null. */
|
|
269
303
|
export function agentForSpan(spanId) {
|
|
270
304
|
let cursor = spanId;
|
|
@@ -557,6 +591,9 @@ export async function* traceAgentRun(stream, metadata) {
|
|
|
557
591
|
span.setAttribute('syndicate.latency.tool_ms', Math.round(stats.toolMs));
|
|
558
592
|
span.setAttribute('syndicate.llm_calls', stats.llmCalls);
|
|
559
593
|
span.setAttribute('syndicate.models', [...stats.models].sort().join(','));
|
|
594
|
+
// Summed over every call, as a provider bills them (adk_turns.cache_read_tokens / cache_write_tokens).
|
|
595
|
+
span.setAttribute('syndicate.tokens.cache_read', stats.cacheRead);
|
|
596
|
+
span.setAttribute('syndicate.tokens.cache_write', stats.cacheWrite);
|
|
560
597
|
// The ledger prices the turn from this, per model (melchizedek_usage_cost, ADR 0136).
|
|
561
598
|
span.setAttribute('syndicate.usage', JSON.stringify(Object.fromEntries([...stats.usage].sort(([a], [b]) => a.localeCompare(b)))));
|
|
562
599
|
}
|
|
@@ -673,6 +710,9 @@ export async function* traceLlmGeneration(meta, inner) {
|
|
|
673
710
|
let inputTokens = 0;
|
|
674
711
|
let outputTokens = 0;
|
|
675
712
|
let thinkingTokens = 0;
|
|
713
|
+
let cacheRead;
|
|
714
|
+
let cacheWrite;
|
|
715
|
+
let finishReason;
|
|
676
716
|
let thinkingPreview = '';
|
|
677
717
|
let errorCode = '';
|
|
678
718
|
let errorMessage = '';
|
|
@@ -689,6 +729,15 @@ export async function* traceLlmGeneration(meta, inner) {
|
|
|
689
729
|
inputTokens = resp.usageMetadata.promptTokenCount ?? inputTokens;
|
|
690
730
|
outputTokens = resp.usageMetadata.candidatesTokenCount ?? outputTokens;
|
|
691
731
|
thinkingTokens = resp.usageMetadata.thoughtsTokenCount ?? thinkingTokens;
|
|
732
|
+
cacheRead = resp.usageMetadata.cachedContentTokenCount ?? cacheRead;
|
|
733
|
+
}
|
|
734
|
+
const source = meta.source?.(resp);
|
|
735
|
+
if (source && !source.partial) {
|
|
736
|
+
finishReason = source.finishReason;
|
|
737
|
+
if (source.usage?.cacheReadTokens !== undefined)
|
|
738
|
+
cacheRead = source.usage.cacheReadTokens;
|
|
739
|
+
if (source.usage?.cacheWriteTokens !== undefined)
|
|
740
|
+
cacheWrite = source.usage.cacheWriteTokens;
|
|
692
741
|
}
|
|
693
742
|
if (resp.errorCode) {
|
|
694
743
|
errorCode = String(resp.errorCode);
|
|
@@ -733,6 +782,15 @@ export async function* traceLlmGeneration(meta, inner) {
|
|
|
733
782
|
span.setAttribute('llm.tokens.input', inputTokens);
|
|
734
783
|
span.setAttribute('llm.tokens.output', outputTokens);
|
|
735
784
|
span.setAttribute('llm.tokens.thinking', thinkingTokens);
|
|
785
|
+
// Parts of llm.tokens.input, set only when the provider reported them (ADR 0139).
|
|
786
|
+
if (cacheRead !== undefined)
|
|
787
|
+
span.setAttribute('llm.tokens.cache_read', cacheRead);
|
|
788
|
+
if (cacheWrite !== undefined)
|
|
789
|
+
span.setAttribute('llm.tokens.cache_write', cacheWrite);
|
|
790
|
+
if (errorCode)
|
|
791
|
+
finishReason = 'error';
|
|
792
|
+
if (finishReason)
|
|
793
|
+
span.setAttribute('llm.finish_reason', finishReason);
|
|
736
794
|
span.setAttribute('gen_ai.usage.input_tokens', inputTokens);
|
|
737
795
|
span.setAttribute('gen_ai.usage.output_tokens', outputTokens);
|
|
738
796
|
// llm.latency_ms mirrors the span duration for the flat adk_telemetry row.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/project.ts — the project manifest, `melchizedek.json` (ADR 0138).
|
|
3
|
+
*
|
|
4
|
+
* A project is a folder, usually a git repository, with a `melchizedek.json`
|
|
5
|
+
* at its root. The manifest records what the engine cannot infer from the
|
|
6
|
+
* files: which folder holds the syndicates, which engine version the project
|
|
7
|
+
* is written for, and how each file ships. It holds names, never values: an
|
|
8
|
+
* `env` entry is a variable name, and no field may carry a secret.
|
|
9
|
+
*
|
|
10
|
+
* `findProject()` walks up from a directory to the nearest manifest and stops
|
|
11
|
+
* at the repository root (the first directory holding `.git`) or the home
|
|
12
|
+
* directory, so a manifest outside the repository never steers it.
|
|
13
|
+
* `resolveAgentsDir()` is the one agents-directory chain every loader and bin
|
|
14
|
+
* uses: explicit option → MELCHIZEDEK_AGENTS_DIR → the manifest's `agents` →
|
|
15
|
+
* <cwd>/config/agents. A deployed server sets the variable, so the manifest
|
|
16
|
+
* only changes what a bare command run inside a project finds.
|
|
17
|
+
*
|
|
18
|
+
* Imports only node built-ins, zod and lib/paths.ts.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
export declare const PROJECT_MANIFEST = "melchizedek.json";
|
|
22
|
+
export declare const MANIFEST_VERSION = 1;
|
|
23
|
+
export declare const projectManifestSchema: z.ZodObject<{
|
|
24
|
+
manifest: z.ZodNumber;
|
|
25
|
+
engine: z.ZodString;
|
|
26
|
+
agents: z.ZodString;
|
|
27
|
+
ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
28
|
+
tests: z.ZodOptional<z.ZodString>;
|
|
29
|
+
env: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
30
|
+
syndicates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
31
|
+
deliverable: z.ZodOptional<z.ZodEnum<{
|
|
32
|
+
library: "library";
|
|
33
|
+
mcp: "mcp";
|
|
34
|
+
service: "service";
|
|
35
|
+
}>>;
|
|
36
|
+
publish: z.ZodOptional<z.ZodObject<{
|
|
37
|
+
kind: z.ZodLiteral<"registry">;
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
}, z.core.$loose>>;
|
|
40
|
+
}, z.core.$loose>>>;
|
|
41
|
+
registry: z.ZodOptional<z.ZodObject<{
|
|
42
|
+
kind: z.ZodLiteral<"supabase">;
|
|
43
|
+
url_env: z.ZodOptional<z.ZodString>;
|
|
44
|
+
restart: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$loose>>;
|
|
46
|
+
production: z.ZodOptional<z.ZodObject<{
|
|
47
|
+
ledger: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
kind: z.ZodLiteral<"postgres">;
|
|
49
|
+
url_env: z.ZodString;
|
|
50
|
+
}, z.core.$loose>>;
|
|
51
|
+
}, z.core.$loose>>;
|
|
52
|
+
tools: z.ZodOptional<z.ZodObject<{
|
|
53
|
+
remote: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
54
|
+
mcp_env: z.ZodString;
|
|
55
|
+
}, z.core.$loose>>>;
|
|
56
|
+
}, z.core.$loose>>;
|
|
57
|
+
}, z.core.$loose>;
|
|
58
|
+
export type ProjectManifest = z.infer<typeof projectManifestSchema>;
|
|
59
|
+
export interface Project {
|
|
60
|
+
/** The folder holding `melchizedek.json`. */
|
|
61
|
+
root: string;
|
|
62
|
+
/** Absolute path of the manifest. */
|
|
63
|
+
file: string;
|
|
64
|
+
manifest: ProjectManifest;
|
|
65
|
+
/** `agents`, resolved under the root. */
|
|
66
|
+
agentsDir: string;
|
|
67
|
+
/** `tests`, resolved under the root (default `tests/agents`). */
|
|
68
|
+
testsDir: string;
|
|
69
|
+
}
|
|
70
|
+
/** Thrown for a manifest that does not parse or validate; names the file and fields, never a value. */
|
|
71
|
+
export declare class ProjectManifestError extends Error {
|
|
72
|
+
readonly file: string;
|
|
73
|
+
readonly issues: string[];
|
|
74
|
+
constructor(file: string, issues: string[]);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The nearest folder at or above `from` holding a manifest, without crossing
|
|
78
|
+
* the repository root or the home directory. Undefined when there is none.
|
|
79
|
+
*/
|
|
80
|
+
export declare function findProjectRoot(from?: string): string | undefined;
|
|
81
|
+
/** Parse and validate a manifest's text. Throws `ProjectManifestError`. */
|
|
82
|
+
export declare function parseProjectManifest(text: string, file?: string): ProjectManifest;
|
|
83
|
+
/** Load the project rooted at `root` (the folder holding the manifest). Throws `ProjectManifestError`. */
|
|
84
|
+
export declare function loadProject(root: string): Project;
|
|
85
|
+
/** The project at or above `from`, or undefined when there is none. Throws on a broken manifest. */
|
|
86
|
+
export declare function findProject(from?: string): Project | undefined;
|
|
87
|
+
/** Forget cached manifest lookups (tests, and an embedder that edits the manifest in-process). */
|
|
88
|
+
export declare function clearProjectCache(): void;
|
|
89
|
+
/**
|
|
90
|
+
* The agents directory: `explicit` → MELCHIZEDEK_AGENTS_DIR → the nearest
|
|
91
|
+
* manifest's `agents` → <cwd>/config/agents. Always absolute.
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolveAgentsDir(explicit?: string, env?: NodeJS.ProcessEnv): string;
|
|
94
|
+
/** True when `relFromRoot` (a `/`-separated path relative to the project root) matches an `ignore` pattern. */
|
|
95
|
+
export declare function isIgnored(project: Pick<Project, 'manifest'>, relFromRoot: string): boolean;
|
|
96
|
+
/** The syndicate files directly in the agents folder, as names relative to it, minus `ignore`. */
|
|
97
|
+
export declare function listProjectSyndicates(project: Project): string[];
|
|
98
|
+
/** The installed engine's package version (no commit suffix). */
|
|
99
|
+
export declare function enginePackageVersion(): string;
|
|
100
|
+
export interface ProjectCheck {
|
|
101
|
+
root: string;
|
|
102
|
+
agentsDir: string;
|
|
103
|
+
/** The manifest's engine pin, and the engine running now. */
|
|
104
|
+
engine: {
|
|
105
|
+
pinned: string;
|
|
106
|
+
running: string;
|
|
107
|
+
matches: boolean;
|
|
108
|
+
};
|
|
109
|
+
syndicates: string[];
|
|
110
|
+
/** Stop `melchizedek-doctor --check`. */
|
|
111
|
+
problems: string[];
|
|
112
|
+
/** Shown, never fatal. */
|
|
113
|
+
warnings: string[];
|
|
114
|
+
}
|
|
115
|
+
/** What is wrong with a project: engine pin, missing folders, stale ignores, unset env names. Never reads a value. */
|
|
116
|
+
export declare function checkProject(project: Project, running?: string, env?: NodeJS.ProcessEnv): ProjectCheck;
|