@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.
- package/CHANGELOG.md +482 -0
- package/README.md +473 -0
- package/dist/types/agent-loop.d.ts +55 -0
- package/dist/types/agent.d.ts +334 -0
- package/dist/types/append-only-context.d.ts +113 -0
- package/dist/types/compaction/branch-summarization.d.ts +94 -0
- package/dist/types/compaction/compaction.d.ts +166 -0
- package/dist/types/compaction/entries.d.ts +103 -0
- package/dist/types/compaction/errors.d.ts +26 -0
- package/dist/types/compaction/index.d.ts +11 -0
- package/dist/types/compaction/messages.d.ts +61 -0
- package/dist/types/compaction/openai.d.ts +58 -0
- package/dist/types/compaction/pruning.d.ts +18 -0
- package/dist/types/compaction/utils.d.ts +32 -0
- package/dist/types/compaction.d.ts +1 -0
- package/dist/types/harmony-leak.d.ts +99 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/proxy.d.ts +84 -0
- package/dist/types/run-collector.d.ts +196 -0
- package/dist/types/telemetry.d.ts +588 -0
- package/dist/types/thinking.d.ts +17 -0
- package/dist/types/types.d.ts +407 -0
- package/package.json +75 -0
- package/src/agent-loop.ts +1279 -0
- package/src/agent.ts +1399 -0
- package/src/append-only-context.ts +297 -0
- package/src/compaction/branch-summarization.ts +339 -0
- package/src/compaction/compaction.ts +1065 -0
- package/src/compaction/entries.ts +133 -0
- package/src/compaction/errors.ts +31 -0
- package/src/compaction/index.ts +12 -0
- package/src/compaction/messages.ts +212 -0
- package/src/compaction/openai.ts +552 -0
- package/src/compaction/prompts/auto-handoff-threshold-focus.md +1 -0
- package/src/compaction/prompts/branch-summary-context.md +5 -0
- package/src/compaction/prompts/branch-summary-preamble.md +2 -0
- package/src/compaction/prompts/branch-summary.md +30 -0
- package/src/compaction/prompts/compaction-short-summary.md +9 -0
- package/src/compaction/prompts/compaction-summary-context.md +5 -0
- package/src/compaction/prompts/compaction-summary.md +38 -0
- package/src/compaction/prompts/compaction-turn-prefix.md +17 -0
- package/src/compaction/prompts/compaction-update-summary.md +45 -0
- package/src/compaction/prompts/file-operations.md +10 -0
- package/src/compaction/prompts/handoff-document.md +49 -0
- package/src/compaction/prompts/summarization-system.md +3 -0
- package/src/compaction/pruning.ts +92 -0
- package/src/compaction/utils.ts +185 -0
- package/src/compaction.ts +1 -0
- package/src/harmony-leak.ts +427 -0
- package/src/index.ts +19 -0
- package/src/proxy.ts +326 -0
- package/src/run-collector.ts +631 -0
- package/src/telemetry.ts +2018 -0
- package/src/thinking.ts +19 -0
- 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">;
|