@linxiraos/pi-agent-core 1.1.20 → 1.1.22

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 (41) hide show
  1. package/CHANGELOG.md +241 -2
  2. package/README.md +4 -3
  3. package/THIRD-PARTY-NOTICES.txt +3 -40
  4. package/dist/types/agent-loop.d.ts +14 -1
  5. package/dist/types/agent.d.ts +36 -0
  6. package/dist/types/compaction/anthropic.d.ts +22 -52
  7. package/dist/types/compaction/branch-summarization.d.ts +1 -1
  8. package/dist/types/compaction/compaction.d.ts +4 -2
  9. package/dist/types/compaction/messages.d.ts +9 -0
  10. package/dist/types/compaction/openai.d.ts +11 -0
  11. package/dist/types/compaction/pruning.d.ts +9 -0
  12. package/dist/types/compaction/transcript-tokens.d.ts +25 -2
  13. package/dist/types/index.d.ts +3 -0
  14. package/dist/types/live-steering.d.ts +34 -0
  15. package/dist/types/output-budget.d.ts +54 -0
  16. package/dist/types/sent-tool-definitions.d.ts +17 -0
  17. package/dist/types/speculative-execution.d.ts +2 -1
  18. package/dist/types/telemetry.d.ts +103 -60
  19. package/dist/types/tool-context.d.ts +32 -0
  20. package/dist/types/types.d.ts +105 -7
  21. package/package.json +10 -10
  22. package/src/agent-loop.ts +386 -90
  23. package/src/agent.ts +175 -26
  24. package/src/compaction/anthropic.ts +62 -105
  25. package/src/compaction/branch-summarization.ts +1 -1
  26. package/src/compaction/compaction-v2-streaming.ts +13 -4
  27. package/src/compaction/compaction.ts +63 -51
  28. package/src/compaction/messages.ts +12 -2
  29. package/src/compaction/openai.ts +37 -9
  30. package/src/compaction/prompts/anthropic-compaction-instructions.md +2 -6
  31. package/src/compaction/pruning.ts +1 -1
  32. package/src/compaction/transcript-tokens.ts +64 -3
  33. package/src/index.ts +6 -0
  34. package/src/live-steering.ts +89 -0
  35. package/src/output-budget.ts +142 -0
  36. package/src/sent-tool-definitions.ts +40 -0
  37. package/src/speculative-execution.ts +12 -0
  38. package/src/telemetry.ts +240 -117
  39. package/src/tokenizer.ts +45 -6
  40. package/src/tool-context.ts +55 -0
  41. package/src/types.ts +109 -7
package/src/telemetry.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * OpenTelemetry instrumentation for the agent loop.
3
3
  *
4
4
  * Implements the OpenTelemetry GenAI semantic conventions
