@kenkaiiii/gg-agent 5.67.0 → 5.68.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/dist/index.cjs +216 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +111 -4
- package/dist/index.d.ts +111 -4
- package/dist/index.js +210 -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;
|
|
@@ -312,6 +411,13 @@ interface AgentOptions {
|
|
|
312
411
|
maxTurns: number;
|
|
313
412
|
extension: number;
|
|
314
413
|
}) => Promise<boolean> | boolean;
|
|
414
|
+
/**
|
|
415
|
+
* Regex rules matched against streamed assistant text and tool-call
|
|
416
|
+
* arguments. A match aborts the attempt, discards it, appends the rule's
|
|
417
|
+
* reminder and retries the step. Each rule fires at most once per run;
|
|
418
|
+
* `maxRetries` (default 3) caps rule retries per run. Unset = no matching.
|
|
419
|
+
*/
|
|
420
|
+
streamRules?: StreamRulesConfig;
|
|
315
421
|
}
|
|
316
422
|
interface AgentResult {
|
|
317
423
|
message: AssistantMessage;
|
|
@@ -415,6 +521,7 @@ declare function cancelledBeforeStartText(name: string): string;
|
|
|
415
521
|
* something never ran when it did.
|
|
416
522
|
*/
|
|
417
523
|
declare function indeterminateOutcomeText(name: string): string;
|
|
524
|
+
declare function repairToolPairingAdjacent(messages: Message[]): void;
|
|
418
525
|
|
|
419
526
|
/**
|
|
420
527
|
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
@@ -425,4 +532,4 @@ declare function indeterminateOutcomeText(name: string): string;
|
|
|
425
532
|
*/
|
|
426
533
|
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
427
534
|
|
|
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 };
|
|
535
|
+
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;
|
|
@@ -312,6 +411,13 @@ interface AgentOptions {
|
|
|
312
411
|
maxTurns: number;
|
|
313
412
|
extension: number;
|
|
314
413
|
}) => Promise<boolean> | boolean;
|
|
414
|
+
/**
|
|
415
|
+
* Regex rules matched against streamed assistant text and tool-call
|
|
416
|
+
* arguments. A match aborts the attempt, discards it, appends the rule's
|
|
417
|
+
* reminder and retries the step. Each rule fires at most once per run;
|
|
418
|
+
* `maxRetries` (default 3) caps rule retries per run. Unset = no matching.
|
|
419
|
+
*/
|
|
420
|
+
streamRules?: StreamRulesConfig;
|
|
315
421
|
}
|
|
316
422
|
interface AgentResult {
|
|
317
423
|
message: AssistantMessage;
|
|
@@ -415,6 +521,7 @@ declare function cancelledBeforeStartText(name: string): string;
|
|
|
415
521
|
* something never ran when it did.
|
|
416
522
|
*/
|
|
417
523
|
declare function indeterminateOutcomeText(name: string): string;
|
|
524
|
+
declare function repairToolPairingAdjacent(messages: Message[]): void;
|
|
418
525
|
|
|
419
526
|
/**
|
|
420
527
|
* A local inference server (llama.cpp, vLLM, Ollama, LM Studio) can spend
|
|
@@ -425,4 +532,4 @@ declare function indeterminateOutcomeText(name: string): string;
|
|
|
425
532
|
*/
|
|
426
533
|
declare function isLocalBackendUrl(baseUrl?: string): boolean;
|
|
427
534
|
|
|
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 };
|
|
535
|
+
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();
|
|
@@ -645,8 +767,14 @@ async function* agentLoop(messages, options) {
|
|
|
645
767
|
}
|
|
646
768
|
if (event.type === "text_delta") {
|
|
647
769
|
attemptText += event.text;
|
|
770
|
+
streamRuleHit = streamRuleMonitor?.checkText(event.text) ?? null;
|
|
771
|
+
if (streamRuleHit) {
|
|
772
|
+
streamController.abort();
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
648
775
|
yield { type: "text_delta", text: event.text };
|
|
649
776
|
} else if (event.type === "thinking_delta") {
|
|
777
|
+
attemptThinkingChars += event.text.length;
|
|
650
778
|
yield { type: "thinking_delta", text: event.text };
|
|
651
779
|
} else if (event.type === "server_toolcall") {
|
|
652
780
|
yield {
|
|
@@ -666,6 +794,11 @@ async function* agentLoop(messages, options) {
|
|
|
666
794
|
const chunkChars = event.argsJson?.length ?? 0;
|
|
667
795
|
toolcallDeltaChars += chunkChars;
|
|
668
796
|
toolcallDeltaCount++;
|
|
797
|
+
streamRuleHit = streamRuleMonitor?.checkToolArgs(event.id, event.name, event.argsJson ?? "") ?? null;
|
|
798
|
+
if (streamRuleHit) {
|
|
799
|
+
streamController.abort();
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
669
802
|
if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallDeltaCount > MAX_TOOLCALL_DELTA_EVENTS)) {
|
|
670
803
|
runawayDetected = {
|
|
671
804
|
kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
|
|
@@ -687,6 +820,54 @@ async function* agentLoop(messages, options) {
|
|
|
687
820
|
lastYieldEndTime = Date.now();
|
|
688
821
|
resetIdleTimer();
|
|
689
822
|
}
|
|
823
|
+
if (streamRuleHit && streamRuleMonitor) {
|
|
824
|
+
const attempt = streamRuleMonitor.recordTrigger(streamRuleHit.rules);
|
|
825
|
+
const ruleNames = streamRuleHit.rules.map((rule) => rule.name);
|
|
826
|
+
const priorPrompt = latestProviderUsage ? latestProviderUsage.inputTokens + (latestProviderUsage.cacheRead ?? 0) + (latestProviderUsage.cacheWrite ?? 0) : 0;
|
|
827
|
+
const newChars = priorPrompt > 0 && usageAnchorIndex !== void 0 ? countMessageChars(messages.slice(usageAnchorIndex + 1)) : msgChars;
|
|
828
|
+
const abortedUsage = {
|
|
829
|
+
inputTokens: Math.ceil(newChars / 4),
|
|
830
|
+
outputTokens: Math.ceil(
|
|
831
|
+
(attemptText.length + attemptThinkingChars + toolcallDeltaChars) / 4
|
|
832
|
+
),
|
|
833
|
+
...priorPrompt > 0 ? { cacheRead: priorPrompt } : {}
|
|
834
|
+
};
|
|
835
|
+
totalUsage.inputTokens += abortedUsage.inputTokens;
|
|
836
|
+
totalUsage.outputTokens += abortedUsage.outputTokens;
|
|
837
|
+
if (abortedUsage.cacheRead) {
|
|
838
|
+
totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + abortedUsage.cacheRead;
|
|
839
|
+
}
|
|
840
|
+
diag("stream_rule_triggered", {
|
|
841
|
+
rules: ruleNames.join(","),
|
|
842
|
+
source: streamRuleHit.source,
|
|
843
|
+
toolName: streamRuleHit.toolName,
|
|
844
|
+
attempt,
|
|
845
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
846
|
+
discardedChars: attemptText.length + toolcallDeltaChars,
|
|
847
|
+
provider: options.provider,
|
|
848
|
+
model: options.model
|
|
849
|
+
});
|
|
850
|
+
messages.push(buildStreamRuleReminder(streamRuleHit.rules));
|
|
851
|
+
yield {
|
|
852
|
+
type: "stream_rule_triggered",
|
|
853
|
+
rules: ruleNames,
|
|
854
|
+
source: streamRuleHit.source,
|
|
855
|
+
...streamRuleHit.toolName !== void 0 ? { toolName: streamRuleHit.toolName } : {},
|
|
856
|
+
attempt,
|
|
857
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
858
|
+
usage: abortedUsage
|
|
859
|
+
};
|
|
860
|
+
yield {
|
|
861
|
+
type: "retry",
|
|
862
|
+
reason: "stream_rule",
|
|
863
|
+
attempt,
|
|
864
|
+
maxAttempts: streamRuleMonitor.maxRetries,
|
|
865
|
+
delayMs: 0,
|
|
866
|
+
silent: true
|
|
867
|
+
};
|
|
868
|
+
turn--;
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
690
871
|
diag("stream_done", {
|
|
691
872
|
events: streamEventCount,
|
|
692
873
|
totalMs: Date.now() - streamCallStart,
|
|
@@ -1733,6 +1914,19 @@ function sanitizeOrphanedServerTools(messages) {
|
|
|
1733
1914
|
break;
|
|
1734
1915
|
}
|
|
1735
1916
|
}
|
|
1917
|
+
function countMessageChars(messages) {
|
|
1918
|
+
let chars = 0;
|
|
1919
|
+
for (const m of messages) {
|
|
1920
|
+
if (typeof m.content === "string") chars += m.content.length;
|
|
1921
|
+
else if (Array.isArray(m.content)) {
|
|
1922
|
+
for (const p of m.content) {
|
|
1923
|
+
if ("text" in p && typeof p.text === "string") chars += p.text.length;
|
|
1924
|
+
if ("content" in p && typeof p.content === "string") chars += p.content.length;
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return chars;
|
|
1929
|
+
}
|
|
1736
1930
|
function repairToolPairingAdjacent(messages) {
|
|
1737
1931
|
for (let i = 0; i < messages.length; i++) {
|
|
1738
1932
|
const msg = messages[i];
|
|
@@ -1911,7 +2105,12 @@ var Agent = class {
|
|
|
1911
2105
|
export {
|
|
1912
2106
|
Agent,
|
|
1913
2107
|
AgentStream,
|
|
2108
|
+
DEFAULT_STREAM_RULE_MAX_RETRIES,
|
|
2109
|
+
DEFAULT_STREAM_RULE_WINDOW_CHARS,
|
|
2110
|
+
JsonEscapeDecoder,
|
|
2111
|
+
StreamRuleMonitor,
|
|
1914
2112
|
agentLoop,
|
|
2113
|
+
buildStreamRuleReminder,
|
|
1915
2114
|
cancelledBeforeStartText,
|
|
1916
2115
|
indeterminateOutcomeText,
|
|
1917
2116
|
isAbortError,
|
|
@@ -1919,6 +2118,7 @@ export {
|
|
|
1919
2118
|
isContextOverflow,
|
|
1920
2119
|
isLocalBackendUrl,
|
|
1921
2120
|
isUsageLimitError,
|
|
2121
|
+
repairToolPairingAdjacent,
|
|
1922
2122
|
setStreamDiagnostic
|
|
1923
2123
|
};
|
|
1924
2124
|
//# sourceMappingURL=index.js.map
|