@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 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();
@@ -599,6 +727,7 @@ async function* agentLoop(messages, options) {
599
727
  transportSessionId: options.transportSessionId,
600
728
  projectId: liveProjectId,
601
729
  cacheRetention: options.cacheRetention,
730
+ onContextPrepared: options.onContextPrepared,
602
731
  promptCacheKey: options.promptCacheKey,
603
732
  serviceTier: options.serviceTier,
604
733
  supportsImages: options.supportsImages,
@@ -672,8 +801,14 @@ async function* agentLoop(messages, options) {
672
801
  }
673
802
  if (event.type === "text_delta") {
674
803
  attemptText += event.text;
804
+ streamRuleHit = streamRuleMonitor?.checkText(event.text) ?? null;
805
+ if (streamRuleHit) {
806
+ streamController.abort();
807
+ break;
808
+ }
675
809
  yield { type: "text_delta", text: event.text };
676
810
  } else if (event.type === "thinking_delta") {
811
+ attemptThinkingChars += event.text.length;
677
812
  yield { type: "thinking_delta", text: event.text };
678
813
  } else if (event.type === "server_toolcall") {
679
814
  yield {
@@ -693,6 +828,11 @@ async function* agentLoop(messages, options) {
693
828
  const chunkChars = event.argsJson?.length ?? 0;
694
829
  toolcallDeltaChars += chunkChars;
695
830
  toolcallDeltaCount++;
831
+ streamRuleHit = streamRuleMonitor?.checkToolArgs(event.id, event.name, event.argsJson ?? "") ?? null;
832
+ if (streamRuleHit) {
833
+ streamController.abort();
834
+ break;
835
+ }
696
836
  if (!runawayDetected && (toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS || toolcallDeltaCount > MAX_TOOLCALL_DELTA_EVENTS)) {
697
837
  runawayDetected = {
698
838
  kind: toolcallDeltaChars > MAX_TOOLCALL_DELTA_CHARS ? "chars" : "events",
@@ -714,6 +854,54 @@ async function* agentLoop(messages, options) {
714
854
  lastYieldEndTime = Date.now();
715
855
  resetIdleTimer();
716
856
  }
857
+ if (streamRuleHit && streamRuleMonitor) {
858
+ const attempt = streamRuleMonitor.recordTrigger(streamRuleHit.rules);
859
+ const ruleNames = streamRuleHit.rules.map((rule) => rule.name);
860
+ const priorPrompt = latestProviderUsage ? latestProviderUsage.inputTokens + (latestProviderUsage.cacheRead ?? 0) + (latestProviderUsage.cacheWrite ?? 0) : 0;
861
+ const newChars = priorPrompt > 0 && usageAnchorIndex !== void 0 ? countMessageChars(messages.slice(usageAnchorIndex + 1)) : msgChars;
862
+ const abortedUsage = {
863
+ inputTokens: Math.ceil(newChars / 4),
864
+ outputTokens: Math.ceil(
865
+ (attemptText.length + attemptThinkingChars + toolcallDeltaChars) / 4
866
+ ),
867
+ ...priorPrompt > 0 ? { cacheRead: priorPrompt } : {}
868
+ };
869
+ totalUsage.inputTokens += abortedUsage.inputTokens;
870
+ totalUsage.outputTokens += abortedUsage.outputTokens;
871
+ if (abortedUsage.cacheRead) {
872
+ totalUsage.cacheRead = (totalUsage.cacheRead ?? 0) + abortedUsage.cacheRead;
873
+ }
874
+ diag("stream_rule_triggered", {
875
+ rules: ruleNames.join(","),
876
+ source: streamRuleHit.source,
877
+ toolName: streamRuleHit.toolName,
878
+ attempt,
879
+ maxAttempts: streamRuleMonitor.maxRetries,
880
+ discardedChars: attemptText.length + toolcallDeltaChars,
881
+ provider: options.provider,
882
+ model: options.model
883
+ });
884
+ messages.push(buildStreamRuleReminder(streamRuleHit.rules));
885
+ yield {
886
+ type: "stream_rule_triggered",
887
+ rules: ruleNames,
888
+ source: streamRuleHit.source,
889
+ ...streamRuleHit.toolName !== void 0 ? { toolName: streamRuleHit.toolName } : {},
890
+ attempt,
891
+ maxAttempts: streamRuleMonitor.maxRetries,
892
+ usage: abortedUsage
893
+ };
894
+ yield {
895
+ type: "retry",
896
+ reason: "stream_rule",
897
+ attempt,
898
+ maxAttempts: streamRuleMonitor.maxRetries,
899
+ delayMs: 0,
900
+ silent: true
901
+ };
902
+ turn--;
903
+ continue;
904
+ }
717
905
  diag("stream_done", {
718
906
  events: streamEventCount,
719
907
  totalMs: Date.now() - streamCallStart,
@@ -1760,6 +1948,19 @@ function sanitizeOrphanedServerTools(messages) {
1760
1948
  break;
1761
1949
  }
1762
1950
  }
1951
+ function countMessageChars(messages) {
1952
+ let chars = 0;
1953
+ for (const m of messages) {
1954
+ if (typeof m.content === "string") chars += m.content.length;
1955
+ else if (Array.isArray(m.content)) {
1956
+ for (const p of m.content) {
1957
+ if ("text" in p && typeof p.text === "string") chars += p.text.length;
1958
+ if ("content" in p && typeof p.content === "string") chars += p.content.length;
1959
+ }
1960
+ }
1961
+ }
1962
+ return chars;
1963
+ }
1763
1964
  function repairToolPairingAdjacent(messages) {
1764
1965
  for (let i = 0; i < messages.length; i++) {
1765
1966
  const msg = messages[i];
@@ -1939,7 +2140,12 @@ var Agent = class {
1939
2140
  0 && (module.exports = {
1940
2141
  Agent,
1941
2142
  AgentStream,
2143
+ DEFAULT_STREAM_RULE_MAX_RETRIES,
2144
+ DEFAULT_STREAM_RULE_WINDOW_CHARS,
2145
+ JsonEscapeDecoder,
2146
+ StreamRuleMonitor,
1942
2147
  agentLoop,
2148
+ buildStreamRuleReminder,
1943
2149
  cancelledBeforeStartText,
1944
2150
  indeterminateOutcomeText,
1945
2151
  isAbortError,
@@ -1947,6 +2153,7 @@ var Agent = class {
1947
2153
  isContextOverflow,
1948
2154
  isLocalBackendUrl,
1949
2155
  isUsageLimitError,
2156
+ repairToolPairingAdjacent,
1950
2157
  setStreamDiagnostic
1951
2158
  });
1952
2159
  //# sourceMappingURL=index.cjs.map