5
- * (https://opentelemetry.io/docs/specs/semconv/gen-ai/) plus `pi.gen_ai.*`
5
+ * (https://opentelemetry.io/docs/specs/semconv/gen-ai/) plus `omp.gen_ai.*`
6
6
  * extension attributes for run summaries, dashboard summaries, and cost hints
7
7
  * that are useful to downstream observability UIs.
8
8
  *
@@ -125,42 +125,48 @@ export const enum OpenAIAttr {
125
125
  }
126
126
 
127
127
  /** Project extension attributes. Kept out of the reserved `gen_ai.*` namespace. */
128
- export const enum PiGenAIAttr {
129
- AgentStepNumber = "pi.gen_ai.agent.step.number",
130
- AgentStepCount = "pi.gen_ai.agent.step.count",
131
- RequestReasoningEffort = "pi.gen_ai.request.reasoning.effort",
132
- RequestToolChoice = "pi.gen_ai.request.tool.choice",
133
- RequestAvailableTools = "pi.gen_ai.request.available_tools",
134
- RequestMessages = "pi.gen_ai.request.messages",
135
- ResponseText = "pi.gen_ai.response.text",
136
- ResponseToolCalls = "pi.gen_ai.response.tool_calls",
137
- ResponseUpstreamProvider = "pi.gen_ai.response.upstream_provider",
138
- UsageTotalTokens = "pi.gen_ai.usage.total_tokens",
139
- UsageServerSideTools = "pi.gen_ai.usage.server_tool_requests",
140
- CostEstimatedUsd = "pi.gen_ai.cost.estimated_usd",
141
- CostInputUsd = "pi.gen_ai.cost.input_usd",
142
- CostOutputUsd = "pi.gen_ai.cost.output_usd",
143
- CostUnavailableReason = "pi.gen_ai.cost.unavailable_reason",
144
- ToolStatus = "pi.gen_ai.tool.status",
145
- ToolCallIntent = "pi.gen_ai.tool.call.intent",
146
- HandoffFromAgentName = "pi.gen_ai.handoff.from_agent.name",
147
- HandoffFromAgentId = "pi.gen_ai.handoff.from_agent.id",
148
- HandoffToAgentName = "pi.gen_ai.handoff.to_agent.name",
149
- HandoffToAgentId = "pi.gen_ai.handoff.to_agent.id",
128
+ export const enum OmpGenAIAttr {
129
+ AgentStepNumber = "omp.gen_ai.agent.step.number",
130
+ AgentStepCount = "omp.gen_ai.agent.step.count",
131
+ RequestReasoningEffort = "omp.gen_ai.request.reasoning.effort",
132
+ RequestToolChoice = "omp.gen_ai.request.tool.choice",
133
+ RequestAvailableTools = "omp.gen_ai.request.available_tools",
134
+ RequestMessages = "omp.gen_ai.request.messages",
135
+ ResponseText = "omp.gen_ai.response.text",
136
+ ResponseToolCalls = "omp.gen_ai.response.tool_calls",
137
+ ResponseUpstreamProvider = "omp.gen_ai.response.upstream_provider",
138
+ UsageTotalTokens = "omp.gen_ai.usage.total_tokens",
139
+ UsageServerSideTools = "omp.gen_ai.usage.server_tool_requests",
140
+ CostEstimatedUsd = "omp.gen_ai.cost.estimated_usd",
141
+ CostInputUsd = "omp.gen_ai.cost.input_usd",
142
+ CostOutputUsd = "omp.gen_ai.cost.output_usd",
143
+ CostUnavailableReason = "omp.gen_ai.cost.unavailable_reason",
144
+ ToolStatus = "omp.gen_ai.tool.status",
145
+ ToolCallIntent = "omp.gen_ai.tool.call.intent",
146
+ HandoffFromAgentName = "omp.gen_ai.handoff.from_agent.name",
147
+ HandoffFromAgentId = "omp.gen_ai.handoff.from_agent.id",
148
+ HandoffToAgentName = "omp.gen_ai.handoff.to_agent.name",
149
+ HandoffToAgentId = "omp.gen_ai.handoff.to_agent.id",
150
150
  // Marks chat spans emitted outside the agent loop (compaction, handoff, branch
151
151
  // summary, image inspection, …). Lets dashboards split oneshot cost / latency
152
152
  // from main-turn cost without overloading the semconv `gen_ai.operation.name`.
153
- OneshotKind = "pi.gen_ai.oneshot.kind",
153
+ OneshotKind = "omp.gen_ai.oneshot.kind",
154
154
  // Gateway / proxy (LiteLLM, Helicone, Portkey, …) — populated when a known
155
155
  // gateway header pattern is detected on the upstream response. The base
156
156
  // `gen_ai.provider.name` continues to track the *upstream* provider (e.g.
157
157
  // `anthropic`) that the gateway routed to.
158
- GatewayName = "pi.gen_ai.gateway.name",
159
- GatewayEndpoint = "pi.gen_ai.gateway.endpoint",
160
- GatewayCallId = "pi.gen_ai.gateway.call_id",
161
- GatewayRoutedTo = "pi.gen_ai.gateway.routed_to",
158
+ GatewayName = "omp.gen_ai.gateway.name",
159
+ GatewayEndpoint = "omp.gen_ai.gateway.endpoint",
160
+ GatewayCallId = "omp.gen_ai.gateway.call_id",
161
+ GatewayRoutedTo = "omp.gen_ai.gateway.routed_to",
162
162
  /** Cloudflare AI Gateway response-cache status (`cf-aig-cache-status`), never prompt-cache. */
163
- GatewayResponseCacheStatus = "pi.gen_ai.gateway.response_cache.status",
163
+ GatewayResponseCacheStatus = "omp.gen_ai.gateway.response_cache.status",
164
+ /** Caller-level reason a judgment ran (`find`, `ttsr`, `judge_batch`, …). */
165
+ JudgmentPurpose = "omp.gen_ai.judgment.purpose",
166
+ /** Questions the caller asked in one judgment request. */
167
+ JudgmentQuestions = "omp.gen_ai.judgment.questions",
168
+ /** Questions answered from the local judgment cache instead of the provider. */
169
+ JudgmentCachedQuestions = "omp.gen_ai.judgment.cached_questions",
164
170
  }
165
171
 
166
172
  /** GenAI operation names — values for {@link GenAIAttr.OperationName}. */
@@ -169,6 +175,8 @@ export const GenAIOperation = {
169
175
  ExecuteTool: "execute_tool",
170
176
  InvokeAgent: "invoke_agent",
171
177
  Handoff: "handoff",
178
+ /** Typed judgment over a state (System One or a prompted judge model); a project extension value. */
179
+ Judgment: "judgment",
172
180
  GenerateContent: "generate_content",
173
181
  TextCompletion: "text_completion",
174
182
  CreateAgent: "create_agent",
@@ -178,7 +186,7 @@ export const GenAIOperation = {
178
186
  export type GenAIOperationName = (typeof GenAIOperation)[keyof typeof GenAIOperation];
179
187
 
180
188
  /** Identifies which agent span a callback is reporting on. */
181
- export type TelemetrySpanKind = "invoke_agent" | "chat" | "execute_tool" | "handoff";
189
+ export type TelemetrySpanKind = "invoke_agent" | "chat" | "execute_tool" | "handoff" | "judgment";
182
190
 
183
191
  /**
184
192
  * Aggregated usage + cost surface passed to {@link AgentTelemetryConfig.costEstimator}.
@@ -204,9 +212,9 @@ export interface CostEstimatorContext {
204
212
 
205
213
  /**
206
214
  * Cost estimator result.
207
- * { usd: number } — cost is known; emitted as pi.gen_ai.cost.estimated_usd
215
+ * { usd: number } — cost is known; emitted as omp.gen_ai.cost.estimated_usd
208
216
  * { unavailable: string } — cost is intentionally unknown; emitted as
209
- * pi.gen_ai.cost.unavailable_reason
217
+ * omp.gen_ai.cost.unavailable_reason
210
218
  * undefined — no opinion; nothing emitted
211
219
  */
212
220
  export type CostEstimate =
@@ -235,6 +243,8 @@ export interface CostDelta {
235
243
  */
236
244
  export interface ChatUsageEvent {
237
245
  readonly span: Span;
246
+ /** Operation that produced the usage: `chat` for model turns, `judgment` for typed judgments. */
247
+ readonly operation: GenAIOperationName;
238
248
  readonly agent: AgentIdentity | undefined;
239
249
  readonly conversationId: string | undefined;
240
250
  readonly stepNumber: number | undefined;
@@ -253,7 +263,7 @@ export interface ChatUsageEvent {
253
263
  *
254
264
  * Use this to reconcile gateway-issued ids (e.g. `x-litellm-call-id`) with
255
265
  * downstream billing / spend dashboards. Known gateway patterns are also
256
- * auto-stamped on the chat span as `pi.gen_ai.gateway.*` attributes.
266
+ * auto-stamped on the chat span as `omp.gen_ai.gateway.*` attributes.
257
267
  */
258
268
  readonly headers: Readonly<Record<string, string>> | undefined;
259
269
  }
@@ -475,6 +485,8 @@ function startSpan(
475
485
  readonly stepNumber?: number;
476
486
  readonly toolCallId?: string;
477
487
  readonly toolName?: string;
488
+ /** Epoch ms; defaults to now. */
489
+ readonly startTime?: number;
478
490
  },
479
491
  ): Span | undefined {
480
492
  if (!telemetry) return undefined;
@@ -497,7 +509,11 @@ function startSpan(
497
509
  if (options.attributes) Object.assign(attrs, options.attributes);
498
510
 
499
511
  const ctx = options.parent ? trace.setSpan(context.active(), options.parent) : context.active();
500
- const span = telemetry.tracer.startSpan(name, { kind: options.spanKind, attributes: attrs }, ctx);
512
+ const span = telemetry.tracer.startSpan(
513
+ name,
514
+ { kind: options.spanKind, attributes: attrs, startTime: options.startTime },
515
+ ctx,
516
+ );
501
517
  safeOnSpanStart(telemetry, { ...attrCtx, span });
502
518
  return span;
503
519
  }
@@ -548,6 +564,8 @@ function kindToOperation(kind: TelemetrySpanKind): GenAIOperationName | undefine
548
564
  return GenAIOperation.ExecuteTool;
549
565
  case "handoff":
550
566
  return GenAIOperation.Handoff;
567
+ case "judgment":
568
+ return GenAIOperation.Judgment;
551
569
  }
552
570
  }
553
571
 
@@ -683,7 +701,7 @@ export function startInvokeAgentSpan(telemetry: AgentTelemetry | undefined, mode
683
701
  /** Stamp the final step count on the `invoke_agent` span. */
684
702
  export function applyInvokeAgentFinish(span: Span | undefined, stepCount: number): void {
685
703
  if (!span) return;
686
- span.setAttribute(PiGenAIAttr.AgentStepCount, stepCount);
704
+ span.setAttribute(OmpGenAIAttr.AgentStepCount, stepCount);
687
705
  }
688
706
 
689
707
  /**
@@ -740,7 +758,7 @@ export interface ChatRequestSnapshot {
740
758
 
741
759
  function buildChatRequestAttributes(stepNumber: number, request: ChatRequestSnapshot, provider: string): Attributes {
742
760
  const attrs: Attributes = {
743
- [PiGenAIAttr.AgentStepNumber]: stepNumber,
761
+ [OmpGenAIAttr.AgentStepNumber]: stepNumber,
744
762
  [GenAIAttr.OutputType]: "text",
745
763
  [GenAIAttr.RequestStream]: true,
746
764
  };
@@ -757,11 +775,11 @@ function buildChatRequestAttributes(stepNumber: number, request: ChatRequestSnap
757
775
  if (request.serviceTier && shouldSendServiceTier(request.serviceTier, provider)) {
758
776
  attrs[OpenAIAttr.RequestServiceTier] = request.serviceTier;
759
777
  }
760
- if (request.reasoningEffort) attrs[PiGenAIAttr.RequestReasoningEffort] = request.reasoningEffort;
778
+ if (request.reasoningEffort) attrs[OmpGenAIAttr.RequestReasoningEffort] = request.reasoningEffort;
761
779
  const toolChoice = serializeToolChoice(request.toolChoice);
762
- if (toolChoice) attrs[PiGenAIAttr.RequestToolChoice] = toolChoice;
780
+ if (toolChoice) attrs[OmpGenAIAttr.RequestToolChoice] = toolChoice;
763
781
  if (request.tools && request.tools.length > 0) {
764
- attrs[PiGenAIAttr.RequestAvailableTools] = request.tools.map(tool => tool.name);
782
+ attrs[OmpGenAIAttr.RequestAvailableTools] = request.tools.map(tool => tool.name);
765
783
  }
766
784
  return attrs;
767
785
  }
@@ -779,7 +797,7 @@ function serializeToolChoice(toolChoice: ToolChoice | undefined): string | undef
779
797
 
780
798
  function applyContentCaptureForRequest(telemetry: AgentTelemetry, span: Span, request: ChatRequestSnapshot): void {
781
799
  const requestMessages = serializeRequestMessagesForTelemetry(telemetry, request);
782
- if (requestMessages) span.setAttribute(PiGenAIAttr.RequestMessages, requestMessages);
800
+ if (requestMessages) span.setAttribute(OmpGenAIAttr.RequestMessages, requestMessages);
783
801
  if (telemetry.contentCapture !== "full") return;
784
802
  const systemInstructions = serializeFullSystemInstructionsForTelemetry(request);
785
803
  if (systemInstructions) span.setAttribute(GenAIAttr.SystemInstructions, systemInstructions);
@@ -789,9 +807,9 @@ function applyContentCaptureForRequest(telemetry: AgentTelemetry, span: Span, re
789
807
 
790
808
  function applyContentCaptureForResponse(telemetry: AgentTelemetry, span: Span, message: AssistantMessage): void {
791
809
  const responseText = serializeResponseTextForTelemetry(telemetry, message);
792
- if (responseText) span.setAttribute(PiGenAIAttr.ResponseText, responseText);
810
+ if (responseText) span.setAttribute(OmpGenAIAttr.ResponseText, responseText);
793
811
  const responseToolCalls = serializeResponseToolCallsForTelemetry(telemetry, message);
794
- if (responseToolCalls) span.setAttribute(PiGenAIAttr.ResponseToolCalls, responseToolCalls);
812
+ if (responseToolCalls) span.setAttribute(OmpGenAIAttr.ResponseToolCalls, responseToolCalls);
795
813
  if (telemetry.contentCapture === "full") {
796
814
  const outputMessages = serializeFullOutputMessagesForTelemetry(message);
797
815
  if (outputMessages) span.setAttribute(GenAIAttr.OutputMessages, outputMessages);
@@ -1131,6 +1149,7 @@ export async function finishChatSpan(
1131
1149
  const cost = applyCostEstimate(telemetry, span, message, options.serviceTier, options.stepNumber);
1132
1150
  if (telemetry) {
1133
1151
  await emitChatUsage(telemetry, span, {
1152
+ operation: GenAIOperation.Chat,
1134
1153
  model: message.model,
1135
1154
  provider: message.provider,
1136
1155
  serviceTier: options.serviceTier,
@@ -1199,7 +1218,7 @@ function applyChatResponseAttributes(span: Span, message: AssistantMessage): voi
1199
1218
  span.setAttribute(GenAIAttr.ResponseModel, message.model);
1200
1219
  if (message.responseId) span.setAttribute(GenAIAttr.ResponseId, message.responseId);
1201
1220
  if (message.upstreamProvider) {
1202
- span.setAttribute(PiGenAIAttr.ResponseUpstreamProvider, message.upstreamProvider);
1221
+ span.setAttribute(OmpGenAIAttr.ResponseUpstreamProvider, message.upstreamProvider);
1203
1222
  }
1204
1223
  if (message.ttft != null) span.setAttribute(GenAIAttr.ResponseTimeToFirstChunk, message.ttft / 1000);
1205
1224
  const finishReason = mapStopReason(message.stopReason);
@@ -1215,7 +1234,7 @@ function applyUsageAttributes(span: Span, usage: Usage | undefined): void {
1215
1234
  span.setAttribute(GenAIAttr.UsageInputTokens, inputTokens);
1216
1235
  span.setAttribute(GenAIAttr.UsageOutputTokens, outputTokens);
1217
1236
  const total = usage.totalTokens ?? inputTokens + outputTokens;
1218
- span.setAttribute(PiGenAIAttr.UsageTotalTokens, total);
1237
+ span.setAttribute(OmpGenAIAttr.UsageTotalTokens, total);
1219
1238
  if (usage.cacheRead != null) span.setAttribute(GenAIAttr.UsageCacheReadInputTokens, usage.cacheRead);
1220
1239
  if (usage.cacheWrite != null) span.setAttribute(GenAIAttr.UsageCacheCreationInputTokens, usage.cacheWrite);
1221
1240
  if (usage.reasoningTokens != null) {
@@ -1223,7 +1242,7 @@ function applyUsageAttributes(span: Span, usage: Usage | undefined): void {
1223
1242
  }
1224
1243
  if (usage.server) {
1225
1244
  const sums = (usage.server.webSearch ?? 0) + (usage.server.webFetch ?? 0);
1226
- if (sums > 0) span.setAttribute(PiGenAIAttr.UsageServerSideTools, sums);
1245
+ if (sums > 0) span.setAttribute(OmpGenAIAttr.UsageServerSideTools, sums);
1227
1246
  }
1228
1247
  }
1229
1248
 
@@ -1284,7 +1303,7 @@ export function detectGatewayFromHeaders(
1284
1303
 
1285
1304
  /**
1286
1305
  * Bounded Cloudflare AI Gateway response-cache statuses emitted on
1287
- * {@link PiGenAIAttr.GatewayResponseCacheStatus}. Distinct from provider
1306
+ * {@link OmpGenAIAttr.GatewayResponseCacheStatus}. Distinct from provider
1288
1307
  * prompt-cache token counters (`gen_ai.usage.cache_*`).
1289
1308
  *
1290
1309
  * Cloudflare documents `HIT` / `MISS` on `cf-aig-cache-status`; `bypass` covers
@@ -1323,14 +1342,14 @@ function applyGatewayAttributes(
1323
1342
  ): void {
1324
1343
  const gateway = detectGatewayFromHeaders(headers);
1325
1344
  if (gateway) {
1326
- span.setAttribute(PiGenAIAttr.GatewayName, gateway.name);
1327
- if (baseUrl) span.setAttribute(PiGenAIAttr.GatewayEndpoint, baseUrl);
1328
- if (gateway.callId) span.setAttribute(PiGenAIAttr.GatewayCallId, gateway.callId);
1329
- if (gateway.routedTo) span.setAttribute(PiGenAIAttr.GatewayRoutedTo, gateway.routedTo);
1345
+ span.setAttribute(OmpGenAIAttr.GatewayName, gateway.name);
1346
+ if (baseUrl) span.setAttribute(OmpGenAIAttr.GatewayEndpoint, baseUrl);
1347
+ if (gateway.callId) span.setAttribute(OmpGenAIAttr.GatewayCallId, gateway.callId);
1348
+ if (gateway.routedTo) span.setAttribute(OmpGenAIAttr.GatewayRoutedTo, gateway.routedTo);
1330
1349
  }
1331
1350
  const responseCacheStatus = classifyGatewayResponseCacheStatus(headers);
1332
1351
  if (responseCacheStatus) {
1333
- span.setAttribute(PiGenAIAttr.GatewayResponseCacheStatus, responseCacheStatus);
1352
+ span.setAttribute(OmpGenAIAttr.GatewayResponseCacheStatus, responseCacheStatus);
1334
1353
  }
1335
1354
  }
1336
1355
 
@@ -1392,7 +1411,7 @@ function applyCostEstimateForUsage(
1392
1411
  }
1393
1412
  if (!result) return EMPTY_COST;
1394
1413
  if ("unavailable" in result) {
1395
- span.setAttribute(PiGenAIAttr.CostUnavailableReason, result.unavailable);
1414
+ span.setAttribute(OmpGenAIAttr.CostUnavailableReason, result.unavailable);
1396
1415
  const cost: AppliedCostEstimate = {
1397
1416
  costUsd: undefined,
1398
1417
  inputUsd: undefined,
@@ -1414,9 +1433,9 @@ function applyCostEstimateForUsage(
1414
1433
  });
1415
1434
  return cost;
1416
1435
  }
1417
- span.setAttribute(PiGenAIAttr.CostEstimatedUsd, result.usd);
1418
- if (result.inputUsd != null) span.setAttribute(PiGenAIAttr.CostInputUsd, result.inputUsd);
1419
- if (result.outputUsd != null) span.setAttribute(PiGenAIAttr.CostOutputUsd, result.outputUsd);
1436
+ span.setAttribute(OmpGenAIAttr.CostEstimatedUsd, result.usd);
1437
+ if (result.inputUsd != null) span.setAttribute(OmpGenAIAttr.CostInputUsd, result.inputUsd);
1438
+ if (result.outputUsd != null) span.setAttribute(OmpGenAIAttr.CostOutputUsd, result.outputUsd);
1420
1439
  const cost: AppliedCostEstimate = {
1421
1440
  costUsd: result.usd,
1422
1441
  inputUsd: result.inputUsd,
@@ -1470,6 +1489,7 @@ async function emitChatUsage(
1470
1489
  telemetry: AgentTelemetry,
1471
1490
  span: Span,
1472
1491
  input: {
1492
+ readonly operation: GenAIOperationName;
1473
1493
  readonly model: string;
1474
1494
  readonly provider: string | undefined;
1475
1495
  readonly serviceTier: ServiceTier | undefined;
@@ -1483,6 +1503,7 @@ async function emitChatUsage(
1483
1503
  if (!hook || !input.usage) return;
1484
1504
  const event: ChatUsageEvent = {
1485
1505
  span,
1506
+ operation: input.operation,
1486
1507
  agent: normalizedTelemetryAgent(telemetry),
1487
1508
  conversationId: telemetry.conversationId,
1488
1509
  stepNumber: input.stepNumber,
@@ -1493,7 +1514,9 @@ async function emitChatUsage(
1493
1514
  cost: costEstimateFromApplied(input.applied),
1494
1515
  attributes: resolveDynamicAttributes(
1495
1516
  telemetry,
1496
- buildTelemetryAttributeContext(telemetry, "chat", { stepNumber: input.stepNumber }),
1517
+ buildTelemetryAttributeContext(telemetry, input.operation === GenAIOperation.Judgment ? "judgment" : "chat", {
1518
+ stepNumber: input.stepNumber,
1519
+ }),
1497
1520
  ),
1498
1521
  headers: input.headers,
1499
1522
  };
@@ -1586,7 +1609,7 @@ export async function recordManualChatTelemetry(
1586
1609
  });
1587
1610
  if (!span) return undefined;
1588
1611
  if (options.span && options.attributes) span.setAttributes(options.attributes);
1589
- if (options.stepNumber != null) span.setAttribute(PiGenAIAttr.AgentStepNumber, options.stepNumber);
1612
+ if (options.stepNumber != null) span.setAttribute(OmpGenAIAttr.AgentStepNumber, options.stepNumber);
1590
1613
  span.setAttribute(GenAIAttr.ResponseModel, options.responseModel ?? options.model.name);
1591
1614
  if (options.responseId) span.setAttribute(GenAIAttr.ResponseId, options.responseId);
1592
1615
  const finishReason = mapStopReason(options.finishReason);
@@ -1602,6 +1625,7 @@ export async function recordManualChatTelemetry(
1602
1625
  usage: options.usage,
1603
1626
  });
1604
1627
  await emitChatUsage(telemetry, span, {
1628
+ operation: GenAIOperation.Chat,
1605
1629
  model: options.responseModel ?? options.model.id,
1606
1630
  provider: options.model.provider,
1607
1631
  serviceTier: options.serviceTier,
@@ -1619,7 +1643,7 @@ export async function recordManualChatTelemetry(
1619
1643
  }
1620
1644
  if (options.responseText) {
1621
1645
  const responseText = stringifyJsonAttribute(summarizeTelemetryTexts([options.responseText]));
1622
- if (responseText) span.setAttribute(PiGenAIAttr.ResponseText, responseText);
1646
+ if (responseText) span.setAttribute(OmpGenAIAttr.ResponseText, responseText);
1623
1647
  }
1624
1648
  if (options.responseToolCalls && options.responseToolCalls.length > 0) {
1625
1649
  const calls = options.responseToolCalls.map(call => ({
@@ -1628,13 +1652,112 @@ export async function recordManualChatTelemetry(
1628
1652
  input: summarizeTelemetryValue(call.input),
1629
1653
  }));
1630
1654
  const responseToolCalls = stringifyJsonAttribute(limitTelemetryToolCalls(calls));
1631
- if (responseToolCalls) span.setAttribute(PiGenAIAttr.ResponseToolCalls, responseToolCalls);
1655
+ if (responseToolCalls) span.setAttribute(OmpGenAIAttr.ResponseToolCalls, responseToolCalls);
1632
1656
  }
1633
1657
  applyTerminalStatus(span, options.finishReason, undefined);
1634
1658
  if (options.endSpan ?? options.span === undefined) span.end();
1635
1659
  return span;
1636
1660
  }
1637
1661
 
1662
+ /**
1663
+ * One judgment attempt reported to {@link recordJudgmentTelemetry}: a billed
1664
+ * System One request, one prompted-model attempt, or a request answered
1665
+ * entirely from the local judgment cache.
1666
+ */
1667
+ export interface JudgmentTelemetryOptions {
1668
+ readonly provider: string;
1669
+ /** Requested model id. */
1670
+ readonly model: string;
1671
+ /** Model that answered, when the provider resolves an alias (`jev-latest` → `jev-1.13.0`). */
1672
+ readonly responseModel?: string;
1673
+ /** Caller-level reason the judgment ran; stamped as {@link OmpGenAIAttr.JudgmentPurpose}. */
1674
+ readonly purpose: string;
1675
+ /** Priced usage of this attempt — the same amount the session ledger bills. */
1676
+ readonly usage: Usage;
1677
+ readonly stopReason: StopReason;
1678
+ readonly errorMessage?: string;
1679
+ /** Epoch ms the attempt started. */
1680
+ readonly startTime: number;
1681
+ /** Questions in the request; omitted for prompted backends, which report per completion attempt. */
1682
+ readonly questions?: number;
1683
+ /** Questions answered from the local cache rather than the provider. */
1684
+ readonly cachedQuestions?: number;
1685
+ }
1686
+
1687
+ /**
1688
+ * Emit one `judgment` span per judgment attempt and forward any billed usage
1689
+ * to `onCostDelta` / `onChatUsage` with `operation: "judgment"`. Cost is the
1690
+ * attempt's own priced usage rather than {@link AgentTelemetryConfig.costEstimator},
1691
+ * so spans, metrics, and the session ledger agree on every judgment dollar.
1692
+ * Attempts that billed nothing (cache hits, pre-response failures) keep their
1693
+ * span but fire no usage hooks. No-op when telemetry is disabled.
1694
+ */
1695
+ export async function recordJudgmentTelemetry(
1696
+ telemetry: AgentTelemetry | undefined,
1697
+ options: JudgmentTelemetryOptions,
1698
+ ): Promise<void> {
1699
+ if (!telemetry) return;
1700
+ const attributes: Attributes = {
1701
+ [GenAIAttr.RequestModel]: options.model,
1702
+ [OmpGenAIAttr.JudgmentPurpose]: options.purpose,
1703
+ };
1704
+ const provider = normalizeProviderName(telemetry, options.provider);
1705
+ if (provider) attributes[GenAIAttr.ProviderName] = provider;
1706
+ if (options.questions !== undefined) attributes[OmpGenAIAttr.JudgmentQuestions] = options.questions;
1707
+ if (options.cachedQuestions !== undefined) {
1708
+ attributes[OmpGenAIAttr.JudgmentCachedQuestions] = options.cachedQuestions;
1709
+ }
1710
+ const span = startSpan(telemetry, "judgment", `judgment ${options.model}`, {
1711
+ spanKind: SpanKind.CLIENT,
1712
+ attributes,
1713
+ startTime: options.startTime,
1714
+ });
1715
+ if (!span) return;
1716
+ const model = options.responseModel ?? options.model;
1717
+ span.setAttribute(GenAIAttr.ResponseModel, model);
1718
+ const finishReason = mapStopReason(options.stopReason);
1719
+ if (finishReason) span.setAttribute(GenAIAttr.ResponseFinishReasons, [finishReason]);
1720
+ applyUsageAttributes(span, options.usage);
1721
+ const { cost } = options.usage;
1722
+ span.setAttribute(OmpGenAIAttr.CostEstimatedUsd, cost.total);
1723
+ span.setAttribute(OmpGenAIAttr.CostInputUsd, cost.input);
1724
+ span.setAttribute(OmpGenAIAttr.CostOutputUsd, cost.output);
1725
+ if (options.usage.totalTokens > 0 || cost.total > 0) {
1726
+ const applied: AppliedCostEstimate = {
1727
+ costUsd: cost.total,
1728
+ inputUsd: cost.input,
1729
+ outputUsd: cost.output,
1730
+ costUnavailableReason: undefined,
1731
+ };
1732
+ emitCostDelta(telemetry, {
1733
+ agent: normalizedTelemetryAgent(telemetry),
1734
+ conversationId: telemetry.conversationId,
1735
+ stepNumber: undefined,
1736
+ provider: provider ?? options.provider,
1737
+ model,
1738
+ serviceTier: undefined,
1739
+ usage: buildUsageSnapshot(options.usage),
1740
+ costUsd: applied.costUsd,
1741
+ inputUsd: applied.inputUsd,
1742
+ outputUsd: applied.outputUsd,
1743
+ costUnavailableReason: undefined,
1744
+ });
1745
+ await emitChatUsage(telemetry, span, {
1746
+ operation: GenAIOperation.Judgment,
1747
+ model,
1748
+ provider: options.provider,
1749
+ serviceTier: undefined,
1750
+ stepNumber: undefined,
1751
+ usage: options.usage,
1752
+ applied,
1753
+ headers: undefined,
1754
+ });
1755
+ }
1756
+ applyTerminalStatus(span, options.stopReason, options.errorMessage);
1757
+ safeOnSpanEnd(telemetry, { ...buildTelemetryAttributeContext(telemetry, "judgment", {}), span });
1758
+ span.end();
1759
+ }
1760
+
1638
1761
  /**
1639
1762
  * Options accepted by {@link instrumentedCompleteSimple}. Mirrors the
1640
1763
  * `streamAssistantResponse` chat-span lifecycle for oneshot LLM calls
@@ -1647,7 +1770,7 @@ export interface InstrumentedChatSpanOptions {
1647
1770
  /** Step index recorded on the span; defaults to `-1` for non-loop calls. */
1648
1771
  readonly stepNumber?: number;
1649
1772
  /**
1650
- * Tag stamped onto `pi.gen_ai.oneshot.kind`. Values used by the agent:
1773
+ * Tag stamped onto `omp.gen_ai.oneshot.kind`. Values used by the agent:
1651
1774
  * `compaction_summary`, `compaction_short_summary`, `compaction_turn_prefix`,
1652
1775
  * `handoff`, `branch_summary`, `image_question`. Free-form to allow callers
1653
1776
  * outside this package to add new kinds without bumping the helper.
@@ -1726,7 +1849,7 @@ export async function instrumentedCompleteSimple<TApi extends Api>(
1726
1849
  },
1727
1850
  });
1728
1851
  if (chatSpan) {
1729
- if (oneshotKind) chatSpan.setAttribute(PiGenAIAttr.OneshotKind, oneshotKind);
1852
+ if (oneshotKind) chatSpan.setAttribute(OmpGenAIAttr.OneshotKind, oneshotKind);
1730
1853
  if (span.attributes) chatSpan.setAttributes(span.attributes);
1731
1854
  }
1732
1855
 
@@ -1881,7 +2004,7 @@ export function finishExecuteToolSpan(
1881
2004
  }
1882
2005
 
1883
2006
  /** Span attribute carrying the terminal {@link ToolStatus}. */
1884
- export const EXECUTE_TOOL_STATUS_ATTR = PiGenAIAttr.ToolStatus;
2007
+ export const EXECUTE_TOOL_STATUS_ATTR = OmpGenAIAttr.ToolStatus;
1885
2008
 
1886
2009
  /**
1887
2010
  * Mapping from non-ok {@link ToolStatus} values to the `error.type` attribute
@@ -1974,66 +2097,66 @@ export function fireOnRunEnd(telemetry: AgentTelemetry, summary: AgentRunSummary
1974
2097
  }
1975
2098
  }
1976
2099
 
1977
- /** Aggregate `pi.gen_ai.agent.*` attributes stamped on the `invoke_agent` span. */
1978
- export const enum PiGenAIAggregateAttr {
1979
- ChatsCount = "pi.gen_ai.agent.chats.count",
1980
- ChatsTotalLatencyMs = "pi.gen_ai.agent.chats.total_latency_ms",
1981
- ChatsStopReasonPrefix = "pi.gen_ai.agent.chats.stop_reason.",
1982
- ToolsCount = "pi.gen_ai.agent.tools.count",
1983
- ToolsOkCount = "pi.gen_ai.agent.tools.ok.count",
1984
- ToolsErrorCount = "pi.gen_ai.agent.tools.error.count",
1985
- ToolsSkippedCount = "pi.gen_ai.agent.tools.skipped.count",
1986
- ToolsBlockedCount = "pi.gen_ai.agent.tools.blocked.count",
1987
- ToolsTimeoutCount = "pi.gen_ai.agent.tools.timeout.count",
1988
- ToolsAbortedCount = "pi.gen_ai.agent.tools.aborted.count",
1989
- ToolsTotalLatencyMs = "pi.gen_ai.agent.tools.total_latency_ms",
1990
- ToolsInvoked = "pi.gen_ai.agent.tools.invoked",
1991
- ToolsAvailable = "pi.gen_ai.agent.tools.available",
1992
- ToolsUnused = "pi.gen_ai.agent.tools.unused",
1993
- UsageInputTokensTotal = "pi.gen_ai.agent.usage.input_tokens.total",
1994
- UsageOutputTokensTotal = "pi.gen_ai.agent.usage.output_tokens.total",
1995
- UsageCacheReadInputTokensTotal = "pi.gen_ai.agent.usage.cache_read.input_tokens.total",
1996
- UsageCacheCreationInputTokensTotal = "pi.gen_ai.agent.usage.cache_creation.input_tokens.total",
1997
- UsageReasoningOutputTokensTotal = "pi.gen_ai.agent.usage.reasoning.output_tokens.total",
1998
- UsageTotalTokensTotal = "pi.gen_ai.agent.usage.total_tokens.total",
1999
- CostEstimatedUsdTotal = "pi.gen_ai.agent.cost.estimated_usd.total",
2000
- ErrorsCount = "pi.gen_ai.agent.errors.count",
2001
- }
2002
-
2003
- /** Stamp the aggregate `pi.gen_ai.agent.*` attributes on the given span. */
2100
+ /** Aggregate `omp.gen_ai.agent.*` attributes stamped on the `invoke_agent` span. */
2101
+ export const enum OmpGenAIAggregateAttr {
2102
+ ChatsCount = "omp.gen_ai.agent.chats.count",
2103
+ ChatsTotalLatencyMs = "omp.gen_ai.agent.chats.total_latency_ms",
2104
+ ChatsStopReasonPrefix = "omp.gen_ai.agent.chats.stop_reason.",
2105
+ ToolsCount = "omp.gen_ai.agent.tools.count",
2106
+ ToolsOkCount = "omp.gen_ai.agent.tools.ok.count",
2107
+ ToolsErrorCount = "omp.gen_ai.agent.tools.error.count",
2108
+ ToolsSkippedCount = "omp.gen_ai.agent.tools.skipped.count",
2109
+ ToolsBlockedCount = "omp.gen_ai.agent.tools.blocked.count",
2110
+ ToolsTimeoutCount = "omp.gen_ai.agent.tools.timeout.count",
2111
+ ToolsAbortedCount = "omp.gen_ai.agent.tools.aborted.count",
2112
+ ToolsTotalLatencyMs = "omp.gen_ai.agent.tools.total_latency_ms",
2113
+ ToolsInvoked = "omp.gen_ai.agent.tools.invoked",
2114
+ ToolsAvailable = "omp.gen_ai.agent.tools.available",
2115
+ ToolsUnused = "omp.gen_ai.agent.tools.unused",
2116
+ UsageInputTokensTotal = "omp.gen_ai.agent.usage.input_tokens.total",
2117
+ UsageOutputTokensTotal = "omp.gen_ai.agent.usage.output_tokens.total",
2118
+ UsageCacheReadInputTokensTotal = "omp.gen_ai.agent.usage.cache_read.input_tokens.total",
2119
+ UsageCacheCreationInputTokensTotal = "omp.gen_ai.agent.usage.cache_creation.input_tokens.total",
2120
+ UsageReasoningOutputTokensTotal = "omp.gen_ai.agent.usage.reasoning.output_tokens.total",
2121
+ UsageTotalTokensTotal = "omp.gen_ai.agent.usage.total_tokens.total",
2122
+ CostEstimatedUsdTotal = "omp.gen_ai.agent.cost.estimated_usd.total",
2123
+ ErrorsCount = "omp.gen_ai.agent.errors.count",
2124
+ }
2125
+
2126
+ /** Stamp the aggregate `omp.gen_ai.agent.*` attributes on the given span. */
2004
2127
  function applyAggregateAttributes(span: Span, summary: AgentRunSummary, coverage: AgentRunCoverage): void {
2005
- span.setAttribute(PiGenAIAggregateAttr.ChatsCount, summary.chats.total);
2006
- span.setAttribute(PiGenAIAggregateAttr.ChatsTotalLatencyMs, summary.chats.totalLatencyMs);
2128
+ span.setAttribute(OmpGenAIAggregateAttr.ChatsCount, summary.chats.total);
2129
+ span.setAttribute(OmpGenAIAggregateAttr.ChatsTotalLatencyMs, summary.chats.totalLatencyMs);
2007
2130
  for (const [reason, count] of Object.entries(summary.chats.byStopReason)) {
2008
- span.setAttribute(`${PiGenAIAggregateAttr.ChatsStopReasonPrefix}${reason}.count`, count);
2009
- }
2010
- span.setAttribute(PiGenAIAggregateAttr.ToolsCount, summary.tools.total);
2011
- span.setAttribute(PiGenAIAggregateAttr.ToolsOkCount, summary.tools.ok);
2012
- span.setAttribute(PiGenAIAggregateAttr.ToolsErrorCount, summary.tools.error);
2013
- span.setAttribute(PiGenAIAggregateAttr.ToolsSkippedCount, summary.tools.skipped);
2014
- span.setAttribute(PiGenAIAggregateAttr.ToolsBlockedCount, summary.tools.blocked);
2015
- span.setAttribute(PiGenAIAggregateAttr.ToolsTimeoutCount, summary.tools.timeout);
2016
- span.setAttribute(PiGenAIAggregateAttr.ToolsAbortedCount, summary.tools.aborted);
2017
- span.setAttribute(PiGenAIAggregateAttr.ToolsTotalLatencyMs, summary.tools.totalLatencyMs);
2131
+ span.setAttribute(`${OmpGenAIAggregateAttr.ChatsStopReasonPrefix}${reason}.count`, count);
2132
+ }
2133
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsCount, summary.tools.total);
2134
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsOkCount, summary.tools.ok);
2135
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsErrorCount, summary.tools.error);
2136
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsSkippedCount, summary.tools.skipped);
2137
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsBlockedCount, summary.tools.blocked);
2138
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsTimeoutCount, summary.tools.timeout);
2139
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsAbortedCount, summary.tools.aborted);
2140
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsTotalLatencyMs, summary.tools.totalLatencyMs);
2018
2141
  if (coverage.toolsInvoked.length > 0) {
2019
- span.setAttribute(PiGenAIAggregateAttr.ToolsInvoked, [...coverage.toolsInvoked]);
2142
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsInvoked, [...coverage.toolsInvoked]);
2020
2143
  }
