@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 CHANGED
@@ -22,7 +22,12 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  Agent: () => Agent,
24
24
  AgentStream: () => AgentStream,
25
+ DEFAULT_STREAM_RULE_MAX_RETRIES: () => DEFAULT_STREAM_RULE_MAX_RETRIES,
26
+ DEFAULT_STREAM_RULE_WINDOW_CHARS: () => DEFAULT_STREAM_RULE_WINDOW_CHARS,
27
+ JsonEscapeDecoder: () => JsonEscapeDecoder,
28
+ StreamRuleMonitor: () => StreamRuleMonitor,
25
29
  agentLoop: () => agentLoop,
30
+ buildStreamRuleReminder: () => buildStreamRuleReminder,
26
31
  cancelledBeforeStartText: () => cancelledBeforeStartText,
27
32
  indeterminateOutcomeText: () => indeterminateOutcomeText,
28
33
  isAbortError: () => isAbortError,
@@ -30,6 +35,7 @@ __export(index_exports, {
30
35
  isContextOverflow: () => isContextOverflow,
31
36
  isLocalBackendUrl: () => isLocalBackendUrl,
32
37
  isUsageLimitError: () => isUsageLimitError,
38
+ repairToolPairingAdjacent: () => repairToolPairingAdjacent,
33
39
  setStreamDiagnostic: () => setStreamDiagnostic
34
40
  });
35
41
  module.exports = __toCommonJS(index_exports);
@@ -117,6 +123,133 @@ function clampOutputTokens(key, requested) {
117
123
  return requested === void 0 ? ceiling : Math.min(requested, ceiling);
118
124
  }
119
125
 
126
+ // src/stream-rules.ts
127
+ var DEFAULT_STREAM_RULE_MAX_RETRIES = 3;
128
+ var DEFAULT_STREAM_RULE_WINDOW_CHARS = 1024;
129
+ var JSON_ESCAPES = {
130
+ n: "\n",
131
+ r: "\r",
132
+ t: " ",
133
+ b: "\b",
134
+ f: "\f",
135
+ '"': '"',
136
+ "\\": "\\",
137
+ "/": "/"
138
+ };
139
+ var JsonEscapeDecoder = class {
140
+ pending = "";
141
+ push(chunk) {
142
+ const input = this.pending + chunk;
143
+ this.pending = "";
144
+ if (!input.includes("\\")) return input;
145
+ let out = "";
146
+ let i = 0;
147
+ while (i < input.length) {
148
+ const ch = input.charAt(i);
149
+ if (ch !== "\\") {
150
+ out += ch;
151
+ i++;
152
+ continue;
153
+ }
154
+ if (i + 1 >= input.length) {
155
+ this.pending = input.slice(i);
156
+ break;
157
+ }
158
+ const next = input.charAt(i + 1);
159
+ if (next === "u") {
160
+ if (i + 6 > input.length) {
161
+ this.pending = input.slice(i);
162
+ break;
163
+ }
164
+ const hex = input.slice(i + 2, i + 6);
165
+ out += /^[0-9a-fA-F]{4}$/.test(hex) ? String.fromCharCode(Number.parseInt(hex, 16)) : input.slice(i, i + 6);
166
+ i += 6;
167
+ continue;
168
+ }
169
+ out += JSON_ESCAPES[next] ?? next;
170
+ i += 2;
171
+ }
172
+ return out;
173
+ }
174
+ };
175
+ var StreamRuleMonitor = class {
176
+ maxRetries;
177
+ rules;
178
+ windowChars;
179
+ fired = /* @__PURE__ */ new Set();
180
+ retries = 0;
181
+ textWindow = "";
182
+ toolStreams = /* @__PURE__ */ new Map();
183
+ constructor(config) {
184
+ this.rules = config.rules;
185
+ this.maxRetries = Math.max(0, config.maxRetries ?? DEFAULT_STREAM_RULE_MAX_RETRIES);
186
+ this.windowChars = Math.max(64, config.windowChars ?? DEFAULT_STREAM_RULE_WINDOW_CHARS);
187
+ }
188
+ /** False once the retry cap is spent or every rule has fired — skip all matching. */
189
+ get active() {
190
+ return this.retries < this.maxRetries && this.fired.size < this.rules.length;
191
+ }
192
+ get retriesUsed() {
193
+ return this.retries;
194
+ }
195
+ beginAttempt() {
196
+ this.textWindow = "";
197
+ this.toolStreams.clear();
198
+ }
199
+ checkText(delta) {
200
+ if (!this.active || !delta) return null;
201
+ const scan = this.textWindow + delta;
202
+ this.textWindow = scan.slice(-this.windowChars);
203
+ const rules = this.match(scan, "text");
204
+ return rules.length > 0 ? { rules, source: "text" } : null;
205
+ }
206
+ checkToolArgs(id, toolName, argsDelta) {
207
+ if (!this.active || !argsDelta) return null;
208
+ let state = this.toolStreams.get(id);
209
+ if (!state) {
210
+ state = { window: "", decoder: new JsonEscapeDecoder() };
211
+ this.toolStreams.set(id, state);
212
+ }
213
+ const scan = state.window + state.decoder.push(argsDelta);
214
+ state.window = scan.slice(-this.windowChars);
215
+ const rules = this.match(scan, "tool", toolName);
216
+ return rules.length > 0 ? { rules, source: "tool", toolName } : null;
217
+ }
218
+ /** Consume one retry and mark the rules fired. Returns the retry ordinal (1-based). */
219
+ recordTrigger(rules) {
220
+ for (const rule of rules) this.fired.add(rule.name);
221
+ this.retries++;
222
+ return this.retries;
223
+ }
224
+ match(scan, source, toolName) {
225
+ const hits = [];
226
+ for (const rule of this.rules) {
227
+ if (this.fired.has(rule.name)) continue;
228
+ if (rule.scope !== "both" && rule.scope !== source) continue;
229
+ if (source === "tool" && rule.tools && toolName !== void 0) {
230
+ if (!rule.tools.includes(toolName)) continue;
231
+ }
232
+ rule.pattern.lastIndex = 0;
233
+ if (rule.pattern.test(scan)) hits.push(rule);
234
+ }
235
+ return hits;
236
+ }
237
+ };
238
+ function buildStreamRuleReminder(rules) {
239
+ const blocks = rules.map(
240
+ (rule) => `<system-reminder reason="stream_rule" rule="${rule.name.replace(/"/g, "'")}">
241
+ 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:
242
+
243
+ ${rule.reminder.trim()}
244
+ </system-reminder>`
245
+ );
246
+ return {
247
+ role: "user",
248
+ content: blocks.join("\n\n"),
249
+ provenance: { source: "runtime", kind: "notification", visibility: "hidden" }
250
+ };
251
+ }
252
+
120
253
  // src/agent-loop.ts
