@linxiraos/pi-agent-core 1.1.26 → 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 -243
- package/README.md +1 -1
- package/dist/types/agent.d.ts +27 -7
- package/dist/types/compaction/anthropic.d.ts +14 -8
- package/dist/types/compaction/branch-summarization.d.ts +1 -1
- package/dist/types/compaction/compaction-v2-streaming.d.ts +5 -1
- package/dist/types/compaction/compaction.d.ts +24 -2
- package/dist/types/compaction/entries.d.ts +2 -2
- package/dist/types/compaction/messages.d.ts +6 -0
- package/dist/types/compaction/openai.d.ts +14 -2
- package/dist/types/compaction/pruning.d.ts +64 -16
- package/dist/types/compaction/tool-protection.d.ts +2 -1
- package/dist/types/compaction/utils.d.ts +5 -0
- package/dist/types/image-tokens.d.ts +29 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/speculative-execution.d.ts +1 -1
- package/dist/types/telemetry.d.ts +70 -60
- package/dist/types/tokenizer.d.ts +9 -6
- 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 +191 -34
- package/src/agent.ts +126 -21
- package/src/append-only-context.ts +38 -29
- package/src/compaction/anthropic.ts +30 -12
- package/src/compaction/branch-summarization.ts +1 -1
- package/src/compaction/compaction-v2-streaming.ts +23 -16
- package/src/compaction/compaction.ts +163 -73
- package/src/compaction/entries.ts +2 -2
- package/src/compaction/message-cache.ts +1 -1
- package/src/compaction/messages.ts +25 -0
- package/src/compaction/openai.ts +65 -28
- package/src/compaction/pruning.ts +328 -96
- package/src/compaction/tool-protection.ts +8 -2
- package/src/compaction/utils.ts +9 -0
- package/src/image-tokens.ts +77 -0
- package/src/index.ts +2 -0
- package/src/prompts/dsml-tool-call-leak.md +19 -0
- package/src/speculative-execution.ts +5 -9
- package/src/telemetry.ts +100 -81
- package/src/tokenizer.ts +24 -17
- package/src/tool-arguments.ts +38 -0
- package/src/types.ts +12 -1
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool output pruning utilities for compaction.
|
|
3
3
|
*/
|
|
4
|
+
import type { ToolResultMessage } from "@linxiraos/pi-ai";
|
|
4
5
|
import type { Tokenizer } from "../tokenizer.js";
|
|
5
6
|
import type { SessionEntry } from "./entries.js";
|
|
7
|
+
import { type ConvertToLlm } from "./messages.js";
|
|
6
8
|
import { type ProtectedToolMatcher } from "./tool-protection.js";
|
|
7
|
-
|
|
9
|
+
/** Bound warm-cache pruning by the provider's prompt-cache lookback window. */
|
|
10
|
+
export interface CacheLookbackConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Prompt-cache lookback of the model the next request goes to, in block
|
|
13
|
+
* positions (catalog `prompt-cache-lookback`; Anthropic: 20). When set,
|
|
14
|
+
* warm-cache pruning also leaves a result whose rewrite would put the next
|
|
15
|
+
* request's cache lookup out of reach, whatever its token suffix (see
|
|
16
|
+
* `cacheLookbackFloor`). Undefined = no lookback bound.
|
|
17
|
+
*/
|
|
18
|
+
cacheLookbackPositions?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Projects context messages to what the provider receives, so lookback
|
|
21
|
+
* positions count app roles (file mentions, command output, custom
|
|
22
|
+
* messages) as sent. Default {@link defaultConvertToLlm}.
|
|
23
|
+
*/
|
|
24
|
+
convertToLlm?: ConvertToLlm;
|
|
25
|
+
}
|
|
26
|
+
export interface PruneConfig extends CacheLookbackConfig {
|
|
8
27
|
/** Keep the most recent tool output tokens intact. */
|
|
9
28
|
protectTokens: number;
|
|
10
29
|
/** Only prune if total savings meets this threshold. */
|
|
@@ -18,6 +37,8 @@ export interface PruneConfig {
|
|
|
18
37
|
* unchanged.
|
|
19
38
|
*/
|
|
20
39
|
supersedeKey?: SupersedeKeyFn;
|
|
40
|
+
/** Whether a keyed result shows its whole target (see {@link SupersedePruneConfig.supersedeComplete}). */
|
|
41
|
+
supersedeComplete?: SupersedeCompleteFn;
|
|
21
42
|
/** Useless-flagged results bypass the protect window (see {@link USELESS_NOTICE}). Default true. */
|
|
22
43
|
pruneUseless?: boolean;
|
|
23
44
|
/**
|
|
@@ -34,7 +55,8 @@ export interface PruneConfig {
|
|
|
34
55
|
* already-sent cache prefix: mutating it forces the provider to re-write the
|
|
35
56
|
* whole suffix (cacheWrite premium). Such results — including superseded and
|
|
36
57
|
* useless ones, which otherwise bypass {@link protectTokens} — are left for
|
|
37
|
-
* compaction/shake (which rebuild the cache anyway) to reclaim
|
|
58
|
+
* compaction/shake (which rebuild the cache anyway) to reclaim, as are
|
|
59
|
+
* results beyond {@link CacheLookbackConfig.cacheLookbackPositions}. Undefined =
|
|
38
60
|
* no cache guard (legacy: superseded/useless prune at any depth).
|
|
39
61
|
*/
|
|
40
62
|
cacheWarmSuffixTokens?: number;
|
|
@@ -56,18 +78,42 @@ export declare const SUPERSEDED_NOTICE = "[Superseded by a newer read of this fi
|
|
|
56
78
|
export declare const USELESS_NOTICE = "[Uneventful result elided]";
|
|
57
79
|
/**
|
|
58
80
|
* Maps a tool call to a supersede key. Results sharing a key form a group in
|
|
59
|
-
* which
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* `undefined` to exempt a call from
|
|
81
|
+
* which a newer successful result supersedes every older one. A key `K` also
|
|
82
|
+
* covers keys with prefix `K + "\u0000"`: they are superseded only when the
|
|
83
|
+
* newest successful result for `K` is complete (see
|
|
84
|
+
* {@link SupersedeCompleteFn}). Return `undefined` to exempt a call from
|
|
85
|
+
* supersede grouping.
|
|
63
86
|
*/
|
|
64
87
|
export type SupersedeKeyFn = (toolName: string, args: Record<string, unknown>) => string | undefined;
|
|
65
|
-
|
|
66
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Whether a successful keyed result shows its whole target, so a result for a
|
|
90
|
+
* parent key (e.g. a selector-free read) may supersede child-key results (e.g.
|
|
91
|
+
* range reads). A summary, truncated page, or notice is not complete.
|
|
92
|
+
*/
|
|
93
|
+
export type SupersedeCompleteFn = (message: ToolResultMessage) => boolean;
|
|
94
|
+
export interface SupersedePruneConfig extends CacheLookbackConfig {
|
|
95
|
+
/** Supersede key function; a newer successful result with the same key supersedes older ones (see {@link SupersedeKeyFn}). */
|
|
67
96
|
supersedeKey?: SupersedeKeyFn;
|
|
97
|
+
/**
|
|
98
|
+
* Whether a successful keyed result is complete. Absent, every successful
|
|
99
|
+
* result is complete. Child-key results are superseded only when the parent
|
|
100
|
+
* key's newest successful result is complete; same-key results follow
|
|
101
|
+
* newest-wins, since a same-key re-read that shows a different view means
|
|
102
|
+
* the target changed. A failed result supersedes only older failed results.
|
|
103
|
+
*
|
|
104
|
+
* Trade-off, by design: after `read foo.ts:50-200` → edit → a bare
|
|
105
|
+
* `read foo.ts` that returns a summary, the pre-edit range stays in context
|
|
106
|
+
* until a complete bare read or a same-range re-read. Keeping possibly stale
|
|
107
|
+
* detail is preferred over losing detail the summary does not show.
|
|
108
|
+
*/
|
|
109
|
+
supersedeComplete?: SupersedeCompleteFn;
|
|
68
110
|
/** Also prune results flagged useless by their tool. Default false. */
|
|
69
111
|
pruneUseless?: boolean;
|
|
70
|
-
/**
|
|
112
|
+
/**
|
|
113
|
+
* Prune a candidate now when all messages after it total at most this many
|
|
114
|
+
* estimated tokens and it sits within
|
|
115
|
+
* {@link CacheLookbackConfig.cacheLookbackPositions}. Default 8 000.
|
|
116
|
+
*/
|
|
71
117
|
suffixTokenLimit?: number;
|
|
72
118
|
/**
|
|
73
119
|
* Prune all candidates when the last message is at least this old: the
|
|
@@ -104,18 +150,20 @@ export declare const MIN_PRUNE_TOKENS = 50;
|
|
|
104
150
|
* candidate is pruned now only when the suffix after it is small (tail case —
|
|
105
151
|
* the read→edit→read loop) or when the context has been idle long enough that
|
|
106
152
|
* the provider cache is cold anyway (then all still-sent candidates flush).
|
|
107
|
-
* Never mutates entries before `keepBoundaryId` (summarized away — not sent)
|
|
153
|
+
* Never mutates entries before `keepBoundaryId` (summarized away — not sent),
|
|
154
|
+
* and never walks them unless a sent result's call lives there.
|
|
108
155
|
*/
|
|
109
|
-
export declare function pruneSupersededToolResults(entries: SessionEntry[], tokenizer: Tokenizer, config: SupersedePruneConfig): PruneResult;
|
|
110
|
-
export declare function pruneToolOutputs(entries: SessionEntry[], tokenizer: Tokenizer, config?: PruneConfig): PruneResult;
|
|
156
|
+
export declare function pruneSupersededToolResults(entries: readonly SessionEntry[], tokenizer: Tokenizer, config: SupersedePruneConfig): PruneResult;
|
|
157
|
+
export declare function pruneToolOutputs(entries: readonly SessionEntry[], tokenizer: Tokenizer, config?: PruneConfig): PruneResult;
|
|
111
158
|
/**
|
|
112
159
|
* Supersede key for the `read` tool: the file path with the trailing line/raw
|
|
113
160
|
* selector stripped (the read tool's own splitter grammar via
|
|
114
161
|
* {@link splitReadSelector}, e.g. `src/foo.ts:50-200`, `:2-4:raw`).
|
|
115
162
|
* Internal/URL-scheme paths (`skill://…`, `https://…`) are exempt.
|
|
116
|
-
* Selector-free reads key on the bare path;
|
|
117
|
-
* `path + "\u0000" + selector`, so
|
|
118
|
-
*
|
|
119
|
-
*
|
|
163
|
+
* Selector-free reads key on the bare path; line-range reads key on
|
|
164
|
+
* `path + "\u0000" + selector`, so a complete bare-path read can supersede them.
|
|
165
|
+
* `raw` and `conflicts` reads show a different form than a plain read (e.g. raw
|
|
166
|
+
* notebook JSON vs converted notebook text), so they key on
|
|
167
|
+
* `path + "\u0001" + selector` and are superseded only by the same selector.
|
|
120
168
|
*/
|
|
121
169
|
export declare function readToolSupersedeKey(toolName: string, args: Record<string, unknown>): string | undefined;
|
|
@@ -6,7 +6,8 @@ export interface ProtectedToolContext {
|
|
|
6
6
|
readonly toolCall: AgentToolCall | undefined;
|
|
7
7
|
}
|
|
8
8
|
export type ProtectedToolMatcher = string | ((context: ProtectedToolContext) => boolean);
|
|
9
|
-
|
|
9
|
+
/** Map tool-call ids to their calls across `entries[start, end)` (later calls win). */
|
|
10
|
+
export declare function collectToolCallsById(entries: readonly SessionEntry[], start?: number, end?: number): Map<string, AgentToolCall>;
|
|
10
11
|
/** Return the tool-result message carried by a session entry, if any. */
|
|
11
12
|
export declare function getToolResultMessage(entry: SessionEntry): ToolResultMessage | undefined;
|
|
12
13
|
/**
|
|
@@ -26,6 +26,11 @@ export declare function splitReadSelector(path: string): {
|
|
|
26
26
|
* and matches its write/edit path when computing Read/Write/RW markers.
|
|
27
27
|
*/
|
|
28
28
|
export declare function stripReadSelector(path: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a selector from {@link splitReadSelector} shows the file in a form other
|
|
31
|
+
* than its plain lines: verbatim `raw` bytes or the merge `conflicts` view.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isAlternateFormReadSelector(sel: string): boolean;
|
|
29
34
|
/**
|
|
30
35
|
* Whether `path` references a `scheme://` URL (internal URI or web URL) rather
|
|
31
36
|
* than a filesystem path that belongs in the compaction `<files>` summary.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ImageContent } from "@linxiraos/pi-ai";
|
|
2
|
+
import { type ImageSize } from "@linxiraos/pi-catalog/compat/image-tokenization";
|
|
3
|
+
/**
|
|
4
|
+
* Dimension-based image token estimates shared by the local context counter
|
|
5
|
+
* ({@link Tokenizer.countMessage}) and the native remote-compaction fit probe.
|
|
6
|
+
* Both sides must price the same image the same way: when they disagreed
|
|
7
|
+
* (1.2k flat locally vs a 12k flat worst case remotely) an image-heavy session
|
|
8
|
+
* stayed under the local compaction trigger yet was refused as over-window by
|
|
9
|
+
* the remote probe.
|
|
10
|
+
*
|
|
11
|
+
* Follows the catalog's image rule for the OpenAI Responses wire (GPT-5.5's
|
|
12
|
+
* 32px patches, a per-detail pixel limit and patch budget, x1.2 multiplier),
|
|
13
|
+
* which is also a close upper estimate for other providers once omp has
|
|
14
|
+
* downscaled the image (≤1568px by default).
|
|
15
|
+
*/
|
|
16
|
+
/** OpenAI image `detail` values; `undefined` means the provider default (`auto`). */
|
|
17
|
+
export type ImageDetail = ImageContent["detail"];
|
|
18
|
+
export type { ImageSize };
|
|
19
|
+
/**
|
|
20
|
+
* Estimated input tokens for one image. Unknown dimensions (undecodable data,
|
|
21
|
+
* remote URLs, provider file ids) charge the detail level's full patch budget.
|
|
22
|
+
*/
|
|
23
|
+
export declare function estimateImageTokens(size: ImageSize | null | undefined, detail?: ImageDetail): number;
|
|
24
|
+
/** Pixel size read from the header of base64-encoded image data, or null. */
|
|
25
|
+
export declare function base64ImageSize(base64: string): ImageSize | null;
|
|
26
|
+
/** Pixel size of a `data:<mime>;base64,` URL, or null for any other URL. */
|
|
27
|
+
export declare function dataUrlImageSize(url: string): ImageSize | null;
|
|
28
|
+
/** {@link estimateImageTokens} for an in-memory image block. */
|
|
29
|
+
export declare function estimateImageContentTokens(image: Pick<ImageContent, "data" | "detail">): number;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { AssistantMessage } from "@linxiraos/pi-ai";
|
|
2
2
|
import type { AgentContext, AgentLoopConfig, AgentTool, AgentToolCall, AgentToolResult, SpeculativeAuthorization, SpeculativeChildDefinition, SpeculativeChildHandle, SpeculativeLaunchContext, SpeculativeToolExecutionConfig, ToolSpeculationEffect, ToolSpeculationStreamSession } from "./types.js";
|
|
3
3
|
export type SpeculativeRawOutcome = {
|
|
4
4
|
result: AgentToolResult<unknown>;
|
|
@@ -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
|
*
|
|
@@ -79,40 +79,40 @@ export declare const enum OpenAIAttr {
|
|
|
79
79
|
}
|
|
80
80
|
/** Project extension attributes. Kept out of the reserved `gen_ai.*` namespace. */
|
|
81
81
|
export declare const enum OmpGenAIAttr {
|
|
82
|
-
AgentStepNumber = "
|
|
83
|
-
AgentStepCount = "
|
|
84
|
-
RequestReasoningEffort = "
|
|
85
|
-
RequestToolChoice = "
|
|
86
|
-
RequestAvailableTools = "
|
|
87
|
-
RequestMessages = "
|
|
88
|
-
ResponseText = "
|
|
89
|
-
ResponseToolCalls = "
|
|
90
|
-
ResponseUpstreamProvider = "
|
|
91
|
-
UsageTotalTokens = "
|
|
92
|
-
UsageServerSideTools = "
|
|
93
|
-
CostEstimatedUsd = "
|
|
94
|
-
CostInputUsd = "
|
|
95
|
-
CostOutputUsd = "
|
|
96
|
-
CostUnavailableReason = "
|
|
97
|
-
ToolStatus = "
|
|
98
|
-
ToolCallIntent = "
|
|
99
|
-
HandoffFromAgentName = "
|
|
100
|
-
HandoffFromAgentId = "
|
|
101
|
-
HandoffToAgentName = "
|
|
102
|
-
HandoffToAgentId = "
|
|
103
|
-
OneshotKind = "
|
|
104
|
-
GatewayName = "
|
|
105
|
-
GatewayEndpoint = "
|
|
106
|
-
GatewayCallId = "
|
|
107
|
-
GatewayRoutedTo = "
|
|
82
|
+
AgentStepNumber = "zeta.gen_ai.agent.step.number",
|
|
83
|
+
AgentStepCount = "zeta.gen_ai.agent.step.count",
|
|
84
|
+
RequestReasoningEffort = "zeta.gen_ai.request.reasoning.effort",
|
|
85
|
+
RequestToolChoice = "zeta.gen_ai.request.tool.choice",
|
|
86
|
+
RequestAvailableTools = "zeta.gen_ai.request.available_tools",
|
|
87
|
+
RequestMessages = "zeta.gen_ai.request.messages",
|
|
88
|
+
ResponseText = "zeta.gen_ai.response.text",
|
|
89
|
+
ResponseToolCalls = "zeta.gen_ai.response.tool_calls",
|
|
90
|
+
ResponseUpstreamProvider = "zeta.gen_ai.response.upstream_provider",
|
|
91
|
+
UsageTotalTokens = "zeta.gen_ai.usage.total_tokens",
|
|
92
|
+
UsageServerSideTools = "zeta.gen_ai.usage.server_tool_requests",
|
|
93
|
+
CostEstimatedUsd = "zeta.gen_ai.cost.estimated_usd",
|
|
94
|
+
CostInputUsd = "zeta.gen_ai.cost.input_usd",
|
|
95
|
+
CostOutputUsd = "zeta.gen_ai.cost.output_usd",
|
|
96
|
+
CostUnavailableReason = "zeta.gen_ai.cost.unavailable_reason",
|
|
97
|
+
ToolStatus = "zeta.gen_ai.tool.status",
|
|
98
|
+
ToolCallIntent = "zeta.gen_ai.tool.call.intent",
|
|
99
|
+
HandoffFromAgentName = "zeta.gen_ai.handoff.from_agent.name",
|
|
100
|
+
HandoffFromAgentId = "zeta.gen_ai.handoff.from_agent.id",
|
|
101
|
+
HandoffToAgentName = "zeta.gen_ai.handoff.to_agent.name",
|
|
102
|
+
HandoffToAgentId = "zeta.gen_ai.handoff.to_agent.id",
|
|
103
|
+
OneshotKind = "zeta.gen_ai.oneshot.kind",
|
|
104
|
+
GatewayName = "zeta.gen_ai.gateway.name",
|
|
105
|
+
GatewayEndpoint = "zeta.gen_ai.gateway.endpoint",
|
|
106
|
+
GatewayCallId = "zeta.gen_ai.gateway.call_id",
|
|
107
|
+
GatewayRoutedTo = "zeta.gen_ai.gateway.routed_to",
|
|
108
108
|
/** Cloudflare AI Gateway response-cache status (`cf-aig-cache-status`), never prompt-cache. */
|
|
109
|
-
GatewayResponseCacheStatus = "
|
|
109
|
+
GatewayResponseCacheStatus = "zeta.gen_ai.gateway.response_cache.status",
|
|
110
110
|
/** Caller-level reason a judgment ran (`find`, `ttsr`, `judge_batch`, …). */
|
|
111
|
-
JudgmentPurpose = "
|
|
111
|
+
JudgmentPurpose = "zeta.gen_ai.judgment.purpose",
|
|
112
112
|
/** Questions the caller asked in one judgment request. */
|
|
113
|
-
JudgmentQuestions = "
|
|
113
|
+
JudgmentQuestions = "zeta.gen_ai.judgment.questions",
|
|
114
114
|
/** Questions answered from the local judgment cache instead of the provider. */
|
|
115
|
-
JudgmentCachedQuestions = "
|
|
115
|
+
JudgmentCachedQuestions = "zeta.gen_ai.judgment.cached_questions"
|
|
116
116
|
}
|
|
117
117
|
/** GenAI operation names — values for {@link GenAIAttr.OperationName}. */
|
|
118
118
|
export declare const GenAIOperation: {
|
|
@@ -145,16 +145,24 @@ export interface ChatUsageSnapshot {
|
|
|
145
145
|
}
|
|
146
146
|
/** Context passed to the cost estimator. */
|
|
147
147
|
export interface CostEstimatorContext {
|
|
148
|
+
/** OTel provider label (`gen_ai.provider.name`). */
|
|
148
149
|
readonly provider: string;
|
|
150
|
+
/** Provider id before OTel normalization (e.g. `openai-codex` for the `openai` label). */
|
|
151
|
+
readonly providerId: string | undefined;
|
|
152
|
+
/** Model id reported on the response; a provider may report a served model instead of the requested one. */
|
|
149
153
|
readonly model: string;
|
|
154
|
+
/** Requested model id, when the caller knows it. */
|
|
155
|
+
readonly modelId: string | undefined;
|
|
150
156
|
readonly serviceTier: ServiceTier | undefined;
|
|
151
157
|
readonly usage: ChatUsageSnapshot;
|
|
158
|
+
/** Cost the provider already computed for this request; the value persisted with the message. */
|
|
159
|
+
readonly usageCost: Usage["cost"];
|
|
152
160
|
}
|
|
153
161
|
/**
|
|
154
162
|
* Cost estimator result.
|
|
155
|
-
* { usd: number } — cost is known; emitted as
|
|
163
|
+
* { usd: number } — cost is known; emitted as zeta.gen_ai.cost.estimated_usd
|
|
156
164
|
* { unavailable: string } — cost is intentionally unknown; emitted as
|
|
157
|
-
*
|
|
165
|
+
* zeta.gen_ai.cost.unavailable_reason
|
|
158
166
|
* undefined — no opinion; nothing emitted
|
|
159
167
|
*/
|
|
160
168
|
export type CostEstimate = {
|
|
@@ -205,7 +213,7 @@ export interface ChatUsageEvent {
|
|
|
205
213
|
*
|
|
206
214
|
* Use this to reconcile gateway-issued ids (e.g. `x-litellm-call-id`) with
|
|
207
215
|
* downstream billing / spend dashboards. Known gateway patterns are also
|
|
208
|
-
* auto-stamped on the chat span as `
|
|
216
|
+
* auto-stamped on the chat span as `zeta.gen_ai.gateway.*` attributes.
|
|
209
217
|
*/
|
|
210
218
|
readonly headers: Readonly<Record<string, string>> | undefined;
|
|
211
219
|
}
|
|
@@ -390,6 +398,8 @@ export interface ChatRequestSnapshot {
|
|
|
390
398
|
*/
|
|
391
399
|
export declare function finishChatSpan(telemetry: AgentTelemetry | undefined, span: Span | undefined, message: AssistantMessage, options: {
|
|
392
400
|
readonly stepNumber: number;
|
|
401
|
+
/** Requested model id; `message.model` may name a served fallback model instead. */
|
|
402
|
+
readonly modelId?: string;
|
|
393
403
|
readonly serviceTier?: ServiceTier;
|
|
394
404
|
readonly responseHeaders?: Readonly<Record<string, string>>;
|
|
395
405
|
readonly baseUrl?: string;
|
|
@@ -508,10 +518,10 @@ export interface InstrumentedChatSpanOptions {
|
|
|
508
518
|
/** Step index recorded on the span; defaults to `-1` for non-loop calls. */
|
|
509
519
|
readonly stepNumber?: number;
|
|
510
520
|
/**
|
|
511
|
-
* Tag stamped onto `
|
|
521
|
+
* Tag stamped onto `zeta.gen_ai.oneshot.kind`. Values used by the agent:
|
|
512
522
|
* `compaction_summary`, `compaction_short_summary`, `compaction_turn_prefix`,
|
|
513
|
-
* `handoff`, `branch_summary`, `image_question`. Free-form
|
|
514
|
-
* outside this package to add new kinds without bumping the helper.
|
|
523
|
+
* `handoff`, `branch_summary`, `image_question`, `skill_description`. Free-form
|
|
524
|
+
* to allow callers outside this package to add new kinds without bumping the helper.
|
|
515
525
|
*/
|
|
516
526
|
readonly oneshotKind?: string;
|
|
517
527
|
/** Extra span attributes applied verbatim. */
|
|
@@ -613,30 +623,30 @@ export declare function finishInvokeAgentSpan(telemetry: AgentTelemetry | undefi
|
|
|
613
623
|
* calling this helper.
|
|
614
624
|
*/
|
|
615
625
|
export declare function fireOnRunEnd(telemetry: AgentTelemetry, summary: AgentRunSummary, coverage: AgentRunCoverage): void;
|
|
616
|
-
/** Aggregate `
|
|
626
|
+
/** Aggregate `zeta.gen_ai.agent.*` attributes stamped on the `invoke_agent` span. */
|
|
617
627
|
export declare const enum OmpGenAIAggregateAttr {
|
|
618
|
-
ChatsCount = "
|
|
619
|
-
ChatsTotalLatencyMs = "
|
|
620
|
-
ChatsStopReasonPrefix = "
|
|
621
|
-
ToolsCount = "
|
|
622
|
-
ToolsOkCount = "
|
|
623
|
-
ToolsErrorCount = "
|
|
624
|
-
ToolsSkippedCount = "
|
|
625
|
-
ToolsBlockedCount = "
|
|
626
|
-
ToolsTimeoutCount = "
|
|
627
|
-
ToolsAbortedCount = "
|
|
628
|
-
ToolsTotalLatencyMs = "
|
|
629
|
-
ToolsInvoked = "
|
|
630
|
-
ToolsAvailable = "
|
|
631
|
-
ToolsUnused = "
|
|
632
|
-
UsageInputTokensTotal = "
|
|
633
|
-
UsageOutputTokensTotal = "
|
|
634
|
-
UsageCacheReadInputTokensTotal = "
|
|
635
|
-
UsageCacheCreationInputTokensTotal = "
|
|
636
|
-
UsageReasoningOutputTokensTotal = "
|
|
637
|
-
UsageTotalTokensTotal = "
|
|
638
|
-
CostEstimatedUsdTotal = "
|
|
639
|
-
ErrorsCount = "
|
|
628
|
+
ChatsCount = "zeta.gen_ai.agent.chats.count",
|
|
629
|
+
ChatsTotalLatencyMs = "zeta.gen_ai.agent.chats.total_latency_ms",
|
|
630
|
+
ChatsStopReasonPrefix = "zeta.gen_ai.agent.chats.stop_reason.",
|
|
631
|
+
ToolsCount = "zeta.gen_ai.agent.tools.count",
|
|
632
|
+
ToolsOkCount = "zeta.gen_ai.agent.tools.ok.count",
|
|
633
|
+
ToolsErrorCount = "zeta.gen_ai.agent.tools.error.count",
|
|
634
|
+
ToolsSkippedCount = "zeta.gen_ai.agent.tools.skipped.count",
|
|
635
|
+
ToolsBlockedCount = "zeta.gen_ai.agent.tools.blocked.count",
|
|
636
|
+
ToolsTimeoutCount = "zeta.gen_ai.agent.tools.timeout.count",
|
|
637
|
+
ToolsAbortedCount = "zeta.gen_ai.agent.tools.aborted.count",
|
|
638
|
+
ToolsTotalLatencyMs = "zeta.gen_ai.agent.tools.total_latency_ms",
|
|
639
|
+
ToolsInvoked = "zeta.gen_ai.agent.tools.invoked",
|
|
640
|
+
ToolsAvailable = "zeta.gen_ai.agent.tools.available",
|
|
641
|
+
ToolsUnused = "zeta.gen_ai.agent.tools.unused",
|
|
642
|
+
UsageInputTokensTotal = "zeta.gen_ai.agent.usage.input_tokens.total",
|
|
643
|
+
UsageOutputTokensTotal = "zeta.gen_ai.agent.usage.output_tokens.total",
|
|
644
|
+
UsageCacheReadInputTokensTotal = "zeta.gen_ai.agent.usage.cache_read.input_tokens.total",
|
|
645
|
+
UsageCacheCreationInputTokensTotal = "zeta.gen_ai.agent.usage.cache_creation.input_tokens.total",
|
|
646
|
+
UsageReasoningOutputTokensTotal = "zeta.gen_ai.agent.usage.reasoning.output_tokens.total",
|
|
647
|
+
UsageTotalTokensTotal = "zeta.gen_ai.agent.usage.total_tokens.total",
|
|
648
|
+
CostEstimatedUsdTotal = "zeta.gen_ai.agent.cost.estimated_usd.total",
|
|
649
|
+
ErrorsCount = "zeta.gen_ai.agent.errors.count"
|
|
640
650
|
}
|
|
641
651
|
/**
|
|
642
652
|
* Run `fn` with `span` activated on the OTEL context. Spans created
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Model } from "@linxiraos/pi-ai";
|
|
2
2
|
import * as natives from "@linxiraos/pi-natives";
|
|
3
|
+
import * as snapcompact from "@linxiraos/pi-snapcompact";
|
|
3
4
|
import type { AgentMessage } from "./types.js";
|
|
4
5
|
/** Maps the catalog-resolved tokenizer family to its native implementation. */
|
|
5
6
|
export declare function tokenizerEncodingForModel(model: Pick<Model, "tokenizer"> | null | undefined): natives.Encoding | null;
|
|
@@ -42,14 +43,15 @@ export interface TokenBudgetCheck {
|
|
|
42
43
|
* Model-aware local token counter. Immutable: the catalog-resolved encoding
|
|
43
44
|
* is fixed at construction, so a cached count can never straddle two
|
|
44
45
|
* encodings. An `Agent` owns one for its active model (swapping the instance
|
|
45
|
-
* when the model's encoding changes); one-shot
|
|
46
|
-
* the model that will be billed. Known
|
|
47
|
-
* counts; unknown models keep the fast
|
|
48
|
-
* `PI_TOKENIZER_ACCURATE=1`).
|
|
46
|
+
* when the model's encoding or snapcompact frame pricing changes); one-shot
|
|
47
|
+
* flows construct their own for the model that will be billed. Known
|
|
48
|
+
* tokenizer families use exact native counts; unknown models keep the fast
|
|
49
|
+
* byte estimate (or o200k when `PI_TOKENIZER_ACCURATE=1`).
|
|
49
50
|
*/
|
|
50
51
|
export declare class Tokenizer {
|
|
51
52
|
#private;
|
|
52
|
-
|
|
53
|
+
readonly frameBillingKey: string;
|
|
54
|
+
constructor(model?: (Pick<Model, "tokenizer"> & snapcompact.ShapeTarget) | null);
|
|
53
55
|
get encoding(): natives.Encoding | null;
|
|
54
56
|
countTokens(text: string | string[], mode?: TokenCountMode): number;
|
|
55
57
|
/**
|
|
@@ -70,7 +72,8 @@ export declare class Tokenizer {
|
|
|
70
72
|
* Settled historical messages are counted once and reused until an owner
|
|
71
73
|
* (prune/shake/strip-images) calls `invalidateMessageCache`; streaming
|
|
72
74
|
* assistants bypass the memo entirely (see the message-cache settle-gate
|
|
73
|
-
* invariant). Image blocks charge
|
|
75
|
+
* invariant). Image blocks charge {@link estimateImageContentTokens}, the
|
|
76
|
+
* same dimension-based estimate the native remote-compaction probe applies.
|
|
74
77
|
*/
|
|
75
78
|
countMessage(message: AgentMessage, options?: MessageCountOptions): number;
|
|
76
79
|
/** Sum of {@link countMessage} over `messages`. */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ToolCall } from "@linxiraos/pi-ai";
|
|
2
|
+
import type { AgentTool } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where a call's `__parseError`/`__rawJson` keys came from.
|
|
5
|
+
* - `"parsed"`: a provider stream parsed the arguments, so `__parseError` is a
|
|
6
|
+
* genuine parse-failure sentinel and is never handed to a tool.
|
|
7
|
+
* - `"payload"`: the arguments are a payload the model wrote (eval tool bridge,
|
|
8
|
+
* `xd://` device content), so those keys are forged and only stripped.
|
|
9
|
+
*/
|
|
10
|
+
export type ToolArgumentSource = "parsed" | "payload";
|
|
11
|
+
/**
|
|
12
|
+
* Validate a call's arguments against `tool`'s schema, honoring
|
|
13
|
+
* {@link AgentTool.lenientArgValidation}: on a schema mismatch a lenient tool
|
|
14
|
+
* receives its raw arguments minus the internal `__parseError`/`__rawJson`
|
|
15
|
+
* sentinels. Malformed JSON from a provider stream is never lenient.
|
|
16
|
+
*
|
|
17
|
+
* @throws the validation error when the tool is strict or the arguments failed to parse.
|
|
18
|
+
*/
|
|
19
|
+
export declare function validateAgentToolArguments(tool: AgentTool, toolCall: ToolCall, source?: ToolArgumentSource): ToolCall["arguments"];
|
package/dist/types/types.d.ts
CHANGED
|
@@ -269,6 +269,16 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
269
269
|
* background.
|
|
270
270
|
*/
|
|
271
271
|
hasBackgroundCompletions?: () => boolean | Promise<boolean>;
|
|
272
|
+
/**
|
|
273
|
+
* Peeks whether a passive aside (an extension or user message queued with
|
|
274
|
+
* `deliverAs: "aside"`) is waiting for injection at the next boundary.
|
|
275
|
+
*
|
|
276
|
+
* Same rules as {@link hasBackgroundCompletions}: non-consuming, ends only
|
|
277
|
+
* *interruptible* waits, never raises {@link ToolCallContext.steeringSignal}.
|
|
278
|
+
* The host still injects the aside once at the boundary; ordinary running
|
|
279
|
+
* tools finish first.
|
|
280
|
+
*/
|
|
281
|
+
hasQueuedAsides?: () => boolean | Promise<boolean>;
|
|
272
282
|
/**
|
|
273
283
|
* Returns follow-up messages to process after the agent would otherwise stop.
|
|
274
284
|
*
|
|
@@ -281,7 +291,8 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
|
|
|
281
291
|
* Returns non-interrupting "aside" messages to inject at a step boundary.
|
|
282
292
|
*
|
|
283
293
|
* Polled after each tool batch (before the next LLM call) AND at the yield
|
|
284
|
-
* check. Unlike steering, these
|
|
294
|
+
* check. Unlike steering, these never abort foreground tools (only an
|
|
295
|
+
* interruptible `wait` ends early, via the peek hooks) — they are passive
|
|
285
296
|
* notifications (e.g. background-job completions, late LSP diagnostics) that
|
|
286
297
|
* should reach the model between requests without waiting for the agent to
|
|
287
298
|
* fully stop. Returned messages are appended to the context with normal
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-agent-core",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
6
6
|
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Linxira-OS",
|
|
@@ -35,16 +35,16 @@
|
|
|
35
35
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@linxiraos/pi-ai": "1.
|
|
39
|
-
"@linxiraos/pi-catalog": "1.
|
|
40
|
-
"@linxiraos/pi-natives": "1.
|
|
41
|
-
"@linxiraos/pi-utils": "1.
|
|
42
|
-
"@linxiraos/pi-wire": "1.
|
|
43
|
-
"@linxiraos/pi-snapcompact": "1.
|
|
38
|
+
"@linxiraos/pi-ai": "1.2.0",
|
|
39
|
+
"@linxiraos/pi-catalog": "1.2.0",
|
|
40
|
+
"@linxiraos/pi-natives": "1.2.0",
|
|
41
|
+
"@linxiraos/pi-utils": "1.2.0",
|
|
42
|
+
"@linxiraos/pi-wire": "1.2.0",
|
|
43
|
+
"@linxiraos/pi-snapcompact": "1.2.0",
|
|
44
44
|
"@opentelemetry/api": "^1.9.1"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@linxiraos/pi-omptype": "1.
|
|
47
|
+
"@linxiraos/pi-omptype": "1.2.0",
|
|
48
48
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
|
49
49
|
"@opentelemetry/sdk-trace-base": "^2.9.0",
|
|
50
50
|
"@types/bun": "^1.3.14"
|