jeopi-agent-core 16.2.13
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 +1016 -0
- package/README.md +473 -0
- package/dist/types/agent-loop.d.ts +66 -0
- package/dist/types/agent.d.ts +427 -0
- package/dist/types/append-only-context.d.ts +133 -0
- package/dist/types/compaction/branch-summarization.d.ts +101 -0
- package/dist/types/compaction/compaction-v2-streaming.d.ts +82 -0
- package/dist/types/compaction/compaction.d.ts +283 -0
- package/dist/types/compaction/entries.d.ts +110 -0
- package/dist/types/compaction/errors.d.ts +26 -0
- package/dist/types/compaction/index.d.ts +12 -0
- package/dist/types/compaction/messages.d.ts +77 -0
- package/dist/types/compaction/openai.d.ts +77 -0
- package/dist/types/compaction/pruning.d.ts +105 -0
- package/dist/types/compaction/shake.d.ts +92 -0
- package/dist/types/compaction/tool-protection.d.ts +17 -0
- package/dist/types/compaction/utils.d.ts +58 -0
- package/dist/types/compaction.d.ts +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/proxy.d.ts +85 -0
- package/dist/types/replay-policy.d.ts +5 -0
- package/dist/types/run-collector.d.ts +196 -0
- package/dist/types/telemetry.d.ts +590 -0
- package/dist/types/thinking.d.ts +17 -0
- package/dist/types/tokenizer.d.ts +1 -0
- package/dist/types/types.d.ts +640 -0
- package/dist/types/utils/yield.d.ts +71 -0
- package/package.json +78 -0
- package/src/agent-loop.ts +2188 -0
- package/src/agent.ts +1457 -0
- package/src/append-only-context.ts +348 -0
- package/src/compaction/branch-summarization.ts +370 -0
- package/src/compaction/compaction-v2-streaming.ts +719 -0
- package/src/compaction/compaction.ts +1553 -0
- package/src/compaction/entries.ts +142 -0
- package/src/compaction/errors.ts +31 -0
- package/src/compaction/index.ts +13 -0
- package/src/compaction/messages.ts +237 -0
- package/src/compaction/openai.ts +581 -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 +5 -0
- package/src/compaction/prompts/handoff-document.md +49 -0
- package/src/compaction/prompts/snapcompact-archive-context.md +3 -0
- package/src/compaction/prompts/summarization-system.md +3 -0
- package/src/compaction/pruning.ts +424 -0
- package/src/compaction/shake.ts +429 -0
- package/src/compaction/tool-protection.ts +55 -0
- package/src/compaction/utils.ts +323 -0
- package/src/compaction.ts +1 -0
- package/src/index.ts +24 -0
- package/src/proxy.ts +376 -0
- package/src/replay-policy.ts +13 -0
- package/src/run-collector.ts +631 -0
- package/src/telemetry.ts +2034 -0
- package/src/thinking.ts +19 -0
- package/src/tokenizer.ts +17 -0
- package/src/types.ts +718 -0
- package/src/utils/yield.ts +183 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool output pruning utilities for compaction.
|
|
3
|
+
*/
|
|
4
|
+
import type { SessionEntry } from "./entries";
|
|
5
|
+
import { type ProtectedToolMatcher } from "./tool-protection";
|
|
6
|
+
export interface PruneConfig {
|
|
7
|
+
/** Keep the most recent tool output tokens intact. */
|
|
8
|
+
protectTokens: number;
|
|
9
|
+
/** Only prune if total savings meets this threshold. */
|
|
10
|
+
minimumSavings: number;
|
|
11
|
+
/** Tool-result protection matchers. String entries protect every result from that tool; predicates may inspect the paired tool call. */
|
|
12
|
+
protectedTools: ProtectedToolMatcher[];
|
|
13
|
+
/**
|
|
14
|
+
* Optional supersede key function (see {@link SupersedePruneConfig.supersedeKey}).
|
|
15
|
+
* When provided, superseded tool results are pruned first — even inside the
|
|
16
|
+
* `protectTokens` window — before age-based victims. Absent, behavior is
|
|
17
|
+
* unchanged.
|
|
18
|
+
*/
|
|
19
|
+
supersedeKey?: SupersedeKeyFn;
|
|
20
|
+
/** Useless-flagged results bypass the protect window (see {@link USELESS_NOTICE}). Default true. */
|
|
21
|
+
pruneUseless?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Compaction boundary: the `firstKeptEntryId` of the latest compaction on
|
|
24
|
+
* the branch. Entries at indices BEFORE this id are summarized away and never
|
|
25
|
+
* sent to the model, so mutating them only churns persisted history without
|
|
26
|
+
* shrinking the prompt — they are skipped. Undefined = no compaction (the
|
|
27
|
+
* whole branch is sent).
|
|
28
|
+
*/
|
|
29
|
+
keepBoundaryId?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Prompt-cache guard. When set, a tool result whose all-message suffix
|
|
32
|
+
* (tokens of every message after it) EXCEEDS this is part of the warm,
|
|
33
|
+
* already-sent cache prefix: mutating it forces the provider to re-write the
|
|
34
|
+
* whole suffix (cacheWrite premium). Such results — including superseded and
|
|
35
|
+
* useless ones, which otherwise bypass {@link protectTokens} — are left for
|
|
36
|
+
* compaction/shake (which rebuild the cache anyway) to reclaim. Undefined =
|
|
37
|
+
* no cache guard (legacy: superseded/useless prune at any depth).
|
|
38
|
+
*/
|
|
39
|
+
cacheWarmSuffixTokens?: number;
|
|
40
|
+
}
|
|
41
|
+
export declare const DEFAULT_PRUNE_CONFIG: PruneConfig;
|
|
42
|
+
export interface PruneResult {
|
|
43
|
+
prunedCount: number;
|
|
44
|
+
tokensSaved: number;
|
|
45
|
+
}
|
|
46
|
+
/** Exact placeholder written over a superseded tool result. */
|
|
47
|
+
export declare const SUPERSEDED_NOTICE = "[Superseded by a newer read of this file]";
|
|
48
|
+
/** Exact placeholder written over an elided useless tool result. */
|
|
49
|
+
export declare const USELESS_NOTICE = "[Uneventful result elided]";
|
|
50
|
+
/**
|
|
51
|
+
* Maps a tool call to a supersede key. Results sharing a key form a group in
|
|
52
|
+
* which every result except the newest is a supersede candidate. A key `K`
|
|
53
|
+
* additionally supersedes keys with prefix `K + "\u0000"` (selector-free read
|
|
54
|
+
* supersedes selector-carrying reads of the same base path). Return
|
|
55
|
+
* `undefined` to exempt a call from supersede grouping.
|
|
56
|
+
*/
|
|
57
|
+
export type SupersedeKeyFn = (toolName: string, args: Record<string, unknown>) => string | undefined;
|
|
58
|
+
export interface SupersedePruneConfig {
|
|
59
|
+
/** Supersede key function; results sharing a key supersede older ones. */
|
|
60
|
+
supersedeKey?: SupersedeKeyFn;
|
|
61
|
+
/** Also prune results flagged useless by their tool. Default false. */
|
|
62
|
+
pruneUseless?: boolean;
|
|
63
|
+
/** Prune a candidate now when all messages after it total at most this many estimated tokens. Default 8 000. */
|
|
64
|
+
suffixTokenLimit?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Prune all candidates when the last message is at least this old: the
|
|
67
|
+
* provider prompt cache is then cold, so re-writing it is free. MUST exceed
|
|
68
|
+
* the cache retention (Anthropic "long" = 1h) or a still-warm prefix is busted
|
|
69
|
+
* by the flush. Default 30 min — callers on long retention override it.
|
|
70
|
+
*/
|
|
71
|
+
idleFlushMs?: number;
|
|
72
|
+
/** Clock override for tests. */
|
|
73
|
+
now?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Compaction boundary (`firstKeptEntryId` of the latest compaction). Entries
|
|
76
|
+
* before it are summarized away and never sent, so they are skipped in every
|
|
77
|
+
* path — including the idle flush — to avoid pointless history churn.
|
|
78
|
+
* Undefined = no compaction (the whole branch is sent).
|
|
79
|
+
*/
|
|
80
|
+
keepBoundaryId?: string;
|
|
81
|
+
/** Tool-result protection matchers (same contract as {@link PruneConfig.protectedTools}). */
|
|
82
|
+
protectedTools: ProtectedToolMatcher[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Prune superseded tool results (e.g. stale `read` outputs replaced by a newer
|
|
86
|
+
* read of the same file) and, when `pruneUseless` is set, results their tool
|
|
87
|
+
* flagged contextually useless. Cheap, incremental, and prompt-cache-aware: a
|
|
88
|
+
* candidate is pruned now only when the suffix after it is small (tail case —
|
|
89
|
+
* the read→edit→read loop) or when the context has been idle long enough that
|
|
90
|
+
* the provider cache is cold anyway (then all still-sent candidates flush).
|
|
91
|
+
* Never mutates entries before `keepBoundaryId` (summarized away — not sent).
|
|
92
|
+
*/
|
|
93
|
+
export declare function pruneSupersededToolResults(entries: SessionEntry[], config: SupersedePruneConfig): PruneResult;
|
|
94
|
+
export declare function pruneToolOutputs(entries: SessionEntry[], config?: PruneConfig): PruneResult;
|
|
95
|
+
/**
|
|
96
|
+
* Supersede key for the `read` tool: the file path with the trailing line/raw
|
|
97
|
+
* selector stripped (the read tool's own splitter grammar via
|
|
98
|
+
* {@link splitReadSelector}, e.g. `src/foo.ts:50-200`, `:2-4:raw`).
|
|
99
|
+
* Internal/URL-scheme paths (`skill://…`, `https://…`) are exempt.
|
|
100
|
+
* Selector-free reads key on the bare path; selector-carrying reads key on
|
|
101
|
+
* `path + "\u0000" + selector`, so two reads collide only when the newer is
|
|
102
|
+
* selector-free or the selectors are identical (the pass's prefix rule lets a
|
|
103
|
+
* bare-path read supersede selector-carrying reads of the same file).
|
|
104
|
+
*/
|
|
105
|
+
export declare function readToolSupersedeKey(toolName: string, args: Record<string, unknown>): string | undefined;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-reducing surgical compaction ("shake").
|
|
3
|
+
*
|
|
4
|
+
* `shake` drops heavy content out of the live context mechanically: whole
|
|
5
|
+
* tool-call results and large fenced/XML blocks are replaced with short
|
|
6
|
+
* placeholders. This module is the pure layer — region detection and in-place
|
|
7
|
+
* mutation only. Artifact offload, persistence, and provider-session teardown
|
|
8
|
+
* are orchestrated by the caller (`AgentSession.shake`).
|
|
9
|
+
*
|
|
10
|
+
* Layering mirrors `pruning.ts`: no I/O here.
|
|
11
|
+
*/
|
|
12
|
+
import type { CustomMessageEntry, SessionEntry, SessionMessageEntry } from "./entries";
|
|
13
|
+
import { type ProtectedToolMatcher } from "./tool-protection";
|
|
14
|
+
export interface ShakeConfig {
|
|
15
|
+
/** Keep the most recent context tokens (across all entries) intact. */
|
|
16
|
+
protectTokens: number;
|
|
17
|
+
/** Only shake when total estimated savings meets this threshold. */
|
|
18
|
+
minSavings: number;
|
|
19
|
+
/** Tool-result protection matchers. String entries protect every result from that tool; predicates may inspect the paired tool call. */
|
|
20
|
+
protectedTools: ProtectedToolMatcher[];
|
|
21
|
+
/** Minimum token size for a fenced/XML block to be eligible. */
|
|
22
|
+
fenceMinTokens: number;
|
|
23
|
+
/**
|
|
24
|
+
* Compaction boundary (`firstKeptEntryId` of the latest compaction). Entries
|
|
25
|
+
* before it are summarized away and never sent, so they are skipped — shaking
|
|
26
|
+
* them only churns persisted history. Undefined = no compaction (whole branch
|
|
27
|
+
* is sent). Note: shake still elides the warm cached prefix at/after the
|
|
28
|
+
* boundary — that is its job as a compaction-class reducer.
|
|
29
|
+
*/
|
|
30
|
+
keepBoundaryId?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Auto-shake config: protects the live tail, conservative thresholds. */
|
|
33
|
+
export declare const DEFAULT_SHAKE_CONFIG: ShakeConfig;
|
|
34
|
+
/** Manual `/shake`: aggressive — drops every eligible region across history. */
|
|
35
|
+
export declare const AGGRESSIVE_SHAKE_CONFIG: ShakeConfig;
|
|
36
|
+
/** A located eligible region. */
|
|
37
|
+
export interface ToolResultShakeRegion {
|
|
38
|
+
kind: "toolResult";
|
|
39
|
+
entry: SessionMessageEntry;
|
|
40
|
+
tokens: number;
|
|
41
|
+
originalText: string;
|
|
42
|
+
/** Human label for the offload doc (tool name). */
|
|
43
|
+
label: string;
|
|
44
|
+
}
|
|
45
|
+
export interface BlockShakeRegion {
|
|
46
|
+
kind: "block";
|
|
47
|
+
entry: SessionMessageEntry | CustomMessageEntry;
|
|
48
|
+
/** Index into the content array, or -1 for string-form content. */
|
|
49
|
+
blockIndex: number;
|
|
50
|
+
/** Character offsets into the target text (start inclusive, end exclusive). */
|
|
51
|
+
start: number;
|
|
52
|
+
end: number;
|
|
53
|
+
tokens: number;
|
|
54
|
+
originalText: string;
|
|
55
|
+
/** Human label for the offload doc (role / customType). */
|
|
56
|
+
label: string;
|
|
57
|
+
}
|
|
58
|
+
export type ShakeRegion = ToolResultShakeRegion | BlockShakeRegion;
|
|
59
|
+
/**
|
|
60
|
+
* Pure detection: locate every eligible shake region on a branch.
|
|
61
|
+
*
|
|
62
|
+
* Walks the protect-recent window (most recent `protectTokens` of context is
|
|
63
|
+
* kept intact), collects whole tool-result messages (honoring `protectedTools`
|
|
64
|
+
* and skipping already-pruned results) and large fenced/XML blocks inside
|
|
65
|
+
* user/developer/assistant/custom messages. Tool results flagged contextually
|
|
66
|
+
* useless by their tool bypass the protect window — there is nothing recent
|
|
67
|
+
* worth keeping in them. Returns regions in document order.
|
|
68
|
+
*
|
|
69
|
+
* `toolCall` blocks are never touched (tool-call/result pairing is preserved)
|
|
70
|
+
* and regions never span a message boundary. When the combined estimated
|
|
71
|
+
* savings is below `minSavings`, returns `[]` (no-op).
|
|
72
|
+
*/
|
|
73
|
+
export declare function collectShakeRegions(entries: SessionEntry[], config: ShakeConfig): ShakeRegion[];
|
|
74
|
+
/**
|
|
75
|
+
* Pure mutation: replace a single region's content in place.
|
|
76
|
+
*
|
|
77
|
+
* Tool-result: replaces the message content with the placeholder text and
|
|
78
|
+
* stamps `prunedAt`. Block: splices `replacement` over `[start, end)` of the
|
|
79
|
+
* target text block. When several block regions share one text block they MUST
|
|
80
|
+
* be applied highest-start-first so earlier offsets stay valid — use
|
|
81
|
+
* {@link applyShakeRegions}, which orders them correctly.
|
|
82
|
+
*/
|
|
83
|
+
export declare function applyShakeRegion(region: ShakeRegion, replacement: string): void;
|
|
84
|
+
/**
|
|
85
|
+
* Apply many regions at once. Block regions are applied highest-start-first so
|
|
86
|
+
* that splicing one region never shifts the offsets of another in the same text
|
|
87
|
+
* block; tool-result regions are independent.
|
|
88
|
+
*/
|
|
89
|
+
export declare function applyShakeRegions(items: Array<{
|
|
90
|
+
region: ShakeRegion;
|
|
91
|
+
replacement: string;
|
|
92
|
+
}>): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ToolResultMessage } from "jeopi-ai";
|
|
2
|
+
import type { AgentToolCall } from "../types";
|
|
3
|
+
import type { SessionEntry } from "./entries";
|
|
4
|
+
export interface ProtectedToolContext {
|
|
5
|
+
readonly toolResult: ToolResultMessage;
|
|
6
|
+
readonly toolCall: AgentToolCall | undefined;
|
|
7
|
+
}
|
|
8
|
+
export type ProtectedToolMatcher = string | ((context: ProtectedToolContext) => boolean);
|
|
9
|
+
export declare function collectToolCallsById(entries: readonly SessionEntry[]): Map<string, AgentToolCall>;
|
|
10
|
+
/**
|
|
11
|
+
* Extract the `path` argument from a paired `read` tool call, when the result
|
|
12
|
+
* is a `read` result carrying a string path. Returns `undefined` otherwise.
|
|
13
|
+
* Shared primitive for read-targeted protection matchers (skills, plans, …).
|
|
14
|
+
*/
|
|
15
|
+
export declare function getReadToolPath({ toolResult, toolCall }: ProtectedToolContext): string | undefined;
|
|
16
|
+
export declare function isSkillReadToolResult(context: ProtectedToolContext): boolean;
|
|
17
|
+
export declare function isProtectedToolResult(toolResult: ToolResultMessage, toolCall: AgentToolCall | undefined, matchers: readonly ProtectedToolMatcher[]): boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for compaction and branch summarization.
|
|
3
|
+
*/
|
|
4
|
+
import type { Message } from "jeopi-ai";
|
|
5
|
+
import { type Dialect } from "jeopi-ai/dialect";
|
|
6
|
+
import type { AgentMessage } from "../types";
|
|
7
|
+
export interface FileOperations {
|
|
8
|
+
read: Set<string>;
|
|
9
|
+
written: Set<string>;
|
|
10
|
+
edited: Set<string>;
|
|
11
|
+
}
|
|
12
|
+
export declare function createFileOps(): FileOperations;
|
|
13
|
+
/**
|
|
14
|
+
* Split a read-tool path into its base path and trailing selector, mirroring the
|
|
15
|
+
* read tool's own splitter. Single source of the grammar in this package: the
|
|
16
|
+
* file-operations list strips selectors via {@link stripReadSelector}, and the
|
|
17
|
+
* supersede-prune pass keys on both parts via `readToolSupersedeKey`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function splitReadSelector(path: string): {
|
|
20
|
+
path: string;
|
|
21
|
+
sel?: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Strip a trailing read-tool selector (`:50-200`, `:raw`, `:1-50:raw`, `:conflicts`, …)
|
|
25
|
+
* so the same file read with different line ranges dedupes to one `<files>` entry
|
|
26
|
+
* and matches its write/edit path when computing Read/Write/RW markers.
|
|
27
|
+
*/
|
|
28
|
+
export declare function stripReadSelector(path: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Whether `path` references a `scheme://` URL (internal URI or web URL) rather
|
|
31
|
+
* than a filesystem path that belongs in the compaction `<files>` summary.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isUrlSchemePath(path: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Extract file operations from tool calls in an assistant message.
|
|
36
|
+
*/
|
|
37
|
+
export declare function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void;
|
|
38
|
+
/**
|
|
39
|
+
* Compute final file lists from file operations.
|
|
40
|
+
* Returns readFiles (files only read, not modified) and modifiedFiles.
|
|
41
|
+
*/
|
|
42
|
+
export declare function computeFileLists(fileOps: FileOperations): {
|
|
43
|
+
readFiles: string[];
|
|
44
|
+
modifiedFiles: string[];
|
|
45
|
+
};
|
|
46
|
+
export declare function formatFileOperations(readFiles: string[], modifiedFiles: string[], readSet?: ReadonlySet<string>): string;
|
|
47
|
+
export declare function upsertFileOperations(summary: string, readFiles: string[], modifiedFiles: string[], readSet?: ReadonlySet<string>): string;
|
|
48
|
+
/**
|
|
49
|
+
* Truncate tool results to the same representation used in summarization prompts.
|
|
50
|
+
*/
|
|
51
|
+
export declare function truncateToolResultForSummary(text: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Serialize LLM messages to text for summarization.
|
|
54
|
+
* This prevents the model from treating it as a conversation to continue.
|
|
55
|
+
* Call convertToLlm() first to handle custom message types.
|
|
56
|
+
*/
|
|
57
|
+
export declare function serializeConversation(messages: Message[], dialect?: Dialect): string;
|
|
58
|
+
export declare const SUMMARIZATION_SYSTEM_PROMPT: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./compaction/index";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./agent";
|
|
2
|
+
export * from "./agent-loop";
|
|
3
|
+
export * from "./append-only-context";
|
|
4
|
+
export * from "./compaction";
|
|
5
|
+
export * from "./proxy";
|
|
6
|
+
export * from "./replay-policy";
|
|
7
|
+
export * from "./run-collector";
|
|
8
|
+
export * from "./telemetry";
|
|
9
|
+
export * from "./thinking";
|
|
10
|
+
export * from "./tokenizer";
|
|
11
|
+
export * from "./types";
|
|
12
|
+
export * from "./utils/yield";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxy stream function for apps that route LLM calls through a server.
|
|
3
|
+
* The server manages auth and proxies requests to LLM providers.
|
|
4
|
+
*/
|
|
5
|
+
import { type AssistantMessage, type AssistantMessageEvent, type Context, EventStream, type FetchImpl, type Model, type SimpleStreamOptions, type StopReason } from "jeopi-ai";
|
|
6
|
+
export declare class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Proxy event types - server sends these with partial field stripped to reduce bandwidth.
|
|
11
|
+
*/
|
|
12
|
+
export type ProxyAssistantMessageEvent = {
|
|
13
|
+
type: "start";
|
|
14
|
+
} | {
|
|
15
|
+
type: "text_start";
|
|
16
|
+
contentIndex: number;
|
|
17
|
+
} | {
|
|
18
|
+
type: "text_delta";
|
|
19
|
+
contentIndex: number;
|
|
20
|
+
delta: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "text_end";
|
|
23
|
+
contentIndex: number;
|
|
24
|
+
contentSignature?: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: "thinking_start";
|
|
27
|
+
contentIndex: number;
|
|
28
|
+
} | {
|
|
29
|
+
type: "thinking_delta";
|
|
30
|
+
contentIndex: number;
|
|
31
|
+
delta: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: "thinking_end";
|
|
34
|
+
contentIndex: number;
|
|
35
|
+
contentSignature?: string;
|
|
36
|
+
} | {
|
|
37
|
+
type: "toolcall_start";
|
|
38
|
+
contentIndex: number;
|
|
39
|
+
id: string;
|
|
40
|
+
toolName: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: "toolcall_delta";
|
|
43
|
+
contentIndex: number;
|
|
44
|
+
delta: string;
|
|
45
|
+
} | {
|
|
46
|
+
type: "toolcall_end";
|
|
47
|
+
contentIndex: number;
|
|
48
|
+
} | {
|
|
49
|
+
type: "done";
|
|
50
|
+
reason: Extract<StopReason, "stop" | "length" | "toolUse">;
|
|
51
|
+
usage: AssistantMessage["usage"];
|
|
52
|
+
} | {
|
|
53
|
+
type: "error";
|
|
54
|
+
reason: Extract<StopReason, "aborted" | "error">;
|
|
55
|
+
errorMessage?: string;
|
|
56
|
+
usage: AssistantMessage["usage"];
|
|
57
|
+
};
|
|
58
|
+
export interface ProxyStreamOptions extends SimpleStreamOptions {
|
|
59
|
+
/** Auth token for the proxy server */
|
|
60
|
+
authToken: string;
|
|
61
|
+
/** Proxy server URL (e.g., "https://genai.example.com") */
|
|
62
|
+
proxyUrl: string;
|
|
63
|
+
/** Optional fetch implementation; defaults to global fetch. */
|
|
64
|
+
fetch?: FetchImpl;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Stream function that proxies through a server instead of calling LLM providers directly.
|
|
68
|
+
* The server strips the partial field from delta events to reduce bandwidth.
|
|
69
|
+
* We reconstruct the partial message client-side.
|
|
70
|
+
*
|
|
71
|
+
* Use this as the `streamFn` option when creating an Agent that needs to go through a proxy.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const agent = new Agent({
|
|
76
|
+
* streamFn: (model, context, options) =>
|
|
77
|
+
* streamProxy(model, context, {
|
|
78
|
+
* ...options,
|
|
79
|
+
* authToken: await getAuthToken(),
|
|
80
|
+
* proxyUrl: "https://genai.example.com",
|
|
81
|
+
* }),
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function streamProxy(model: Model, context: Context, options: ProxyStreamOptions): ProxyMessageEventStream;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AssistantMessage, Message } from "jeopi-ai";
|
|
2
|
+
/** Detects API-level provider refusals that are terminal errors, not dialogue to replay. */
|
|
3
|
+
export declare function isProviderRefusalMessage(message: AssistantMessage): boolean;
|
|
4
|
+
/** Removes API-level provider refusals from live provider replay while preserving other messages. */
|
|
5
|
+
export declare function filterProviderReplayMessages(messages: readonly Message[]): Message[];
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-invocation run aggregator. Buffers per-chat and per-tool records as the
|
|
3
|
+
* loop executes and folds them into a single {@link AgentRunSummary} +
|
|
4
|
+
* {@link AgentRunCoverage} value at the end.
|
|
5
|
+
*
|
|
6
|
+
* One collector lives on each {@link AgentTelemetry} handle, which is
|
|
7
|
+
* constructed once per `agentLoop` invocation in {@link resolveTelemetry}.
|
|
8
|
+
* Collector lookups use the live `Span` as a `WeakMap` key — bounded memory,
|
|
9
|
+
* no cross-invoke leakage.
|
|
10
|
+
*
|
|
11
|
+
* The collector is fed exclusively by helpers in `./telemetry.ts`. Loop
|
|
12
|
+
* authors do not interact with it directly except via the public
|
|
13
|
+
* `recordSkippedTool` helper used for the two skip paths that bypass spans
|
|
14
|
+
* entirely (pre-run interrupt and the tail-sweep for tool calls that never
|
|
15
|
+
* produced a result message).
|
|
16
|
+
*/
|
|
17
|
+
import type { Span } from "@opentelemetry/api";
|
|
18
|
+
import type { AssistantMessage, Model, StopReason } from "jeopi-ai";
|
|
19
|
+
/** Terminal status reported by an `execute_tool` span. */
|
|
20
|
+
export type ToolStatus = "ok" | "error" | "skipped" | "blocked" | "timeout" | "aborted";
|
|
21
|
+
/** Raw record for a single `chat` step, finalized by `finishChatSpan`. */
|
|
22
|
+
export interface ChatRecord {
|
|
23
|
+
readonly stepNumber: number;
|
|
24
|
+
readonly model: string;
|
|
25
|
+
readonly provider: string;
|
|
26
|
+
readonly stopReason: StopReason | undefined;
|
|
27
|
+
readonly latencyMs: number;
|
|
28
|
+
readonly inputTokens: number;
|
|
29
|
+
readonly outputTokens: number;
|
|
30
|
+
readonly cachedInputTokens: number;
|
|
31
|
+
readonly cacheWriteTokens: number;
|
|
32
|
+
readonly reasoningOutputTokens: number;
|
|
33
|
+
readonly totalTokens: number;
|
|
34
|
+
readonly costUsd: number | undefined;
|
|
35
|
+
readonly costUnavailableReason: string | undefined;
|
|
36
|
+
readonly errorType: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Raw record for a single `execute_tool` invocation. */
|
|
39
|
+
export interface ToolRecord {
|
|
40
|
+
readonly toolCallId: string;
|
|
41
|
+
readonly toolName: string;
|
|
42
|
+
readonly status: ToolStatus;
|
|
43
|
+
readonly latencyMs: number;
|
|
44
|
+
readonly errorType: string | undefined;
|
|
45
|
+
}
|
|
46
|
+
/** Per-tool counters surfaced under {@link AgentRunSummary.tools.byName}. */
|
|
47
|
+
export interface ToolCounters {
|
|
48
|
+
readonly total: number;
|
|
49
|
+
readonly ok: number;
|
|
50
|
+
readonly error: number;
|
|
51
|
+
readonly skipped: number;
|
|
52
|
+
readonly blocked: number;
|
|
53
|
+
readonly timeout: number;
|
|
54
|
+
readonly aborted: number;
|
|
55
|
+
readonly totalLatencyMs: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run-level rollup returned in the `agent_end` event and passed to
|
|
59
|
+
* {@link AgentTelemetryConfig.onRunEnd}. Pure aggregation — no references to
|
|
60
|
+
* spans, no callbacks, no live state. Safe to persist / diff / assert.
|
|
61
|
+
*/
|
|
62
|
+
export interface AgentRunSummary {
|
|
63
|
+
readonly chats: {
|
|
64
|
+
readonly total: number;
|
|
65
|
+
/** Bucketed by raw {@link StopReason}; absent reasons omitted. */
|
|
66
|
+
readonly byStopReason: Readonly<Record<string, number>>;
|
|
67
|
+
readonly totalLatencyMs: number;
|
|
68
|
+
};
|
|
69
|
+
readonly tools: {
|
|
70
|
+
readonly total: number;
|
|
71
|
+
readonly ok: number;
|
|
72
|
+
readonly error: number;
|
|
73
|
+
readonly skipped: number;
|
|
74
|
+
readonly blocked: number;
|
|
75
|
+
readonly timeout: number;
|
|
76
|
+
readonly aborted: number;
|
|
77
|
+
readonly totalLatencyMs: number;
|
|
78
|
+
/** Per-tool-name counters; keys sorted by name on snapshot. */
|
|
79
|
+
readonly byName: Readonly<Record<string, ToolCounters>>;
|
|
80
|
+
};
|
|
81
|
+
readonly usage: {
|
|
82
|
+
readonly inputTokens: number;
|
|
83
|
+
readonly outputTokens: number;
|
|
84
|
+
readonly cachedInputTokens: number;
|
|
85
|
+
readonly cacheWriteTokens: number;
|
|
86
|
+
readonly reasoningOutputTokens: number;
|
|
87
|
+
readonly totalTokens: number;
|
|
88
|
+
};
|
|
89
|
+
readonly cost: {
|
|
90
|
+
readonly estimatedUsd: number;
|
|
91
|
+
/** Sorted, deduped. */
|
|
92
|
+
readonly unavailableReasons: readonly string[];
|
|
93
|
+
};
|
|
94
|
+
readonly errors: {
|
|
95
|
+
readonly total: number;
|
|
96
|
+
readonly byType: Readonly<Record<string, number>>;
|
|
97
|
+
};
|
|
98
|
+
readonly stepCount: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Coverage rollup: registered-vs-invoked across the run. All arrays are
|
|
102
|
+
* sorted ascending and deduped so the value is stable for diffing.
|
|
103
|
+
*/
|
|
104
|
+
export interface AgentRunCoverage {
|
|
105
|
+
readonly toolsAvailable: readonly string[];
|
|
106
|
+
readonly toolsInvoked: readonly string[];
|
|
107
|
+
readonly toolsUnused: readonly string[];
|
|
108
|
+
readonly modelsUsed: readonly string[];
|
|
109
|
+
readonly providersUsed: readonly string[];
|
|
110
|
+
}
|
|
111
|
+
export declare class AgentRunCollector {
|
|
112
|
+
#private;
|
|
113
|
+
/** True once `markRunEnded()` has been called for this invocation. */
|
|
114
|
+
get runEnded(): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Mark this run as logically ended. Callers use this to coordinate the
|
|
117
|
+
* `onRunEnd` hook between the success path (fires inside
|
|
118
|
+
* `buildAgentEndEvent`, before `stream.end()`) and the error path (fires
|
|
119
|
+
* inside `finishInvokeAgentSpan`'s finally). Idempotent — returns `true`
|
|
120
|
+
* the first time, `false` on subsequent calls.
|
|
121
|
+
*/
|
|
122
|
+
markRunEnded(): boolean;
|
|
123
|
+
/** Record the tool names exposed on a single chat step. */
|
|
124
|
+
noteAvailableTools(tools: readonly {
|
|
125
|
+
readonly name: string;
|
|
126
|
+
}[] | undefined): void;
|
|
127
|
+
beginChat(span: Span, init: {
|
|
128
|
+
readonly stepNumber: number;
|
|
129
|
+
readonly model: Model;
|
|
130
|
+
readonly provider?: string;
|
|
131
|
+
}): void;
|
|
132
|
+
endChat(span: Span, message: AssistantMessage, fields: {
|
|
133
|
+
readonly costUsd: number | undefined;
|
|
134
|
+
readonly costUnavailableReason: string | undefined;
|
|
135
|
+
}): void;
|
|
136
|
+
/**
|
|
137
|
+
* Stamp the chat span as failed without a finalized AssistantMessage. Used
|
|
138
|
+
* by the `catch` arm of `streamAssistantResponse` so error chats still
|
|
139
|
+
* appear in the run summary.
|
|
140
|
+
*/
|
|
141
|
+
failChat(span: Span, fields: {
|
|
142
|
+
readonly errorType: string;
|
|
143
|
+
}): void;
|
|
144
|
+
beginTool(span: Span, init: {
|
|
145
|
+
readonly toolCallId: string;
|
|
146
|
+
readonly toolName: string;
|
|
147
|
+
}): void;
|
|
148
|
+
endTool(span: Span, fields: {
|
|
149
|
+
readonly status: ToolStatus;
|
|
150
|
+
readonly errorType: string | undefined;
|
|
151
|
+
}): void;
|
|
152
|
+
/**
|
|
153
|
+
* Record a tool that never produced a span — pre-run interrupt or tail
|
|
154
|
+
* sweep. The LLM still asked for it, so it counts toward
|
|
155
|
+
* {@link AgentRunCoverage.toolsInvoked}.
|
|
156
|
+
*/
|
|
157
|
+
recordOrphanTool(record: {
|
|
158
|
+
readonly toolCallId: string;
|
|
159
|
+
readonly toolName: string;
|
|
160
|
+
readonly status: ToolStatus;
|
|
161
|
+
}): void;
|
|
162
|
+
/** Build the immutable summary value from buffered records. */
|
|
163
|
+
snapshot(opts: {
|
|
164
|
+
readonly stepCount: number;
|
|
165
|
+
}): {
|
|
166
|
+
readonly summary: AgentRunSummary;
|
|
167
|
+
readonly coverage: AgentRunCoverage;
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Fold multiple per-run summaries into one. Pure aggregation — useful when a
|
|
172
|
+
* caller (verify pass, benchmark harness) drives the agent loop N times and
|
|
173
|
+
* needs a single rollup across all invocations.
|
|
174
|
+
*
|
|
175
|
+
* Counters sum element-wise. Sets (cost reasons, error types, per-tool
|
|
176
|
+
* counters) merge by key. Numeric totals sum. The output is in the same
|
|
177
|
+
* shape as a single `AgentRunSummary`, so all dashboards and persistence
|
|
178
|
+
* layers handle it uniformly.
|
|
179
|
+
*/
|
|
180
|
+
export declare function aggregateAgentRunSummaries(summaries: readonly AgentRunSummary[]): AgentRunSummary;
|
|
181
|
+
/** Union-merge multiple coverage values, preserving the sorted+deduped invariant. */
|
|
182
|
+
export declare function aggregateAgentRunCoverage(coverages: readonly AgentRunCoverage[]): AgentRunCoverage;
|
|
183
|
+
/** Empty `AgentRunSummary` constant. Exported for tests and default-initializers. */
|
|
184
|
+
export declare function emptyAgentRunSummary(): AgentRunSummary;
|
|
185
|
+
/** Empty `AgentRunCoverage` constant. Exported for tests and default-initializers. */
|
|
186
|
+
export declare function emptyAgentRunCoverage(): AgentRunCoverage;
|
|
187
|
+
/**
|
|
188
|
+
* Distinguishable error class thrown when `beforeToolCall` returns
|
|
189
|
+
* `{ block: true }`. Lets the catch arm of `runTool` set the terminal status
|
|
190
|
+
* on the execute_tool span to `"blocked"` instead of conflating with a real
|
|
191
|
+
* tool exception.
|
|
192
|
+
*/
|
|
193
|
+
export declare class ToolCallBlockedError extends Error {
|
|
194
|
+
readonly name = "ToolCallBlockedError";
|
|
195
|
+
constructor(reason?: string);
|
|
196
|
+
}
|