@linxiraos/pi-agent-core 1.0.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +1263 -0
  2. package/README.md +473 -0
  3. package/dist/types/agent-loop.d.ts +137 -0
  4. package/dist/types/agent.d.ts +465 -0
  5. package/dist/types/append-only-context.d.ts +131 -0
  6. package/dist/types/compaction/branch-summarization.d.ts +101 -0
  7. package/dist/types/compaction/compaction-v2-streaming.d.ts +85 -0
  8. package/dist/types/compaction/compaction.d.ts +307 -0
  9. package/dist/types/compaction/entries.d.ts +111 -0
  10. package/dist/types/compaction/errors.d.ts +38 -0
  11. package/dist/types/compaction/index.d.ts +13 -0
  12. package/dist/types/compaction/message-cache.d.ts +23 -0
  13. package/dist/types/compaction/messages.d.ts +79 -0
  14. package/dist/types/compaction/openai.d.ts +117 -0
  15. package/dist/types/compaction/pruning.d.ts +105 -0
  16. package/dist/types/compaction/shake.d.ts +97 -0
  17. package/dist/types/compaction/tool-protection.d.ts +19 -0
  18. package/dist/types/compaction/utils.d.ts +61 -0
  19. package/dist/types/compaction.d.ts +1 -0
  20. package/dist/types/index.d.ts +13 -0
  21. package/dist/types/pause.d.ts +46 -0
  22. package/dist/types/proxy.d.ts +91 -0
  23. package/dist/types/replay-policy.d.ts +5 -0
  24. package/dist/types/run-collector.d.ts +196 -0
  25. package/dist/types/telemetry.d.ts +608 -0
  26. package/dist/types/thinking.d.ts +18 -0
  27. package/dist/types/tokenizer.d.ts +2 -0
  28. package/dist/types/types.d.ts +781 -0
  29. package/dist/types/utils/yield.d.ts +71 -0
  30. package/package.json +79 -0
  31. package/src/agent-loop.ts +2926 -0
  32. package/src/agent.ts +1748 -0
  33. package/src/append-only-context.ts +348 -0
  34. package/src/compaction/branch-summarization.ts +369 -0
  35. package/src/compaction/compaction-v2-streaming.ts +846 -0
  36. package/src/compaction/compaction.ts +1733 -0
  37. package/src/compaction/entries.ts +141 -0
  38. package/src/compaction/errors.ts +47 -0
  39. package/src/compaction/index.ts +14 -0
  40. package/src/compaction/message-cache.ts +92 -0
  41. package/src/compaction/messages.ts +241 -0
  42. package/src/compaction/openai.ts +992 -0
  43. package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
  44. package/src/compaction/prompts/branch-summary-context.md +5 -0
  45. package/src/compaction/prompts/branch-summary-preamble.md +2 -0
  46. package/src/compaction/prompts/branch-summary.md +30 -0
  47. package/src/compaction/prompts/compaction-short-summary.md +9 -0
  48. package/src/compaction/prompts/compaction-summary-context.md +5 -0
  49. package/src/compaction/prompts/compaction-summary.md +38 -0
  50. package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
  51. package/src/compaction/prompts/compaction-update-summary.md +45 -0
  52. package/src/compaction/prompts/context-window-truncated-output.md +1 -0
  53. package/src/compaction/prompts/file-operations.md +5 -0
  54. package/src/compaction/prompts/handoff-document.md +49 -0
  55. package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
  56. package/src/compaction/prompts/summarization-system.md +3 -0
  57. package/src/compaction/pruning.ts +427 -0
  58. package/src/compaction/shake.ts +446 -0
  59. package/src/compaction/tool-protection.ts +65 -0
  60. package/src/compaction/utils.ts +343 -0
  61. package/src/compaction.ts +1 -0
  62. package/src/index.ts +26 -0
  63. package/src/pause.ts +107 -0
  64. package/src/proxy.ts +391 -0
  65. package/src/replay-policy.ts +13 -0
  66. package/src/run-collector.ts +631 -0
  67. package/src/telemetry.ts +2078 -0
  68. package/src/thinking.ts +20 -0
  69. package/src/tokenizer.ts +27 -0
  70. package/src/types.ts +875 -0
  71. package/src/utils/yield.ts +183 -0
