autotel-genai 0.3.1 → 0.3.2

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.
Files changed (40) hide show
  1. package/README.md +105 -4
  2. package/dist/agent/index.js +2 -1
  3. package/dist/agent-security-ChL0rIh-.js +181 -0
  4. package/dist/agent-security-ChL0rIh-.js.map +1 -0
  5. package/dist/{agent-DthMDQbW.js → agent-yHB-GF6g.js} +96 -3
  6. package/dist/agent-yHB-GF6g.js.map +1 -0
  7. package/dist/ai-sdk-bridge.cjs +31 -0
  8. package/dist/ai-sdk-bridge.cjs.map +1 -1
  9. package/dist/ai-sdk-bridge.d.cts +42 -1
  10. package/dist/ai-sdk-bridge.d.cts.map +1 -1
  11. package/dist/ai-sdk-bridge.d.ts +42 -1
  12. package/dist/ai-sdk-bridge.d.ts.map +1 -1
  13. package/dist/ai-sdk-bridge.js +30 -1
  14. package/dist/ai-sdk-bridge.js.map +1 -1
  15. package/dist/{index-CIkLg2LA.d.ts → index-BiTlgIvW.d.ts} +255 -2
  16. package/dist/index-BiTlgIvW.d.ts.map +1 -0
  17. package/dist/{index-CGHFYLGQ.d.cts → index-BkYnyG--.d.cts} +255 -2
  18. package/dist/index-BkYnyG--.d.cts.map +1 -0
  19. package/dist/index.cjs +7 -1
  20. package/dist/index.d.cts +3 -3
  21. package/dist/index.d.ts +3 -3
  22. package/dist/index.js +4 -4
  23. package/dist/observer/index.cjs +6 -2
  24. package/dist/observer/index.d.cts +2 -2
  25. package/dist/observer/index.d.ts +2 -2
  26. package/dist/observer/index.js +2 -2
  27. package/dist/observer-CNaW3fb1.js +1060 -0
  28. package/dist/observer-CNaW3fb1.js.map +1 -0
  29. package/dist/{observer-BbhXgZBA.cjs → observer-YBI0hQqC.cjs} +551 -14
  30. package/dist/observer-YBI0hQqC.cjs.map +1 -0
  31. package/package.json +3 -3
  32. package/skills/autotel-genai/SKILL.md +25 -3
  33. package/dist/agent-DthMDQbW.js.map +0 -1
  34. package/dist/context-B2wjX3O1.js +0 -73
  35. package/dist/context-B2wjX3O1.js.map +0 -1
  36. package/dist/index-CGHFYLGQ.d.cts.map +0 -1
  37. package/dist/index-CIkLg2LA.d.ts.map +0 -1
  38. package/dist/observer-2SKgcQI_.js +0 -613
  39. package/dist/observer-2SKgcQI_.js.map +0 -1
  40. package/dist/observer-BbhXgZBA.cjs.map +0 -1
