@gajae-code/agent-core 0.1.1

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