2021
2144
  if (coverage.toolsAvailable.length > 0) {
2022
- span.setAttribute(PiGenAIAggregateAttr.ToolsAvailable, [...coverage.toolsAvailable]);
2145
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsAvailable, [...coverage.toolsAvailable]);
2023
2146
  }
2024
2147
  if (coverage.toolsUnused.length > 0) {
2025
- span.setAttribute(PiGenAIAggregateAttr.ToolsUnused, [...coverage.toolsUnused]);
2026
- }
2027
- span.setAttribute(PiGenAIAggregateAttr.UsageInputTokensTotal, summary.usage.inputTokens);
2028
- span.setAttribute(PiGenAIAggregateAttr.UsageOutputTokensTotal, summary.usage.outputTokens);
2029
- span.setAttribute(PiGenAIAggregateAttr.UsageCacheReadInputTokensTotal, summary.usage.cachedInputTokens);
2030
- span.setAttribute(PiGenAIAggregateAttr.UsageCacheCreationInputTokensTotal, summary.usage.cacheWriteTokens);
2031
- span.setAttribute(PiGenAIAggregateAttr.UsageReasoningOutputTokensTotal, summary.usage.reasoningOutputTokens);
2032
- span.setAttribute(PiGenAIAggregateAttr.UsageTotalTokensTotal, summary.usage.totalTokens);
2148
+ span.setAttribute(OmpGenAIAggregateAttr.ToolsUnused, [...coverage.toolsUnused]);
2149
+ }
2150
+ span.setAttribute(OmpGenAIAggregateAttr.UsageInputTokensTotal, summary.usage.inputTokens);
2151
+ span.setAttribute(OmpGenAIAggregateAttr.UsageOutputTokensTotal, summary.usage.outputTokens);
2152
+ span.setAttribute(OmpGenAIAggregateAttr.UsageCacheReadInputTokensTotal, summary.usage.cachedInputTokens);
2153
+ span.setAttribute(OmpGenAIAggregateAttr.UsageCacheCreationInputTokensTotal, summary.usage.cacheWriteTokens);
2154
+ span.setAttribute(OmpGenAIAggregateAttr.UsageReasoningOutputTokensTotal, summary.usage.reasoningOutputTokens);
2155
+ span.setAttribute(OmpGenAIAggregateAttr.UsageTotalTokensTotal, summary.usage.totalTokens);
2033
2156
  if (summary.cost.estimatedUsd > 0) {
2034
- span.setAttribute(PiGenAIAggregateAttr.CostEstimatedUsdTotal, summary.cost.estimatedUsd);
2157
+ span.setAttribute(OmpGenAIAggregateAttr.CostEstimatedUsdTotal, summary.cost.estimatedUsd);
2035
2158
  }