package/README.md CHANGED
@@ -68,7 +68,8 @@ export const chat = traceGenAI({
68
68
  | **Trace** | `autotel-genai/trace` | `traceGenAI()`, `recordGenAiResponse/Usage` |
69
69
  | **Guard** | `autotel-genai/guard` | inline cost/token/loop kill-switch — `createGenAiBudget`, `createGenAiGuard`, `parseGuardRules` |
70
70
  | **Streaming** | `autotel-genai/streaming` | TTFC, throughput, inter-chunk distribution — `createStreamTimer`, `recordStreamTiming` |
71
- | **AI SDK** | `autotel-genai/ai-sdk` | Vercel `ai.*` `gen_ai.*` mapping + cost |
71
+ | **AI SDK** | `autotel-genai/observer` | `autotelTelemetry()` `registerTelemetry()` integration: live `gen_ai.*` spans + cost + streaming + nested traces + opt-in content. `subscribeAiTelemetry()` — zero-config `ai:telemetry` channel path |
72
+ | **AI SDK (legacy)** | `autotel-genai/ai-sdk` | `ai.*` → `gen_ai.*` mapping + cost for `LegacyOpenTelemetry`/older versions; `autotelEnrich()` for `@ai-sdk/otel` `enrichSpan` |
72
73
  | **Agents** | `autotel-genai/agent` | identity, delegation, policy, audit, privacy, non-repudiation |
73
74
 
74
75
  ### Cost
@@ -201,9 +202,75 @@ It honours spec breaking change #242 (`gen_ai.agent.id` is dropped on internal
201
202
 
202
203
  ### Vercel AI SDK
203
204
 
204
- The current AI SDK (`@ai-sdk/otel`'s `OpenTelemetry` integration) already emits
205
- `gen_ai.*` nothing to map. For `LegacyOpenTelemetry`/older versions, or to add
206
- cost:
205
+ Register `autotelTelemetry()` once and every `generateText` / `streamText` /
206
+ `embed` call streams a canonical `gen_ai.*` span tree — live, as it runs:
207
+
208
+ ```ts
209
+ import { registerTelemetry } from 'ai';
210
+ import { autotelTelemetry } from 'autotel-genai/observer';
211
+
212
+ registerTelemetry(autotelTelemetry()); // once, at startup
213
+ ```
214
+
215
+ See `apps/example-ai-sdk-observer` for a runnable AI SDK + Ollama demo
216
+ (generateText, tool loop, streamText timing, embeddings).
217
+
218
+ It implements the AI SDK's stable `Telemetry` lifecycle interface (ai v7+), so
219
+ it slots in exactly where `@ai-sdk/otel`'s `OpenTelemetry` does — but it also,
220
+ on every `chat` span:
221
+
222
+ - **prices the call** (`gen_ai.usage.cost.usd`) from `MODEL_PRICING`;
223
+ - records **streaming throughput** (`time_to_first_chunk`, `time_to_finish`,
224
+ `output_tokens_per_second`);
225
+ - keeps token usage on leaf `chat` spans only, so the `invoke_agent` root never
226
+ double-counts.
227
+
228
+ It is push-based and concurrency-safe (every event carries the SDK `callId`),
229
+ and it pulls in **no** dependency on `ai` — the returned object satisfies the
230
+ `Telemetry` interface structurally, so the snippet above type-checks as-is.
231
+ `rerank` has no canonical `gen_ai` operation and is intentionally not mapped.
232
+
233
+ **Nested traces.** It implements the SDK's `executeTool` / `executeLanguageModelCall`
234
+ context runners, so a tool whose `execute` calls `generateText` — and the
235
+ provider's own auto-instrumented HTTP spans — nest under the right span
236
+ automatically.
237
+
238
+ **Content capture (opt-in).** Off by default for privacy. Turn it on to record
239
+ prompts, responses, system instructions, and tool I/O, mapped to the
240
+ [GenAI SemConv message format](#genai-message-format). The SDK's per-call
241
+ `recordInputs` / `recordOutputs` are honored, and `exportContent` lets you redact
242
+ or drop content per event:
243
+
244
+ ```ts
245
+ registerTelemetry(
246
+ autotelTelemetry({
247
+ captureContent: true,
248
+ exportContent: (event) => redact(event), // optional: redact before write
249
+ }),
250
+ );
251
+ ```
252
+
253
+ **Zero-config (no `registerTelemetry`).** Subscribe to the SDK's `ai:telemetry`
254
+ Node tracing channel instead. The SDK publishes operation spans as soon as the
255
+ channel has a subscriber:
256
+
257
+ ```ts
258
+ import { subscribeAiTelemetry } from 'autotel-genai/observer';
259
+
260
+ const unsubscribe = subscribeAiTelemetry(); // once, at startup
261
+ ```
262
+
263
+ The channel path gives you the same `invoke_agent › chat › execute_tool` tree
264
+ with usage and cost, but not the per-call streaming timing (which only the
265
+ lifecycle `onLanguageModelCallEnd` event carries) — prefer
266
+ `registerTelemetry(autotelTelemetry())` when you can.
267
+
268
+ Register globally, or pass per-call via `telemetry.integrations` to scope it to
269
+ one call. For the **legacy** `LegacyOpenTelemetry`/older-version path, or to
270
+ enrich spans another integration already emitted, the attribute bridge maps
271
+ `ai.*` → `gen_ai.*` and adds cost; for versions before the `Telemetry` interface,
272
+ walk the finished result with `observeAiSdkResult` (see
273
+ [Observer](#observer-event-stream--spans)):
207
274
 
208
275
  ```ts
209
276
  import { mapAiSdkAttributes, recordAiSdkCost } from 'autotel-genai/ai-sdk';
@@ -212,6 +279,40 @@ const canonical = mapAiSdkAttributes(span.attributes); // ai.* → gen_ai.*
212
279
  recordAiSdkCost(ctx, span.attributes); // sets gen_ai.usage.cost.usd
213
280
  ```
214
281
 
282
+ #### Already using `@ai-sdk/otel`?
283
+
284
+ Drop `autotelEnrich()` into its `enrichSpan` to stamp autotel provenance and
285
+ promote your `runtimeContext` onto every span:
286
+
287
+ ```ts
288
+ import { OpenTelemetry } from '@ai-sdk/otel';
289
+ import { autotelEnrich } from 'autotel-genai/ai-sdk';
290
+
291
+ registerTelemetry(new OpenTelemetry({ enrichSpan: autotelEnrich() }));
292
+ ```
293
+
294
+ `enrichSpan` **cannot add cost** — the SDK passes it only
295
+ `{ spanType, operationId, callId, runtimeContext }` (no usage, no model), and its
296
+ own attributes win over custom keys. To get `gen_ai.usage.cost.usd` on the model
297
+ span, use `autotelTelemetry()` (it owns span creation). Either way,
298
+ [`autotel-devtools`](../autotel-devtools) prices `gen_ai` spans on render, so cost
299
+ shows there regardless of which integration emitted them.
300
+
301
+ #### Local devtools, one line
302
+
303
+ Point an OTLP exporter at a running `autotel-devtools` receiver and you get a
304
+ live GenAI run view — cost, token breakdown, tool timeline, and a narrated
305
+ "Explain run" walkthrough — that works in production too (unlike
306
+ `@ai-sdk/devtools`, which is dev-only):
307
+
308
+ ```ts
309
+ import { registerTelemetry } from 'ai';
310
+ import { autotelTelemetry } from 'autotel-genai/observer';
311
+
312
+ registerTelemetry(autotelTelemetry()); // → your OTLP pipeline → autotel-devtools
313
+ // npx autotel-devtools → http://localhost:4318
314
+ ```
315
+
215
316
  ### Observer (event-stream → spans)
216
317
 
217
318
  When you instrument a framework that emits its own lifecycle stream (agent
@@ -1,3 +1,4 @@
1
- import { A as withAgentAction, C as withAgentSession, D as defineAgentToolCall, E as defineAgentAction, F as canonicalizeForHash, I as hashPayload, L as AGENT_AUDIT_SCHEMA_VERSION, M as flattenAgentAttributes, N as setAgentAttributes, O as recordDecisionBasis, P as createAgentAuditMetadata, S as createAgentIdentityRegistry, T as recordAgentHandoff, _ as withScopedTool, a as heuristicPlanRiskClassifier, b as resolvePrivacyProfile, c as recordControllerId, d as recordMemoryAccess, f as recordPlanRiskAssessment, g as tryRecordHumanApproval, h as runAgentPlanClassifier, i as deriveActionRiskClass, j as withAgentToolCall, k as recordPolicyDecision, l as recordHumanApproval, m as recordRenderOutput, n as AGENT_SECURITY_ATTR, o as recordActionRiskClass, p as recordPlanStep, r as agentContextFromSpan, s as recordActiveScopes, t as AGENT_PLAN_RISK_ATTR, u as recordInputProvenance, v as createSignedEventEnvelope, w as delegateToAgent, x as sanitizeAuditPayload, y as verifyEventEnvelopeHash } from "../agent-DthMDQbW.js";
1
+ import { a as recordControllerId, c as recordMemoryAccess, d as tryRecordHumanApproval, i as recordActiveScopes, l as recordPlanStep, n as deriveActionRiskClass, o as recordHumanApproval, p as agentContextFromSpan, r as recordActionRiskClass, s as recordInputProvenance, t as AGENT_SECURITY_ATTR, u as recordRenderOutput } from "../agent-security-ChL0rIh-.js";
2
+ import { C as canonicalizeForHash, S as createAgentAuditMetadata, T as AGENT_AUDIT_SCHEMA_VERSION, _ as recordPolicyDecision, a as withScopedTool, b as flattenAgentAttributes, c as resolvePrivacyProfile, d as withAgentSession, f as delegateToAgent, g as recordDecisionBasis, h as defineAgentToolCall, i as runAgentPlanClassifier, l as sanitizeAuditPayload, m as defineAgentAction, n as heuristicPlanRiskClassifier, o as createSignedEventEnvelope, p as recordAgentHandoff, r as recordPlanRiskAssessment, s as verifyEventEnvelopeHash, t as AGENT_PLAN_RISK_ATTR, u as createAgentIdentityRegistry, v as withAgentAction, w as hashPayload, x as setAgentAttributes, y as withAgentToolCall } from "../agent-yHB-GF6g.js";
2
3
 
3
4
  export { AGENT_AUDIT_SCHEMA_VERSION, AGENT_PLAN_RISK_ATTR, AGENT_SECURITY_ATTR, agentContextFromSpan, canonicalizeForHash, createAgentAuditMetadata, createAgentIdentityRegistry, createSignedEventEnvelope, defineAgentAction, defineAgentToolCall, delegateToAgent, deriveActionRiskClass, flattenAgentAttributes, hashPayload, heuristicPlanRiskClassifier, recordActionRiskClass, recordActiveScopes, recordAgentHandoff, recordControllerId, recordDecisionBasis, recordHumanApproval, recordInputProvenance, recordMemoryAccess, recordPlanRiskAssessment, recordPlanStep, recordPolicyDecision, recordRenderOutput, resolvePrivacyProfile, runAgentPlanClassifier, sanitizeAuditPayload, setAgentAttributes, tryRecordHumanApproval, verifyEventEnvelopeHash, withAgentAction, withAgentSession, withAgentToolCall, withScopedTool };
@@ -0,0 +1,181 @@
1
+ import { getTraceContext, otelTrace } from "autotel";
2
+ import { hashIdentifier } from "autotel-audit";
3
+
4
+ //#region src/agent/context.ts
5
+ 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.";
6
+ /**
7
+ * Resolve an agent context without throwing. Returns `null` when no trace context
8
+ * is available, so callers can degrade gracefully (best-effort instrumentation).
9
+ */
10
+ const INVALID_TRACE_ID = "00000000000000000000000000000000";
11
+ function resolveContextSafe(ctx) {
12
+ if (ctx) return ctx;
13
+ const span = otelTrace.getActiveSpan();
14
+ if (!span) return null;
15
+ const ids = getTraceContext();
16
+ const sc = span.spanContext();
17
+ const traceId = ids?.traceId ?? sc.traceId;
18
+ if (!traceId || traceId === INVALID_TRACE_ID) return null;
19
+ return {
20
+ traceId,
21
+ spanId: ids?.spanId ?? sc.spanId,
22
+ correlationId: ids?.correlationId ?? traceId.slice(0, 16),
23
+ setAttribute: (key, value) => span.setAttribute(key, value),
24
+ setAttributes: (attrs) => span.setAttributes(attrs)
25
+ };
26
+ }
27
+ function resolveContext(ctx) {
28
+ const resolved = resolveContextSafe(ctx);
29
+ if (resolved) return resolved;
30
+ throw new Error(MISSING_CONTEXT_MESSAGE);
31
+ }
32
+ const warnedMissingContext = /* @__PURE__ */ new Set();
33
+ /** Warn (once per action) that an agent action is running without a trace context. */
34
+ function warnMissingContextOnce(action) {
35
+ if (warnedMissingContext.has(action)) return;
36
+ warnedMissingContext.add(action);
37
+ 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)`);
38
+ }
39
+ /** Adapt an OpenTelemetry span (or span-like object) to {@link AgentContext}. */
40
+ function agentContextFromSpan(span) {
41
+ const sc = span.spanContext();
42
+ return {
43
+ traceId: sc.traceId,
44
+ spanId: sc.spanId,
45
+ correlationId: sc.traceId.slice(0, 16),
46
+ setAttribute: (key, value) => span.setAttribute(key, value),
47
+ setAttributes: (attrs) => span.setAttributes(attrs)
48
+ };
49
+ }
50
+ function toAttributeValue(value) {
51
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return value;
52
+ if (Array.isArray(value)) {
53
+ if (value.every((entry) => typeof entry === "string")) return value;
54
+ if (value.every((entry) => typeof entry === "number")) return value;
55
+ if (value.every((entry) => typeof entry === "boolean")) return value;
56
+ try {
57
+ return JSON.stringify(value);
58
+ } catch {
59
+ return "<serialization-failed>";
60
+ }
61
+ }
62
+ if (value instanceof Date) return value.toISOString();
63
+ if (typeof value === "bigint") return value.toString(10);
64
+ if (value === null || value === void 0) return;
65
+ try {
66
+ return JSON.stringify(value);
67
+ } catch {
68
+ return "<serialization-failed>";
69
+ }
70
+ }
71
+
72
+ //#endregion
73
+ //#region src/agent/agent-security.ts
74
+ /** Canonical agent security attribute keys (Google SAIF / human-control aligned). */
75
+ const AGENT_SECURITY_ATTR = {
76
+ controllerId: "agent.controller.id",
77
+ inputProvenance: "agent.input.provenance",
78
+ actionRiskClass: "agent.action.risk_class",
79
+ consentRequired: "agent.consent.required",
80
+ consentOutcome: "agent.consent.outcome",
81
+ scopeActive: "agent.scope.active",
82
+ memoryOperation: "agent.memory.operation",
83
+ memoryIsolationKey: "agent.memory.isolation_key",
84
+ memoryContentHash: "agent.memory.content_hash",
85
+ planStepIndex: "agent.plan.step_index",
86
+ planToolIntents: "agent.plan.tool_intents",
87
+ planPolicyIds: "agent.plan.policy_ids",
88
+ outputFormat: "agent.output.format",
89
+ outputContainsUrl: "agent.output.contains_url",
90
+ outputUrlCount: "agent.output.url_count"
91
+ };
92
+ function setSecurityAttr(ctx, key, value) {
93
+ const attr = toAttributeValue(value);
94
+ if (attr === void 0) return;
95
+ const traceCtx = resolveContext(ctx);
96
+ if (Array.isArray(attr)) {
97
+ traceCtx.setAttributes({ [key]: attr });
98
+ return;
99
+ }
100
+ traceCtx.setAttribute(key, attr);
101
+ }
102
+ function setSecurityAttrs(ctx, attrs) {
103
+ const traceCtx = resolveContext(ctx);
104
+ const mapped = {};
105
+ for (const [key, value] of Object.entries(attrs)) {
106
+ const attr = toAttributeValue(value);
107
+ if (attr !== void 0) mapped[key] = attr;
108
+ }
109
+ if (Object.keys(mapped).length > 0) traceCtx.setAttributes(mapped);
110
+ }
111
+ /**
112
+ * Derive a coarse action risk class from MCP tool hints or explicit overrides.
113
+ */
114
+ function deriveActionRiskClass(hints, override) {
115
+ if (override) return override;
116
+ if (hints.financial) return "financial";
117
+ if (hints.exfiltrationCapable || hints.openWorldHint) return "exfiltration_capable";
118
+ if (hints.destructiveHint) return "destructive";
119
+ if (hints.readOnlyHint) return "read";
120
+ if (hints.untrustedContentHint) return "read";
121
+ return "write";
122
+ }
123
+ function recordControllerId(input) {
124
+ const id = input.hash === false ? input.controllerId : hashIdentifier(input.controllerId, { salt: input.hashSalt });
125
+ setSecurityAttr(input.ctx, AGENT_SECURITY_ATTR.controllerId, id);
126
+ }
127
+ function recordInputProvenance(input) {
128
+ setSecurityAttr(input.ctx, AGENT_SECURITY_ATTR.inputProvenance, input.provenance);
129
+ }
130
+ function recordHumanApproval(input) {
131
+ const attrs = {
132
+ [AGENT_SECURITY_ATTR.consentRequired]: input.required ?? true,
133
+ [AGENT_SECURITY_ATTR.consentOutcome]: input.approved ? "approved" : "denied",
134
+ "tool.call.id": input.toolCallId
135
+ };
136
+ if (input.toolName) attrs["tool.name"] = input.toolName;
137
+ if (input.controllerId) attrs[AGENT_SECURITY_ATTR.controllerId] = hashIdentifier(input.controllerId, { salt: input.hashSalt });
138
+ setSecurityAttrs(input.ctx, attrs);
139
+ }
140
+ function recordActiveScopes(input) {
141
+ setSecurityAttr(input.ctx, AGENT_SECURITY_ATTR.scopeActive, input.scopes);
142
+ }
143
+ function recordActionRiskClass(riskClass, options = {}) {
144
+ setSecurityAttr(options.ctx, AGENT_SECURITY_ATTR.actionRiskClass, riskClass);
145
+ }
146
+ function recordMemoryAccess(input) {
147
+ setSecurityAttrs(input.ctx, {
148
+ [AGENT_SECURITY_ATTR.memoryOperation]: input.operation,
149
+ [AGENT_SECURITY_ATTR.memoryIsolationKey]: input.isolationKey,
150
+ ...input.contentHash !== void 0 && { [AGENT_SECURITY_ATTR.memoryContentHash]: input.contentHash }
151
+ });
152
+ }
153
+ function recordPlanStep(input) {
154
+ setSecurityAttrs(input.ctx, {
155
+ [AGENT_SECURITY_ATTR.planStepIndex]: input.stepIndex,
156
+ ...input.toolIntents !== void 0 && { [AGENT_SECURITY_ATTR.planToolIntents]: input.toolIntents },
157
+ ...input.policyIds !== void 0 && { [AGENT_SECURITY_ATTR.planPolicyIds]: input.policyIds },
158
+ ...input.summary !== void 0 && { "decision.summary": input.summary }
159
+ });
160
+ }
161
+ function recordRenderOutput(input) {
162
+ setSecurityAttrs(input.ctx, {
163
+ ...input.format !== void 0 && { [AGENT_SECURITY_ATTR.outputFormat]: input.format },
164
+ ...input.containsUrl !== void 0 && { [AGENT_SECURITY_ATTR.outputContainsUrl]: input.containsUrl },
165
+ ...input.urlCount !== void 0 && { [AGENT_SECURITY_ATTR.outputUrlCount]: input.urlCount }
166
+ });
167
+ }
168
+ /** Best-effort variant — no throw when trace context is missing. */
169
+ function tryRecordHumanApproval(input) {
170
+ const ctx = resolveContextSafe(input.ctx);
171
+ if (!ctx) return false;
172
+ recordHumanApproval({
173
+ ...input,
174
+ ctx
175
+ });
176
+ return true;
177
+ }
178
+
179
+ //#endregion
180
+ export { warnMissingContextOnce as _, recordControllerId as a, recordMemoryAccess as c, tryRecordHumanApproval as d, MISSING_CONTEXT_MESSAGE as f, toAttributeValue as g, resolveContextSafe as h, recordActiveScopes as i, recordPlanStep as l, resolveContext as m, deriveActionRiskClass as n, recordHumanApproval as o, agentContextFromSpan as p, recordActionRiskClass as r, recordInputProvenance as s, AGENT_SECURITY_ATTR as t, recordRenderOutput as u };
181
+ //# sourceMappingURL=agent-security-ChL0rIh-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-security-ChL0rIh-.js","names":[],"sources":["../src/agent/context.ts","../src/agent/agent-security.ts"],"sourcesContent":["import { getTraceContext, otelTrace } from 'autotel';\n\nexport interface AgentContext {\n traceId: string;\n spanId: string;\n correlationId: string;\n setAttribute(key: string, value: string | number | boolean): void;\n setAttributes(\n attrs: Record<string, string | number | boolean | string[] | number[] | boolean[]>,\n ): void;\n}\n\nexport const MISSING_CONTEXT_MESSAGE =\n '[autotel-genai] No active trace context. Wrap the call in trace()/instrument(), pass options.ctx, ' +\n 'or set options.onMissingContext to \"warn\"/\"skip\" to degrade gracefully instead of throwing.';\n\n/**\n * Resolve an agent context without throwing. Returns `null` when no trace context\n * is available, so callers can degrade gracefully (best-effort instrumentation).\n */\nconst INVALID_TRACE_ID = '00000000000000000000000000000000';\n\nexport function resolveContextSafe(ctx?: AgentContext): AgentContext | null {\n if (ctx) return ctx;\n\n const span = otelTrace.getActiveSpan();\n if (!span) return null;\n\n // Resolve trace ids from autotel's context when available, otherwise from the\n // active OTel span itself. This makes agent audit work in *any* OTel setup —\n // @effect/opentelemetry, a vanilla NodeSDK, autotel-cloudflare's instrumented\n // fetch handler — not just inside autotel's own `trace()`.\n const ids = getTraceContext();\n const sc = span.spanContext();\n const traceId = ids?.traceId ?? sc.traceId;\n if (!traceId || traceId === INVALID_TRACE_ID) return null;\n\n return {\n traceId,\n spanId: ids?.spanId ?? sc.spanId,\n correlationId: ids?.correlationId ?? traceId.slice(0, 16),\n setAttribute: (key, value) => span.setAttribute(key, value),\n setAttributes: (attrs) => span.setAttributes(attrs),\n };\n}\n\nexport function resolveContext(ctx?: AgentContext): AgentContext {\n const resolved = resolveContextSafe(ctx);\n if (resolved) return resolved;\n throw new Error(MISSING_CONTEXT_MESSAGE);\n}\n\nconst warnedMissingContext = new Set<string>();\n\n/** Warn (once per action) that an agent action is running without a trace context. */\nexport function warnMissingContextOnce(action: string): void {\n if (warnedMissingContext.has(action)) return;\n warnedMissingContext.add(action);\n console.warn(\n `[autotel-genai] No active trace context for \"${action}\" — running un-audited. ` +\n 'Wrap the call in trace()/instrument() or pass options.ctx to capture agent audit telemetry. ' +\n '(set options.onMissingContext: \"throw\" to fail fast, or \"skip\" to silence this warning)',\n );\n}\n\n/** A no-op {@link AgentContext} whose attribute setters do nothing. */\nexport function noopAgentContext(): AgentContext {\n return {\n traceId: '',\n spanId: '',\n correlationId: '',\n setAttribute() {},\n setAttributes() {},\n };\n}\n\n/** Adapt an OpenTelemetry span (or span-like object) to {@link AgentContext}. */\nexport function agentContextFromSpan(span: {\n spanContext(): { traceId: string; spanId: string };\n setAttribute(key: string, value: string | number | boolean): void;\n setAttributes(\n attrs: Record<string, string | number | boolean | string[] | number[] | boolean[]>,\n ): void;\n}): AgentContext {\n const sc = span.spanContext();\n return {\n traceId: sc.traceId,\n spanId: sc.spanId,\n correlationId: sc.traceId.slice(0, 16),\n setAttribute: (key, value) => span.setAttribute(key, value),\n setAttributes: (attrs) => span.setAttributes(attrs),\n };\n}\n\nexport function toAttributeValue(\n value: unknown,\n): string | number | boolean | string[] | number[] | boolean[] | undefined {\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n\n if (Array.isArray(value)) {\n if (value.every((entry) => typeof entry === 'string')) {\n return value;\n }\n\n if (value.every((entry) => typeof entry === 'number')) {\n return value;\n }\n\n if (value.every((entry) => typeof entry === 'boolean')) {\n return value;\n }\n\n try {\n return JSON.stringify(value);\n } catch {\n return '<serialization-failed>';\n }\n }\n\n if (value instanceof Date) {\n return value.toISOString();\n }\n\n if (typeof value === 'bigint') {\n return value.toString(10);\n }\n\n if (value === null || value === undefined) {\n return undefined;\n }\n\n try {\n return JSON.stringify(value);\n } catch {\n return '<serialization-failed>';\n }\n}\n","import { hashIdentifier } from 'autotel-audit';\nimport { resolveContext, resolveContextSafe, toAttributeValue, type AgentContext } from './context.js';\n\n/** Canonical agent security attribute keys (Google SAIF / human-control aligned). */\nexport const AGENT_SECURITY_ATTR = {\n controllerId: 'agent.controller.id',\n inputProvenance: 'agent.input.provenance',\n actionRiskClass: 'agent.action.risk_class',\n consentRequired: 'agent.consent.required',\n consentOutcome: 'agent.consent.outcome',\n scopeActive: 'agent.scope.active',\n memoryOperation: 'agent.memory.operation',\n memoryIsolationKey: 'agent.memory.isolation_key',\n memoryContentHash: 'agent.memory.content_hash',\n planStepIndex: 'agent.plan.step_index',\n planToolIntents: 'agent.plan.tool_intents',\n planPolicyIds: 'agent.plan.policy_ids',\n outputFormat: 'agent.output.format',\n outputContainsUrl: 'agent.output.contains_url',\n outputUrlCount: 'agent.output.url_count',\n} as const;\n\nexport type AgentInputProvenance =\n | 'user_direct'\n | 'user_voice'\n | 'rag'\n | 'memory'\n | 'tool_result'\n | 'external_untrusted';\n\nexport type AgentActionRiskClass =\n | 'read'\n | 'write'\n | 'destructive'\n | 'financial'\n | 'exfiltration_capable';\n\nexport type AgentConsentOutcome = 'approved' | 'denied' | 'timeout' | 'revoked';\n\nexport type AgentMemoryOperation = 'read' | 'write' | 'delete' | 'search';\n\nexport type AgentOutputFormat = 'text' | 'markdown' | 'html' | 'json' | 'mixed';\n\nexport interface AgentSecurityRecordOptions {\n ctx?: AgentContext;\n}\n\nexport interface RecordControllerInput extends AgentSecurityRecordOptions {\n /** Controlling human user id — hashed before emission unless already a digest. */\n controllerId: string;\n /** Pass a stable per-deployment salt to `hashIdentifier`. */\n hashSalt?: string;\n /** When false, emit the id as given (must not be raw PII). Default true. */\n hash?: boolean;\n}\n\nexport interface RecordInputProvenanceInput extends AgentSecurityRecordOptions {\n provenance: AgentInputProvenance;\n}\n\nexport interface RecordHumanApprovalInput extends AgentSecurityRecordOptions {\n toolCallId: string;\n toolName?: string;\n approved: boolean;\n required?: boolean;\n controllerId?: string;\n hashSalt?: string;\n}\n\nexport interface RecordActiveScopesInput extends AgentSecurityRecordOptions {\n scopes: string[];\n}\n\nexport interface RecordMemoryAccessInput extends AgentSecurityRecordOptions {\n operation: AgentMemoryOperation;\n isolationKey: string;\n contentHash?: string;\n}\n\nexport interface RecordPlanStepInput extends AgentSecurityRecordOptions {\n stepIndex: number;\n toolIntents?: string[];\n policyIds?: string[];\n summary?: string;\n}\n\nexport interface RecordRenderOutputInput extends AgentSecurityRecordOptions {\n format?: AgentOutputFormat;\n containsUrl?: boolean;\n urlCount?: number;\n}\n\nexport interface ActionRiskHints {\n readOnlyHint?: boolean;\n destructiveHint?: boolean;\n openWorldHint?: boolean;\n untrustedContentHint?: boolean;\n financial?: boolean;\n exfiltrationCapable?: boolean;\n}\n\nfunction setSecurityAttr(\n ctx: AgentContext | undefined,\n key: string,\n value: unknown,\n): void {\n const attr = toAttributeValue(value);\n if (attr === undefined) return;\n const traceCtx = resolveContext(ctx);\n if (Array.isArray(attr)) {\n traceCtx.setAttributes({ [key]: attr });\n return;\n }\n traceCtx.setAttribute(key, attr);\n}\n\nfunction setSecurityAttrs(\n ctx: AgentContext | undefined,\n attrs: Record<string, unknown>,\n): void {\n const traceCtx = resolveContext(ctx);\n const mapped: Record<string, string | number | boolean | string[] | number[] | boolean[]> =\n {};\n for (const [key, value] of Object.entries(attrs)) {\n const attr = toAttributeValue(value);\n if (attr !== undefined) {\n mapped[key] = attr;\n }\n }\n if (Object.keys(mapped).length > 0) {\n traceCtx.setAttributes(mapped);\n }\n}\n\n/**\n * Derive a coarse action risk class from MCP tool hints or explicit overrides.\n */\nexport function deriveActionRiskClass(\n hints: ActionRiskHints,\n override?: AgentActionRiskClass,\n): AgentActionRiskClass {\n if (override) return override;\n if (hints.financial) return 'financial';\n if (hints.exfiltrationCapable || hints.openWorldHint) {\n return 'exfiltration_capable';\n }\n if (hints.destructiveHint) return 'destructive';\n if (hints.readOnlyHint) return 'read';\n if (hints.untrustedContentHint) return 'read';\n return 'write';\n}\n\nexport function recordControllerId(input: RecordControllerInput): void {\n const id =\n input.hash === false\n ? input.controllerId\n : hashIdentifier(input.controllerId, { salt: input.hashSalt });\n setSecurityAttr(input.ctx, AGENT_SECURITY_ATTR.controllerId, id);\n}\n\nexport function recordInputProvenance(input: RecordInputProvenanceInput): void {\n setSecurityAttr(input.ctx, AGENT_SECURITY_ATTR.inputProvenance, input.provenance);\n}\n\nexport function recordHumanApproval(input: RecordHumanApprovalInput): void {\n const attrs: Record<string, unknown> = {\n [AGENT_SECURITY_ATTR.consentRequired]: input.required ?? true,\n [AGENT_SECURITY_ATTR.consentOutcome]: input.approved ? 'approved' : 'denied',\n 'tool.call.id': input.toolCallId,\n };\n if (input.toolName) {\n attrs['tool.name'] = input.toolName;\n }\n if (input.controllerId) {\n attrs[AGENT_SECURITY_ATTR.controllerId] = hashIdentifier(input.controllerId, {\n salt: input.hashSalt,\n });\n }\n setSecurityAttrs(input.ctx, attrs);\n}\n\nexport function recordActiveScopes(input: RecordActiveScopesInput): void {\n setSecurityAttr(input.ctx, AGENT_SECURITY_ATTR.scopeActive, input.scopes);\n}\n\nexport function recordActionRiskClass(\n riskClass: AgentActionRiskClass,\n options: AgentSecurityRecordOptions = {},\n): void {\n setSecurityAttr(options.ctx, AGENT_SECURITY_ATTR.actionRiskClass, riskClass);\n}\n\nexport function recordMemoryAccess(input: RecordMemoryAccessInput): void {\n setSecurityAttrs(input.ctx, {\n [AGENT_SECURITY_ATTR.memoryOperation]: input.operation,\n [AGENT_SECURITY_ATTR.memoryIsolationKey]: input.isolationKey,\n ...(input.contentHash !== undefined && {\n [AGENT_SECURITY_ATTR.memoryContentHash]: input.contentHash,\n }),\n });\n}\n\nexport function recordPlanStep(input: RecordPlanStepInput): void {\n setSecurityAttrs(input.ctx, {\n [AGENT_SECURITY_ATTR.planStepIndex]: input.stepIndex,\n ...(input.toolIntents !== undefined && {\n [AGENT_SECURITY_ATTR.planToolIntents]: input.toolIntents,\n }),\n ...(input.policyIds !== undefined && {\n [AGENT_SECURITY_ATTR.planPolicyIds]: input.policyIds,\n }),\n ...(input.summary !== undefined && { 'decision.summary': input.summary }),\n });\n}\n\nexport function recordRenderOutput(input: RecordRenderOutputInput): void {\n setSecurityAttrs(input.ctx, {\n ...(input.format !== undefined && {\n [AGENT_SECURITY_ATTR.outputFormat]: input.format,\n }),\n ...(input.containsUrl !== undefined && {\n [AGENT_SECURITY_ATTR.outputContainsUrl]: input.containsUrl,\n }),\n ...(input.urlCount !== undefined && {\n [AGENT_SECURITY_ATTR.outputUrlCount]: input.urlCount,\n }),\n });\n}\n\n/** Best-effort variant — no throw when trace context is missing. */\nexport function tryRecordHumanApproval(input: RecordHumanApprovalInput): boolean {\n const ctx = resolveContextSafe(input.ctx);\n if (!ctx) return false;\n recordHumanApproval({ ...input, ctx });\n return true;\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { estimateLLMCost } from "./cost.js";
2
2
  import { i as genAiResponseAttributes, r as genAiRequestAttributes, s as genAiUsageAttributes } from "./attributes-DU-PmLqZ.js";
3
- import { a as toAttributeValue, i as resolveContextSafe, o as warnMissingContextOnce, r as resolveContext, t as MISSING_CONTEXT_MESSAGE } from "./context-B2wjX3O1.js";
3
+ import { _ as warnMissingContextOnce, f as MISSING_CONTEXT_MESSAGE, g as toAttributeValue, h as resolveContextSafe, m as resolveContext } from "./agent-security-ChL0rIh-.js";
4
4
  import { createNoopRequestLogger, createStructuredError, getRequestLoggerSafe } from "autotel";
5
5
  import { forceKeepAuditEvent, securityEvent, withAudit } from "autotel-audit";
6
6
  import { createHash } from "node:crypto";
@@ -903,5 +903,98 @@ async function withScopedTool(definition, input, fn, options = {}) {
903
903
  }
904
904
 
905
905
  //#endregion
906
- export { withAgentAction as A, withAgentSession as C, defineAgentToolCall as D, defineAgentAction as E, canonicalizeForHash as F, hashPayload as I, AGENT_AUDIT_SCHEMA_VERSION as L, flattenAgentAttributes as M, setAgentAttributes as N, recordDecisionBasis as O, createAgentAuditMetadata as P, createAgentIdentityRegistry as S, recordAgentHandoff as T, withScopedTool as _, heuristicPlanRiskClassifier as a, resolvePrivacyProfile as b, recordControllerId as c, recordMemoryAccess as d, recordPlanRiskAssessment as f, tryRecordHumanApproval as g, runAgentPlanClassifier as h, deriveActionRiskClass as i, withAgentToolCall as j, recordPolicyDecision as k, recordHumanApproval as l, recordRenderOutput as m, AGENT_SECURITY_ATTR as n, recordActionRiskClass as o, recordPlanStep as p, agentContextFromSpan as r, recordActiveScopes as s, AGENT_PLAN_RISK_ATTR as t, recordInputProvenance as u, createSignedEventEnvelope as v, delegateToAgent as w, sanitizeAuditPayload as x, verifyEventEnvelopeHash as y };
907
- //# sourceMappingURL=agent-DthMDQbW.js.map
906
+ //#region src/agent/agent-plan-classifier.ts
907
+ /** Canonical plan-risk attribute keys (Google SAIF plan-risk predictor aligned). */
908
+ const AGENT_PLAN_RISK_ATTR = {
909
+ verdict: "agent.plan.risk.verdict",
910
+ score: "agent.plan.risk.score",
911
+ categories: "agent.plan.risk.categories",
912
+ toolSequence: "agent.plan.risk.tool_sequence"
913
+ };
914
+ function setPlanRiskAttrs(ctx, attrs) {
915
+ const traceCtx = resolveContext(ctx);
916
+ const mapped = {};
917
+ for (const [key, value] of Object.entries(attrs)) {
918
+ const attr = toAttributeValue(value);
919
+ if (attr !== void 0) mapped[key] = attr;
920
+ }
921
+ if (Object.keys(mapped).length > 0) traceCtx.setAttributes(mapped);
922
+ }
923
+ /**
924
+ * Stamp plan-risk assessment attrs on the active span.
925
+ */
926
+ function recordPlanRiskAssessment(options) {
927
+ const { assessment, toolSequence } = options;
928
+ setPlanRiskAttrs(options.ctx, {
929
+ [AGENT_PLAN_RISK_ATTR.verdict]: assessment.verdict,
930
+ ...assessment.score !== void 0 && { [AGENT_PLAN_RISK_ATTR.score]: assessment.score },
931
+ ...assessment.categories?.length && { [AGENT_PLAN_RISK_ATTR.categories]: assessment.categories },
932
+ ...toolSequence?.length && { [AGENT_PLAN_RISK_ATTR.toolSequence]: toolSequence },
933
+ ...assessment.reason !== void 0 && { "decision.summary": assessment.reason }
934
+ });
935
+ if (options.emitSecurityEvent && assessment.verdict !== "low") securityEvent({
936
+ name: "llm.plan.risk.elevated",
937
+ category: "llm",
938
+ outcome: assessment.verdict === "critical" ? "blocked" : "denied",
939
+ severity: assessment.verdict === "critical" ? "critical" : assessment.verdict === "high" ? "error" : "warning",
940
+ reason: assessment.reason ?? assessment.verdict,
941
+ ...assessment.score !== void 0 && { score: assessment.score },
942
+ ...assessment.categories?.length && { categories: assessment.categories.join(",") }
943
+ }, { ctx: options.ctx });
944
+ }
945
+ /**
946
+ * Run a pluggable plan-risk classifier and record its verdict on the span.
947
+ * Classifier failures degrade quietly (no assessment recorded).
948
+ */
949
+ async function runAgentPlanClassifier(classifier, input, options = {}) {
950
+ let assessment;
951
+ try {
952
+ assessment = await classifier(input);
953
+ } catch {
954
+ return;
955
+ }
956
+ if (!assessment) return void 0;
957
+ recordPlanRiskAssessment({
958
+ ...options,
959
+ assessment,
960
+ toolSequence: input.toolSequence
961
+ });
962
+ return assessment;
963
+ }
964
+ const DESTRUCTIVE_TOOL = /\b(delete|remove|send|post|transfer|pay|upload|execute)\b/i;
965
+ const UNTRUSTED_READ = /\b(read|fetch|get|search|load|parse|inbox|email|web|scrape)\b/i;
966
+ /**
967
+ * Dependency-free first-pass plan-risk heuristic. Opt-in — pass as
968
+ * `AgentPlanClassifier` or wrap your own Model Armor / Llama Guard adapter.
969
+ */
970
+ function heuristicPlanRiskClassifier() {
971
+ return ({ toolSequence }) => {
972
+ if (toolSequence.length === 0) return {
973
+ verdict: "low",
974
+ score: 0
975
+ };
976
+ const normalized = toolSequence.map((name) => name.replaceAll("_", " "));
977
+ const hasDestructive = normalized.some((name) => DESTRUCTIVE_TOOL.test(name));
978
+ const hasUntrustedRead = normalized.some((name) => UNTRUSTED_READ.test(name));
979
+ if (hasDestructive && hasUntrustedRead) return {
980
+ verdict: "high",
981
+ score: .85,
982
+ categories: ["untrusted_to_destructive_chain"],
983
+ reason: "mixed_untrusted_and_destructive_tools"
984
+ };
985
+ if (toolSequence.length >= 8) return {
986
+ verdict: "medium",
987
+ score: .55,
988
+ categories: ["long_tool_chain"],
989
+ reason: "long_tool_sequence"
990
+ };
991
+ return {
992
+ verdict: "low",
993
+ score: .1
994
+ };
995
+ };
996
+ }
997
+
998
+ //#endregion
999
+ export { canonicalizeForHash as C, createAgentAuditMetadata as S, AGENT_AUDIT_SCHEMA_VERSION as T, recordPolicyDecision as _, withScopedTool as a, flattenAgentAttributes as b, resolvePrivacyProfile as c, withAgentSession as d, delegateToAgent as f, recordDecisionBasis as g, defineAgentToolCall as h, runAgentPlanClassifier as i, sanitizeAuditPayload as l, defineAgentAction as m, heuristicPlanRiskClassifier as n, createSignedEventEnvelope as o, recordAgentHandoff as p, recordPlanRiskAssessment as r, verifyEventEnvelopeHash as s, AGENT_PLAN_RISK_ATTR as t, createAgentIdentityRegistry as u, withAgentAction as v, hashPayload as w, setAgentAttributes as x, withAgentToolCall as y };
1000
+ //# sourceMappingURL=agent-yHB-GF6g.js.map