@kenkaiiii/gg-agent 5.67.1 → 5.68.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/dist/index.cjs +217 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -4
- package/dist/index.d.ts +112 -4
- package/dist/index.js +211 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Message, StreamOptions, Tool, ToolResultContent, ServerToolDefinition, Usage, StopReason, AssistantMessage } from '@kenkaiiii/gg-ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Stream rules: regex guards matched against a response WHILE it streams.
|
|
6
|
+
*
|
|
7
|
+
* A rule costs zero prompt tokens until the model actually breaks it. On a
|
|
8
|
+
* match the agent loop aborts the in-flight response, discards the partial
|
|
9
|
+
* assistant message (nothing is persisted, no partial tool call runs), appends
|
|
10
|
+
* the rule's reminder as a hidden runtime note, and retries the same step.
|
|
11
|
+
*
|
|
12
|
+
* Matching is incremental: each stream (assistant text, or one tool call's
|
|
13
|
+
* argument JSON) keeps a bounded rolling window, so a delta is scanned together
|
|
14
|
+
* with only the last `windowChars` characters before it — never the whole
|
|
15
|
+
* output. A pattern whose match spans more than the window is not detected.
|
|
16
|
+
*
|
|
17
|
+
* Tool-call arguments arrive as JSON text; they are unescaped incrementally so
|
|
18
|
+
* rules see `\n`, `"`, etc. as the model meant them, not as JSON escapes.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Which streamed output a rule watches. Thinking is never matched. */
|
|
22
|
+
type StreamRuleScope = "text" | "tool" | "both";
|
|
23
|
+
interface StreamRule {
|
|
24
|
+
/** Stable identifier; also the once-per-run dedupe key. */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Pattern tested against the rolling window. `g`/`y` flags are tolerated. */
|
|
27
|
+
pattern: RegExp;
|
|
28
|
+
scope: StreamRuleScope;
|
|
29
|
+
/** Reminder appended to the context when the rule fires. */
|
|
30
|
+
reminder: string;
|
|
31
|
+
/** Restrict tool-scope matching to these tool names. Unset = every tool. */
|
|
32
|
+
tools?: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
interface StreamRulesConfig {
|
|
35
|
+
rules: readonly StreamRule[];
|
|
36
|
+
/** Rule-triggered retries allowed per agent run (one user turn). Default 3. */
|
|
37
|
+
maxRetries?: number;
|
|
38
|
+
/** Characters of prior output kept per stream for cross-delta matches. Default 1024. */
|
|
39
|
+
windowChars?: number;
|
|
40
|
+
}
|
|
41
|
+
type StreamRuleSource = "text" | "tool";
|
|
42
|
+
interface StreamRuleMatch {
|
|
43
|
+
rules: StreamRule[];
|
|
44
|
+
source: StreamRuleSource;
|
|
45
|
+
toolName?: string;
|
|
46
|
+
}
|
|
47
|
+
declare const DEFAULT_STREAM_RULE_MAX_RETRIES = 3;
|
|
48
|
+
declare const DEFAULT_STREAM_RULE_WINDOW_CHARS = 1024;
|
|
49
|
+
/** Incremental JSON string-escape decoder that tolerates escapes split across chunks. */
|
|
50
|
+
declare class JsonEscapeDecoder {
|
|
51
|
+
private pending;
|
|
52
|
+
push(chunk: string): string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Per-run rule state. Create one per `agentLoop` run; call `beginAttempt()`
|
|
56
|
+
* before every provider attempt so windows never leak across attempts.
|
|
57
|
+
*/
|
|
58
|
+
declare class StreamRuleMonitor {
|
|
59
|
+
readonly maxRetries: number;
|
|
60
|
+
private readonly rules;
|
|
61
|
+
private readonly windowChars;
|
|
62
|
+
private readonly fired;
|
|
63
|
+
private retries;
|
|
64
|
+
private textWindow;
|
|
65
|
+
private readonly toolStreams;
|
|
66
|
+
constructor(config: StreamRulesConfig);
|
|
67
|
+
/** False once the retry cap is spent or every rule has fired — skip all matching. */
|
|
68
|
+
get active(): boolean;
|
|
69
|
+
get retriesUsed(): number;
|
|
70
|
+
beginAttempt(): void;
|
|
71
|
+
checkText(delta: string): StreamRuleMatch | null;
|
|
72
|
+
checkToolArgs(id: string, toolName: string, argsDelta: string): StreamRuleMatch | null;
|
|
73
|
+
/** Consume one retry and mark the rules fired. Returns the retry ordinal (1-based). */
|
|
74
|
+
recordTrigger(rules: readonly StreamRule[]): number;
|
|
75
|
+
private match;
|
|
76
|
+
}
|
|
77
|
+
/** The hidden runtime note appended before the retry. */
|
|
78
|
+
declare function buildStreamRuleReminder(rules: readonly StreamRule[]): Message;
|
|
79
|
+
|
|
4
80
|
interface StructuredToolResult {
|
|
5
81
|
content: ToolResultContent;
|
|
6
82
|
details?: unknown;
|
|
@@ -146,7 +222,7 @@ interface AgentTruncatedEvent {
|
|
|
146
222
|
}
|
|
147
223
|
interface AgentRetryEvent {
|
|
148
224
|
type: "retry";
|
|
149
|
-
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall";
|
|
225
|
+
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall" | "stream_rule";
|
|
150
226
|
attempt: number;
|
|
151
227
|
maxAttempts: number;
|
|
152
228
|
delayMs: number;
|
|
@@ -164,6 +240,29 @@ interface AgentRetryEvent {
|
|
|
164
240
|
*/
|
|
165
241
|
preservedChars?: number;
|
|
166
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* A stream rule matched mid-response. The attempt was aborted and discarded
|
|
245
|
+
* (no partial message persisted, no partial tool call executed), the rule's
|
|
246
|
+
* reminder was appended to the context, and the step is retried. Always
|
|
247
|
+
* followed by a silent `retry` (reason `stream_rule`) so UIs roll back the
|
|
248
|
+
* streamed partial exactly as for any other replayed attempt.
|
|
249
|
+
*/
|
|
250
|
+
interface AgentStreamRuleTriggeredEvent {
|
|
251
|
+
type: "stream_rule_triggered";
|
|
252
|
+
/** Names of the rules that matched (usually one). */
|
|
253
|
+
rules: string[];
|
|
254
|
+
source: "text" | "tool";
|
|
255
|
+
/** Tool whose streamed arguments matched, for `source: "tool"`. */
|
|
256
|
+
toolName?: string;
|
|
257
|
+
attempt: number;
|
|
258
|
+
maxAttempts: number;
|
|
259
|
+
/**
|
|
260
|
+
* ESTIMATED usage of the aborted attempt (providers report none for an
|
|
261
|
+
* aborted stream): prompt chars/4 in, streamed chars/4 out. Already added to
|
|
262
|
+
* the run's `totalUsage`.
|
|
263
|
+
*/
|
|
264
|
+
usage: Usage;
|
|
265
|
+
}
|
|
167
266
|
interface AgentToolCallDeltaEvent {
|
|
168
267
|
type: "toolcall_delta";
|
|
169
268
|
chars: number;
|
|
@@ -192,7 +291,7 @@ interface AgentFollowUpMessageEvent {
|
|
|
192
291
|
type: "follow_up_message";
|
|
193
292
|
content: Message["content"];
|
|
194
293
|
}
|
|
195
|
-
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentCheckpointEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent;
|
|
294
|
+
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentStreamRuleTriggeredEvent | AgentTurnEndEvent | AgentCheckpointEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent;
|
|
196
295
|
interface TransformContextOptions {
|
|
197
296
|
/** Force a transform after the provider reports context overflow. */
|
|
198
297
|
force?: boolean;
|
|
@@ -244,6 +343,7 @@ interface AgentOptions {
|
|
|
244
343
|
transportSessionId?: StreamOptions["transportSessionId"];
|
|
245
344
|
projectId?: StreamOptions["projectId"];
|
|
246
345
|
cacheRetention?: StreamOptions["cacheRetention"];
|
|
346
|
+
onContextPrepared?: StreamOptions["onContextPrepared"];
|
|
247
347
|
/** Stable per-session cache routing key for providers that support it. */
|
|
248
348
|
promptCacheKey?: StreamOptions["promptCacheKey"];
|
|
249
349
|
/** Override the User-Agent sent with OAuth-authenticated Anthropic requests. */
|
|
@@ -312,6 +412,13 @@ interface AgentOptions {
|
|
|
312
412
|
maxTurns: number;
|
|
313
413
|
extension: number;
|
|
314
414
|
}) => Promise<boolean> | boolean;
|
|
415
|
+
/**
|
|
416
|
+
* Regex rules matched against streamed assistant text and tool-call
|
|
417
|
+
* arguments. A match aborts the attempt, discards it, appends the rule's
|
|
418
|
+
* reminder and retries the step. Each rule fires at most once per run;
|
|
419
|
+
* `maxRetries` (default 3) caps rule retries per run. Unset = no matching.
|
|
420
|
+
*/
|
|
421
|
+
streamRules?: StreamRulesConfig;
|
|
315
422
|
}
|
|
316
423
|
interface AgentResult {
|
|
317
424
|
message: AssistantMessage;
|
|
@@ -415,6 +522,7 @@ declare function cancelledBeforeStartText(name: string): string;
|
|
|
415
522
|
* something never ran when it did.
|
|
416
523
|
*/
|
|
417
524
|
declare function indeterminateOutcomeText(name: string): string;
|
|
525
|
+
declare function repairToolPairingAdjacent(messages: Message[]): void;
|
|
418
526
|
|
|
419
527
|
/**
|
|
420
528
|
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
@@ -425,4 +533,4 @@ declare function indeterminateOutcomeText(name: string): string;
|
|
|
425
533
|
*/
|
|
426
534
|
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
427
535
|
|
|
428
|
-
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };
|
|
536
|
+
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentStreamRuleTriggeredEvent, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, DEFAULT_STREAM_RULE_MAX_RETRIES, DEFAULT_STREAM_RULE_WINDOW_CHARS, JsonEscapeDecoder, type StreamDiagnosticFn, type StreamRule, type StreamRuleMatch, StreamRuleMonitor, type StreamRuleScope, type StreamRuleSource, type StreamRulesConfig, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, buildStreamRuleReminder, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, repairToolPairingAdjacent, setStreamDiagnostic };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Message, StreamOptions, Tool, ToolResultContent, ServerToolDefinition, Usage, StopReason, AssistantMessage } from '@kenkaiiii/gg-ai';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Stream rules: regex guards matched against a response WHILE it streams.
|
|
6
|
+
*
|
|
7
|
+
* A rule costs zero prompt tokens until the model actually breaks it. On a
|
|
8
|
+
* match the agent loop aborts the in-flight response, discards the partial
|
|
9
|
+
* assistant message (nothing is persisted, no partial tool call runs), appends
|
|
10
|
+
* the rule's reminder as a hidden runtime note, and retries the same step.
|
|
11
|
+
*
|
|
12
|
+
* Matching is incremental: each stream (assistant text, or one tool call's
|
|
13
|
+
* argument JSON) keeps a bounded rolling window, so a delta is scanned together
|
|
14
|
+
* with only the last `windowChars` characters before it — never the whole
|
|
15
|
+
* output. A pattern whose match spans more than the window is not detected.
|
|
16
|
+
*
|
|
17
|
+
* Tool-call arguments arrive as JSON text; they are unescaped incrementally so
|
|
18
|
+
* rules see `\n`, `"`, etc. as the model meant them, not as JSON escapes.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Which streamed output a rule watches. Thinking is never matched. */
|
|
22
|
+
type StreamRuleScope = "text" | "tool" | "both";
|
|
23
|
+
interface StreamRule {
|
|
24
|
+
/** Stable identifier; also the once-per-run dedupe key. */
|
|
25
|
+
name: string;
|
|
26
|
+
/** Pattern tested against the rolling window. `g`/`y` flags are tolerated. */
|
|
27
|
+
pattern: RegExp;
|
|
28
|
+
scope: StreamRuleScope;
|
|
29
|
+
/** Reminder appended to the context when the rule fires. */
|
|
30
|
+
reminder: string;
|
|
31
|
+
/** Restrict tool-scope matching to these tool names. Unset = every tool. */
|
|
32
|
+
tools?: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
interface StreamRulesConfig {
|
|
35
|
+
rules: readonly StreamRule[];
|
|
36
|
+
/** Rule-triggered retries allowed per agent run (one user turn). Default 3. */
|
|
37
|
+
maxRetries?: number;
|
|
38
|
+
/** Characters of prior output kept per stream for cross-delta matches. Default 1024. */
|
|
39
|
+
windowChars?: number;
|
|
40
|
+
}
|
|
41
|
+
type StreamRuleSource = "text" | "tool";
|
|
42
|
+
interface StreamRuleMatch {
|
|
43
|
+
rules: StreamRule[];
|
|
44
|
+
source: StreamRuleSource;
|
|
45
|
+
toolName?: string;
|
|
46
|
+
}
|
|
47
|
+
declare const DEFAULT_STREAM_RULE_MAX_RETRIES = 3;
|
|
48
|
+
declare const DEFAULT_STREAM_RULE_WINDOW_CHARS = 1024;
|
|
49
|
+
/** Incremental JSON string-escape decoder that tolerates escapes split across chunks. */
|
|
50
|
+
declare class JsonEscapeDecoder {
|
|
51
|
+
private pending;
|
|
52
|
+
push(chunk: string): string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Per-run rule state. Create one per `agentLoop` run; call `beginAttempt()`
|
|
56
|
+
* before every provider attempt so windows never leak across attempts.
|
|
57
|
+
*/
|
|
58
|
+
declare class StreamRuleMonitor {
|
|
59
|
+
readonly maxRetries: number;
|
|
60
|
+
private readonly rules;
|
|
61
|
+
private readonly windowChars;
|
|
62
|
+
private readonly fired;
|
|
63
|
+
private retries;
|
|
64
|
+
private textWindow;
|
|
65
|
+
private readonly toolStreams;
|
|
66
|
+
constructor(config: StreamRulesConfig);
|
|
67
|
+
/** False once the retry cap is spent or every rule has fired — skip all matching. */
|
|
68
|
+
get active(): boolean;
|
|
69
|
+
get retriesUsed(): number;
|
|
70
|
+
beginAttempt(): void;
|
|
71
|
+
checkText(delta: string): StreamRuleMatch | null;
|
|
72
|
+
checkToolArgs(id: string, toolName: string, argsDelta: string): StreamRuleMatch | null;
|
|
73
|
+
/** Consume one retry and mark the rules fired. Returns the retry ordinal (1-based). */
|
|
74
|
+
recordTrigger(rules: readonly StreamRule[]): number;
|
|
75
|
+
private match;
|
|
76
|
+
}
|
|
77
|
+
/** The hidden runtime note appended before the retry. */
|
|
78
|
+
declare function buildStreamRuleReminder(rules: readonly StreamRule[]): Message;
|
|
79
|
+
|
|
4
80
|
interface StructuredToolResult {
|
|
5
81
|
content: ToolResultContent;
|
|
6
82
|
details?: unknown;
|
|
@@ -146,7 +222,7 @@ interface AgentTruncatedEvent {
|
|
|
146
222
|
}
|
|
147
223
|
interface AgentRetryEvent {
|
|
148
224
|
type: "retry";
|
|
149
|
-
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall";
|
|
225
|
+
reason: "overloaded" | "rate_limit" | "provider_error" | "empty_response" | "stream_stall" | "overflow_compact" | "tool_argument_glitch" | "runaway_toolcall" | "stream_rule";
|
|
150
226
|
attempt: number;
|
|
151
227
|
maxAttempts: number;
|
|
152
228
|
delayMs: number;
|
|
@@ -164,6 +240,29 @@ interface AgentRetryEvent {
|
|
|
164
240
|
*/
|
|
165
241
|
preservedChars?: number;
|
|
166
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* A stream rule matched mid-response. The attempt was aborted and discarded
|
|
245
|
+
* (no partial message persisted, no partial tool call executed), the rule's
|
|
246
|
+
* reminder was appended to the context, and the step is retried. Always
|
|
247
|
+
* followed by a silent `retry` (reason `stream_rule`) so UIs roll back the
|
|
248
|
+
* streamed partial exactly as for any other replayed attempt.
|
|
249
|
+
*/
|
|
250
|
+
interface AgentStreamRuleTriggeredEvent {
|
|
251
|
+
type: "stream_rule_triggered";
|
|
252
|
+
/** Names of the rules that matched (usually one). */
|
|
253
|
+
rules: string[];
|
|
254
|
+
source: "text" | "tool";
|
|
255
|
+
/** Tool whose streamed arguments matched, for `source: "tool"`. */
|
|
256
|
+
toolName?: string;
|
|
257
|
+
attempt: number;
|
|
258
|
+
maxAttempts: number;
|
|
259
|
+
/**
|
|
260
|
+
* ESTIMATED usage of the aborted attempt (providers report none for an
|
|
261
|
+
* aborted stream): prompt chars/4 in, streamed chars/4 out. Already added to
|
|
262
|
+
* the run's `totalUsage`.
|
|
263
|
+
*/
|
|
264
|
+
usage: Usage;
|
|
265
|
+
}
|
|
167
266
|
interface AgentToolCallDeltaEvent {
|
|
168
267
|
type: "toolcall_delta";
|
|
169
268
|
chars: number;
|
|
@@ -192,7 +291,7 @@ interface AgentFollowUpMessageEvent {
|
|
|
192
291
|
type: "follow_up_message";
|
|
193
292
|
content: Message["content"];
|
|
194
293
|
}
|
|
195
|
-
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentTurnEndEvent | AgentCheckpointEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent;
|
|
294
|
+
type AgentEvent = AgentTextDeltaEvent | AgentThinkingDeltaEvent | AgentToolCallStartEvent | AgentToolCallUpdateEvent | AgentToolCallEndEvent | AgentToolCallDeltaEvent | AgentServerToolCallEvent | AgentServerToolResultEvent | AgentSteeringMessageEvent | AgentFollowUpMessageEvent | AgentRetryEvent | AgentStreamRuleTriggeredEvent | AgentTurnEndEvent | AgentCheckpointEvent | AgentDoneEvent | AgentMaxTurnsEvent | AgentTurnBudgetExtendedEvent | AgentTruncatedEvent | AgentErrorEvent;
|
|
196
295
|
interface TransformContextOptions {
|
|
197
296
|
/** Force a transform after the provider reports context overflow. */
|
|
198
297
|
force?: boolean;
|
|
@@ -244,6 +343,7 @@ interface AgentOptions {
|
|
|
244
343
|
transportSessionId?: StreamOptions["transportSessionId"];
|
|
245
344
|
projectId?: StreamOptions["projectId"];
|
|
246
345
|
cacheRetention?: StreamOptions["cacheRetention"];
|
|
346
|
+
onContextPrepared?: StreamOptions["onContextPrepared"];
|
|
247
347
|
/** Stable per-session cache routing key for providers that support it. */
|
|
248
348
|
promptCacheKey?: StreamOptions["promptCacheKey"];
|
|
249
349
|
/** Override the User-Agent sent with OAuth-authenticated Anthropic requests. */
|
|
@@ -312,6 +412,13 @@ interface AgentOptions {
|
|
|
312
412
|
maxTurns: number;
|
|
313
413
|
extension: number;
|
|
314
414
|
}) => Promise<boolean> | boolean;
|
|
415
|
+
/**
|
|
416
|
+
* Regex rules matched against streamed assistant text and tool-call
|
|
417
|
+
* arguments. A match aborts the attempt, discards it, appends the rule's
|
|
418
|
+
* reminder and retries the step. Each rule fires at most once per run;
|
|
419
|
+
* `maxRetries` (default 3) caps rule retries per run. Unset = no matching.
|
|
420
|
+
*/
|
|
421
|
+
streamRules?: StreamRulesConfig;
|
|
315
422
|
}
|
|
316
423
|
interface AgentResult {
|
|
317
424
|
message: AssistantMessage;
|
|
@@ -415,6 +522,7 @@ declare function cancelledBeforeStartText(name: string): string;
|
|
|
415
522
|
* something never ran when it did.
|
|
416
523
|
*/
|
|
417
524
|
declare function indeterminateOutcomeText(name: string): string;
|
|
525
|
+
declare function repairToolPairingAdjacent(messages: Message[]): void;
|
|
418
526
|
|
|
419
527
|
/**
|
|
420
528
|
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
@@ -425,4 +533,4 @@ declare function indeterminateOutcomeText(name: string): string;
|
|
|
425
533
|
*/
|
|
426
534
|
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
427
535
|
|
|
428
|
-
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, type StreamDiagnosticFn, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, setStreamDiagnostic };
|
|
536
|
+
export { Agent, type AgentDoneEvent, type AgentErrorEvent, type AgentEvent, type AgentFollowUpMessageEvent, type AgentOptions, type AgentResult, type AgentRetryEvent, type AgentServerToolCallEvent, type AgentServerToolResultEvent, type AgentSteeringMessageEvent, AgentStream, type AgentStreamRuleTriggeredEvent, type AgentTextDeltaEvent, type AgentThinkingDeltaEvent, type AgentTool, type AgentToolCallDeltaEvent, type AgentToolCallEndEvent, type AgentToolCallStartEvent, type AgentToolCallUpdateEvent, type AgentTurnEndEvent, type AgentTurnTiming, DEFAULT_STREAM_RULE_MAX_RETRIES, DEFAULT_STREAM_RULE_WINDOW_CHARS, JsonEscapeDecoder, type StreamDiagnosticFn, type StreamRule, type StreamRuleMatch, StreamRuleMonitor, type StreamRuleScope, type StreamRuleSource, type StreamRulesConfig, type StructuredToolResult, type ToolContext, type ToolExecuteResult, type ToolExecutionMode, type TransformContextOptions, agentLoop, buildStreamRuleReminder, cancelledBeforeStartText, indeterminateOutcomeText, isAbortError, isBillingError, isContextOverflow, isLocalBackendUrl, isUsageLimitError, repairToolPairingAdjacent, setStreamDiagnostic };
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,133 @@ function clampOutputTokens(key, requested) {
|
|
|
90
90
|
return requested === void 0 ? ceiling : Math.min(requested, ceiling);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
// src/stream-rules.ts
|
|
94
|
+
var DEFAULT_STREAM_RULE_MAX_RETRIES = 3;
|
|
95
|
+
var DEFAULT_STREAM_RULE_WINDOW_CHARS = 1024;
|
|
96
|
+
var JSON_ESCAPES = {
|
|
97
|
+
n: "\n",
|
|
98
|
+
r: "\r",
|
|
99
|
+
t: " ",
|
|
100
|
+
b: "\b",
|
|
101
|
+
f: "\f",
|
|
102
|
+
'"': '"',
|
|
103
|
+
"\\": "\\",
|
|
104
|
+
"/": "/"
|
|
105
|
+
};
|
|
106
|
+
var JsonEscapeDecoder = class {
|
|
107
|
+
pending = "";
|
|
108
|
+
push(chunk) {
|
|
109
|
+
const input = this.pending + chunk;
|
|
110
|
+
this.pending = "";
|
|
111
|
+
if (!input.includes("\\")) return input;
|
|
112
|
+
let out = "";
|
|
113
|
+
let i = 0;
|
|
114
|
+
while (i < input.length) {
|
|
115
|
+
const ch = input.charAt(i);
|
|
116
|
+
if (ch !== "\\") {
|
|
117
|
+
out += ch;
|
|
118
|
+
i++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (i + 1 >= input.length) {
|
|
122
|
+
this.pending = input.slice(i);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
const next = input.charAt(i + 1);
|
|
126
|
+
if (next === "u") {
|
|
127
|
+
if (i + 6 > input.length) {
|
|
128
|
+
this.pending = input.slice(i);
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
const hex = input.slice(i + 2, i + 6);
|
|
132
|
+
out += /^[0-9a-fA-F]{4}$/.test(hex) ? String.fromCharCode(Number.parseInt(hex, 16)) : input.slice(i, i + 6);
|
|
133
|
+
i += 6;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
out += JSON_ESCAPES[next] ?? next;
|
|
137
|
+
i += 2;
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var StreamRuleMonitor = class {
|
|
143
|
+
maxRetries;
|
|
144
|
+
rules;
|
|
145
|
+
windowChars;
|
|
146
|
+
fired = /* @__PURE__ */ new Set();
|
|
147
|
+
retries = 0;
|
|
148
|
+
textWindow = "";
|
|
149
|
+
toolStreams = /* @__PURE__ */ new Map();
|
|
150
|
+
constructor(config) {
|
|
151
|
+
this.rules = config.rules;
|
|
152
|
+
this.maxRetries = Math.max(0, config.maxRetries ?? DEFAULT_STREAM_RULE_MAX_RETRIES);
|
|
153
|
+
this.windowChars = Math.max(64, config.windowChars ?? DEFAULT_STREAM_RULE_WINDOW_CHARS);
|
|
154
|
+
}
|
|
155
|
+
/** False once the retry cap is spent or every rule has fired — skip all matching. */
|
|
156
|
+
get active() {
|
|
157
|
+
return this.retries < this.maxRetries && this.fired.size < this.rules.length;
|
|
158
|
+
}
|
|
159
|
+
get retriesUsed() {
|
|
160
|
+
return this.retries;
|
|
161
|
+
}
|
|
162
|
+
beginAttempt() {
|
|
163
|
+
this.textWindow = "";
|
|
164
|
+
this.toolStreams.clear();
|
|
165
|
+
}
|
|
166
|
+
checkText(delta) {
|
|
167
|
+
if (!this.active || !delta) return null;
|
|
168
|
+
const scan = this.textWindow + delta;
|
|
169
|
+
this.textWindow = scan.slice(-this.windowChars);
|
|
170
|
+
const rules = this.match(scan, "text");
|
|
171
|
+
return rules.length > 0 ? { rules, source: "text" } : null;
|
|
172
|
+
}
|
|
173
|
+
checkToolArgs(id, toolName, argsDelta) {
|
|
174
|
+
if (!this.active || !argsDelta) return null;
|
|
175
|
+
let state = this.toolStreams.get(id);
|
|
176
|
+
if (!state) {
|
|
177
|
+
state = { window: "", decoder: new JsonEscapeDecoder() };
|
|
178
|
+
this.toolStreams.set(id, state);
|
|
179
|
+
}
|
|
180
|
+
const scan = state.window + state.decoder.push(argsDelta);
|
|
181
|
+
state.window = scan.slice(-this.windowChars);
|
|
182
|
+
const rules = this.match(scan, "tool", toolName);
|
|
183
|
+
return rules.length > 0 ? { rules, source: "tool", toolName } : null;
|
|
184
|
+
}
|
|
185
|
+
/** Consume one retry and mark the rules fired. Returns the retry ordinal (1-based). */
|
|
186
|
+
recordTrigger(rules) {
|
|
187
|
+
for (const rule of rules) this.fired.add(rule.name);
|
|
188
|
+
this.retries++;
|
|
189
|
+
return this.retries;
|
|
190
|
+
}
|
|
191
|
+
match(scan, source, toolName) {
|
|
192
|
+
const hits = [];
|
|
193
|
+
for (const rule of this.rules) {
|
|
194
|
+
if (this.fired.has(rule.name)) continue;
|
|
195
|
+
if (rule.scope !== "both" && rule.scope !== source) continue;
|
|
196
|
+
if (source === "tool" && rule.tools && toolName !== void 0) {
|
|
197
|
+
if (!rule.tools.includes(toolName)) continue;
|
|
198
|
+
}
|
|
199
|
+
rule.pattern.lastIndex = 0;
|
|
200
|
+
if (rule.pattern.test(scan)) hits.push(rule);
|
|
201
|
+
}
|
|
202
|
+
return hits;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
function buildStreamRuleReminder(rules) {
|
|
206
|
+
const blocks = rules.map(
|
|
207
|
+
(rule) => `<system-reminder reason="stream_rule" rule="${rule.name.replace(/"/g, "'")}">
|
|
208
|
+
Your previous response was interrupted mid-stream because it violated this rule. The interrupted output was discarded: the user never saw it and no tool call from it ran. Regenerate the response from the start and follow the rule:
|
|
209
|
+
|
|
210
|
+
${rule.reminder.trim()}
|
|
211
|
+
</system-reminder>`
|
|
212
|
+
);
|
|
213
|
+
return {
|
|
214
|
+
role: "user",
|
|
215
|
+
content: blocks.join("\n\n"),
|
|
216
|
+
provenance: { source: "runtime", kind: "notification", visibility: "hidden" }
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
93
220
|
// src/agent-loop.ts
|
|
94
221
|
var DEFAULT_MAX_TURNS = 300;
|
|
95
222
|
var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
|
|
@@ -355,6 +482,7 @@ async function* agentLoop(messages, options) {
|
|
|
355
482
|
let runawayToolcallRetries = 0;
|
|
356
483
|
let overflowCompactionAttempts = 0;
|
|
357
484
|
let toolResultTruncationAttempted = false;
|
|
485
|
+
const streamRuleMonitor = options.streamRules && options.streamRules.rules.length > 0 ? new StreamRuleMonitor(options.streamRules) : null;
|
|
358
486
|
const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
|
|
359
487
|
let toolArgumentAutoContinueUsed = false;
|
|
360
488
|
let useNonStreamingFallback = false;
|
|
@@ -408,16 +536,7 @@ async function* agentLoop(messages, options) {
|
|
|
408
536
|
turn++;
|
|
409
537
|
if (logicalTurnStartedAt === 0) logicalTurnStartedAt = Date.now();
|
|
410
538
|
toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
|
|
411
|
-
|
|
412
|
-
for (const m of messages) {
|
|
413
|
-
if (typeof m.content === "string") msgChars += m.content.length;
|
|
414
|
-
else if (Array.isArray(m.content)) {
|
|
415
|
-
for (const p of m.content) {
|
|
416
|
-
if ("text" in p && typeof p.text === "string") msgChars += p.text.length;
|
|
417
|
-
if ("content" in p && typeof p.content === "string") msgChars += p.content.length;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
539
|
+
const msgChars = countMessageChars(messages);
|
|
421
540
|
let firstEventTimeoutMs;
|
|
422
541
|
let initialHardTimeoutMs;
|
|
423
542
|
if (baseFirstEventTimeoutMs === STREAM_FIRST_EVENT_TIMEOUT_MS) {
|
|
@@ -487,6 +606,9 @@ async function* agentLoop(messages, options) {
|
|
|
487
606
|
let toolcallDeltaCount = 0;
|
|
488
607
|
let runawayDetected = null;
|
|
489
608
|
let attemptText = "";
|
|
609
|
+
let streamRuleHit = null;
|
|
610
|
+
let attemptThinkingChars = 0;
|
|
611
|
+
streamRuleMonitor?.beginAttempt();
|
|
490
612
|
let lastYieldEndTime = Date.now();
|
|
491
613
|
let maxConsumerLagMs = 0;
|
|
492
614
|
const forwardAbort = () => streamController.abort();
|
|
@@ -572,6 +694,7 @@ async function* agentLoop(messages, options) {
|
|
|
572
694
|
transportSessionId: options.transportSessionId,
|
|
573
695
|
projectId: liveProjectId,
|
|
574
696
|
cacheRetention: options.cacheRetention,
|
|
697
|
+
onContextPrepared: options.onContextPrepared,
|
|
575
698
|
promptCacheKey: options.promptCacheKey,
|
|
576
699
|
serviceTier: options.serviceTier,
|
|
577
700
|
supportsImages: options.supportsImages,
|
|
@@ -645,8 +768,14 @@ async function* agentLoop(messages, options) {
|
|
|
645
768
|
}
|
|
646
769
|
if (event.type === "text_delta") {
|
|
647
770
|
attemptText += event.text;
|
|
771
|
+
streamRuleHit = streamRuleMonitor?.checkText(event.text) ?? null;
|
|
772
|
+
if (streamRuleHit) {
|
|
773
|
+
streamController.abort();
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
648
776
|
yield { type: "text_delta", text: event.text };
|
|
649
777
|
} else if (event.type === "thinking_delta") {
|
|
778
|
+
attemptThinkingChars += event.text.length;
|
|
650
779
|
yield { type: "thinking_delta", text: event.text };
|
|
651
780
|
} else if (event.type === "server_toolcall") {
|
|
652
781
|
yield {
|
|
@@ -666,6 +795,11 @@ async function* agentLoop(messages, options) {
|
|
|
666
795
|
const chunkChars = event.argsJson?.length ?? 0;
|
|
667
796
|
toolcallDeltaChars += chunkChars;
|
|
668
797
|
toolcallDeltaCount++;
|
|
798
|
+
streamRuleHit = streamRuleMonitor?.checkToolArgs(event.id, event.name, event.argsJson ?? "") ?? null;
|
|
799
|
+
if (streamRuleHit) {
|
|
800
|
+
streamController.abort();
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
669
803
|
if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallDeltaCount > MAX_TOOLCALL_DELTA_EVENTS)) {
|
|
670
804
|
runawayDetected = {
|
|
671
805
|
kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
|
|
@@ -687,6 +821,54 @@ async function* agentLoop(messages, options) {
|
|
|
687
821
|
lastYieldEndTime = Date.now();
|
|
688
822
|
resetIdleTimer();
|
|
689
823
|
}
|
|
824
|
+
if (streamRuleHit && streamRuleMonitor) {
|
|
825
|
+
const attempt = streamRuleMonitor.recordTrigger(streamRuleHit.rules);
|
|
826
|
+
const ruleNames = streamRuleHit.rules.map((rule) => rule.name);
|
|
827
|
+
const priorPrompt = latestProviderUsage ? latestProviderUsage.inputTokens + (latestProviderUsage.cacheRead ?? 0) + (latestProviderUsage.cacheWrite ?? 0) : 0;
|
|
828
|
+
const newChars = priorPrompt > 0 && usageAnchorIndex !== void 0 ? countMessageChars(messages.slice(usageAnchorIndex + 1)) : msgChars;
|
|
829
|
+
const abortedUsage = {
|
|
830
|
+
inputTokens: Math.ceil(newChars / 4),
|
|
831
|
+
outputTokens: Math.ceil(
|
|
832
|
+
(attemptText.length + attemptThinkingChars + toolcallDeltaChars) / 4
|
|
833
|
+
),
|
|
834
|
+
...priorPrompt > 0 ? { cacheRead: priorPrompt } : {}
|
|
835
|
+
};
|
|
836
|
+
totalUsage.inputTokens += abortedUsage.inputTokens;
|
|
837
|
+
totalUsage.outputTokens += abortedUsage.outputTokens;
|
|
838
|
+
if (abortedUsage.cacheRead) {
|
|
839
|
+
totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + abortedUsage.cacheRead;
|
|
840
|
+
}
|
|
841
|
+
diag("stream_rule_triggered", {
|
|
842
|
+
rules: ruleNames.join(","),
|
|
843
|
+
source: streamRuleHit.source,
|
|
844
|
+
toolName: streamRuleHit.toolName,
|
|
845
|
+
attempt,
|
|
846
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
847
|
+
discardedChars: attemptText.length + toolcallDeltaChars,
|
|
848
|
+
provider: options.provider,
|
|
849
|
+
model: options.model
|
|
850
|
+
});
|
|
851
|
+
messages.push(buildStreamRuleReminder(streamRuleHit.rules));
|
|
852
|
+
yield {
|
|
853
|
+
type: "stream_rule_triggered",
|
|
854
|
+
rules: ruleNames,
|
|
855
|
+
source: streamRuleHit.source,
|
|
856
|
+
...streamRuleHit.toolName !== void 0 ? { toolName: streamRuleHit.toolName } : {},
|
|
857
|
+
attempt,
|
|
858
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
859
|
+
usage: abortedUsage
|
|
860
|
+
};
|
|
861
|
+
yield {
|
|
862
|
+
type: "retry",
|
|
863
|
+
reason: "stream_rule",
|
|
864
|
+
attempt,
|
|
865
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
866
|
+
delayMs: 0,
|
|
867
|
+
silent: true
|
|
868
|
+
};
|
|
869
|
+
turn--;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
690
872
|
diag("stream_done", {
|
|
691
873
|
events: streamEventCount,
|
|
692
874
|
totalMs: Date.now() - streamCallStart,
|
|
@@ -1733,6 +1915,19 @@ function sanitizeOrphanedServerTools(messages) {
|
|
|
1733
1915
|
break;
|
|
1734
1916
|
}
|
|
1735
1917
|
}
|
|
1918
|
+
function countMessageChars(messages) {
|
|
1919
|
+
let chars = 0;
|
|
1920
|
+
for (const m of messages) {
|
|
1921
|
+
if (typeof m.content === "string") chars += m.content.length;
|
|
1922
|
+
else if (Array.isArray(m.content)) {
|
|
1923
|
+
for (const p of m.content) {
|
|
1924
|
+
if ("text" in p && typeof p.text === "string") chars += p.text.length;
|
|
1925
|
+
if ("content" in p && typeof p.content === "string") chars += p.content.length;
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return chars;
|
|
1930
|
+
}
|
|
1736
1931
|
function repairToolPairingAdjacent(messages) {
|
|
1737
1932
|
for (let i = 0; i < messages.length; i++) {
|
|
1738
1933
|
const msg = messages[i];
|
|
@@ -1911,7 +2106,12 @@ var Agent = class {
|
|
|
1911
2106
|
export {
|
|
1912
2107
|
Agent,
|
|
1913
2108
|
AgentStream,
|
|
2109
|
+
DEFAULT_STREAM_RULE_MAX_RETRIES,
|
|
2110
|
+
DEFAULT_STREAM_RULE_WINDOW_CHARS,
|
|
2111
|
+
JsonEscapeDecoder,
|
|
2112
|
+
StreamRuleMonitor,
|
|
1914
2113
|
agentLoop,
|
|
2114
|
+
buildStreamRuleReminder,
|
|
1915
2115
|
cancelledBeforeStartText,
|
|
1916
2116
|
indeterminateOutcomeText,
|
|
1917
2117
|
isAbortError,
|
|
@@ -1919,6 +2119,7 @@ export {
|
|
|
1919
2119
|
isContextOverflow,
|
|
1920
2120
|
isLocalBackendUrl,
|
|
1921
2121
|
isUsageLimitError,
|
|
2122
|
+
repairToolPairingAdjacent,
|
|
1922
2123
|
setStreamDiagnostic
|
|
1923
2124
|
};
|
|
1924
2125
|
//# sourceMappingURL=index.js.map
|