121
254
  var DEFAULT_MAX_TURNS = 300;
122
255
  var DEFAULT_TOOL_TIMEOUT_MS = 3e5;
@@ -382,6 +515,7 @@ async function* agentLoop(messages, options) {
382
515
  let runawayToolcallRetries = 0;
383
516
  let overflowCompactionAttempts = 0;
384
517
  let toolResultTruncationAttempted = false;
518
+ const streamRuleMonitor = options.streamRules && options.streamRules.rules.length > 0 ? new StreamRuleMonitor(options.streamRules) : null;
385
519
  const invalidToolArgumentCounts = /* @__PURE__ */ new Map();
386
520
  let toolArgumentAutoContinueUsed = false;
387
521
  let useNonStreamingFallback = false;
@@ -435,16 +569,7 @@ async function* agentLoop(messages, options) {
435
569
  turn++;
436
570
  if (logicalTurnStartedAt === 0) logicalTurnStartedAt = Date.now();
437
571
  toolMap = new Map((options.tools ?? []).map((t) => [t.name, t]));
438
- let msgChars = 0;
439
- for (const m of messages) {
440
- if (typeof m.content === "string") msgChars += m.content.length;
441
- else if (Array.isArray(m.content)) {
442
- for (const p of m.content) {
443
- if ("text" in p && typeof p.text === "string") msgChars += p.text.length;
444
- if ("content" in p && typeof p.content === "string") msgChars += p.content.length;
445
- }
446
- }
447
- }
572
+ const msgChars = countMessageChars(messages);
448
573
  let firstEventTimeoutMs;
449
574
  let initialHardTimeoutMs;
450
575
  if (baseFirstEventTimeoutMs === STREAM_FIRST_EVENT_TIMEOUT_MS) {
@@ -514,6 +639,9 @@ async function* agentLoop(messages, options) {
514
639
  let toolcallDeltaCount = 0;
515
640
  let runawayDetected = null;
516
641
  let attemptText = "";
642
+ let streamRuleHit = null;
643
+ let attemptThinkingChars = 0;
644
+ streamRuleMonitor?.beginAttempt();
517
645
  let lastYieldEndTime = Date.now();
518
646
  let maxConsumerLagMs = 0;
519
647
  const forwardAbort = () => streamController.abort();
@@ -672,8 +800,14 @@ async function* agentLoop(messages, options) {
672
800
  }
673
801
  if (event.type === "text_delta") {
674
802
  attemptText += event.text;
803
+ streamRuleHit = streamRuleMonitor?.checkText(event.text) ?? null;
804
+ if (streamRuleHit) {
805
+ streamController.abort();
806
+ break;
807
+ }
675
808
  yield { type: "text_delta", text: event.text };
676
809
  } else if (event.type === "thinking_delta") {
810
+ attemptThinkingChars += event.text.length;
677
811
  yield { type: "thinking_delta", text: event.text };
678
812
  } else if (event.type === "server_toolcall") {
679
813
  yield {
@@ -693,6 +827,11 @@ async function* agentLoop(messages, options) {
693
827
  const chunkChars = event.argsJson?.length ?? 0;
694
828
  toolcallDeltaChars += chunkChars;
695
829
  toolcallDeltaCount++;
830
+ streamRuleHit = streamRuleMonitor?.checkToolArgs(event.id, event.name, event.argsJson ?? "") ?? null;
831
+ if (streamRuleHit) {
832
+ streamController.abort();
833
+ break;
834
+ }
696
835
  if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallDeltaCount > MAX_TOOLCALL_DELTA_EVENTS)) {
697
836
  runawayDetected = {
698
837
  kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
@@ -714,6 +853,54 @@ async function* agentLoop(messages, options) {
714
853
  lastYieldEndTime = Date.now();
715
854
  resetIdleTimer();
716
855
  }
856
+ if (streamRuleHit && streamRuleMonitor) {
857
+ const attempt = streamRuleMonitor.recordTrigger(streamRuleHit.rules);
858
+ const ruleNames = streamRuleHit.rules.map((rule) => rule.name);
859
+ const priorPrompt = latestProviderUsage ? latestProviderUsage.inputTokens + (latestProviderUsage.cacheRead ?? 0) + (latestProviderUsage.cacheWrite ?? 0) : 0;
860
+ const newChars = priorPrompt > 0 && usageAnchorIndex !== void 0 ? countMessageChars(messages.slice(usageAnchorIndex + 1)) : msgChars;
861
+ const abortedUsage = {
862
+ inputTokens: Math.ceil(newChars / 4),
863
+ outputTokens: Math.ceil(
864
+ (attemptText.length + attemptThinkingChars + toolcallDeltaChars) / 4
865
+ ),
866
+ ...priorPrompt > 0 ? { cacheRead: priorPrompt } : {}
867
+ };
868
+ totalUsage.inputTokens += abortedUsage.inputTokens;
869
+ totalUsage.outputTokens += abortedUsage.outputTokens;
870
+ if (abortedUsage.cacheRead) {
871
+ totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + abortedUsage.cacheRead;
872
+ }
873
+ diag("stream_rule_triggered", {
874
+ rules: ruleNames.join(","),
875
+ source: streamRuleHit.source,
876
+ toolName: streamRuleHit.toolName,
877
+ attempt,
878
+ maxAttempts: streamRuleMonitor.maxRetries,
879
+ discardedChars: attemptText.length + toolcallDeltaChars,
880
+ provider: options.provider,
881
+ model: options.model
882
+ });
883
+ messages.push(buildStreamRuleReminder(streamRuleHit.rules));
884
+ yield {
885
+ type: "stream_rule_triggered",
886
+ rules: ruleNames,
887
+ source: streamRuleHit.source,
888
+ ...streamRuleHit.toolName !== void 0 ? { toolName: streamRuleHit.toolName } : {},
889
+ attempt,
890
+ maxAttempts: streamRuleMonitor.maxRetries,
891
+ usage: abortedUsage
892
+ };
893
+ yield {
894
+ type: "retry",
895
+ reason: "stream_rule",
896
+ attempt,
897
+ maxAttempts: streamRuleMonitor.maxRetries,
898
+ delayMs: 0,
899
+ silent: true
900
+ };
901
+ turn--;
902
+ continue;
903
+ }
717
904
  diag("stream_done", {
718
905
  events: streamEventCount,
719
906
  totalMs: Date.now() - streamCallStart,
@@ -1760,6 +1947,19 @@ function sanitizeOrphanedServerTools(messages) {
1760
1947
  break;
1761
1948
  }
1762
1949
  }
1950
+ function countMessageChars(messages) {
1951
+ let chars = 0;
1952
+ for (const m of messages) {
1953
+ if (typeof m.content === "string") chars += m.content.length;
1954
+ else if (Array.isArray(m.content)) {
1955
+ for (const p of m.content) {
1956
+ if ("text" in p && typeof p.text === "string") chars += p.text.length;
1957
+ if ("content" in p && typeof p.content === "string") chars += p.content.length;
1958
+ }
1959
+ }
1960
+ }
1961
+ return chars;
1962
+ }
1763
1963
  function repairToolPairingAdjacent(messages) {
1764
1964
  for (let i = 0; i < messages.length; i++) {
1765
1965
  const msg = messages[i];
@@ -1939,7 +2139,12 @@ var Agent = class {
1939
2139
  0 && (module.exports = {
1940
2140
  Agent,
1941
2141
  AgentStream,
2142
+ DEFAULT_STREAM_RULE_MAX_RETRIES,
2143
+ DEFAULT_STREAM_RULE_WINDOW_CHARS,
2144
+ JsonEscapeDecoder,
2145
+ StreamRuleMonitor,
1942
2146
  agentLoop,
2147
+ buildStreamRuleReminder,
1943
2148
  cancelledBeforeStartText,
1944
2149
  indeterminateOutcomeText,
1945
2150
  isAbortError,
@@ -1947,6 +2152,7 @@ var Agent = class {
1947
2152
  isContextOverflow,
1948
2153
  isLocalBackendUrl,
1949
2154
  isUsageLimitError,
2155
+ repairToolPairingAdjacent,
1950
2156
  setStreamDiagnostic
1951
2157
  });
1952
2158
  //# sourceMappingURL=index.cjs.map