@@ -0,0 +1,608 @@
1
+ /**
2
+ * OpenTelemetry instrumentation for the agent loop.
3
+ *
4
+ * Implements the OpenTelemetry GenAI semantic conventions
5
+ * (https://opentelemetry.io/docs/specs/semconv/gen-ai/) plus `pi.gen_ai.*`
6
+ * extension attributes for run summaries, dashboard summaries, and cost hints
7
+ * that are useful to downstream observability UIs.
8
+ *
9
+ * Span hierarchy emitted by the loop:
10
+ *
11
+ * invoke_agent {agent.name} (one per runLoop, gen_ai.operation.name=invoke_agent)
12
+ * ├── chat {model} (one per LLM call, gen_ai.operation.name=chat)
13
+ * ├── execute_tool {tool.name} (one per tool call, gen_ai.operation.name=execute_tool)
14
+ * └── ...
15
+ *
16
+ * The `handoff` operation is emitted via the public {@link recordHandoff}
17
+ * helper for hosts that route work between named agents.
18
+ *
19
+ * Activation is opt-in: callers pass an {@link AgentTelemetryConfig} on
20
+ * `AgentLoopConfig.telemetry`. When unset, every helper short-circuits and
21
+ * the loop performs zero tracer lookups. When set but no OTEL SDK is
22
+ * registered, `@opentelemetry/api` returns a no-op tracer and all calls are
23
+ * cheap pass-throughs.
24
+ */
25
+ import { type Api, type AssistantMessage, type Context, type Message, type Model, type ServiceTier, type SimpleStreamOptions, type StopReason, type ToolChoice, type Usage } from "@linxiraos/pi-ai";
26
+ import { type Attributes, type AttributeValue, type Span, SpanKind, SpanStatusCode, type Tracer, trace } from "@opentelemetry/api";
27
+ import { AgentRunCollector, type AgentRunCoverage, type AgentRunSummary, type ToolStatus } from "./run-collector.js";
28
+ import type { AgentTool } from "./types.js";
29
+ /** Default tracer name. Override via {@link AgentTelemetryConfig.tracerName}. */
30
+ export declare const DEFAULT_TRACER_NAME = "@linxiraos/pi-agent-core";
31
+ /**
32
+ * GenAI semantic-convention attribute keys grouped by operation. Hoisted so
33
+ * call sites stay typo-proof and easy to grep.
34
+ */
35
+ export declare const enum GenAIAttr {
36
+ ProviderName = "gen_ai.provider.name",
37
+ OperationName = "gen_ai.operation.name",
38
+ ConversationId = "gen_ai.conversation.id",
39
+ OutputType = "gen_ai.output.type",
40
+ AgentId = "gen_ai.agent.id",
41
+ AgentName = "gen_ai.agent.name",
42
+ AgentDescription = "gen_ai.agent.description",
43
+ RequestModel = "gen_ai.request.model",
44
+ RequestMaxTokens = "gen_ai.request.max_tokens",
45
+ RequestTemperature = "gen_ai.request.temperature",
46
+ RequestTopP = "gen_ai.request.top_p",
47
+ RequestTopK = "gen_ai.request.top_k",
48
+ RequestFrequencyPenalty = "gen_ai.request.frequency_penalty",
49
+ RequestPresencePenalty = "gen_ai.request.presence_penalty",
50
+ RequestStopSequences = "gen_ai.request.stop_sequences",
51
+ RequestSeed = "gen_ai.request.seed",
52
+ RequestChoiceCount = "gen_ai.request.choice.count",
53
+ RequestStream = "gen_ai.request.stream",
54
+ ResponseModel = "gen_ai.response.model",
55
+ ResponseId = "gen_ai.response.id",
56
+ ResponseFinishReasons = "gen_ai.response.finish_reasons",
57
+ ResponseTimeToFirstChunk = "gen_ai.response.time_to_first_chunk",
58
+ UsageInputTokens = "gen_ai.usage.input_tokens",
59
+ UsageOutputTokens = "gen_ai.usage.output_tokens",
60
+ UsageCacheReadInputTokens = "gen_ai.usage.cache_read.input_tokens",
61
+ UsageCacheCreationInputTokens = "gen_ai.usage.cache_creation.input_tokens",
62
+ UsageReasoningOutputTokens = "gen_ai.usage.reasoning.output_tokens",
63
+ ToolCallId = "gen_ai.tool.call.id",
64
+ ToolName = "gen_ai.tool.name",
65
+ ToolDescription = "gen_ai.tool.description",
66
+ ToolType = "gen_ai.tool.type",
67
+ ToolCallArguments = "gen_ai.tool.call.arguments",
68
+ ToolCallResult = "gen_ai.tool.call.result",
69
+ ToolDefinitions = "gen_ai.tool.definitions",
70
+ InputMessages = "gen_ai.input.messages",
71
+ OutputMessages = "gen_ai.output.messages",
72
+ SystemInstructions = "gen_ai.system_instructions",
73
+ ErrorType = "error.type"
74
+ }
75
+ /** OpenAI semantic-convention attribute keys. */
76
+ export declare const enum OpenAIAttr {
77
+ RequestServiceTier = "openai.request.service_tier",
78
+ ResponseServiceTier = "openai.response.service_tier"
79
+ }
80
+ /** Project extension attributes. Kept out of the reserved `gen_ai.*` namespace. */
81
+ export declare const enum PiGenAIAttr {
82
+ AgentStepNumber = "pi.gen_ai.agent.step.number",
83
+ AgentStepCount = "pi.gen_ai.agent.step.count",
84
+ RequestReasoningEffort = "pi.gen_ai.request.reasoning.effort",
85
+ RequestToolChoice = "pi.gen_ai.request.tool.choice",
86
+ RequestAvailableTools = "pi.gen_ai.request.available_tools",
87
+ RequestMessages = "pi.gen_ai.request.messages",
88
+ ResponseText = "pi.gen_ai.response.text",
89
+ ResponseToolCalls = "pi.gen_ai.response.tool_calls",
90
+ ResponseUpstreamProvider = "pi.gen_ai.response.upstream_provider",
91
+ UsageTotalTokens = "pi.gen_ai.usage.total_tokens",
92
+ UsageServerSideTools = "pi.gen_ai.usage.server_tool_requests",
93
+ CostEstimatedUsd = "pi.gen_ai.cost.estimated_usd",
94
+ CostInputUsd = "pi.gen_ai.cost.input_usd",
95
+ CostOutputUsd = "pi.gen_ai.cost.output_usd",
96
+ CostUnavailableReason = "pi.gen_ai.cost.unavailable_reason",
97
+ ToolStatus = "pi.gen_ai.tool.status",
98
+ ToolCallIntent = "pi.gen_ai.tool.call.intent",
99
+ HandoffFromAgentName = "pi.gen_ai.handoff.from_agent.name",
100
+ HandoffFromAgentId = "pi.gen_ai.handoff.from_agent.id",
101
+ HandoffToAgentName = "pi.gen_ai.handoff.to_agent.name",
102
+ HandoffToAgentId = "pi.gen_ai.handoff.to_agent.id",
103
+ OneshotKind = "pi.gen_ai.oneshot.kind",
104
+ GatewayName = "pi.gen_ai.gateway.name",
105
+ GatewayEndpoint = "pi.gen_ai.gateway.endpoint",
106
+ GatewayCallId = "pi.gen_ai.gateway.call_id",
107
+ GatewayRoutedTo = "pi.gen_ai.gateway.routed_to",
108
+ /** Cloudflare AI Gateway response-cache status (`cf-aig-cache-status`), never prompt-cache. */
109
+ GatewayResponseCacheStatus = "pi.gen_ai.gateway.response_cache.status"
110
+ }
111
+ /** GenAI operation names — values for {@link GenAIAttr.OperationName}. */
112
+ export declare const GenAIOperation: {
113
+ readonly Chat: "chat";
114
+ readonly ExecuteTool: "execute_tool";
115
+ readonly InvokeAgent: "invoke_agent";
116
+ readonly Handoff: "handoff";
117
+ readonly GenerateContent: "generate_content";
118
+ readonly TextCompletion: "text_completion";
119
+ readonly CreateAgent: "create_agent";
120
+ readonly Embeddings: "embeddings";
121
+ };
122
+ export type GenAIOperationName = (typeof GenAIOperation)[keyof typeof GenAIOperation];
123
+ /** Identifies which agent span a callback is reporting on. */
124
+ export type TelemetrySpanKind = "invoke_agent" | "chat" | "execute_tool" | "handoff";
125
+ /**
126
+ * Aggregated usage + cost surface passed to {@link AgentTelemetryConfig.costEstimator}.
127
+ * Mirrors the bucketed shape we already emit as span attributes so the
128
+ * estimator never has to re-derive cache-read vs cache-write breakdowns.
129
+ */
130
+ export interface ChatUsageSnapshot {
131
+ readonly inputTokens: number;
132
+ readonly outputTokens: number;
133
+ readonly totalTokens: number;
134
+ readonly cachedInputTokens: number | undefined;
135
+ readonly cacheWriteTokens: number | undefined;
136
+ readonly reasoningOutputTokens: number | undefined;
137
+ }
138
+ /** Context passed to the cost estimator. */
139
+ export interface CostEstimatorContext {
140
+ readonly provider: string;
141
+ readonly model: string;
142
+ readonly serviceTier: ServiceTier | undefined;
143
+ readonly usage: ChatUsageSnapshot;
144
+ }
145
+ /**
146
+ * Cost estimator result.
147
+ * { usd: number } — cost is known; emitted as pi.gen_ai.cost.estimated_usd
148
+ * { unavailable: string } — cost is intentionally unknown; emitted as
149
+ * pi.gen_ai.cost.unavailable_reason
150
+ * undefined — no opinion; nothing emitted
151
+ */
152
+ export type CostEstimate = {
153
+ readonly usd: number;
154
+ readonly inputUsd?: number;
155
+ readonly outputUsd?: number;
156
+ } | {
157
+ readonly unavailable: string;
158
+ };
159
+ export interface CostDelta {
160
+ readonly conversationId: string | undefined;
161
+ readonly agent: AgentIdentity | undefined;
162
+ readonly stepNumber: number | undefined;
163
+ readonly provider: string;
164
+ readonly model: string;
165
+ readonly serviceTier: ServiceTier | undefined;
166
+ readonly usage: ChatUsageSnapshot;
167
+ readonly costUsd: number | undefined;
168
+ readonly inputUsd: number | undefined;
169
+ readonly outputUsd: number | undefined;
170
+ readonly costUnavailableReason: string | undefined;
171
+ }
172
+ /**
173
+ * Event fired for every chat step that produced usage, regardless of whether
174
+ * a {@link AgentTelemetryConfig.costEstimator} is configured. Use this to
175
+ * forward token usage to metrics pipelines or dashboards without taking a
176
+ * dependency on the cost estimator path.
177
+ */
178
+ export interface ChatUsageEvent {
179
+ readonly span: Span;
180
+ readonly agent: AgentIdentity | undefined;
181
+ readonly conversationId: string | undefined;
182
+ readonly stepNumber: number | undefined;
183
+ readonly model: string;
184
+ readonly provider: string | undefined;
185
+ readonly serviceTier: ServiceTier | undefined;
186
+ readonly usage: ChatUsageSnapshot;
187
+ readonly cost: CostEstimate | undefined;
188
+ /** Resolved dynamic attributes for this chat span (from `resolveAttributes`). */
189
+ readonly attributes: Attributes | undefined;
190
+ /**
191
+ * Response headers captured from the upstream HTTP response, with keys
192
+ * lowercased (mirrors {@link ProviderResponseMetadata.headers}). `undefined`
193
+ * when the provider transport did not surface headers (non-HTTP providers,
194
+ * mocked streams, requests that aborted before headers arrived).
195
+ *
196
+ * Use this to reconcile gateway-issued ids (e.g. `x-litellm-call-id`) with
197
+ * downstream billing / spend dashboards. Known gateway patterns are also
198
+ * auto-stamped on the chat span as `pi.gen_ai.gateway.*` attributes.
199
+ */
200
+ readonly headers: Readonly<Record<string, string>> | undefined;
201
+ }
202
+ export type TelemetryContentCapture = boolean | "none" | "summary" | "full";
203
+ export type ResolvedTelemetryContentCapture = "none" | "summary" | "full";
204
+ export interface TelemetryContentSerializer {
205
+ readonly requestMessages?: (request: ChatRequestSnapshot) => string | undefined;
206
+ readonly responseText?: (message: AssistantMessage) => string | undefined;
207
+ readonly responseToolCalls?: (message: AssistantMessage) => string | undefined;
208
+ readonly toolCallArguments?: (args: unknown) => string | undefined;
209
+ readonly toolCallResult?: (result: unknown) => string | undefined;
210
+ }
211
+ /** Identity recorded on every invoke_agent and on emitted handoff spans. */
212
+ export interface AgentIdentity {
213
+ readonly id?: string;
214
+ readonly name?: string;
215
+ readonly description?: string;
216
+ }
217
+ export interface AgentTelemetryWarning {
218
+ readonly code: "resolve_attributes_failed" | "content_serializer_failed" | "on_cost_delta_failed" | "on_chat_usage_failed" | "cost_estimator_failed" | "on_run_end_failed" | "on_span_start_failed" | "on_span_end_failed" | "normalize_agent_name_failed" | "normalize_provider_failed" | "on_telemetry_warning_failed";
219
+ readonly message: string;
220
+ readonly error?: unknown;
221
+ }
222
+ /** Context passed to attribute resolvers and lifecycle hooks. */
223
+ export interface TelemetryAttributeContext {
224
+ readonly kind: TelemetrySpanKind;
225
+ readonly model: Model | undefined;
226
+ readonly agent: AgentIdentity | undefined;
227
+ readonly conversationId: string | undefined;
228
+ /** Per-step number on chat spans (0-indexed); undefined on other kinds. */
229
+ readonly stepNumber?: number;
230
+ /** Tool call info on execute_tool spans. */
231
+ readonly toolCallId?: string;
232
+ readonly toolName?: string;
233
+ }
234
+ /** Context passed to {@link AgentTelemetryConfig.onSpanStart} / `onSpanEnd`. */
235
+ export interface TelemetryHookContext extends TelemetryAttributeContext {
236
+ readonly span: Span;
237
+ }
238
+ /**
239
+ * Opt-in OpenTelemetry configuration accepted by the agent loop.
240
+ *
241
+ * All fields are optional. Passing the empty object `{}` enables
242
+ * instrumentation with sensible defaults. Pass `undefined` (or omit the
243
+ * `telemetry` field entirely) to disable everything — the loop performs zero
244
+ * tracer lookups in that case.
245
+ */
246
+ export interface AgentTelemetryConfig {
247
+ /**
248
+ * Override the tracer instance. When omitted, the loop calls
249
+ * `trace.getTracer(tracerName ?? DEFAULT_TRACER_NAME)` lazily on first use.
250
+ */
251
+ readonly tracer?: Tracer;
252
+ /** Override the tracer name passed to `trace.getTracer`. */
253
+ readonly tracerName?: string;
254
+ /**
255
+ * Capture request/response content. `true` preserves the historical full
256
+ * payload capture; `"summary"` emits bounded dashboard-friendly summaries;
257
+ * `"full"` emits both summaries and full OTEL message payloads.
258
+ *
259
+ * Defaults to the value of the `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT`
260
+ * env var (`true`/`1`/`yes` => `"full"`, `"summary"` => `"summary"`).
261
+ */
262
+ readonly captureMessageContent?: TelemetryContentCapture;
263
+ /** Extra attributes merged onto every emitted span. */
264
+ readonly attributes?: Attributes;
265
+ /**
266
+ * Attribute resolver merged onto every emitted span after static
267
+ * `attributes` and before span-specific attributes. Use this for ambient
268
+ * run, tenant, deployment, or request metadata.
269
+ */
270
+ readonly resolveAttributes?: (ctx: TelemetryAttributeContext) => Attributes | undefined;
271
+ /** Agent identity stamped onto invoke_agent + propagated to children. */
272
+ readonly agent?: AgentIdentity;
273
+ /**
274
+ * Conversation identifier. When omitted, the loop falls back to
275
+ * `AgentLoopConfig.sessionId` for the `gen_ai.conversation.id` attribute.
276
+ */
277
+ readonly conversationId?: string;
278
+ /**
279
+ * Per-step cost estimator. Synchronous on purpose — runs inside the chat
280
+ * span's finish path. Return `undefined` to emit no cost attribute.
281
+ */
282
+ readonly costEstimator?: (input: CostEstimatorContext) => CostEstimate | undefined;
283
+ /** Called after cost estimation for a chat step. */
284
+ readonly onCostDelta?: (delta: CostDelta) => void;
285
+ /**
286
+ * Fired once per chat step that produced usage, regardless of whether a
287
+ * {@link costEstimator} is configured. Use this for usage-only metrics
288
+ * pipelines (token counters, cache-hit ratios) without paying the cost of
289
+ * estimating dollars per call.
290
+ *
291
+ * **Non-fatal.** Synchronous and asynchronous failures are caught, surfaced
292
+ * via {@link onTelemetryWarning}, and swallowed.
293
+ */
294
+ readonly onChatUsage?: (event: ChatUsageEvent) => void | Promise<void>;
295
+ /** Override provider labels before they are emitted or passed to cost hooks. */
296
+ readonly normalizeProvider?: (provider: string | undefined) => string | undefined;
297
+ /** Override agent names before they are emitted on spans. */
298
+ readonly normalizeAgentName?: (name: string | undefined) => string | undefined;
299
+ /** Override the default bounded JSON serializers used by summary capture. */
300
+ readonly contentSerializer?: TelemetryContentSerializer;
301
+ /**
302
+ * Called immediately after a span starts. Use to stamp request-side
303
+ * context (user id, deployment id, route name) without forking the loop.
304
+ */
305
+ readonly onSpanStart?: (ctx: TelemetryHookContext) => void;
306
+ /**
307
+ * Called just before `span.end()`. Use to stamp response-side context
308
+ * that depends on the final result.
309
+ */
310
+ readonly onSpanEnd?: (ctx: TelemetryHookContext) => void;
311
+ /**
312
+ * Fired once per `invoke_agent`, immediately after the run-level summary
313
+ * is built and aggregate attributes are stamped on the `invoke_agent`
314
+ * span. Use this to persist, log, or forward the {@link AgentRunSummary} /
315
+ * {@link AgentRunCoverage} value without parsing OTEL spans.
316
+ *
317
+ * **Non-fatal.** Exceptions thrown from this callback are caught, logged
318
+ * via `console.warn`, and swallowed — a misbehaving telemetry consumer can
319
+ * NEVER turn a successful agent run into a failed one.
320
+ */
321
+ readonly onRunEnd?: (summary: AgentRunSummary, coverage: AgentRunCoverage) => void;
322
+ /** Receives non-fatal telemetry callback failures and host-defined warnings. */
323
+ readonly onTelemetryWarning?: (warning: AgentTelemetryWarning) => void;
324
+ }
325
+ /**
326
+ * Public handle used internally to thread the resolved tracer + config
327
+ * through the loop. Constructed once per `agentLoop` invocation.
328
+ */
329
+ export interface AgentTelemetry {
330
+ readonly config: AgentTelemetryConfig;
331
+ readonly tracer: Tracer;
332
+ readonly captureMessageContent: boolean;
333
+ readonly contentCapture: ResolvedTelemetryContentCapture;
334
+ readonly conversationId: string | undefined;
335
+ readonly agent: AgentIdentity | undefined;
336
+ /** Per-invocation event collector. See {@link AgentRunCollector}. */
337
+ readonly collector: AgentRunCollector;
338
+ }
339
+ /** Lazily resolve the {@link AgentTelemetry} handle. Returns `undefined` when disabled. */
340
+ export declare function resolveTelemetry(config: AgentTelemetryConfig | undefined, sessionId: string | undefined): AgentTelemetry | undefined;
341
+ export declare function recordTelemetryWarning(telemetry: AgentTelemetry | undefined, warning: AgentTelemetryWarning): void;
342
+ /**
343
+ * Start the outer `invoke_agent` span that wraps a full `runLoop` invocation.
344
+ * Returns `undefined` when telemetry is disabled.
345
+ */
346
+ export declare function startInvokeAgentSpan(telemetry: AgentTelemetry | undefined, model: Model): Span | undefined;
347
+ /** Stamp the final step count on the `invoke_agent` span. */
348
+ export declare function applyInvokeAgentFinish(span: Span | undefined, stepCount: number): void;
349
+ /**
350
+ * Start a `chat` span representing one provider call. Parented under the
351
+ * supplied `invoke_agent` span (or whatever is active if none is passed).
352
+ */
353
+ export declare function startChatSpan(telemetry: AgentTelemetry | undefined, model: Model, options: {
354
+ readonly parent?: Span;
355
+ readonly stepNumber: number;
356
+ readonly request: ChatRequestSnapshot;
357
+ }): Span | undefined;
358
+ /** Mutable snapshot of every request-side field worth recording. */
359
+ export interface ChatRequestSnapshot {
360
+ readonly maxTokens?: number;
361
+ readonly temperature?: number;
362
+ readonly topP?: number;
363
+ readonly topK?: number;
364
+ readonly frequencyPenalty?: number;
365
+ readonly presencePenalty?: number;
366
+ readonly stopSequences?: readonly string[];
367
+ readonly seed?: number;
368
+ readonly serviceTier?: ServiceTier;
369
+ readonly reasoningEffort?: string;
370
+ readonly toolChoice?: ToolChoice;
371
+ readonly tools?: readonly {
372
+ readonly name: string;
373
+ }[];
374
+ readonly systemPrompt?: string | readonly string[];
375
+ readonly messages?: readonly Message[];
376
+ }
377
+ /**
378
+ * Stamp the final response onto a chat span, fire the cost estimator hook,
379
+ * and end the span. No-op when `span` is undefined.
380
+ */
381
+ export declare function finishChatSpan(telemetry: AgentTelemetry | undefined, span: Span | undefined, message: AssistantMessage, options: {
382
+ readonly stepNumber: number;
383
+ readonly serviceTier?: ServiceTier;
384
+ readonly responseHeaders?: Readonly<Record<string, string>>;
385
+ readonly baseUrl?: string;
386
+ }): Promise<void>;
387
+ /**
388
+ * Record a chat that failed before producing a final `AssistantMessage`
389
+ * (e.g. the provider stream threw mid-iteration). Mirrors `finishChatSpan`'s
390
+ * span-end side effects and pushes a failed `ChatRecord` to the collector so
391
+ * the run summary still reflects the failed step.
392
+ */
393
+ export declare function failChatSpan(telemetry: AgentTelemetry | undefined, span: Span | undefined, options: {
394
+ readonly errorObject: unknown;
395
+ readonly errorType?: string;
396
+ readonly responseHeaders?: Readonly<Record<string, string>>;
397
+ readonly baseUrl?: string;
398
+ }): void;
399
+ /**
400
+ * Result of {@link detectGatewayFromHeaders}. `callId` and `routedTo` are
401
+ * populated only when the gateway surfaces them; consumers should treat
402
+ * `undefined` as "unknown for this gateway" rather than "no value".
403
+ */
404
+ export interface GatewayHeaderDetection {
405
+ readonly name: string;
406
+ readonly callId: string | undefined;
407
+ readonly routedTo: string | undefined;
408
+ }
409
+ /**
410
+ * Identify a known LLM gateway / proxy from response headers (LiteLLM,
411
+ * Helicone, Portkey). Returns `undefined` when no recognizable pattern is
412
+ * present so direct-API traffic stays unaffected.
413
+ *
414
+ * Header keys are matched case-insensitively against the lowercased map that
415
+ * {@link ProviderResponseMetadata.headers} produces.
416
+ */
417
+ export declare function detectGatewayFromHeaders(headers: Readonly<Record<string, string>> | undefined): GatewayHeaderDetection | undefined;
418
+ /**
419
+ * Bounded Cloudflare AI Gateway response-cache statuses emitted on
420
+ * {@link PiGenAIAttr.GatewayResponseCacheStatus}. Distinct from provider
421
+ * prompt-cache token counters (`gen_ai.usage.cache_*`).
422
+ *
423
+ * Cloudflare documents `HIT` / `MISS` on `cf-aig-cache-status`; `bypass` covers
424
+ * skip-cache / CDN-aligned BYPASS values. Any other present value is `unknown`.
425
+ */
426
+ export type GatewayResponseCacheStatus = "hit" | "miss" | "bypass" | "unknown";
427
+ /**
428
+ * Classify Cloudflare AI Gateway `cf-aig-cache-status` into a bounded
429
+ * response-cache status. Returns `undefined` when the allow-listed header is
430
+ * absent so non-Cloudflare traffic stays unaffected. Does not read TTL,
431
+ * skip-cache, custom-key, or other Cloudflare headers.
432
+ */
433
+ export declare function classifyGatewayResponseCacheStatus(headers: Readonly<Record<string, string>> | undefined): GatewayResponseCacheStatus | undefined;
434
+ export interface ManualChatToolCallTelemetry {
435
+ readonly toolCallId: string;
436
+ readonly toolName: string;
437
+ readonly input?: unknown;
438
+ }
439
+ export interface ManualChatTelemetryOptions {
440
+ readonly span?: Span;
441
+ readonly parent?: Span;
442
+ readonly model: Model;
443
+ readonly usage?: Usage;
444
+ readonly finishReason?: StopReason;
445
+ readonly serviceTier?: ServiceTier;
446
+ readonly stepNumber?: number;
447
+ readonly responseId?: string;
448
+ readonly responseModel?: string;
449
+ readonly responseText?: string;
450
+ readonly responseToolCalls?: readonly ManualChatToolCallTelemetry[];
451
+ readonly attributes?: Attributes;
452
+ readonly responseHeaders?: Readonly<Record<string, string>>;
453
+ readonly endSpan?: boolean;
454
+ }
455
+ export declare function recordManualChatTelemetry(telemetry: AgentTelemetry | undefined, options: ManualChatTelemetryOptions): Promise<Span | undefined>;
456
+ /**
457
+ * Options accepted by {@link instrumentedCompleteSimple}. Mirrors the
458
+ * `streamAssistantResponse` chat-span lifecycle for oneshot LLM calls
459
+ * (compaction summaries, handoff document, branch summary, inspect_image).
460
+ */
461
+ export interface InstrumentedChatSpanOptions {
462
+ readonly telemetry: AgentTelemetry | undefined;
463
+ /** Optional explicit parent span. Defaults to `context.active()`. */
464
+ readonly parent?: Span;
465
+ /** Step index recorded on the span; defaults to `-1` for non-loop calls. */
466
+ readonly stepNumber?: number;
467
+ /**
468
+ * Tag stamped onto `pi.gen_ai.oneshot.kind`. Values used by the agent:
469
+ * `compaction_summary`, `compaction_short_summary`, `compaction_turn_prefix`,
470
+ * `handoff`, `branch_summary`, `inspect_image`. Free-form to allow callers
471
+ * outside this package to add new kinds without bumping the helper.
472
+ */
473
+ readonly oneshotKind?: string;
474
+ /** Extra span attributes applied verbatim. */
475
+ readonly attributes?: Attributes;
476
+ /**
477
+ * Override for the underlying {@link completeSimple} call. Defaults to
478
+ * `completeSimple` from `@linxiraos/pi-ai`. Use to retain a test injection
479
+ * seam while still going through the chat-span lifecycle.
480
+ */
481
+ readonly completeImpl?: <TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions) => Promise<AssistantMessage>;
482
+ }
483
+ /**
484
+ * Wrap a {@link completeSimple} round-trip with the same chat-span lifecycle
485
+ * the agent loop uses for streamed turns: `startChatSpan` → run inside the
486
+ * active span → `finishChatSpan` on success, `failChatSpan` on throw.
487
+ *
488
+ * Short-circuits when `telemetry` is `undefined` so cost / overhead stays at
489
+ * zero for installations without an OTEL SDK.
490
+ */
491
+ export declare function instrumentedCompleteSimple<TApi extends Api>(model: Model<TApi>, ctx: Context, options: SimpleStreamOptions, span: InstrumentedChatSpanOptions): Promise<AssistantMessage>;
492
+ /**
493
+ * Start an `execute_tool` span representing one tool invocation. Parented
494
+ * under the supplied `invoke_agent` span by default — pass `parent` to
495
+ * override.
496
+ */
497
+ export declare function startExecuteToolSpan(telemetry: AgentTelemetry | undefined, options: {
498
+ readonly tool: AgentTool | undefined;
499
+ readonly toolName: string;
500
+ readonly toolCallId: string;
501
+ readonly args: unknown;
502
+ readonly parent?: Span;
503
+ }): Span | undefined;
504
+ /**
505
+ * End an `execute_tool` span. Pass `status` to specify the terminal status
506
+ * explicitly (`"ok" | "error" | "skipped" | "blocked" | "timeout" |
507
+ * "aborted"`); when omitted, `status` is derived from `isError`. Passing
508
+ * `errorObject` (the thrown value) additionally records an exception with
509
+ * stack.
510
+ */
511
+ export declare function finishExecuteToolSpan(telemetry: AgentTelemetry | undefined, span: Span | undefined, options: {
512
+ readonly result?: unknown;
513
+ readonly isError: boolean;
514
+ readonly status?: ToolStatus;
515
+ readonly errorMessage?: string;
516
+ readonly errorObject?: unknown;
517
+ readonly toolCallId: string;
518
+ readonly toolName: string;
519
+ }): void;
520
+ /** Span attribute carrying the terminal {@link ToolStatus}. */
521
+ export declare const EXECUTE_TOOL_STATUS_ATTR = PiGenAIAttr.ToolStatus;
522
+ /**
523
+ * Record a tool that bypassed the span lifecycle entirely (pre-run
524
+ * interrupt, post-execution tail sweep for calls that never produced a
525
+ * result message). The LLM still asked for the tool, so it counts toward
526
+ * coverage and toward the relevant `tools.<status>` counter; no span is
527
+ * emitted because the loop never started one.
528
+ */
529
+ export declare function recordSkippedTool(telemetry: AgentTelemetry | undefined, options: {
530
+ readonly toolCallId: string;
531
+ readonly toolName: string;
532
+ readonly status: Extract<ToolStatus, "skipped" | "aborted" | "error">;
533
+ }): void;
534
+ /**
535
+ * End an `invoke_agent` span. Snapshots the run collector, stamps aggregate
536
+ * `gen_ai.agent.*` attributes on the span, fires the non-fatal
537
+ * {@link AgentTelemetryConfig.onRunEnd} hook, then records any uncaught
538
+ * error and ends the span.
539
+ */
540
+ export declare function finishInvokeAgentSpan(telemetry: AgentTelemetry | undefined, span: Span | undefined, options: {
541
+ readonly stepCount: number;
542
+ readonly errorObject?: unknown;
543
+ }): {
544
+ readonly summary: AgentRunSummary;
545
+ readonly coverage: AgentRunCoverage;
546
+ } | undefined;
547
+ /**
548
+ * Invoke {@link AgentTelemetryConfig.onRunEnd} on `telemetry` if set. Throws
549
+ * are caught and surfaced via the `onTelemetryWarning` hook (falling back to `console.warn`
550
+ * when no hook is set) — telemetry callbacks NEVER turn a
551
+ * successful agent run into a failed one. Idempotent at the call site via
552
+ * {@link AgentRunCollector.markRunEnded}; callers must check that before
553
+ * calling this helper.
554
+ */
555
+ export declare function fireOnRunEnd(telemetry: AgentTelemetry, summary: AgentRunSummary, coverage: AgentRunCoverage): void;
556
+ /** Aggregate `pi.gen_ai.agent.*` attributes stamped on the `invoke_agent` span. */
557
+ export declare const enum PiGenAIAggregateAttr {
558
+ ChatsCount = "pi.gen_ai.agent.chats.count",
559
+ ChatsTotalLatencyMs = "pi.gen_ai.agent.chats.total_latency_ms",
560
+ ChatsStopReasonPrefix = "pi.gen_ai.agent.chats.stop_reason.",
561
+ ToolsCount = "pi.gen_ai.agent.tools.count",
562
+ ToolsOkCount = "pi.gen_ai.agent.tools.ok.count",
563
+ ToolsErrorCount = "pi.gen_ai.agent.tools.error.count",
564
+ ToolsSkippedCount = "pi.gen_ai.agent.tools.skipped.count",
565
+ ToolsBlockedCount = "pi.gen_ai.agent.tools.blocked.count",
566
+ ToolsTimeoutCount = "pi.gen_ai.agent.tools.timeout.count",
567
+ ToolsAbortedCount = "pi.gen_ai.agent.tools.aborted.count",
568
+ ToolsTotalLatencyMs = "pi.gen_ai.agent.tools.total_latency_ms",
569
+ ToolsInvoked = "pi.gen_ai.agent.tools.invoked",
570
+ ToolsAvailable = "pi.gen_ai.agent.tools.available",
571
+ ToolsUnused = "pi.gen_ai.agent.tools.unused",
572
+ UsageInputTokensTotal = "pi.gen_ai.agent.usage.input_tokens.total",
573
+ UsageOutputTokensTotal = "pi.gen_ai.agent.usage.output_tokens.total",
574
+ UsageCacheReadInputTokensTotal = "pi.gen_ai.agent.usage.cache_read.input_tokens.total",
575
+ UsageCacheCreationInputTokensTotal = "pi.gen_ai.agent.usage.cache_creation.input_tokens.total",
576
+ UsageReasoningOutputTokensTotal = "pi.gen_ai.agent.usage.reasoning.output_tokens.total",
577
+ UsageTotalTokensTotal = "pi.gen_ai.agent.usage.total_tokens.total",
578
+ CostEstimatedUsdTotal = "pi.gen_ai.agent.cost.estimated_usd.total",
579
+ ErrorsCount = "pi.gen_ai.agent.errors.count"
580
+ }
581
+ /**
582
+ * Run `fn` with `span` activated on the OTEL context. Spans created
583
+ * downstream (provider HTTP clients, MCP tools, user code) attach as
584
+ * children. No-op when `span` is undefined.
585
+ *
586
+ * Required because `tracer.startSpan` creates the span object but does not
587
+ * activate it — without this wrapper, downstream spans attach to whatever
588
+ * context was active before and the parent linkage we advertise is lost.
589
+ */
590
+ export declare function runInActiveSpan<T>(span: Span | undefined, fn: () => Promise<T>): Promise<T>;
591
+ /**
592
+ * Emit a one-shot `handoff` span describing a transition between two named
593
+ * agents. Pass `parent` to make the span a child of an in-flight
594
+ * invoke_agent span; otherwise the active context's span is used.
595
+ */
596
+ export declare function recordHandoff(telemetry: AgentTelemetry | undefined, options: {
597
+ readonly fromAgent: AgentIdentity | undefined;
598
+ readonly toAgent: AgentIdentity;
599
+ readonly parent?: Span;
600
+ readonly attributes?: Attributes;
601
+ }): void;
602
+ /**
603
+ * Set a single attribute on a possibly-undefined span. Use when the caller
604
+ * needs to attach context outside the standard helpers without a branch.
605
+ */
606
+ export declare function setSpanAttribute(span: Span | undefined, key: string, value: AttributeValue): void;
607
+ /** Re-exports so consumers can write hooks without depending on @opentelemetry/api directly. */
608
+ export { type Attributes, type Span, SpanKind, SpanStatusCode, type Tracer, trace };
@@ -0,0 +1,18 @@
1
+ import { Effort } from "@linxiraos/pi-ai";
2
+ /**
3
+ * Agent-local thinking selector.
4
+ *
5
+ * `off` disables reasoning, while `inherit` defers to a higher-level selector.
6
+ */
7
+ export declare const ThinkingLevel: {
8
+ readonly Inherit: "inherit";
9
+ readonly Off: "off";
10
+ readonly Minimal: Effort.Minimal;
11
+ readonly Low: Effort.Low;
12
+ readonly Medium: Effort.Medium;
13
+ readonly High: Effort.High;
14
+ readonly XHigh: Effort.XHigh;
15
+ readonly Max: Effort.Max;
16
+ };
17
+ export type ThinkingLevel = (typeof ThinkingLevel)[keyof typeof ThinkingLevel];
18
+ export type ResolvedThinkingLevel = Exclude<ThinkingLevel, "inherit">;
@@ -0,0 +1,2 @@
1
+ export declare function countTokens(text: string | string[]): number;
2
+ export declare function countTokensConservatively(text: string | string[]): number;