2036
- span.setAttribute(PiGenAIAggregateAttr.ErrorsCount, summary.errors.total);
2159
+ span.setAttribute(OmpGenAIAggregateAttr.ErrorsCount, summary.errors.total);
2037
2160
  }
2038
2161
 
2039
2162
  /**
@@ -2068,10 +2191,10 @@ export function recordHandoff(
2068
2191
  const attrs: Attributes = {};
2069
2192
  const fromAgent = options.fromAgent ? normalizeAgentIdentity(telemetry, options.fromAgent) : undefined;
2070
2193
  const toAgent = normalizeAgentIdentity(telemetry, options.toAgent);
2071
- if (fromAgent?.name) attrs[PiGenAIAttr.HandoffFromAgentName] = fromAgent.name;
2072
- if (fromAgent?.id) attrs[PiGenAIAttr.HandoffFromAgentId] = fromAgent.id;
2073
- if (toAgent.name) attrs[PiGenAIAttr.HandoffToAgentName] = toAgent.name;
2074
- if (toAgent.id) attrs[PiGenAIAttr.HandoffToAgentId] = toAgent.id;
2194
+ if (fromAgent?.name) attrs[OmpGenAIAttr.HandoffFromAgentName] = fromAgent.name;
2195
+ if (fromAgent?.id) attrs[OmpGenAIAttr.HandoffFromAgentId] = fromAgent.id;
2196
+ if (toAgent.name) attrs[OmpGenAIAttr.HandoffToAgentName] = toAgent.name;
2197
+ if (toAgent.id) attrs[OmpGenAIAttr.HandoffToAgentId] = toAgent.id;
2075
2198
  const name = toAgent.name
2076
2199
  ? fromAgent?.name
2077
2200
  ? `handoff ${fromAgent.name} → ${toAgent.name}`