autotel-genai 0.1.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/README.md +149 -0
- package/dist/agent/index.cjs +24 -0
- package/dist/agent/index.d.cts +3 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +3 -0
- package/dist/agent-CqXauTpC.js +951 -0
- package/dist/agent-CqXauTpC.js.map +1 -0
- package/dist/agent-eo4jY4Xg.cjs +1076 -0
- package/dist/agent-eo4jY4Xg.cjs.map +1 -0
- package/dist/ai-sdk-bridge.cjs +134 -0
- package/dist/ai-sdk-bridge.cjs.map +1 -0
- package/dist/ai-sdk-bridge.d.cts +59 -0
- package/dist/ai-sdk-bridge.d.cts.map +1 -0
- package/dist/ai-sdk-bridge.d.ts +59 -0
- package/dist/ai-sdk-bridge.d.ts.map +1 -0
- package/dist/ai-sdk-bridge.js +127 -0
- package/dist/ai-sdk-bridge.js.map +1 -0
- package/dist/attributes--Pdezjj6.js +119 -0
- package/dist/attributes--Pdezjj6.js.map +1 -0
- package/dist/attributes-DB35Boji.cjs +166 -0
- package/dist/attributes-DB35Boji.cjs.map +1 -0
- package/dist/attributes-QRj0DPJH.d.ts +106 -0
- package/dist/attributes-QRj0DPJH.d.ts.map +1 -0
- package/dist/attributes-kyiOdake.d.cts +106 -0
- package/dist/attributes-kyiOdake.d.cts.map +1 -0
- package/dist/cost-DXTkBUUW.d.cts +72 -0
- package/dist/cost-DXTkBUUW.d.cts.map +1 -0
- package/dist/cost-DXTkBUUW.d.ts +72 -0
- package/dist/cost-DXTkBUUW.d.ts.map +1 -0
- package/dist/cost.cjs +138 -0
- package/dist/cost.cjs.map +1 -0
- package/dist/cost.d.cts +2 -0
- package/dist/cost.d.ts +2 -0
- package/dist/cost.js +134 -0
- package/dist/cost.js.map +1 -0
- package/dist/events.cjs +88 -0
- package/dist/events.cjs.map +1 -0
- package/dist/events.d.cts +89 -0
- package/dist/events.d.cts.map +1 -0
- package/dist/events.d.ts +89 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +84 -0
- package/dist/events.js.map +1 -0
- package/dist/index-D1gH80Xd.d.ts +403 -0
- package/dist/index-D1gH80Xd.d.ts.map +1 -0
- package/dist/index-DtjGz80V.d.cts +403 -0
- package/dist/index-DtjGz80V.d.cts.map +1 -0
- package/dist/index.cjs +72 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/metrics.cjs +166 -0
- package/dist/metrics.cjs.map +1 -0
- package/dist/metrics.d.cts +52 -0
- package/dist/metrics.d.cts.map +1 -0
- package/dist/metrics.d.ts +52 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +161 -0
- package/dist/metrics.js.map +1 -0
- package/dist/semconv-CGiMo_LV.d.cts +190 -0
- package/dist/semconv-CGiMo_LV.d.cts.map +1 -0
- package/dist/semconv-CGiMo_LV.d.ts +190 -0
- package/dist/semconv-CGiMo_LV.d.ts.map +1 -0
- package/dist/semconv.cjs +196 -0
- package/dist/semconv.cjs.map +1 -0
- package/dist/semconv.d.cts +2 -0
- package/dist/semconv.d.ts +2 -0
- package/dist/semconv.js +186 -0
- package/dist/semconv.js.map +1 -0
- package/dist/trace.cjs +119 -0
- package/dist/trace.cjs.map +1 -0
- package/dist/trace.d.cts +42 -0
- package/dist/trace.d.cts.map +1 -0
- package/dist/trace.d.ts +42 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +115 -0
- package/dist/trace.js.map +1 -0
- package/package.json +113 -0
- package/src/agent/agent.test.ts +874 -0
- package/src/agent/attributes.ts +162 -0
- package/src/agent/constants.ts +1 -0
- package/src/agent/context.ts +125 -0
- package/src/agent/delegation.ts +98 -0
- package/src/agent/hash.ts +44 -0
- package/src/agent/identity-registry.ts +188 -0
- package/src/agent/index.ts +45 -0
- package/src/agent/metadata.ts +254 -0
- package/src/agent/non-repudiation.ts +70 -0
- package/src/agent/privacy.ts +128 -0
- package/src/agent/runtime.ts +294 -0
- package/src/agent/scoped-tool.ts +221 -0
- package/src/agent/session.ts +66 -0
- package/src/agent/types.ts +311 -0
- package/src/ai-sdk-bridge.test.ts +96 -0
- package/src/ai-sdk-bridge.ts +209 -0
- package/src/attributes.test.ts +148 -0
- package/src/attributes.ts +265 -0
- package/src/cost.test.ts +92 -0
- package/src/cost.ts +196 -0
- package/src/events.test.ts +82 -0
- package/src/events.ts +210 -0
- package/src/index.ts +178 -0
- package/src/metrics.test.ts +60 -0
- package/src/metrics.ts +128 -0
- package/src/semconv.test.ts +88 -0
- package/src/semconv.ts +240 -0
- package/src/trace.test.ts +167 -0
- package/src/trace.ts +181 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
import { estimateLLMCost } from "./cost.js";
|
|
2
|
+
import { i as genAiResponseAttributes, r as genAiRequestAttributes, s as genAiUsageAttributes } from "./attributes--Pdezjj6.js";
|
|
3
|
+
import { createNoopRequestLogger, createStructuredError, getRequestLoggerSafe, getTraceContext, otelTrace } from "autotel";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { forceKeepAuditEvent, withAudit } from "autotel-audit";
|
|
6
|
+
|
|
7
|
+
//#region src/agent/constants.ts
|
|
8
|
+
const AGENT_AUDIT_SCHEMA_VERSION = "1.1.0";
|
|
9
|
+
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/agent/hash.ts
|
|
12
|
+
function canonicalize(value) {
|
|
13
|
+
if (value instanceof Date) return value.toISOString();
|
|
14
|
+
if (typeof value === "bigint") return value.toString(10);
|
|
15
|
+
if (Array.isArray(value)) return value.map((entry) => canonicalize(entry));
|
|
16
|
+
if (value && typeof value === "object") {
|
|
17
|
+
const entries = Object.entries(value);
|
|
18
|
+
entries.sort(([left], [right]) => left.localeCompare(right));
|
|
19
|
+
return Object.fromEntries(entries.map(([key, entryValue]) => [key, canonicalize(entryValue)]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function canonicalizeForHash(value) {
|
|
24
|
+
return JSON.stringify(canonicalize(value));
|
|
25
|
+
}
|
|
26
|
+
function hashPayload(value, options = {}) {
|
|
27
|
+
const algorithm = options.algorithm ?? "sha256";
|
|
28
|
+
const canonical = canonicalizeForHash(value);
|
|
29
|
+
return `${algorithm}:${createHash(algorithm).update(canonical).digest("hex")}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/agent/metadata.ts
|
|
34
|
+
function defaultEventKind(metadata) {
|
|
35
|
+
if (metadata.eventKind) return metadata.eventKind;
|
|
36
|
+
if (metadata.tool) return "tool_call";
|
|
37
|
+
if (metadata.policy) return "policy_decision";
|
|
38
|
+
return "action";
|
|
39
|
+
}
|
|
40
|
+
function normalizeTool(tool) {
|
|
41
|
+
if (!tool) return void 0;
|
|
42
|
+
return {
|
|
43
|
+
name: tool.name,
|
|
44
|
+
...tool.callId !== void 0 && { callId: tool.callId },
|
|
45
|
+
inputHash: tool.input === void 0 ? tool.inputHash : tool.inputHash ?? hashPayload(tool.input),
|
|
46
|
+
outputHash: tool.output === void 0 ? tool.outputHash : tool.outputHash ?? hashPayload(tool.output),
|
|
47
|
+
...tool.status !== void 0 && { status: tool.status },
|
|
48
|
+
...tool.executionMs !== void 0 && { executionMs: tool.executionMs }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function sanitizeTool(tool) {
|
|
52
|
+
if (!tool) return void 0;
|
|
53
|
+
return {
|
|
54
|
+
name: tool.name,
|
|
55
|
+
...tool.callId !== void 0 && { callId: tool.callId },
|
|
56
|
+
...tool.inputHash !== void 0 && { inputHash: tool.inputHash },
|
|
57
|
+
...tool.outputHash !== void 0 && { outputHash: tool.outputHash },
|
|
58
|
+
...tool.status !== void 0 && { status: tool.status },
|
|
59
|
+
...tool.executionMs !== void 0 && { executionMs: tool.executionMs }
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function sanitizeGovernance(governance) {
|
|
63
|
+
if (!governance) return void 0;
|
|
64
|
+
return {
|
|
65
|
+
...governance.reviewRequired !== void 0 && { reviewRequired: governance.reviewRequired },
|
|
66
|
+
...governance.reviewerId !== void 0 && { reviewerId: governance.reviewerId },
|
|
67
|
+
...governance.controlId !== void 0 && { controlId: governance.controlId },
|
|
68
|
+
...governance.documentationUrl !== void 0 && { documentationUrl: governance.documentationUrl },
|
|
69
|
+
...governance.lifecycleStage !== void 0 && { lifecycleStage: governance.lifecycleStage },
|
|
70
|
+
...governance.framework !== void 0 && { framework: governance.framework }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function normalizeDecision(decision, reasoningSummary) {
|
|
74
|
+
if (decision) return {
|
|
75
|
+
...decision,
|
|
76
|
+
summary: decision.summary ?? reasoningSummary ?? ""
|
|
77
|
+
};
|
|
78
|
+
if (reasoningSummary === void 0) return void 0;
|
|
79
|
+
return { summary: reasoningSummary };
|
|
80
|
+
}
|
|
81
|
+
function createAgentAuditMetadata(metadata) {
|
|
82
|
+
const eventKind = defaultEventKind(metadata);
|
|
83
|
+
if (eventKind === "tool_call" && !metadata.tool) throw new Error("[autotel-genai] eventKind \"tool_call\" requires metadata.tool.");
|
|
84
|
+
if (eventKind === "policy_decision" && !metadata.policy) throw new Error("[autotel-genai] eventKind \"policy_decision\" requires metadata.policy.");
|
|
85
|
+
if (eventKind === "handoff" && !metadata.delegation) throw new Error("[autotel-genai] eventKind \"handoff\" requires metadata.delegation.");
|
|
86
|
+
const delegation = metadata.delegation && (metadata.delegation.authorityLineageHash === void 0 || metadata.delegation.depth === void 0) ? {
|
|
87
|
+
...metadata.delegation,
|
|
88
|
+
...metadata.delegation.authorityLineage && {
|
|
89
|
+
authorityLineageHash: metadata.delegation.authorityLineageHash ?? hashPayload(metadata.delegation.authorityLineage),
|
|
90
|
+
depth: metadata.delegation.depth ?? Math.max(metadata.delegation.authorityLineage.length - 1, 0)
|
|
91
|
+
}
|
|
92
|
+
} : metadata.delegation;
|
|
93
|
+
return {
|
|
94
|
+
...metadata,
|
|
95
|
+
schemaVersion: metadata.schemaVersion ?? "1.1.0",
|
|
96
|
+
eventKind,
|
|
97
|
+
decision: normalizeDecision(metadata.decision, metadata.reasoningSummary),
|
|
98
|
+
...delegation !== void 0 && { delegation }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function normalizeMetadata(metadata) {
|
|
102
|
+
const normalized = createAgentAuditMetadata(metadata);
|
|
103
|
+
return {
|
|
104
|
+
...normalized,
|
|
105
|
+
tool: normalizeTool(normalized.tool)
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function buildAuditMetadata(metadata) {
|
|
109
|
+
return {
|
|
110
|
+
action: metadata.action,
|
|
111
|
+
...metadata.resource !== void 0 && { resource: metadata.resource },
|
|
112
|
+
actorId: metadata.actorId ?? metadata.delegation?.parentIdentity ?? metadata.agent.id,
|
|
113
|
+
category: metadata.category ?? "agent",
|
|
114
|
+
...metadata.outcome !== void 0 && { outcome: metadata.outcome },
|
|
115
|
+
agentId: metadata.agent.id,
|
|
116
|
+
agentEventKind: metadata.eventKind,
|
|
117
|
+
agentAuditVersion: metadata.schemaVersion,
|
|
118
|
+
...metadata.agent.version !== void 0 && { agentVersion: metadata.agent.version },
|
|
119
|
+
...metadata.tool?.name !== void 0 && { toolName: metadata.tool.name },
|
|
120
|
+
...metadata.policy?.decision !== void 0 && { policyDecision: metadata.policy.decision },
|
|
121
|
+
...metadata.session?.status !== void 0 && { sessionStatus: metadata.session.status }
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function buildLoggerContext(metadata) {
|
|
125
|
+
const tool = sanitizeTool(metadata.tool);
|
|
126
|
+
const governance = sanitizeGovernance(metadata.governance);
|
|
127
|
+
const context = {
|
|
128
|
+
agent: {
|
|
129
|
+
...metadata.agent,
|
|
130
|
+
...metadata.resource !== void 0 && { resource: metadata.resource },
|
|
131
|
+
...metadata.outcome !== void 0 && { outcome: metadata.outcome },
|
|
132
|
+
...metadata.reasoningSummary !== void 0 && { reasoningSummary: metadata.reasoningSummary },
|
|
133
|
+
schemaVersion: metadata.schemaVersion ?? "1.1.0",
|
|
134
|
+
eventKind: metadata.eventKind ?? defaultEventKind(metadata)
|
|
135
|
+
},
|
|
136
|
+
...metadata.delegation !== void 0 && { delegation: metadata.delegation },
|
|
137
|
+
...tool !== void 0 && { tool },
|
|
138
|
+
...metadata.policy !== void 0 && { policy: metadata.policy },
|
|
139
|
+
...governance !== void 0 && { governance },
|
|
140
|
+
...metadata.session !== void 0 && { session: metadata.session },
|
|
141
|
+
...metadata.decision !== void 0 && { decision: metadata.decision }
|
|
142
|
+
};
|
|
143
|
+
return structuredClone(context);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Context for the *completion* `logger.set()` of a specialized lifecycle
|
|
147
|
+
* wrapper. Carries only the domain state that finished mutating — tool or
|
|
148
|
+
* session status. Outcome is owned by `withAgentAction`, which wraps every
|
|
149
|
+
* variant and stamps it on both span and log. This deliberately omits the
|
|
150
|
+
* request-level `delegation`/`decision` blocks: those were set once at the
|
|
151
|
+
* start, and re-sending them would concatenate their array fields into the
|
|
152
|
+
* wide event (see `buildLoggerContext`).
|
|
153
|
+
*/
|
|
154
|
+
function buildLifecycleUpdateContext(metadata) {
|
|
155
|
+
const tool = sanitizeTool(metadata.tool);
|
|
156
|
+
return {
|
|
157
|
+
...tool !== void 0 && { tool },
|
|
158
|
+
...metadata.session !== void 0 && { session: structuredClone(metadata.session) }
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/agent/context.ts
|
|
164
|
+
const MISSING_CONTEXT_MESSAGE = "[autotel-genai] No active trace context. Wrap the call in trace()/instrument(), pass options.ctx, or set options.onMissingContext to \"warn\"/\"skip\" to degrade gracefully instead of throwing.";
|
|
165
|
+
/**
|
|
166
|
+
* Resolve an agent context without throwing. Returns `null` when no trace context
|
|
167
|
+
* is available, so callers can degrade gracefully (best-effort instrumentation).
|
|
168
|
+
*/
|
|
169
|
+
const INVALID_TRACE_ID = "00000000000000000000000000000000";
|
|
170
|
+
function resolveContextSafe(ctx) {
|
|
171
|
+
if (ctx) return ctx;
|
|
172
|
+
const span = otelTrace.getActiveSpan();
|
|
173
|
+
if (!span) return null;
|
|
174
|
+
const ids = getTraceContext();
|
|
175
|
+
const sc = span.spanContext();
|
|
176
|
+
const traceId = ids?.traceId ?? sc.traceId;
|
|
177
|
+
if (!traceId || traceId === INVALID_TRACE_ID) return null;
|
|
178
|
+
return {
|
|
179
|
+
traceId,
|
|
180
|
+
spanId: ids?.spanId ?? sc.spanId,
|
|
181
|
+
correlationId: ids?.correlationId ?? traceId.slice(0, 16),
|
|
182
|
+
setAttribute: (key, value) => span.setAttribute(key, value),
|
|
183
|
+
setAttributes: (attrs) => span.setAttributes(attrs)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function resolveContext(ctx) {
|
|
187
|
+
const resolved = resolveContextSafe(ctx);
|
|
188
|
+
if (resolved) return resolved;
|
|
189
|
+
throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
190
|
+
}
|
|
191
|
+
const warnedMissingContext = /* @__PURE__ */ new Set();
|
|
192
|
+
/** Warn (once per action) that an agent action is running without a trace context. */
|
|
193
|
+
function warnMissingContextOnce(action) {
|
|
194
|
+
if (warnedMissingContext.has(action)) return;
|
|
195
|
+
warnedMissingContext.add(action);
|
|
196
|
+
console.warn(`[autotel-genai] No active trace context for "${action}" — running un-audited. Wrap the call in trace()/instrument() or pass options.ctx to capture agent audit telemetry. (set options.onMissingContext: "throw" to fail fast, or "skip" to silence this warning)`);
|
|
197
|
+
}
|
|
198
|
+
function toAttributeValue(value) {
|
|
199
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
|
|
200
|
+
if (Array.isArray(value)) {
|
|
201
|
+
if (value.every((entry) => typeof entry === "string")) return value;
|
|
202
|
+
if (value.every((entry) => typeof entry === "number")) return value;
|
|
203
|
+
if (value.every((entry) => typeof entry === "boolean")) return value;
|
|
204
|
+
try {
|
|
205
|
+
return JSON.stringify(value);
|
|
206
|
+
} catch {
|
|
207
|
+
return "<serialization-failed>";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (value instanceof Date) return value.toISOString();
|
|
211
|
+
if (typeof value === "bigint") return value.toString(10);
|
|
212
|
+
if (value === null || value === void 0) return;
|
|
213
|
+
try {
|
|
214
|
+
return JSON.stringify(value);
|
|
215
|
+
} catch {
|
|
216
|
+
return "<serialization-failed>";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/agent/attributes.ts
|
|
222
|
+
function setIfPresent(target, key, value) {
|
|
223
|
+
const attr = toAttributeValue(value);
|
|
224
|
+
if (attr !== void 0) target[key] = attr;
|
|
225
|
+
}
|
|
226
|
+
function appendIdentityAttributes(attrs, agent) {
|
|
227
|
+
setIfPresent(attrs, "agent.id", agent.id);
|
|
228
|
+
setIfPresent(attrs, "agent.version", agent.version);
|
|
229
|
+
setIfPresent(attrs, "agent.framework", agent.framework);
|
|
230
|
+
setIfPresent(attrs, "agent.model", agent.model);
|
|
231
|
+
setIfPresent(attrs, "agent.role", agent.role);
|
|
232
|
+
setIfPresent(attrs, "agent.session.id", agent.sessionId);
|
|
233
|
+
setIfPresent(attrs, "agent.conversation.id", agent.conversationId);
|
|
234
|
+
}
|
|
235
|
+
function appendDelegationAttributes(attrs, delegation) {
|
|
236
|
+
if (!delegation) return;
|
|
237
|
+
setIfPresent(attrs, "delegation.parent_identity", delegation.parentIdentity);
|
|
238
|
+
setIfPresent(attrs, "delegation.scope", delegation.scope);
|
|
239
|
+
setIfPresent(attrs, "delegation.token_id", delegation.tokenId);
|
|
240
|
+
setIfPresent(attrs, "delegation.id", delegation.delegationId);
|
|
241
|
+
setIfPresent(attrs, "delegation.authority_lineage", delegation.authorityLineage);
|
|
242
|
+
setIfPresent(attrs, "delegation.authority_lineage_hash", delegation.authorityLineageHash);
|
|
243
|
+
setIfPresent(attrs, "delegation.depth", delegation.depth);
|
|
244
|
+
setIfPresent(attrs, "delegation.issued_at", delegation.issuedAt);
|
|
245
|
+
setIfPresent(attrs, "delegation.expires_at", delegation.expiresAt);
|
|
246
|
+
}
|
|
247
|
+
function appendToolAttributes(attrs, tool) {
|
|
248
|
+
if (!tool) return;
|
|
249
|
+
setIfPresent(attrs, "tool.name", tool.name);
|
|
250
|
+
setIfPresent(attrs, "tool.call.id", tool.callId);
|
|
251
|
+
setIfPresent(attrs, "tool.input_hash", tool.inputHash);
|
|
252
|
+
setIfPresent(attrs, "tool.output_hash", tool.outputHash);
|
|
253
|
+
setIfPresent(attrs, "tool.status", tool.status);
|
|
254
|
+
setIfPresent(attrs, "tool.execution_ms", tool.executionMs);
|
|
255
|
+
}
|
|
256
|
+
function appendPolicyAttributes(attrs, policy) {
|
|
257
|
+
if (!policy) return;
|
|
258
|
+
setIfPresent(attrs, "policy.decision", policy.decision);
|
|
259
|
+
setIfPresent(attrs, "policy.id", policy.policyId);
|
|
260
|
+
setIfPresent(attrs, "policy.risk_score", policy.riskScore);
|
|
261
|
+
setIfPresent(attrs, "policy.reason", policy.reason);
|
|
262
|
+
}
|
|
263
|
+
function appendGovernanceAttributes(attrs, governance) {
|
|
264
|
+
if (!governance) return;
|
|
265
|
+
setIfPresent(attrs, "governance.review_required", governance.reviewRequired);
|
|
266
|
+
setIfPresent(attrs, "governance.reviewer_id", governance.reviewerId);
|
|
267
|
+
setIfPresent(attrs, "governance.control_id", governance.controlId);
|
|
268
|
+
setIfPresent(attrs, "governance.documentation_url", governance.documentationUrl);
|
|
269
|
+
setIfPresent(attrs, "governance.lifecycle_stage", governance.lifecycleStage);
|
|
270
|
+
setIfPresent(attrs, "governance.framework", governance.framework);
|
|
271
|
+
}
|
|
272
|
+
function appendSessionAttributes(attrs, session) {
|
|
273
|
+
if (!session) return;
|
|
274
|
+
setIfPresent(attrs, "agent.session.status", session.status);
|
|
275
|
+
setIfPresent(attrs, "agent.session.started_at", session.startedAt);
|
|
276
|
+
setIfPresent(attrs, "agent.session.ended_at", session.endedAt);
|
|
277
|
+
setIfPresent(attrs, "agent.session.delegated_by", session.delegatedBy);
|
|
278
|
+
}
|
|
279
|
+
function appendDecisionAttributes(attrs, decision) {
|
|
280
|
+
if (!decision) return;
|
|
281
|
+
setIfPresent(attrs, "decision.summary", decision.summary);
|
|
282
|
+
setIfPresent(attrs, "decision.input_hash", decision.inputHash);
|
|
283
|
+
setIfPresent(attrs, "decision.policy_ids", decision.policyIds);
|
|
284
|
+
setIfPresent(attrs, "decision.justification_codes", decision.justificationCodes);
|
|
285
|
+
setIfPresent(attrs, "decision.evidence_ids", decision.evidenceIds);
|
|
286
|
+
setIfPresent(attrs, "decision.review_required", decision.reviewRequired);
|
|
287
|
+
setIfPresent(attrs, "decision.confidence", decision.confidence);
|
|
288
|
+
}
|
|
289
|
+
function flattenAgentAttributes(metadata) {
|
|
290
|
+
const normalized = normalizeMetadata(metadata);
|
|
291
|
+
const attrs = {
|
|
292
|
+
"autotel.agent": true,
|
|
293
|
+
"agent.action": normalized.action,
|
|
294
|
+
"agent.audit.version": normalized.schemaVersion ?? "1.1.0",
|
|
295
|
+
"agent.event.kind": normalized.eventKind ?? defaultEventKind(normalized)
|
|
296
|
+
};
|
|
297
|
+
setIfPresent(attrs, "agent.resource", normalized.resource);
|
|
298
|
+
setIfPresent(attrs, "agent.outcome", normalized.outcome);
|
|
299
|
+
setIfPresent(attrs, "reasoning.summary", normalized.reasoningSummary);
|
|
300
|
+
appendIdentityAttributes(attrs, normalized.agent);
|
|
301
|
+
appendDelegationAttributes(attrs, normalized.delegation);
|
|
302
|
+
appendToolAttributes(attrs, normalized.tool);
|
|
303
|
+
appendPolicyAttributes(attrs, normalized.policy);
|
|
304
|
+
appendGovernanceAttributes(attrs, normalized.governance);
|
|
305
|
+
appendSessionAttributes(attrs, normalized.session);
|
|
306
|
+
appendDecisionAttributes(attrs, normalized.decision);
|
|
307
|
+
return attrs;
|
|
308
|
+
}
|
|
309
|
+
function setAgentAttributes(metadata, ctx) {
|
|
310
|
+
resolveContext(ctx).setAttributes(flattenAgentAttributes(metadata));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Stamp only the terminal outcome on the active span. Used by lifecycle
|
|
314
|
+
* wrappers on completion so they don't re-flatten (and clobber) richer state a
|
|
315
|
+
* nested step already wrote — e.g. a tool call's `tool.status=complete`.
|
|
316
|
+
*/
|
|
317
|
+
function setAgentOutcome(outcome, ctx) {
|
|
318
|
+
resolveContext(ctx).setAttribute("agent.outcome", outcome);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/agent/runtime.ts
|
|
323
|
+
/**
|
|
324
|
+
* Record canonical OpenTelemetry GenAI semantic attributes for an LLM-backed
|
|
325
|
+
* agent action, reusing the local cost model. Best-effort: anything unknown is
|
|
326
|
+
* simply omitted. `gen_ai.request.model` is always recorded; token counts and
|
|
327
|
+
* the estimated `gen_ai.usage.cost.usd` are recorded when `usage` is available
|
|
328
|
+
* (up front via metadata, or post-call via `options.extractUsage`).
|
|
329
|
+
*
|
|
330
|
+
* Emits canonical v1.42.0 keys only — no `gen_ai.usage.total_tokens` (not a
|
|
331
|
+
* registry attribute) and no legacy `gen.ai.*` names.
|
|
332
|
+
*/
|
|
333
|
+
function recordAiTelemetry(ctx, ai, usage) {
|
|
334
|
+
const attrs = {
|
|
335
|
+
...genAiRequestAttributes({
|
|
336
|
+
operation: ai.operation,
|
|
337
|
+
provider: ai.provider,
|
|
338
|
+
model: ai.model
|
|
339
|
+
}),
|
|
340
|
+
...genAiResponseAttributes({
|
|
341
|
+
model: ai.responseModel,
|
|
342
|
+
id: ai.responseId,
|
|
343
|
+
finishReasons: ai.finishReasons
|
|
344
|
+
})
|
|
345
|
+
};
|
|
346
|
+
if (usage) {
|
|
347
|
+
const cost = estimateLLMCost(ai.model, usage, ai.pricing ? { pricing: ai.pricing } : void 0);
|
|
348
|
+
Object.assign(attrs, genAiUsageAttributes({
|
|
349
|
+
...usage,
|
|
350
|
+
costUsd: cost
|
|
351
|
+
}));
|
|
352
|
+
}
|
|
353
|
+
ctx.setAttributes(attrs);
|
|
354
|
+
}
|
|
355
|
+
async function withAgentAction(metadata, fn, options = {}) {
|
|
356
|
+
const normalized = normalizeMetadata(metadata);
|
|
357
|
+
return withAudit(buildAuditMetadata(normalized), async (ctx, logger) => {
|
|
358
|
+
setAgentAttributes(normalized, ctx);
|
|
359
|
+
logger.set(buildLoggerContext(normalized));
|
|
360
|
+
try {
|
|
361
|
+
const result = await fn(ctx, logger);
|
|
362
|
+
const outcome = normalized.outcome ?? "success";
|
|
363
|
+
setAgentOutcome(outcome, ctx);
|
|
364
|
+
logger.set({ agent: { outcome } });
|
|
365
|
+
if (normalized.ai) recordAiTelemetry(ctx, normalized.ai, options.extractUsage?.(result) ?? normalized.ai.usage);
|
|
366
|
+
return result;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
setAgentOutcome("failure", ctx);
|
|
369
|
+
logger.set({ agent: { outcome: "failure" } });
|
|
370
|
+
if (normalized.ai) recordAiTelemetry(ctx, normalized.ai);
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
}, options);
|
|
374
|
+
}
|
|
375
|
+
function recordPolicyDecision(metadata, options = {}) {
|
|
376
|
+
const normalized = normalizeMetadata(metadata);
|
|
377
|
+
const traceCtx = resolveContextSafe(options.ctx);
|
|
378
|
+
if (!traceCtx) {
|
|
379
|
+
const mode = options.onMissingContext ?? "warn";
|
|
380
|
+
if (mode === "throw") throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
381
|
+
if (mode === "warn") warnMissingContextOnce(normalized.action);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const logger = options.logger ?? getRequestLoggerSafe() ?? createNoopRequestLogger();
|
|
385
|
+
if (options.forceKeep !== false) forceKeepAuditEvent(traceCtx);
|
|
386
|
+
setAgentAttributes(normalized, traceCtx);
|
|
387
|
+
logger.set(buildLoggerContext(normalized));
|
|
388
|
+
if (options.emitNow) logger.emitNow();
|
|
389
|
+
}
|
|
390
|
+
function recordDecisionBasis(metadata, options = {}) {
|
|
391
|
+
if (!metadata.decision && !metadata.reasoningSummary) throw new Error("[autotel-genai] recordDecisionBasis requires metadata.decision or metadata.reasoningSummary.");
|
|
392
|
+
recordPolicyDecision(metadata, options);
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Define a reusable, instrumented agent action — the `trace()`-style factory
|
|
396
|
+
* companion to `withAgentAction`. Declare it once at module scope and call the
|
|
397
|
+
* returned function many times; each call opens its own audit scope.
|
|
398
|
+
*
|
|
399
|
+
* `metadata` may be a static object or a function of the call arguments, so
|
|
400
|
+
* call-specific fields can be derived per invocation.
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```ts
|
|
404
|
+
* const planTrip = defineAgentAction(
|
|
405
|
+
* (req: TripRequest) => ({
|
|
406
|
+
* action: 'agent.trip.plan',
|
|
407
|
+
* agent: { id: 'planner' },
|
|
408
|
+
* delegation: { parentIdentity: req.userId, scope: ['trip:plan'] },
|
|
409
|
+
* }),
|
|
410
|
+
* (ctx) => async (req: TripRequest) => planItinerary(req),
|
|
411
|
+
* );
|
|
412
|
+
*
|
|
413
|
+
* await planTrip({ userId: 'usr_1', destination: 'Lisbon' });
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
function defineAgentAction(metadata, factory, options = {}) {
|
|
417
|
+
return (...args) => {
|
|
418
|
+
return withAgentAction(typeof metadata === "function" ? metadata(...args) : metadata, (ctx, logger) => factory(ctx, logger)(...args), options);
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Define a reusable, instrumented agent tool call — the `trace()`-style factory
|
|
423
|
+
* companion to `withAgentToolCall`. Declare it once and call it per invocation;
|
|
424
|
+
* tool inputs/results are hashed (never attached raw) on every call.
|
|
425
|
+
*
|
|
426
|
+
* Pass `metadata` as a function of the arguments when `tool.input` (or any other
|
|
427
|
+
* field) depends on the call, so each invocation hashes its own input.
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* ```ts
|
|
431
|
+
* const handleRefund = defineAgentToolCall(
|
|
432
|
+
* (req: RefundRequest) => ({
|
|
433
|
+
* action: 'agent.refund.tool_call',
|
|
434
|
+
* resource: 'stripe_refund_v3',
|
|
435
|
+
* agent: { id: 'refunds-specialist' },
|
|
436
|
+
* tool: { name: 'stripe_refund_v3', input: { refundId: req.refundId } },
|
|
437
|
+
* }),
|
|
438
|
+
* (ctx) => async (req: RefundRequest) => stripe.refunds.create(req),
|
|
439
|
+
* );
|
|
440
|
+
*
|
|
441
|
+
* await handleRefund({ refundId: 're_123' });
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
function defineAgentToolCall(metadata, factory, options = {}) {
|
|
445
|
+
return (...args) => {
|
|
446
|
+
return withAgentToolCall(typeof metadata === "function" ? metadata(...args) : metadata, (ctx, logger) => factory(ctx, logger)(...args), options);
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
async function withAgentToolCall(metadata, fn, options = {}) {
|
|
450
|
+
const start = Date.now();
|
|
451
|
+
const normalized = normalizeMetadata({
|
|
452
|
+
...metadata,
|
|
453
|
+
tool: {
|
|
454
|
+
...metadata.tool,
|
|
455
|
+
status: metadata.tool?.status ?? "planned"
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
return withAgentAction(normalized, async (ctx, logger) => {
|
|
459
|
+
try {
|
|
460
|
+
const result = await fn(ctx, logger);
|
|
461
|
+
const executionMs = Date.now() - start;
|
|
462
|
+
const completed = {
|
|
463
|
+
...normalized,
|
|
464
|
+
outcome: normalized.outcome ?? "success",
|
|
465
|
+
tool: {
|
|
466
|
+
...metadata.tool,
|
|
467
|
+
inputHash: normalized.tool?.inputHash,
|
|
468
|
+
outputHash: normalized.tool?.outputHash ?? (options.hashResult === false ? void 0 : hashPayload(result)),
|
|
469
|
+
status: "complete",
|
|
470
|
+
executionMs
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
setAgentAttributes(completed, ctx);
|
|
474
|
+
logger.set(buildLifecycleUpdateContext(normalizeMetadata(completed)));
|
|
475
|
+
return result;
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const failed = {
|
|
478
|
+
...normalized,
|
|
479
|
+
outcome: "failure",
|
|
480
|
+
tool: {
|
|
481
|
+
...metadata.tool,
|
|
482
|
+
inputHash: normalized.tool?.inputHash,
|
|
483
|
+
status: "error",
|
|
484
|
+
executionMs: Date.now() - start
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
setAgentAttributes(failed, ctx);
|
|
488
|
+
logger.set(buildLifecycleUpdateContext(normalizeMetadata(failed)));
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
}, options);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/agent/delegation.ts
|
|
496
|
+
function buildAuthorityLineage(parentIdentity, agentId, existingLineage) {
|
|
497
|
+
const lineage = existingLineage ? [...existingLineage] : [parentIdentity];
|
|
498
|
+
if (lineage.at(-1) !== agentId) lineage.push(agentId);
|
|
499
|
+
return lineage;
|
|
500
|
+
}
|
|
501
|
+
function delegateToAgent(input) {
|
|
502
|
+
const authorityLineage = buildAuthorityLineage(input.parentIdentity, input.targetAgentId, input.authorityLineage);
|
|
503
|
+
return {
|
|
504
|
+
parentIdentity: input.parentIdentity,
|
|
505
|
+
...input.scope !== void 0 && { scope: input.scope },
|
|
506
|
+
...input.tokenId !== void 0 && { tokenId: input.tokenId },
|
|
507
|
+
...input.delegationId !== void 0 && { delegationId: input.delegationId },
|
|
508
|
+
authorityLineage,
|
|
509
|
+
authorityLineageHash: hashPayload(authorityLineage),
|
|
510
|
+
depth: Math.max(authorityLineage.length - 1, 0),
|
|
511
|
+
issuedAt: input.issuedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
512
|
+
...input.expiresAt !== void 0 && { expiresAt: input.expiresAt }
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function recordAgentHandoff(metadata, options = {}) {
|
|
516
|
+
const authorityLineage = metadata.authorityLineage ?? [metadata.parentIdentity, metadata.fromAgent.id];
|
|
517
|
+
const delegation = delegateToAgent({
|
|
518
|
+
parentIdentity: metadata.parentIdentity,
|
|
519
|
+
targetAgentId: metadata.toAgent.id,
|
|
520
|
+
scope: metadata.scope,
|
|
521
|
+
tokenId: metadata.tokenId,
|
|
522
|
+
delegationId: metadata.delegationId,
|
|
523
|
+
authorityLineage
|
|
524
|
+
});
|
|
525
|
+
recordPolicyDecision({
|
|
526
|
+
action: metadata.action,
|
|
527
|
+
resource: metadata.resource,
|
|
528
|
+
eventKind: "handoff",
|
|
529
|
+
agent: metadata.toAgent,
|
|
530
|
+
delegation,
|
|
531
|
+
governance: metadata.governance,
|
|
532
|
+
reasoningSummary: `Control passed from ${metadata.fromAgent.id} to ${metadata.toAgent.id}.`
|
|
533
|
+
}, options);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
//#endregion
|
|
537
|
+
//#region src/agent/session.ts
|
|
538
|
+
function toIsoString$2(value) {
|
|
539
|
+
if (!value) return (/* @__PURE__ */ new Date()).toISOString();
|
|
540
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
541
|
+
}
|
|
542
|
+
async function withAgentSession(metadata, fn, options = {}) {
|
|
543
|
+
const startedAt = toIsoString$2(metadata.session?.startedAt);
|
|
544
|
+
return withAgentAction({
|
|
545
|
+
...metadata,
|
|
546
|
+
category: metadata.category ?? "agent_session",
|
|
547
|
+
session: {
|
|
548
|
+
...metadata.session,
|
|
549
|
+
status: metadata.session?.status ?? "active",
|
|
550
|
+
startedAt
|
|
551
|
+
}
|
|
552
|
+
}, async (ctx, logger) => {
|
|
553
|
+
try {
|
|
554
|
+
const result = await fn(ctx, logger);
|
|
555
|
+
const completed = {
|
|
556
|
+
...metadata,
|
|
557
|
+
outcome: metadata.outcome ?? "success",
|
|
558
|
+
session: {
|
|
559
|
+
...metadata.session,
|
|
560
|
+
status: "completed",
|
|
561
|
+
startedAt,
|
|
562
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
setAgentAttributes(completed, ctx);
|
|
566
|
+
logger.set(buildLifecycleUpdateContext(completed));
|
|
567
|
+
return result;
|
|
568
|
+
} catch (error) {
|
|
569
|
+
const failed = {
|
|
570
|
+
...metadata,
|
|
571
|
+
outcome: "failure",
|
|
572
|
+
session: {
|
|
573
|
+
...metadata.session,
|
|
574
|
+
status: "failed",
|
|
575
|
+
startedAt,
|
|
576
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
setAgentAttributes(failed, ctx);
|
|
580
|
+
logger.set(buildLifecycleUpdateContext(failed));
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
}, options);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
//#endregion
|
|
587
|
+
//#region src/agent/identity-registry.ts
|
|
588
|
+
function toIsoString$1(value) {
|
|
589
|
+
if (!value) return void 0;
|
|
590
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
591
|
+
}
|
|
592
|
+
function normalizeScopes$1(scope) {
|
|
593
|
+
if (!scope) return [];
|
|
594
|
+
return Array.isArray(scope) ? [...scope] : [scope];
|
|
595
|
+
}
|
|
596
|
+
function isExpired(record, at) {
|
|
597
|
+
return record.expiresAt !== void 0 && record.expiresAt < at;
|
|
598
|
+
}
|
|
599
|
+
function createAgentIdentityRegistry(initial = []) {
|
|
600
|
+
const records = /* @__PURE__ */ new Map();
|
|
601
|
+
const provisionIdentity = (input) => {
|
|
602
|
+
const now = toIsoString$1(input.provisionedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
603
|
+
const record = {
|
|
604
|
+
agent: input.agent,
|
|
605
|
+
scopes: input.scopes ?? [],
|
|
606
|
+
status: "active",
|
|
607
|
+
tokenId: input.tokenId,
|
|
608
|
+
tokenHash: input.tokenId === void 0 ? void 0 : hashPayload(input.tokenId),
|
|
609
|
+
delegatedBy: input.delegatedBy,
|
|
610
|
+
provisionedAt: now,
|
|
611
|
+
expiresAt: toIsoString$1(input.expiresAt),
|
|
612
|
+
metadata: input.metadata
|
|
613
|
+
};
|
|
614
|
+
records.set(input.agent.id, record);
|
|
615
|
+
return record;
|
|
616
|
+
};
|
|
617
|
+
for (const item of initial) provisionIdentity(item);
|
|
618
|
+
return {
|
|
619
|
+
provisionIdentity,
|
|
620
|
+
rotateIdentity(agentId, input = {}) {
|
|
621
|
+
const existing = records.get(agentId);
|
|
622
|
+
if (!existing) throw new Error(`[autotel-genai] Cannot rotate unknown agent identity "${agentId}".`);
|
|
623
|
+
const rotatedAt = toIsoString$1(input.rotatedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
624
|
+
const record = {
|
|
625
|
+
...existing,
|
|
626
|
+
scopes: input.scopes ?? existing.scopes,
|
|
627
|
+
status: "rotated",
|
|
628
|
+
tokenId: input.tokenId ?? existing.tokenId,
|
|
629
|
+
tokenHash: input.tokenId === void 0 ? existing.tokenHash : hashPayload(input.tokenId),
|
|
630
|
+
delegatedBy: input.delegatedBy ?? existing.delegatedBy,
|
|
631
|
+
rotatedAt,
|
|
632
|
+
expiresAt: toIsoString$1(input.expiresAt) ?? existing.expiresAt,
|
|
633
|
+
metadata: input.metadata ?? existing.metadata
|
|
634
|
+
};
|
|
635
|
+
records.set(agentId, record);
|
|
636
|
+
return record;
|
|
637
|
+
},
|
|
638
|
+
revokeIdentity(agentId, input) {
|
|
639
|
+
const existing = records.get(agentId);
|
|
640
|
+
if (!existing) throw new Error(`[autotel-genai] Cannot revoke unknown agent identity "${agentId}".`);
|
|
641
|
+
const revokedAt = toIsoString$1(input.revokedAt) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
642
|
+
const record = {
|
|
643
|
+
...existing,
|
|
644
|
+
status: "revoked",
|
|
645
|
+
revokedAt,
|
|
646
|
+
revocationReason: input.reason
|
|
647
|
+
};
|
|
648
|
+
records.set(agentId, record);
|
|
649
|
+
return record;
|
|
650
|
+
},
|
|
651
|
+
getIdentity(agentId) {
|
|
652
|
+
return records.get(agentId);
|
|
653
|
+
},
|
|
654
|
+
getIdentityStatus(agentId, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
655
|
+
const record = records.get(agentId);
|
|
656
|
+
if (!record) return;
|
|
657
|
+
return isExpired(record, at) ? "expired" : record.status;
|
|
658
|
+
},
|
|
659
|
+
assertUsable(agentId, at = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
660
|
+
const record = records.get(agentId);
|
|
661
|
+
if (!record) throw new Error(`[autotel-genai] Unknown agent identity "${agentId}". Provision it before use.`);
|
|
662
|
+
const status = isExpired(record, at) ? "expired" : record.status;
|
|
663
|
+
if (status !== "active" && status !== "rotated") throw new Error(`[autotel-genai] Agent identity "${agentId}" is ${status} and cannot execute delegated work.`);
|
|
664
|
+
return record;
|
|
665
|
+
},
|
|
666
|
+
assertScopes(agentId, requiredScopes) {
|
|
667
|
+
const record = this.assertUsable(agentId);
|
|
668
|
+
const missing = requiredScopes.filter((scope) => !record.scopes.includes(scope));
|
|
669
|
+
if (missing.length > 0) throw new Error(`[autotel-genai] Agent identity "${agentId}" is missing delegated scopes: ${missing.join(", ")}.`);
|
|
670
|
+
return record;
|
|
671
|
+
},
|
|
672
|
+
issueDelegation(agentId, input) {
|
|
673
|
+
const record = this.assertUsable(agentId);
|
|
674
|
+
const scope = normalizeScopes$1(input.scope);
|
|
675
|
+
if (scope.length > 0) this.assertScopes(agentId, scope);
|
|
676
|
+
return delegateToAgent({
|
|
677
|
+
parentIdentity: input.parentIdentity,
|
|
678
|
+
targetAgentId: record.agent.id,
|
|
679
|
+
scope: input.scope ?? record.scopes,
|
|
680
|
+
tokenId: input.tokenId ?? record.tokenId,
|
|
681
|
+
delegationId: input.delegationId,
|
|
682
|
+
authorityLineage: input.authorityLineage,
|
|
683
|
+
issuedAt: input.issuedAt,
|
|
684
|
+
expiresAt: input.expiresAt ?? record.expiresAt
|
|
685
|
+
});
|
|
686
|
+
},
|
|
687
|
+
list() {
|
|
688
|
+
return [...records.values()];
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/agent/privacy.ts
|
|
695
|
+
const PRIVACY_PROFILES = {
|
|
696
|
+
strict: {
|
|
697
|
+
name: "strict",
|
|
698
|
+
hashKeys: [
|
|
699
|
+
/email/i,
|
|
700
|
+
/phone/i,
|
|
701
|
+
/user_?id/i,
|
|
702
|
+
/account/i,
|
|
703
|
+
/customer/i,
|
|
704
|
+
/card/i,
|
|
705
|
+
/iban/i,
|
|
706
|
+
/tax/i
|
|
707
|
+
],
|
|
708
|
+
dropKeys: [
|
|
709
|
+
/secret/i,
|
|
710
|
+
/token/i,
|
|
711
|
+
/api[_-]?key/i,
|
|
712
|
+
/authorization/i,
|
|
713
|
+
/cookie/i,
|
|
714
|
+
/password/i,
|
|
715
|
+
/bearer/i
|
|
716
|
+
],
|
|
717
|
+
maskKeys: [
|
|
718
|
+
/name/i,
|
|
719
|
+
/address/i,
|
|
720
|
+
/prompt/i,
|
|
721
|
+
/message/i,
|
|
722
|
+
/content/i
|
|
723
|
+
],
|
|
724
|
+
maxStringLength: 256
|
|
725
|
+
},
|
|
726
|
+
pci: {
|
|
727
|
+
name: "pci",
|
|
728
|
+
hashKeys: [
|
|
729
|
+
/card/i,
|
|
730
|
+
/pan/i,
|
|
731
|
+
/account/i,
|
|
732
|
+
/customer/i,
|
|
733
|
+
/email/i
|
|
734
|
+
],
|
|
735
|
+
dropKeys: [
|
|
736
|
+
/cvv/i,
|
|
737
|
+
/cvc/i,
|
|
738
|
+
/secret/i,
|
|
739
|
+
/token/i,
|
|
740
|
+
/api[_-]?key/i
|
|
741
|
+
],
|
|
742
|
+
maskKeys: [/name/i, /address/i],
|
|
743
|
+
maxStringLength: 128
|
|
744
|
+
},
|
|
745
|
+
healthcare: {
|
|
746
|
+
name: "healthcare",
|
|
747
|
+
hashKeys: [
|
|
748
|
+
/patient/i,
|
|
749
|
+
/mrn/i,
|
|
750
|
+
/member/i,
|
|
751
|
+
/email/i,
|
|
752
|
+
/phone/i
|
|
753
|
+
],
|
|
754
|
+
dropKeys: [
|
|
755
|
+
/diagnosis/i,
|
|
756
|
+
/notes/i,
|
|
757
|
+
/secret/i,
|
|
758
|
+
/token/i,
|
|
759
|
+
/password/i
|
|
760
|
+
],
|
|
761
|
+
maskKeys: [
|
|
762
|
+
/name/i,
|
|
763
|
+
/address/i,
|
|
764
|
+
/symptom/i
|
|
765
|
+
],
|
|
766
|
+
maxStringLength: 128
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
function maskValue(value) {
|
|
770
|
+
if (typeof value !== "string") return "<masked>";
|
|
771
|
+
if (value.length <= 6) return "***";
|
|
772
|
+
return `${value.slice(0, 3)}***${value.slice(-3)}`;
|
|
773
|
+
}
|
|
774
|
+
function matches(patterns, key) {
|
|
775
|
+
return patterns?.some((pattern) => pattern.test(key)) ?? false;
|
|
776
|
+
}
|
|
777
|
+
function truncateString(value, maxLength) {
|
|
778
|
+
if (maxLength === void 0 || value.length <= maxLength) return value;
|
|
779
|
+
return `${value.slice(0, maxLength)}…`;
|
|
780
|
+
}
|
|
781
|
+
function sanitizeNode(value, profile, keyPath) {
|
|
782
|
+
if (value instanceof Date) return value.toISOString();
|
|
783
|
+
const lowered = keyPath.toLowerCase();
|
|
784
|
+
if (matches(profile.dropKeys, lowered)) return "<redacted>";
|
|
785
|
+
if (matches(profile.hashKeys, lowered)) return hashPayload(value);
|
|
786
|
+
if (matches(profile.maskKeys, lowered)) return maskValue(value);
|
|
787
|
+
if (typeof value === "string") return truncateString(value, profile.maxStringLength);
|
|
788
|
+
if (Array.isArray(value)) return value.map((entry, index) => sanitizeNode(entry, profile, `${keyPath}[${index}]`));
|
|
789
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, sanitizeNode(entry, profile, keyPath ? `${keyPath}.${key}` : key)]));
|
|
790
|
+
if (typeof value === "bigint") return value.toString(10);
|
|
791
|
+
return value;
|
|
792
|
+
}
|
|
793
|
+
function resolvePrivacyProfile(profile = "strict") {
|
|
794
|
+
return typeof profile === "string" ? PRIVACY_PROFILES[profile] : profile;
|
|
795
|
+
}
|
|
796
|
+
function sanitizeAuditPayload(value, profile = "strict") {
|
|
797
|
+
return sanitizeNode(value, resolvePrivacyProfile(profile), "");
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region src/agent/non-repudiation.ts
|
|
802
|
+
function toIsoString(value) {
|
|
803
|
+
if (!value) return (/* @__PURE__ */ new Date()).toISOString();
|
|
804
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
805
|
+
}
|
|
806
|
+
async function createSignedEventEnvelope(metadata, options = {}) {
|
|
807
|
+
const normalized = normalizeMetadata(metadata);
|
|
808
|
+
const envelopeBase = {
|
|
809
|
+
schemaVersion: normalized.schemaVersion ?? "1.1.0",
|
|
810
|
+
emittedAt: toIsoString(options.emittedAt),
|
|
811
|
+
...options.previousEventHash !== void 0 && { previousEventHash: options.previousEventHash },
|
|
812
|
+
metadata: normalized,
|
|
813
|
+
...options.evidence !== void 0 && { evidence: sanitizeAuditPayload(options.evidence, options.privacyProfile ?? "strict") }
|
|
814
|
+
};
|
|
815
|
+
const eventHash = hashPayload(envelopeBase);
|
|
816
|
+
const signature = options.signer ? await options.signer(canonicalizeForHash(envelopeBase)) : void 0;
|
|
817
|
+
return {
|
|
818
|
+
...envelopeBase,
|
|
819
|
+
eventHash,
|
|
820
|
+
...signature !== void 0 && { signature }
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function verifyEventEnvelopeHash(envelope) {
|
|
824
|
+
const expected = hashPayload({
|
|
825
|
+
schemaVersion: envelope.schemaVersion,
|
|
826
|
+
emittedAt: envelope.emittedAt,
|
|
827
|
+
...envelope.previousEventHash !== void 0 && { previousEventHash: envelope.previousEventHash },
|
|
828
|
+
metadata: envelope.metadata,
|
|
829
|
+
...envelope.evidence !== void 0 && { evidence: envelope.evidence }
|
|
830
|
+
});
|
|
831
|
+
return envelope.eventHash === expected;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
//#endregion
|
|
835
|
+
//#region src/agent/scoped-tool.ts
|
|
836
|
+
function normalizeScopes(scope) {
|
|
837
|
+
if (!scope) return [];
|
|
838
|
+
return Array.isArray(scope) ? scope : [scope];
|
|
839
|
+
}
|
|
840
|
+
function missingScopes(delegated, required) {
|
|
841
|
+
return required.filter((scope) => !delegated.includes(scope));
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Decide whether a scoped tool call must be denied.
|
|
845
|
+
*
|
|
846
|
+
* When the identity is registry-backed, the registry is authoritative: a
|
|
847
|
+
* delegation may only *narrow* the stored grant, never widen it. Scopes a
|
|
848
|
+
* caller claims that the registry never granted are a forged escalation and are
|
|
849
|
+
* denied before any missing-scope check — otherwise passing an explicit
|
|
850
|
+
* `delegation.scope` could grant access the registry record does not allow.
|
|
851
|
+
*/
|
|
852
|
+
function resolveScopeDenial(claimedScopes, requiredScopes, registryScopes) {
|
|
853
|
+
const unauthorized = registryScopes ? claimedScopes.filter((scope) => !registryScopes.includes(scope)) : [];
|
|
854
|
+
if (unauthorized.length > 0) return {
|
|
855
|
+
scopes: unauthorized,
|
|
856
|
+
reason: `unauthorized_scope:${unauthorized.join(",")}`,
|
|
857
|
+
why: `Delegation claims scopes the identity was never granted: ${unauthorized.join(", ")}`
|
|
858
|
+
};
|
|
859
|
+
const missing = missingScopes(claimedScopes, requiredScopes);
|
|
860
|
+
if (missing.length > 0) return {
|
|
861
|
+
scopes: missing,
|
|
862
|
+
reason: `missing_scope:${missing.join(",")}`,
|
|
863
|
+
why: `Missing delegated scopes: ${missing.join(", ")}`
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function resolveDelegation(definition) {
|
|
867
|
+
const registry = definition.identityRegistry;
|
|
868
|
+
if (registry) registry.assertUsable(definition.agent.id);
|
|
869
|
+
return definition.delegation;
|
|
870
|
+
}
|
|
871
|
+
function buildGovernance(governance, reviewRequired) {
|
|
872
|
+
if (!governance && reviewRequired === void 0) return governance;
|
|
873
|
+
return {
|
|
874
|
+
...governance,
|
|
875
|
+
...reviewRequired !== void 0 && { reviewRequired }
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function buildDecision(input, decision, privacyProfile) {
|
|
879
|
+
if (!decision) return void 0;
|
|
880
|
+
const sanitizedInput = sanitizeAuditPayload(input, privacyProfile ?? "strict");
|
|
881
|
+
return {
|
|
882
|
+
...decision,
|
|
883
|
+
inputHash: decision.inputHash ?? hashPayload(sanitizedInput)
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
async function withScopedTool(definition, input, fn, options = {}) {
|
|
887
|
+
const requiredScopes = definition.requiredScopes ?? [];
|
|
888
|
+
const delegation = resolveDelegation(definition);
|
|
889
|
+
const registryScopes = definition.identityRegistry?.getIdentity(definition.agent.id)?.scopes;
|
|
890
|
+
const denial = resolveScopeDenial(normalizeScopes(delegation?.scope ?? registryScopes), requiredScopes, registryScopes);
|
|
891
|
+
const policy = definition.policyId || definition.riskScore !== void 0 || denial ? {
|
|
892
|
+
decision: denial ? "deny" : "permit",
|
|
893
|
+
...definition.policyId !== void 0 && { policyId: definition.policyId },
|
|
894
|
+
...definition.riskScore !== void 0 && { riskScore: definition.riskScore },
|
|
895
|
+
...denial && { reason: denial.reason }
|
|
896
|
+
} : void 0;
|
|
897
|
+
const governance = buildGovernance(definition.governance, definition.reviewRequired);
|
|
898
|
+
if (policy && policy.decision === "deny" && denial) {
|
|
899
|
+
recordPolicyDecision({
|
|
900
|
+
action: definition.action,
|
|
901
|
+
resource: definition.resource ?? definition.tool.name,
|
|
902
|
+
category: definition.category,
|
|
903
|
+
agent: definition.agent,
|
|
904
|
+
delegation,
|
|
905
|
+
policy,
|
|
906
|
+
governance,
|
|
907
|
+
decision: buildDecision(input, definition.decision, definition.privacyProfile)
|
|
908
|
+
}, options);
|
|
909
|
+
throw createStructuredError({
|
|
910
|
+
status: 403,
|
|
911
|
+
code: "AGENT_SCOPE_DENIED",
|
|
912
|
+
message: `Agent "${definition.agent.id}" cannot invoke ${definition.tool.name}.`,
|
|
913
|
+
why: denial.why,
|
|
914
|
+
fix: "Grant the missing scopes or route the task to an agent with the required delegation."
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
if (policy) recordPolicyDecision({
|
|
918
|
+
action: `${definition.action}.policy`,
|
|
919
|
+
resource: definition.resource ?? definition.tool.name,
|
|
920
|
+
category: definition.category,
|
|
921
|
+
agent: definition.agent,
|
|
922
|
+
delegation,
|
|
923
|
+
policy,
|
|
924
|
+
governance
|
|
925
|
+
}, options);
|
|
926
|
+
if (definition.decision) recordDecisionBasis({
|
|
927
|
+
action: `${definition.action}.decision`,
|
|
928
|
+
resource: definition.resource ?? definition.tool.name,
|
|
929
|
+
category: definition.category,
|
|
930
|
+
agent: definition.agent,
|
|
931
|
+
delegation,
|
|
932
|
+
governance,
|
|
933
|
+
decision: buildDecision(input, definition.decision, definition.privacyProfile)
|
|
934
|
+
}, options);
|
|
935
|
+
return withAgentToolCall({
|
|
936
|
+
action: definition.action,
|
|
937
|
+
resource: definition.resource ?? definition.tool.name,
|
|
938
|
+
category: definition.category,
|
|
939
|
+
agent: definition.agent,
|
|
940
|
+
delegation,
|
|
941
|
+
governance,
|
|
942
|
+
tool: {
|
|
943
|
+
...definition.tool,
|
|
944
|
+
input
|
|
945
|
+
}
|
|
946
|
+
}, fn, options);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
//#endregion
|
|
950
|
+
export { setAgentAttributes as _, sanitizeAuditPayload as a, hashPayload as b, delegateToAgent as c, defineAgentToolCall as d, recordDecisionBasis as f, flattenAgentAttributes as g, withAgentToolCall as h, resolvePrivacyProfile as i, recordAgentHandoff as l, withAgentAction as m, createSignedEventEnvelope as n, createAgentIdentityRegistry as o, recordPolicyDecision as p, verifyEventEnvelopeHash as r, withAgentSession as s, withScopedTool as t, defineAgentAction as u, createAgentAuditMetadata as v, AGENT_AUDIT_SCHEMA_VERSION as x, canonicalizeForHash as y };
|
|
951
|
+
//# sourceMappingURL=agent-CqXauTpC.js.map
|