@linxiraos/pi-agent-core 1.1.27 → 1.2.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.
- package/CHANGELOG.md +6 -264
- package/README.md +1 -1
- package/dist/types/agent.d.ts +15 -2
- package/dist/types/compaction/branch-summarization.d.ts +1 -1
- package/dist/types/compaction/compaction.d.ts +10 -2
- package/dist/types/compaction/messages.d.ts +6 -0
- package/dist/types/compaction/pruning.d.ts +31 -7
- package/dist/types/compaction/tool-protection.d.ts +2 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/speculative-execution.d.ts +1 -1
- package/dist/types/telemetry.d.ts +60 -60
- package/dist/types/tool-arguments.d.ts +19 -0
- package/dist/types/types.d.ts +12 -1
- package/package.json +8 -8
- package/src/agent-loop.ts +15 -15
- package/src/agent.ts +49 -4
- package/src/append-only-context.ts +38 -29
- package/src/compaction/branch-summarization.ts +1 -1
- package/src/compaction/compaction-v2-streaming.ts +10 -1
- package/src/compaction/compaction.ts +36 -42
- package/src/compaction/message-cache.ts +1 -1
- package/src/compaction/messages.ts +25 -0
- package/src/compaction/openai.ts +2 -0
- package/src/compaction/pruning.ts +249 -77
- package/src/compaction/tool-protection.ts +8 -2
- package/src/index.ts +2 -0
- package/src/speculative-execution.ts +5 -9
- package/src/telemetry.ts +81 -80
- package/src/tool-arguments.ts +38 -0
- package/src/types.ts +12 -1
package/src/telemetry.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* OpenTelemetry instrumentation for the agent loop.
|
|
3
3
|
*
|
|
4
4
|
* Implements the OpenTelemetry GenAI semantic conventions
|
|
5
|
-
* (https://opentelemetry.io/docs/specs/semconv/gen-ai/) plus `
|
|
5
|
+
* (https://opentelemetry.io/docs/specs/semconv/gen-ai/) plus `zeta.gen_ai.*`
|
|
6
6
|
* extension attributes for run summaries, dashboard summaries, and cost hints
|
|
7
7
|
* that are useful to downstream observability UIs.
|
|
8
8
|
*
|
|
@@ -126,47 +126,47 @@ export const enum OpenAIAttr {
|
|
|
126
126
|
|
|
127
127
|
/** Project extension attributes. Kept out of the reserved `gen_ai.*` namespace. */
|
|
128
128
|
export const enum OmpGenAIAttr {
|
|
129
|
-
AgentStepNumber = "
|
|
130
|
-
AgentStepCount = "
|
|
131
|
-
RequestReasoningEffort = "
|
|
132
|
-
RequestToolChoice = "
|
|
133
|
-
RequestAvailableTools = "
|
|
134
|
-
RequestMessages = "
|
|
135
|
-
ResponseText = "
|
|
136
|
-
ResponseToolCalls = "
|
|
137
|
-
ResponseUpstreamProvider = "
|
|
138
|
-
UsageTotalTokens = "
|
|
139
|
-
UsageServerSideTools = "
|
|
140
|
-
CostEstimatedUsd = "
|
|
141
|
-
CostInputUsd = "
|
|
142
|
-
CostOutputUsd = "
|
|
143
|
-
CostUnavailableReason = "
|
|
144
|
-
ToolStatus = "
|
|
145
|
-
ToolCallIntent = "
|
|
146
|
-
HandoffFromAgentName = "
|
|
147
|
-
HandoffFromAgentId = "
|
|
148
|
-
HandoffToAgentName = "
|
|
149
|
-
HandoffToAgentId = "
|
|
129
|
+
AgentStepNumber = "zeta.gen_ai.agent.step.number",
|
|
130
|
+
AgentStepCount = "zeta.gen_ai.agent.step.count",
|
|
131
|
+
RequestReasoningEffort = "zeta.gen_ai.request.reasoning.effort",
|
|
132
|
+
RequestToolChoice = "zeta.gen_ai.request.tool.choice",
|
|
133
|
+
RequestAvailableTools = "zeta.gen_ai.request.available_tools",
|
|
134
|
+
RequestMessages = "zeta.gen_ai.request.messages",
|
|
135
|
+
ResponseText = "zeta.gen_ai.response.text",
|
|
136
|
+
ResponseToolCalls = "zeta.gen_ai.response.tool_calls",
|
|
137
|
+
ResponseUpstreamProvider = "zeta.gen_ai.response.upstream_provider",
|
|
138
|
+
UsageTotalTokens = "zeta.gen_ai.usage.total_tokens",
|
|
139
|
+
UsageServerSideTools = "zeta.gen_ai.usage.server_tool_requests",
|
|
140
|
+
CostEstimatedUsd = "zeta.gen_ai.cost.estimated_usd",
|
|
141
|
+
CostInputUsd = "zeta.gen_ai.cost.input_usd",
|
|
142
|
+
CostOutputUsd = "zeta.gen_ai.cost.output_usd",
|
|
143
|
+
CostUnavailableReason = "zeta.gen_ai.cost.unavailable_reason",
|
|
144
|
+
ToolStatus = "zeta.gen_ai.tool.status",
|
|
145
|
+
ToolCallIntent = "zeta.gen_ai.tool.call.intent",
|
|
146
|
+
HandoffFromAgentName = "zeta.gen_ai.handoff.from_agent.name",
|
|
147
|
+
HandoffFromAgentId = "zeta.gen_ai.handoff.from_agent.id",
|
|
148
|
+
HandoffToAgentName = "zeta.gen_ai.handoff.to_agent.name",
|
|
149
|
+
HandoffToAgentId = "zeta.gen_ai.handoff.to_agent.id",
|
|
150
150
|
// Marks chat spans emitted outside the agent loop (compaction, handoff, branch
|
|
151
151
|
// summary, image inspection, …). Lets dashboards split oneshot cost / latency
|
|
152
152
|
// from main-turn cost without overloading the semconv `gen_ai.operation.name`.
|
|
153
|
-
OneshotKind = "
|
|
153
|
+
OneshotKind = "zeta.gen_ai.oneshot.kind",
|
|
154
154
|
// Gateway / proxy (LiteLLM, Helicone, Portkey, …) — populated when a known
|
|
155
155
|
// gateway header pattern is detected on the upstream response. The base
|
|
156
156
|
// `gen_ai.provider.name` continues to track the *upstream* provider (e.g.
|
|
157
157
|
// `anthropic`) that the gateway routed to.
|
|
158
|
-
GatewayName = "
|
|
159
|
-
GatewayEndpoint = "
|
|
160
|
-
GatewayCallId = "
|
|
161
|
-
GatewayRoutedTo = "
|
|
158
|
+
GatewayName = "zeta.gen_ai.gateway.name",
|
|
159
|
+
GatewayEndpoint = "zeta.gen_ai.gateway.endpoint",
|
|
160
|
+
GatewayCallId = "zeta.gen_ai.gateway.call_id",
|
|
161
|
+
GatewayRoutedTo = "zeta.gen_ai.gateway.routed_to",
|
|
162
162
|
/** Cloudflare AI Gateway response-cache status (`cf-aig-cache-status`), never prompt-cache. */
|
|
163
|
-
GatewayResponseCacheStatus = "
|
|
163
|
+
GatewayResponseCacheStatus = "zeta.gen_ai.gateway.response_cache.status",
|
|
164
164
|
/** Caller-level reason a judgment ran (`find`, `ttsr`, `judge_batch`, …). */
|
|
165
|
-
JudgmentPurpose = "
|
|
165
|
+
JudgmentPurpose = "zeta.gen_ai.judgment.purpose",
|
|
166
166
|
/** Questions the caller asked in one judgment request. */
|
|
167
|
-
JudgmentQuestions = "
|
|
167
|
+
JudgmentQuestions = "zeta.gen_ai.judgment.questions",
|
|
168
168
|
/** Questions answered from the local judgment cache instead of the provider. */
|
|
169
|
-
JudgmentCachedQuestions = "
|
|
169
|
+
JudgmentCachedQuestions = "zeta.gen_ai.judgment.cached_questions",
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
/** GenAI operation names — values for {@link GenAIAttr.OperationName}. */
|
|
@@ -220,9 +220,9 @@ export interface CostEstimatorContext {
|
|
|
220
220
|
|
|
221
221
|
/**
|
|
222
222
|
* Cost estimator result.
|
|
223
|
-
* { usd: number } — cost is known; emitted as
|
|
223
|
+
* { usd: number } — cost is known; emitted as zeta.gen_ai.cost.estimated_usd
|
|
224
224
|
* { unavailable: string } — cost is intentionally unknown; emitted as
|
|
225
|
-
*
|
|
225
|
+
* zeta.gen_ai.cost.unavailable_reason
|
|
226
226
|
* undefined — no opinion; nothing emitted
|
|
227
227
|
*/
|
|
228
228
|
export type CostEstimate =
|
|
@@ -271,7 +271,7 @@ export interface ChatUsageEvent {
|
|
|
271
271
|
*
|
|
272
272
|
* Use this to reconcile gateway-issued ids (e.g. `x-litellm-call-id`) with
|
|
273
273
|
* downstream billing / spend dashboards. Known gateway patterns are also
|
|
274
|
-
* auto-stamped on the chat span as `
|
|
274
|
+
* auto-stamped on the chat span as `zeta.gen_ai.gateway.*` attributes.
|
|
275
275
|
*/
|
|
276
276
|
readonly headers: Readonly<Record<string, string>> | undefined;
|
|
277
277
|
}
|
|
@@ -836,15 +836,27 @@ function serializeRequestMessagesForTelemetry(
|
|
|
836
836
|
): string | undefined {
|
|
837
837
|
const serializer = telemetry.config.contentSerializer?.requestMessages;
|
|
838
838
|
if (serializer) return callContentSerializer(telemetry, "requestMessages", () => serializer(request));
|
|
839
|
+
const systemParts = normalizeSystemPromptParts(request.systemPrompt);
|
|
840
|
+
const requestMessages = request.messages ?? [];
|
|
841
|
+
const total = systemParts.length + requestMessages.length;
|
|
842
|
+
if (total === 0) return undefined;
|
|
843
|
+
// Summarize only what survives the message cap; the rest is counted.
|
|
839
844
|
const messages: TelemetryMessageSummary[] = [];
|
|
840
|
-
for (const text of
|
|
845
|
+
for (const text of systemParts) {
|
|
846
|
+
if (messages.length === MAX_TELEMETRY_MESSAGE_COUNT) break;
|
|
841
847
|
messages.push({ role: "system", content: summarizeTelemetryValue(text) });
|
|
842
|
-
if (request.messages) {
|
|
843
|
-
for (const message of request.messages) {
|
|
844
|
-
messages.push({ role: message.role, content: summarizeTelemetryValue(message.content) });
|
|
845
|
-
}
|
|
846
848
|
}
|
|
847
|
-
|
|
849
|
+
for (const message of requestMessages) {
|
|
850
|
+
if (messages.length === MAX_TELEMETRY_MESSAGE_COUNT) break;
|
|
851
|
+
messages.push({ role: message.role, content: summarizeTelemetryValue(message.content) });
|
|
852
|
+
}
|
|
853
|
+
if (total > MAX_TELEMETRY_MESSAGE_COUNT) {
|
|
854
|
+
messages.push({
|
|
855
|
+
role: "system",
|
|
856
|
+
content: { kind: "truncated", omittedMessages: total - MAX_TELEMETRY_MESSAGE_COUNT },
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
return stringifyJsonAttribute(messages);
|
|
848
860
|
}
|
|
849
861
|
|
|
850
862
|
function serializeResponseTextForTelemetry(telemetry: AgentTelemetry, message: AssistantMessage): string | undefined {
|
|
@@ -1022,17 +1034,6 @@ function callContentSerializer(
|
|
|
1022
1034
|
}
|
|
1023
1035
|
}
|
|
1024
1036
|
|
|
1025
|
-
function limitTelemetryMessages(messages: readonly TelemetryMessageSummary[]): TelemetryMessageSummary[] {
|
|
1026
|
-
const limited = messages.slice(0, MAX_TELEMETRY_MESSAGE_COUNT);
|
|
1027
|
-
if (messages.length > MAX_TELEMETRY_MESSAGE_COUNT) {
|
|
1028
|
-
limited.push({
|
|
1029
|
-
role: "system",
|
|
1030
|
-
content: { kind: "truncated", omittedMessages: messages.length - MAX_TELEMETRY_MESSAGE_COUNT },
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
return limited;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
1037
|
function limitTelemetryToolCalls(toolCalls: readonly TelemetryToolCallSummary[]): TelemetryToolCallSummary[] {
|
|
1037
1038
|
const limited = toolCalls.slice(0, MAX_TELEMETRY_ARRAY_ITEMS);
|
|
1038
1039
|
if (toolCalls.length > MAX_TELEMETRY_ARRAY_ITEMS) {
|
|
@@ -1788,10 +1789,10 @@ export interface InstrumentedChatSpanOptions {
|
|
|
1788
1789
|
/** Step index recorded on the span; defaults to `-1` for non-loop calls. */
|
|
1789
1790
|
readonly stepNumber?: number;
|
|
1790
1791
|
/**
|
|
1791
|
-
* Tag stamped onto `
|
|
1792
|
+
* Tag stamped onto `zeta.gen_ai.oneshot.kind`. Values used by the agent:
|
|
1792
1793
|
* `compaction_summary`, `compaction_short_summary`, `compaction_turn_prefix`,
|
|
1793
|
-
* `handoff`, `branch_summary`, `image_question`. Free-form
|
|
1794
|
-
* outside this package to add new kinds without bumping the helper.
|
|
1794
|
+
* `handoff`, `branch_summary`, `image_question`, `skill_description`. Free-form
|
|
1795
|
+
* to allow callers outside this package to add new kinds without bumping the helper.
|
|
1795
1796
|
*/
|
|
1796
1797
|
readonly oneshotKind?: string;
|
|
1797
1798
|
/** Extra span attributes applied verbatim. */
|
|
@@ -2116,33 +2117,33 @@ export function fireOnRunEnd(telemetry: AgentTelemetry, summary: AgentRunSummary
|
|
|
2116
2117
|
}
|
|
2117
2118
|
}
|
|
2118
2119
|
|
|
2119
|
-
/** Aggregate `
|
|
2120
|
+
/** Aggregate `zeta.gen_ai.agent.*` attributes stamped on the `invoke_agent` span. */
|
|
2120
2121
|
export const enum OmpGenAIAggregateAttr {
|
|
2121
|
-
ChatsCount = "
|
|
2122
|
-
ChatsTotalLatencyMs = "
|
|
2123
|
-
ChatsStopReasonPrefix = "
|
|
2124
|
-
ToolsCount = "
|
|
2125
|
-
ToolsOkCount = "
|
|
2126
|
-
ToolsErrorCount = "
|
|
2127
|
-
ToolsSkippedCount = "
|
|
2128
|
-
ToolsBlockedCount = "
|
|
2129
|
-
ToolsTimeoutCount = "
|
|
2130
|
-
ToolsAbortedCount = "
|
|
2131
|
-
ToolsTotalLatencyMs = "
|
|
2132
|
-
ToolsInvoked = "
|
|
2133
|
-
ToolsAvailable = "
|
|
2134
|
-
ToolsUnused = "
|
|
2135
|
-
UsageInputTokensTotal = "
|
|
2136
|
-
UsageOutputTokensTotal = "
|
|
2137
|
-
UsageCacheReadInputTokensTotal = "
|
|
2138
|
-
UsageCacheCreationInputTokensTotal = "
|
|
2139
|
-
UsageReasoningOutputTokensTotal = "
|
|
2140
|
-
UsageTotalTokensTotal = "
|
|
2141
|
-
CostEstimatedUsdTotal = "
|
|
2142
|
-
ErrorsCount = "
|
|
2143
|
-
}
|
|
2144
|
-
|
|
2145
|
-
/** Stamp the aggregate `
|
|
2122
|
+
ChatsCount = "zeta.gen_ai.agent.chats.count",
|
|
2123
|
+
ChatsTotalLatencyMs = "zeta.gen_ai.agent.chats.total_latency_ms",
|
|
2124
|
+
ChatsStopReasonPrefix = "zeta.gen_ai.agent.chats.stop_reason.",
|
|
2125
|
+
ToolsCount = "zeta.gen_ai.agent.tools.count",
|
|
2126
|
+
ToolsOkCount = "zeta.gen_ai.agent.tools.ok.count",
|
|
2127
|
+
ToolsErrorCount = "zeta.gen_ai.agent.tools.error.count",
|
|
2128
|
+
ToolsSkippedCount = "zeta.gen_ai.agent.tools.skipped.count",
|
|
2129
|
+
ToolsBlockedCount = "zeta.gen_ai.agent.tools.blocked.count",
|
|
2130
|
+
ToolsTimeoutCount = "zeta.gen_ai.agent.tools.timeout.count",
|
|
2131
|
+
ToolsAbortedCount = "zeta.gen_ai.agent.tools.aborted.count",
|
|
2132
|
+
ToolsTotalLatencyMs = "zeta.gen_ai.agent.tools.total_latency_ms",
|
|
2133
|
+
ToolsInvoked = "zeta.gen_ai.agent.tools.invoked",
|
|
2134
|
+
ToolsAvailable = "zeta.gen_ai.agent.tools.available",
|
|
2135
|
+
ToolsUnused = "zeta.gen_ai.agent.tools.unused",
|
|
2136
|
+
UsageInputTokensTotal = "zeta.gen_ai.agent.usage.input_tokens.total",
|
|
2137
|
+
UsageOutputTokensTotal = "zeta.gen_ai.agent.usage.output_tokens.total",
|
|
2138
|
+
UsageCacheReadInputTokensTotal = "zeta.gen_ai.agent.usage.cache_read.input_tokens.total",
|
|
2139
|
+
UsageCacheCreationInputTokensTotal = "zeta.gen_ai.agent.usage.cache_creation.input_tokens.total",
|
|
2140
|
+
UsageReasoningOutputTokensTotal = "zeta.gen_ai.agent.usage.reasoning.output_tokens.total",
|
|
2141
|
+
UsageTotalTokensTotal = "zeta.gen_ai.agent.usage.total_tokens.total",
|
|
2142
|
+
CostEstimatedUsdTotal = "zeta.gen_ai.agent.cost.estimated_usd.total",
|
|
2143
|
+
ErrorsCount = "zeta.gen_ai.agent.errors.count",
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
/** Stamp the aggregate `zeta.gen_ai.agent.*` attributes on the given span. */
|
|
2146
2147
|
function applyAggregateAttributes(span: Span, summary: AgentRunSummary, coverage: AgentRunCoverage): void {
|
|
2147
2148
|
span.setAttribute(OmpGenAIAggregateAttr.ChatsCount, summary.chats.total);
|
|
2148
2149
|
span.setAttribute(OmpGenAIAggregateAttr.ChatsTotalLatencyMs, summary.chats.totalLatencyMs);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ToolCall, validateToolArguments } from "@linxiraos/pi-ai";
|
|
2
|
+
import type { AgentTool } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where a call's `__parseError`/`__rawJson` keys came from.
|
|
6
|
+
* - `"parsed"`: a provider stream parsed the arguments, so `__parseError` is a
|
|
7
|
+
* genuine parse-failure sentinel and is never handed to a tool.
|
|
8
|
+
* - `"payload"`: the arguments are a payload the model wrote (eval tool bridge,
|
|
9
|
+
* `xd://` device content), so those keys are forged and only stripped.
|
|
10
|
+
*/
|
|
11
|
+
export type ToolArgumentSource = "parsed" | "payload";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validate a call's arguments against `tool`'s schema, honoring
|
|
15
|
+
* {@link AgentTool.lenientArgValidation}: on a schema mismatch a lenient tool
|
|
16
|
+
* receives its raw arguments minus the internal `__parseError`/`__rawJson`
|
|
17
|
+
* sentinels. Malformed JSON from a provider stream is never lenient.
|
|
18
|
+
*
|
|
19
|
+
* @throws the validation error when the tool is strict or the arguments failed to parse.
|
|
20
|
+
*/
|
|
21
|
+
export function validateAgentToolArguments(
|
|
22
|
+
tool: AgentTool,
|
|
23
|
+
toolCall: ToolCall,
|
|
24
|
+
source: ToolArgumentSource = "parsed",
|
|
25
|
+
): ToolCall["arguments"] {
|
|
26
|
+
try {
|
|
27
|
+
return validateToolArguments(tool, toolCall);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const args: unknown = toolCall.arguments;
|
|
30
|
+
if (!tool.lenientArgValidation) throw error;
|
|
31
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return args as ToolCall["arguments"];
|
|
32
|
+
if (source === "parsed" && "__parseError" in args) throw error;
|
|
33
|
+
const fallback = { ...(args as Record<string, unknown>) };
|
|
34
|
+
delete fallback.__parseError;
|
|
35
|
+
delete fallback.__rawJson;
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -328,6 +328,16 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
328
328
|
* background.
|
|
329
329
|
*/
|
|
330
330
|
hasBackgroundCompletions?: () => boolean | Promise<boolean>;
|
|
331
|
+
/**
|
|
332
|
+
* Peeks whether a passive aside (an extension or user message queued with
|
|
333
|
+
* `deliverAs: "aside"`) is waiting for injection at the next boundary.
|
|
334
|
+
*
|
|
335
|
+
* Same rules as {@link hasBackgroundCompletions}: non-consuming, ends only
|
|
336
|
+
* *interruptible* waits, never raises {@link ToolCallContext.steeringSignal}.
|
|
337
|
+
* The host still injects the aside once at the boundary; ordinary running
|
|
338
|
+
* tools finish first.
|
|
339
|
+
*/
|
|
340
|
+
hasQueuedAsides?: () => boolean | Promise<boolean>;
|
|
331
341
|
|
|
332
342
|
/**
|
|
333
343
|
* Returns follow-up messages to process after the agent would otherwise stop.
|
|
@@ -341,7 +351,8 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
341
351
|
* Returns non-interrupting "aside" messages to inject at a step boundary.
|
|
342
352
|
*
|
|
343
353
|
* Polled after each tool batch (before the next LLM call) AND at the yield
|
|
344
|
-
* check. Unlike steering, these
|
|
354
|
+
* check. Unlike steering, these never abort foreground tools (only an
|
|
355
|
+
* interruptible `wait` ends early, via the peek hooks) — they are passive
|
|
345
356
|
* notifications (e.g. background-job completions, late LSP diagnostics) that
|
|
346
357
|
* should reach the model between requests without waiting for the agent to
|
|
347
358
|
* fully stop. Returned messages are appended to the context with normal
|