@caupulican/pi-agent-core 0.93.16 → 0.93.17
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/degenerate-assistant-text.d.ts.map +1 -1
- package/dist/degenerate-assistant-text.js +20 -8
- package/dist/degenerate-assistant-text.js.map +1 -1
- package/dist/reliability/watchdogs.d.ts +7 -0
- package/dist/reliability/watchdogs.d.ts.map +1 -1
- package/dist/reliability/watchdogs.js +15 -2
- package/dist/reliability/watchdogs.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"degenerate-assistant-text.d.ts","sourceRoot":"","sources":["../src/degenerate-assistant-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,8GAA8G;AAC9G,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,qGAAqG;AACrG,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,2FAA2F;AAC3F,eAAO,MAAM,2BAA2B,KAAK,CAAC;AA6D9C,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAY1F;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW1D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOjE;AAID,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,
|
|
1
|
+
{"version":3,"file":"degenerate-assistant-text.d.ts","sourceRoot":"","sources":["../src/degenerate-assistant-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,8GAA8G;AAC9G,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAE9C,qGAAqG;AACrG,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,2FAA2F;AAC3F,eAAO,MAAM,2BAA2B,KAAK,CAAC;AA6D9C,uFAAuF;AACvF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAY1F;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW1D;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOjE;AAID,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB,CAsC9F;AAED,2EAA2E;AAC3E,wBAAgB,qCAAqC,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAExF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAMtE"}
|
|
@@ -116,20 +116,33 @@ export function collapseDegenerateAssistantMessage(message) {
|
|
|
116
116
|
const seenText = new Set();
|
|
117
117
|
const content = [];
|
|
118
118
|
for (const block of message.content) {
|
|
119
|
-
if (block.type !== "text") {
|
|
119
|
+
if (block.type !== "text" && block.type !== "thinking") {
|
|
120
120
|
content.push(block);
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
|
-
const
|
|
124
|
-
|
|
123
|
+
const isThinking = block.type === "thinking";
|
|
124
|
+
const source = isThinking ? block.thinking : block.text;
|
|
125
|
+
const canRewriteThinking = !isThinking ||
|
|
126
|
+
(!block.redacted &&
|
|
127
|
+
(!block.thinkingSignature ||
|
|
128
|
+
message.api === "openai-completions" ||
|
|
129
|
+
message.api === "openai-responses" ||
|
|
130
|
+
message.api === "azure-openai-responses" ||
|
|
131
|
+
message.api === "openai-codex-responses"));
|
|
132
|
+
if (!canRewriteThinking) {
|
|
133
|
+
content.push(block);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const collapsed = collapseRepeatedLines(source);
|
|
137
|
+
if (collapsed !== source)
|
|
125
138
|
changed = true;
|
|
126
|
-
if (collapsed.trim() !== "" && seenText.has(collapsed)) {
|
|
139
|
+
if (!isThinking && collapsed.trim() !== "" && seenText.has(collapsed)) {
|
|
127
140
|
changed = true;
|
|
128
141
|
continue;
|
|
129
142
|
}
|
|
130
|
-
if (collapsed.trim() !== "")
|
|
143
|
+
if (!isThinking && collapsed.trim() !== "")
|
|
131
144
|
seenText.add(collapsed);
|
|
132
|
-
content.push(collapsed === block
|
|
145
|
+
content.push(collapsed === source ? block : isThinking ? { ...block, thinking: collapsed } : { ...block, text: collapsed });
|
|
133
146
|
}
|
|
134
147
|
if (!changed)
|
|
135
148
|
return message;
|
|
@@ -143,8 +156,7 @@ export function isCollapsedDegenerateAssistantMessage(message) {
|
|
|
143
156
|
}
|
|
144
157
|
export function assistantMessageText(message) {
|
|
145
158
|
return message.content
|
|
146
|
-
.
|
|
147
|
-
.map((block) => block.text)
|
|
159
|
+
.flatMap((block) => block.type === "text" ? [block.text] : block.type === "thinking" && !block.redacted ? [block.thinking] : [])
|
|
148
160
|
.join("\n");
|
|
149
161
|
}
|
|
150
162
|
//# sourceMappingURL=degenerate-assistant-text.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"degenerate-assistant-text.js","sourceRoot":"","sources":["../src/degenerate-assistant-text.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AssistantMessage } from \"@caupulican/pi-ai/types\";\n\n/** Consecutive identical non-empty units (lines or sentences) at or above this run count are degeneration. */\nexport const DEGENERATE_REPEATED_LINE_MIN = 4;\n\n/** Mid-stream abort once a generation loop is already this long; cheaper than waiting for `done`. */\nexport const DEGENERATE_STREAM_ABORT_RUN = 8;\n\n/** Shortest unit that may be an exact tiled status sentence (`Foo.Foo.` with no space). */\nexport const DEGENERATE_EXACT_REPEAT_MIN = 24;\n\nfunction collapseRepeatedUnits(units: readonly string[], joinWith: string): string {\n\tif (units.length < DEGENERATE_REPEATED_LINE_MIN) return units.join(joinWith);\n\tconst out: string[] = [];\n\tlet index = 0;\n\twhile (index < units.length) {\n\t\tconst unit = units[index];\n\t\tlet run = 1;\n\t\twhile (index + run < units.length && units[index + run] === unit) run++;\n\t\tif (unit.trim() !== \"\" && run >= DEGENERATE_REPEATED_LINE_MIN) {\n\t\t\tout.push(unit);\n\t\t\tindex += run;\n\t\t\tcontinue;\n\t\t}\n\t\tif (index + 1 < units.length) {\n\t\t\tconst next = units[index + 1];\n\t\t\tif (unit.trim() !== \"\" && next.trim() !== \"\" && unit !== next) {\n\t\t\t\tlet pairs = 1;\n\t\t\t\twhile (\n\t\t\t\t\tindex + (pairs + 1) * 2 <= units.length &&\n\t\t\t\t\tunits[index + pairs * 2] === unit &&\n\t\t\t\t\tunits[index + pairs * 2 + 1] === next\n\t\t\t\t) {\n\t\t\t\t\tpairs++;\n\t\t\t\t}\n\t\t\t\tif (pairs >= DEGENERATE_REPEATED_LINE_MIN) {\n\t\t\t\t\tout.push(unit, next);\n\t\t\t\t\tindex += pairs * 2;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tout.push(unit);\n\t\tindex++;\n\t}\n\treturn out.join(joinWith);\n}\n\nfunction maxRepeatedUnitRun(units: readonly string[]): number {\n\tlet maxRun = 1;\n\tlet run = 1;\n\tfor (let index = 1; index < units.length; index++) {\n\t\tif (units[index] === units[index - 1] && units[index].trim() !== \"\") {\n\t\t\trun++;\n\t\t\tif (run > maxRun) maxRun = run;\n\t\t} else {\n\t\t\trun = 1;\n\t\t}\n\t}\n\treturn units.length === 0 ? 0 : maxRun;\n}\n\nfunction splitSentences(text: string): string[] {\n\treturn text.split(/(?<=[.!?])\\s+/);\n}\n\nfunction isProseTile(unit: string): boolean {\n\treturn /[.!?]/.test(unit) || /\\s/.test(unit);\n}\n\n/** Smallest power-of-two prose tile that repeats to `text` (`Foo.Foo.` / `Foo.`×4). */\nexport function exactTiledRepeat(text: string): { unit: string; times: number } | undefined {\n\tif (text.length < DEGENERATE_EXACT_REPEAT_MIN * 2) return undefined;\n\tlet unit = text;\n\tlet times = 1;\n\twhile (unit.length >= DEGENERATE_EXACT_REPEAT_MIN * 2 && unit.length % 2 === 0) {\n\t\tconst half = unit.length / 2;\n\t\tconst next = unit.slice(0, half);\n\t\tif (!isProseTile(next) || next !== unit.slice(half)) break;\n\t\tunit = next;\n\t\ttimes *= 2;\n\t}\n\treturn times > 1 ? { unit, times } : undefined;\n}\n\n/**\n * Collapse consecutive identical assistant lines, period-2 ABAB line runs, and the same\n * sentence repeated inside one paragraph. Session 01a0016f stored 551 copies of one\n * sentence as a single line; newline-only collapse cannot see that.\n * Session 01a00337 stored the same status sentence concatenated with no space (`Foo.Foo.`)\n * because Responses deltas replayed the full sentence; whitespace split cannot see that.\n */\nexport function collapseRepeatedLines(text: string): string {\n\tconst tiled = exactTiledRepeat(text);\n\tconst source = tiled ? tiled.unit : text;\n\tconst lineCollapsed = collapseRepeatedUnits(source.split(\"\\n\"), \"\\n\");\n\treturn lineCollapsed\n\t\t.split(\"\\n\")\n\t\t.map((line) => {\n\t\t\tconst lineTile = exactTiledRepeat(line);\n\t\t\treturn collapseRepeatedUnits(splitSentences(lineTile ? lineTile.unit : line), \" \");\n\t\t})\n\t\t.join(\"\\n\");\n}\n\nexport function isDegenerateRepeatedText(text: string): boolean {\n\treturn collapseRepeatedLines(text) !== text;\n}\n\nexport function shouldAbortDegenerateStream(text: string): boolean {\n\tif (text.length < 400) return false;\n\tconst tiled = exactTiledRepeat(text);\n\tif (tiled && tiled.times >= DEGENERATE_STREAM_ABORT_RUN) return true;\n\tconst sentences = splitSentences(text);\n\tif (maxRepeatedUnitRun(sentences) >= DEGENERATE_STREAM_ABORT_RUN) return true;\n\treturn maxRepeatedUnitRun(text.split(\"\\n\")) >= DEGENERATE_STREAM_ABORT_RUN;\n}\n\nconst collapsedDegenerateMessages = new WeakSet<AssistantMessage>();\n\nexport function collapseDegenerateAssistantMessage(message: AssistantMessage): AssistantMessage {\n\tlet changed = false;\n\tconst seenText = new Set<string>();\n\tconst content: AssistantMessage[\"content\"] = [];\n\tfor (const block of message.content) {\n\t\tif (block.type !== \"text\") {\n\t\t\tcontent.push(block);\n\t\t\tcontinue;\n\t\t}\n\t\tconst collapsed = collapseRepeatedLines(block.text);\n\t\tif (collapsed !== block.text) changed = true;\n\t\tif (collapsed.trim() !== \"\" && seenText.has(collapsed)) {\n\t\t\tchanged = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (collapsed.trim() !== \"\") seenText.add(collapsed);\n\t\tcontent.push(collapsed === block.text ? block : { ...block, text: collapsed });\n\t}\n\tif (!changed) return message;\n\tconst next = { ...message, content };\n\tcollapsedDegenerateMessages.add(next);\n\treturn next;\n}\n\n/** True when this message is (or was collapsed from) a generation loop. */\nexport function isCollapsedDegenerateAssistantMessage(message: AssistantMessage): boolean {\n\treturn collapsedDegenerateMessages.has(message);\n}\n\nexport function assistantMessageText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.filter((block) => block.type === \"text\")\n\t\t.map((block) => block.text)\n\t\t.join(\"\\n\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"degenerate-assistant-text.js","sourceRoot":"","sources":["../src/degenerate-assistant-text.ts"],"names":[],"mappings":"AAEA,8GAA8G;AAC9G,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,CAAC;AAE9C,qGAAqG;AACrG,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAE7C,2FAA2F;AAC3F,MAAM,CAAC,MAAM,2BAA2B,GAAG,EAAE,CAAC;AAE9C,SAAS,qBAAqB,CAAC,KAAwB,EAAE,QAAgB;IACxE,IAAI,KAAK,CAAC,MAAM,GAAG,4BAA4B;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC7E,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,OAAO,KAAK,GAAG,GAAG,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI;YAAE,GAAG,EAAE,CAAC;QACxE,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,GAAG,IAAI,4BAA4B,EAAE,CAAC;YAC/D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,KAAK,IAAI,GAAG,CAAC;YACb,SAAS;QACV,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;YAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC9B,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAC/D,IAAI,KAAK,GAAG,CAAC,CAAC;gBACd,OACC,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM;oBACvC,KAAK,CAAC,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI;oBACjC,KAAK,CAAC,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EACpC,CAAC;oBACF,KAAK,EAAE,CAAC;gBACT,CAAC;gBACD,IAAI,KAAK,IAAI,4BAA4B,EAAE,CAAC;oBAC3C,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACrB,KAAK,IAAI,KAAK,GAAG,CAAC,CAAC;oBACnB,SAAS;gBACV,CAAC;YACF,CAAC;QACF,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,KAAK,EAAE,CAAC;IACT,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAwB;IACnD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACnD,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACrE,GAAG,EAAE,CAAC;YACN,IAAI,GAAG,GAAG,MAAM;gBAAE,MAAM,GAAG,GAAG,CAAC;QAChC,CAAC;aAAM,CAAC;YACP,GAAG,GAAG,CAAC,CAAC;QACT,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACxC,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAChC,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC5C,IAAI,IAAI,CAAC,MAAM,GAAG,2BAA2B,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACpE,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,IAAI,CAAC,MAAM,IAAI,2BAA2B,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,MAAM;QAC3D,IAAI,GAAG,IAAI,CAAC;QACZ,KAAK,IAAI,CAAC,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IACjD,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACrC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACzC,MAAM,aAAa,GAAG,qBAAqB,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;IACtE,OAAO,aAAa;SAClB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACxC,OAAO,qBAAqB,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC;IACpF,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACpD,OAAO,qBAAqB,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACvD,IAAI,IAAI,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,IAAI,2BAA2B;QAAE,OAAO,IAAI,CAAC;IACrE,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,kBAAkB,CAAC,SAAS,CAAC,IAAI,2BAA2B;QAAE,OAAO,IAAI,CAAC;IAC9E,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,2BAA2B,CAAC;AAC5E,CAAC;AAED,MAAM,2BAA2B,GAAG,IAAI,OAAO,EAAoB,CAAC;AAEpE,MAAM,UAAU,kCAAkC,CAAC,OAAyB;IAC3E,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,OAAO,GAAgC,EAAE,CAAC;IAChD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YACxD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,SAAS;QACV,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;QAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;QACxD,MAAM,kBAAkB,GACvB,CAAC,UAAU;YACX,CAAC,CAAC,KAAK,CAAC,QAAQ;gBACf,CAAC,CAAC,KAAK,CAAC,iBAAiB;oBACxB,OAAO,CAAC,GAAG,KAAK,oBAAoB;oBACpC,OAAO,CAAC,GAAG,KAAK,kBAAkB;oBAClC,OAAO,CAAC,GAAG,KAAK,wBAAwB;oBACxC,OAAO,CAAC,GAAG,KAAK,wBAAwB,CAAC,CAAC,CAAC;QAC9C,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,SAAS;QACV,CAAC;QACD,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,SAAS,KAAK,MAAM;YAAE,OAAO,GAAG,IAAI,CAAC;QACzC,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACvE,OAAO,GAAG,IAAI,CAAC;YACf,SAAS;QACV,CAAC;QACD,IAAI,CAAC,UAAU,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CACX,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAC7G,CAAC;IACH,CAAC;IACD,IAAI,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC;IAC7B,MAAM,IAAI,GAAG,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC;IACrC,2BAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACtC,OAAO,IAAI,CAAC;AACb,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,qCAAqC,CAAC,OAAyB;IAC9E,OAAO,2BAA2B,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAyB;IAC7D,OAAO,OAAO,CAAC,OAAO;SACpB,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAClB,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAC3G;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC","sourcesContent":["import type { AssistantMessage } from \"@caupulican/pi-ai/types\";\n\n/** Consecutive identical non-empty units (lines or sentences) at or above this run count are degeneration. */\nexport const DEGENERATE_REPEATED_LINE_MIN = 4;\n\n/** Mid-stream abort once a generation loop is already this long; cheaper than waiting for `done`. */\nexport const DEGENERATE_STREAM_ABORT_RUN = 8;\n\n/** Shortest unit that may be an exact tiled status sentence (`Foo.Foo.` with no space). */\nexport const DEGENERATE_EXACT_REPEAT_MIN = 24;\n\nfunction collapseRepeatedUnits(units: readonly string[], joinWith: string): string {\n\tif (units.length < DEGENERATE_REPEATED_LINE_MIN) return units.join(joinWith);\n\tconst out: string[] = [];\n\tlet index = 0;\n\twhile (index < units.length) {\n\t\tconst unit = units[index];\n\t\tlet run = 1;\n\t\twhile (index + run < units.length && units[index + run] === unit) run++;\n\t\tif (unit.trim() !== \"\" && run >= DEGENERATE_REPEATED_LINE_MIN) {\n\t\t\tout.push(unit);\n\t\t\tindex += run;\n\t\t\tcontinue;\n\t\t}\n\t\tif (index + 1 < units.length) {\n\t\t\tconst next = units[index + 1];\n\t\t\tif (unit.trim() !== \"\" && next.trim() !== \"\" && unit !== next) {\n\t\t\t\tlet pairs = 1;\n\t\t\t\twhile (\n\t\t\t\t\tindex + (pairs + 1) * 2 <= units.length &&\n\t\t\t\t\tunits[index + pairs * 2] === unit &&\n\t\t\t\t\tunits[index + pairs * 2 + 1] === next\n\t\t\t\t) {\n\t\t\t\t\tpairs++;\n\t\t\t\t}\n\t\t\t\tif (pairs >= DEGENERATE_REPEATED_LINE_MIN) {\n\t\t\t\t\tout.push(unit, next);\n\t\t\t\t\tindex += pairs * 2;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tout.push(unit);\n\t\tindex++;\n\t}\n\treturn out.join(joinWith);\n}\n\nfunction maxRepeatedUnitRun(units: readonly string[]): number {\n\tlet maxRun = 1;\n\tlet run = 1;\n\tfor (let index = 1; index < units.length; index++) {\n\t\tif (units[index] === units[index - 1] && units[index].trim() !== \"\") {\n\t\t\trun++;\n\t\t\tif (run > maxRun) maxRun = run;\n\t\t} else {\n\t\t\trun = 1;\n\t\t}\n\t}\n\treturn units.length === 0 ? 0 : maxRun;\n}\n\nfunction splitSentences(text: string): string[] {\n\treturn text.split(/(?<=[.!?])\\s+/);\n}\n\nfunction isProseTile(unit: string): boolean {\n\treturn /[.!?]/.test(unit) || /\\s/.test(unit);\n}\n\n/** Smallest power-of-two prose tile that repeats to `text` (`Foo.Foo.` / `Foo.`×4). */\nexport function exactTiledRepeat(text: string): { unit: string; times: number } | undefined {\n\tif (text.length < DEGENERATE_EXACT_REPEAT_MIN * 2) return undefined;\n\tlet unit = text;\n\tlet times = 1;\n\twhile (unit.length >= DEGENERATE_EXACT_REPEAT_MIN * 2 && unit.length % 2 === 0) {\n\t\tconst half = unit.length / 2;\n\t\tconst next = unit.slice(0, half);\n\t\tif (!isProseTile(next) || next !== unit.slice(half)) break;\n\t\tunit = next;\n\t\ttimes *= 2;\n\t}\n\treturn times > 1 ? { unit, times } : undefined;\n}\n\n/**\n * Collapse consecutive identical assistant lines, period-2 ABAB line runs, and the same\n * sentence repeated inside one paragraph. Session 01a0016f stored 551 copies of one\n * sentence as a single line; newline-only collapse cannot see that.\n * Session 01a00337 stored the same status sentence concatenated with no space (`Foo.Foo.`)\n * because Responses deltas replayed the full sentence; whitespace split cannot see that.\n */\nexport function collapseRepeatedLines(text: string): string {\n\tconst tiled = exactTiledRepeat(text);\n\tconst source = tiled ? tiled.unit : text;\n\tconst lineCollapsed = collapseRepeatedUnits(source.split(\"\\n\"), \"\\n\");\n\treturn lineCollapsed\n\t\t.split(\"\\n\")\n\t\t.map((line) => {\n\t\t\tconst lineTile = exactTiledRepeat(line);\n\t\t\treturn collapseRepeatedUnits(splitSentences(lineTile ? lineTile.unit : line), \" \");\n\t\t})\n\t\t.join(\"\\n\");\n}\n\nexport function isDegenerateRepeatedText(text: string): boolean {\n\treturn collapseRepeatedLines(text) !== text;\n}\n\nexport function shouldAbortDegenerateStream(text: string): boolean {\n\tif (text.length < 400) return false;\n\tconst tiled = exactTiledRepeat(text);\n\tif (tiled && tiled.times >= DEGENERATE_STREAM_ABORT_RUN) return true;\n\tconst sentences = splitSentences(text);\n\tif (maxRepeatedUnitRun(sentences) >= DEGENERATE_STREAM_ABORT_RUN) return true;\n\treturn maxRepeatedUnitRun(text.split(\"\\n\")) >= DEGENERATE_STREAM_ABORT_RUN;\n}\n\nconst collapsedDegenerateMessages = new WeakSet<AssistantMessage>();\n\nexport function collapseDegenerateAssistantMessage(message: AssistantMessage): AssistantMessage {\n\tlet changed = false;\n\tconst seenText = new Set<string>();\n\tconst content: AssistantMessage[\"content\"] = [];\n\tfor (const block of message.content) {\n\t\tif (block.type !== \"text\" && block.type !== \"thinking\") {\n\t\t\tcontent.push(block);\n\t\t\tcontinue;\n\t\t}\n\t\tconst isThinking = block.type === \"thinking\";\n\t\tconst source = isThinking ? block.thinking : block.text;\n\t\tconst canRewriteThinking =\n\t\t\t!isThinking ||\n\t\t\t(!block.redacted &&\n\t\t\t\t(!block.thinkingSignature ||\n\t\t\t\t\tmessage.api === \"openai-completions\" ||\n\t\t\t\t\tmessage.api === \"openai-responses\" ||\n\t\t\t\t\tmessage.api === \"azure-openai-responses\" ||\n\t\t\t\t\tmessage.api === \"openai-codex-responses\"));\n\t\tif (!canRewriteThinking) {\n\t\t\tcontent.push(block);\n\t\t\tcontinue;\n\t\t}\n\t\tconst collapsed = collapseRepeatedLines(source);\n\t\tif (collapsed !== source) changed = true;\n\t\tif (!isThinking && collapsed.trim() !== \"\" && seenText.has(collapsed)) {\n\t\t\tchanged = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isThinking && collapsed.trim() !== \"\") seenText.add(collapsed);\n\t\tcontent.push(\n\t\t\tcollapsed === source ? block : isThinking ? { ...block, thinking: collapsed } : { ...block, text: collapsed },\n\t\t);\n\t}\n\tif (!changed) return message;\n\tconst next = { ...message, content };\n\tcollapsedDegenerateMessages.add(next);\n\treturn next;\n}\n\n/** True when this message is (or was collapsed from) a generation loop. */\nexport function isCollapsedDegenerateAssistantMessage(message: AssistantMessage): boolean {\n\treturn collapsedDegenerateMessages.has(message);\n}\n\nexport function assistantMessageText(message: AssistantMessage): string {\n\treturn message.content\n\t\t.flatMap((block) =>\n\t\t\tblock.type === \"text\" ? [block.text] : block.type === \"thinking\" && !block.redacted ? [block.thinking] : [],\n\t\t)\n\t\t.join(\"\\n\");\n}\n"]}
|
|
@@ -19,6 +19,13 @@ export interface SilenceWatchdogOptions {
|
|
|
19
19
|
export declare function createSilenceWatchdog(opts: SilenceWatchdogOptions): SilenceWatchdog;
|
|
20
20
|
import type { StreamFn } from "../types.ts";
|
|
21
21
|
export type StallPhase = "connect" | "first-progress" | "quiet" | "active";
|
|
22
|
+
/** Structured abort reason shared with profilers; the user-facing message remains stable. */
|
|
23
|
+
export declare class StreamStallError extends Error {
|
|
24
|
+
readonly phase: StallPhase;
|
|
25
|
+
readonly elapsedMs: number;
|
|
26
|
+
constructor(phase: StallPhase, elapsedMs: number);
|
|
27
|
+
}
|
|
28
|
+
export declare function isStreamStallError(value: unknown): value is StreamStallError;
|
|
22
29
|
export interface StreamIdleOptions {
|
|
23
30
|
/** Max ms to wait for the FIRST event (connection/first-token allowance). */
|
|
24
31
|
connectMs: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watchdogs.d.ts","sourceRoot":"","sources":["../../src/reliability/watchdogs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC/B;sFACkF;IAClF,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wDAAwD;IACxD,MAAM,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,SAAS,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CA8BnF;AAOD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE3E,MAAM,WAAW,iBAAiB;IACjC,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB;0FACsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB;2FACuF;IACvF,eAAe,EAAE,MAAM,CAAC;IACxB;;oEAEgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACnE;AAED;;8FAE8F;AAC9F,eAAO,MAAM,mBAAmB,EAAE,iBAKjC,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAStG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,yBAAyB,GAC9D,QAAQ,
|
|
1
|
+
{"version":3,"file":"watchdogs.d.ts","sourceRoot":"","sources":["../../src/reliability/watchdogs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,eAAe;IAC/B;sFACkF;IAClF,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wDAAwD;IACxD,MAAM,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,SAAS,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,eAAe,CA8BnF;AAOD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE3E,6FAA6F;AAC7F,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,YAAY,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAK/C;CACD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED,MAAM,WAAW,iBAAiB;IACjC,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB;0FACsF;IACtF,YAAY,EAAE,MAAM,CAAC;IACrB;2FACuF;IACvF,eAAe,EAAE,MAAM,CAAC;IACxB;;oEAEgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACnE;AAED;;8FAE8F;AAC9F,eAAO,MAAM,mBAAmB,EAAE,iBAKjC,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,yBAAyB,GAAG,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAStG;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,GAAG,yBAAyB,GAC9D,QAAQ,CAoNV"}
|
|
@@ -39,6 +39,18 @@ export function createSilenceWatchdog(opts) {
|
|
|
39
39
|
// --- Stream-idle watchdog (wraps a StreamFn) -------------------------------
|
|
40
40
|
import { createAssistantMessageEventStream } from "@caupulican/pi-ai/event-stream";
|
|
41
41
|
import { createEmptyUsage } from "@caupulican/pi-ai/usage";
|
|
42
|
+
/** Structured abort reason shared with profilers; the user-facing message remains stable. */
|
|
43
|
+
export class StreamStallError extends Error {
|
|
44
|
+
constructor(phase, elapsedMs) {
|
|
45
|
+
super(`stream stalled: no events for ${elapsedMs}ms (${phase} phase)`);
|
|
46
|
+
this.name = "StreamStallError";
|
|
47
|
+
this.phase = phase;
|
|
48
|
+
this.elapsedMs = elapsedMs;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function isStreamStallError(value) {
|
|
52
|
+
return value instanceof StreamStallError;
|
|
53
|
+
}
|
|
42
54
|
/** Defaults: connect 120s / first progress 120s / active 180s / quiet 600s. The quiet bound must stay
|
|
43
55
|
* below the HTTP dispatcher idle timeout (see coding-agent http-dispatcher.ts, 660s) or the
|
|
44
56
|
* HTTP layer would kill quiet-but-healthy streams before this watchdog ever sees the gap. */
|
|
@@ -163,8 +175,9 @@ export function withStreamIdleWatchdog(streamFn, options) {
|
|
|
163
175
|
return;
|
|
164
176
|
stalled = true;
|
|
165
177
|
opts.onStall?.({ phase, elapsedMs });
|
|
166
|
-
const
|
|
167
|
-
|
|
178
|
+
const stallError = new StreamStallError(phase, elapsedMs);
|
|
179
|
+
const description = stallError.message;
|
|
180
|
+
controller.abort(stallError);
|
|
168
181
|
const message = {
|
|
169
182
|
...latest,
|
|
170
183
|
stopReason: "error",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watchdogs.js","sourceRoot":"","sources":["../../src/reliability/watchdogs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Silence/idle watchdogs for the reliability kernel.\n *\n * A silence watchdog bounds \"running but mute\" — it never bounds total runtime,\n * so long tasks that produce output are never killed (autonomy constraint).\n */\n\nexport interface SilenceWatchdog {\n\t/** Report activity (output chunk / stream event); resets the countdown.\n\t * Pass silenceMs to also change the bound for this and subsequent countdowns. */\n\ttouch(silenceMs?: number): void;\n\t/** Stop permanently (normal completion). Idempotent. */\n\tdisarm(): void;\n}\n\nexport interface SilenceWatchdogOptions {\n\tsilenceMs: number;\n\t/** Fired at most once, after silenceMs with no touch(). The watchdog self-disarms. */\n\tonSilence: () => void;\n}\n\nexport function createSilenceWatchdog(opts: SilenceWatchdogOptions): SilenceWatchdog {\n\tlet timer: NodeJS.Timeout | undefined;\n\tlet disarmed = false;\n\tlet currentSilenceMs = opts.silenceMs;\n\n\tconst arm = () => {\n\t\ttimer = setTimeout(() => {\n\t\t\tdisarmed = true;\n\t\t\ttimer = undefined;\n\t\t\topts.onSilence();\n\t\t}, currentSilenceMs);\n\t\t// Never keep the host process alive just for a watchdog.\n\t\ttimer.unref?.();\n\t};\n\n\tarm();\n\n\treturn {\n\t\ttouch(silenceMs?: number): void {\n\t\t\tif (disarmed) return;\n\t\t\tif (silenceMs !== undefined) currentSilenceMs = silenceMs;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tarm();\n\t\t},\n\t\tdisarm(): void {\n\t\t\tdisarmed = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t},\n\t};\n}\n\n// --- Stream-idle watchdog (wraps a StreamFn) -------------------------------\n\nimport { createAssistantMessageEventStream } from \"@caupulican/pi-ai/event-stream\";\nimport type { Api, AssistantMessage, AssistantMessageEvent, Model, ProviderResponse } from \"@caupulican/pi-ai/types\";\nimport { createEmptyUsage } from \"@caupulican/pi-ai/usage\";\nimport type { StreamFn } from \"../types.ts\";\n\nexport type StallPhase = \"connect\" | \"first-progress\" | \"quiet\" | \"active\";\n\nexport interface StreamIdleOptions {\n\t/** Max ms to wait for the FIRST event (connection/first-token allowance). */\n\tconnectMs: number;\n\t/** Max ms between events while content is flowing — the latest content block is\n\t * text or toolCall. A flowing stream that goes silent this long is presumed dead. */\n\tactiveIdleMs: number;\n\t/** Max ms after transport confirmation until the first non-empty model delta.\n\t * Headers and structural start frames prove connectivity, not generation progress. */\n\tfirstProgressMs: number;\n\t/** Max ms between events while the model is quietly working — no content blocks\n\t * after it has emitted non-empty thinking. Deep-thinking models legitimately sit\n\t * here for minutes, so this bound is deliberately generous. */\n\tquietIdleMs: number;\n\t/** Fired once when a stall is detected, before the inner request is aborted. */\n\tonStall?: (info: { phase: StallPhase; elapsedMs: number }) => void;\n}\n\n/** Defaults: connect 120s / first progress 120s / active 180s / quiet 600s. The quiet bound must stay\n * below the HTTP dispatcher idle timeout (see coding-agent http-dispatcher.ts, 660s) or the\n * HTTP layer would kill quiet-but-healthy streams before this watchdog ever sees the gap. */\nexport const DEFAULT_STREAM_IDLE: StreamIdleOptions = {\n\tconnectMs: 120_000,\n\tfirstProgressMs: 120_000,\n\tactiveIdleMs: 180_000,\n\tquietIdleMs: 600_000,\n};\n\n/** Re-resolved at the start of every request, so hosts can wire live-tunable settings. */\nexport type StreamIdleOptionsResolver = (...args: Parameters<StreamFn>) => Partial<StreamIdleOptions>;\n\n/** Extracts the current AssistantMessage snapshot carried by any stream event variant. */\nfunction partialFromEvent(event: AssistantMessageEvent): AssistantMessage {\n\tif (event.type === \"done\") return event.message;\n\tif (event.type === \"error\") return event.error;\n\treturn event.partial;\n}\n\n/**\n * Wrap a StreamFn so a silently dead connection cannot wedge a turn forever.\n *\n * Phase-aware: `connectMs` bounds the wait for transport confirmation,\n * `firstProgressMs` bounds headers and structural frames that contain no model output,\n * `quietIdleMs` covers silence after non-empty thinking, and `activeIdleMs` covers\n * silence after text or tool-call output. This keeps detection fast where silence is\n * anomalous without killing healthy deep-thinking or compaction-sized requests.\n * No bound ever limits total runtime (autonomy constraint).\n *\n * On stall, the inner request is aborted and the returned stream resolves immediately\n * with a synthetic `AssistantMessage` (`stopReason: \"error\"`, `errorMessage: \"stream\n * stalled: no events for <n>ms (<phase> phase)\"`) — the `stream stalled` phrasing is\n * what `classifyFailure` maps to a retryable `stream_stall`, so the host's\n * retry/failover path takes it from there.\n *\n * Options may be a resolver function; it is re-invoked at the start of every request,\n * so settings changes apply without rewrapping.\n *\n * A caller-initiated abort (via the options `signal`) is never treated as a stall: it\n * is chained into the wrapper's own controller. The inner stream's abort result is\n * forwarded untouched when it provides one; if it just ends, the wrapper synthesizes\n * an aborted terminal event so the returned stream always settles.\n */\nexport function withStreamIdleWatchdog(\n\tstreamFn: StreamFn,\n\toptions?: Partial<StreamIdleOptions> | StreamIdleOptionsResolver,\n): StreamFn {\n\treturn (model, context, streamOptions) => {\n\t\tconst resolved = typeof options === \"function\" ? options(model, context, streamOptions) : options;\n\t\tconst cleaned: Partial<StreamIdleOptions> = {};\n\t\tif (resolved) {\n\t\t\tfor (const [key, val] of Object.entries(resolved)) {\n\t\t\t\tif (val !== undefined) {\n\t\t\t\t\t(cleaned as any)[key] = val;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst opts = { ...DEFAULT_STREAM_IDLE, ...cleaned };\n\n\t\tconst controller = new AbortController();\n\t\tconst callerSignal = streamOptions?.signal;\n\t\tlet callerAborted = callerSignal?.aborted ?? false;\n\t\tconst outer = createAssistantMessageEventStream();\n\t\tlet readySettled = false;\n\t\tlet resolveReady: (stream: typeof outer) => void = () => {};\n\t\tconst ready = new Promise<typeof outer>((resolve) => {\n\t\t\tresolveReady = resolve;\n\t\t});\n\t\tconst settleReady = () => {\n\t\t\tif (readySettled) return;\n\t\t\treadySettled = true;\n\t\t\tresolveReady(outer);\n\t\t};\n\n\t\t// Seeded so a connect-phase stall (no event ever arrived) still has a base message\n\t\t// to report on; overwritten with the latest real snapshot once events start flowing.\n\t\tlet latest: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: createEmptyUsage(),\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tlet stalled = false;\n\t\tlet meaningfulProgressSeen = false;\n\t\tlet transportConfirmed = false;\n\t\tlet streamSetupSettled = false;\n\t\tlet terminalPushed = false;\n\t\tconst pushFailure = (error?: unknown) => {\n\t\t\tif (terminalPushed || stalled) return;\n\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\tconst detail = error instanceof Error ? `: ${error.message}` : error === undefined ? \"\" : `: ${String(error)}`;\n\t\t\tterminalPushed = true;\n\t\t\touter.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: stopReason,\n\t\t\t\terror: {\n\t\t\t\t\t...latest,\n\t\t\t\t\tstopReason,\n\t\t\t\t\terrorMessage: callerAborted\n\t\t\t\t\t\t? `stream aborted before terminal event${detail}`\n\t\t\t\t\t\t: `stream ended without terminal event${detail}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\tsettleReady();\n\t\t};\n\n\t\t// The idle bound adapts per event: quiet while nothing/thinking, active while\n\t\t// text/toolCall content is flowing. Mutable so the onSilence closure always\n\t\t// reports the phase/bound that actually elapsed.\n\t\tlet currentPhase: StallPhase = \"connect\";\n\t\tlet currentBoundMs = opts.connectMs;\n\t\tconst idleBoundFor = (message: AssistantMessage): { phase: StallPhase; ms: number } => {\n\t\t\tconst lastBlock = message.content[message.content.length - 1];\n\t\t\treturn !lastBlock || lastBlock.type === \"thinking\"\n\t\t\t\t? { phase: \"quiet\", ms: opts.quietIdleMs }\n\t\t\t\t: { phase: \"active\", ms: opts.activeIdleMs };\n\t\t};\n\n\t\t// Emits the stall result directly (rather than after the inner loop finishes) so a\n\t\t// connection that never resolves at all still yields a result promptly — providers\n\t\t// are contractually expected to end their stream after abort, but the watchdog does\n\t\t// not depend on that to report the stall itself.\n\t\tconst stall = (phase: StallPhase, elapsedMs: number) => {\n\t\t\tif (callerAborted || stalled || terminalPushed) return;\n\t\t\tstalled = true;\n\t\t\topts.onStall?.({ phase, elapsedMs });\n\t\t\tconst description = `stream stalled: no events for ${elapsedMs}ms (${phase} phase)`;\n\t\t\tcontroller.abort(new Error(description));\n\t\t\tconst message: AssistantMessage = {\n\t\t\t\t...latest,\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: description,\n\t\t\t};\n\t\t\tterminalPushed = true;\n\t\t\touter.push({ type: \"error\", reason: \"error\", error: message });\n\t\t\tsettleReady();\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t};\n\n\t\tconst watchdog = createSilenceWatchdog({\n\t\t\tsilenceMs: opts.connectMs,\n\t\t\tonSilence: () => stall(currentPhase, currentBoundMs),\n\t\t});\n\t\tconst markTransportConfirmed = () => {\n\t\t\tif (callerAborted || stalled || meaningfulProgressSeen || transportConfirmed) return;\n\t\t\ttransportConfirmed = true;\n\t\t\tcurrentPhase = \"first-progress\";\n\t\t\tcurrentBoundMs = opts.firstProgressMs;\n\t\t\twatchdog.touch(opts.firstProgressMs);\n\t\t};\n\t\tconst originalOnResponse = streamOptions?.onResponse;\n\t\tconst pushSetupAbort = () => {\n\t\t\tif (streamSetupSettled || terminalPushed || stalled) return;\n\t\t\tterminalPushed = true;\n\t\t\twatchdog.disarm();\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\touter.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"aborted\",\n\t\t\t\terror: {\n\t\t\t\t\t...latest,\n\t\t\t\t\tstopReason: \"aborted\",\n\t\t\t\t\terrorMessage: \"stream aborted before terminal event during stream setup\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tsettleReady();\n\t\t};\n\t\tconst onCallerAbort = () => {\n\t\t\tcallerAborted = true;\n\t\t\tcontroller.abort(callerSignal?.reason);\n\t\t\tpushSetupAbort();\n\t\t};\n\t\tif (callerAborted) {\n\t\t\tonCallerAbort();\n\t\t\treturn ready;\n\t\t}\n\t\tcallerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tlet inner: Awaited<ReturnType<StreamFn>>;\n\t\t\t\ttry {\n\t\t\t\t\tinner = await streamFn(model, context, {\n\t\t\t\t\t\t...streamOptions,\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\tonResponse: async (response: ProviderResponse, responseModel: Model<Api>) => {\n\t\t\t\t\t\t\tmarkTransportConfirmed();\n\t\t\t\t\t\t\tawait originalOnResponse?.(response, responseModel);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tstreamSetupSettled = true;\n\t\t\t\t\tsettleReady();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tstreamSetupSettled = true;\n\t\t\t\t\tif (terminalPushed || stalled) return;\n\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\tpushFailure(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A setup promise may resolve after a connect stall or caller abort. The outer\n\t\t\t\t// stream is already terminal in that case, so never attach a late event pump.\n\t\t\t\tif (terminalPushed || stalled || callerAborted) return;\n\t\t\t\tfor await (const event of inner) {\n\t\t\t\t\tif (stalled) break;\n\t\t\t\t\tlatest = partialFromEvent(event);\n\t\t\t\t\tconst meaningfulProgress = eventHasMeaningfulProgress(event);\n\t\t\t\t\tif (meaningfulProgress) meaningfulProgressSeen = true;\n\t\t\t\t\tif (!transportConfirmed && !meaningfulProgress) {\n\t\t\t\t\t\tmarkTransportConfirmed();\n\t\t\t\t\t} else if (meaningfulProgressSeen) {\n\t\t\t\t\t\tconst bound = idleBoundFor(latest);\n\t\t\t\t\t\tcurrentPhase = bound.phase;\n\t\t\t\t\t\tcurrentBoundMs = bound.ms;\n\t\t\t\t\t\twatchdog.touch(bound.ms);\n\t\t\t\t\t}\n\t\t\t\t\t// A terminal event ends the turn: disarm synchronously, in the same tick as\n\t\t\t\t\t// the push below, so no watchdog can fire after the consumer's `result()`\n\t\t\t\t\t// promise resolves — a disarm that only happened once the loop later notices\n\t\t\t\t\t// `inner` is done would race with that resolution (it runs a tick or more\n\t\t\t\t\t// later) and could fire a spurious stall on an already-finished stream.\n\t\t\t\t\tconst terminal = event.type === \"done\" || event.type === \"error\";\n\t\t\t\t\tif (terminal) {\n\t\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\t}\n\t\t\t\t\touter.push(event);\n\t\t\t\t\tif (terminal) return;\n\t\t\t\t}\n\t\t\t\tif (!terminalPushed && !stalled) {\n\t\t\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\t\t\tconst description = callerAborted\n\t\t\t\t\t\t? \"stream aborted before terminal event\"\n\t\t\t\t\t\t: \"stream ended without terminal event\";\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\touter.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\treason: stopReason,\n\t\t\t\t\t\terror: { ...latest, stopReason, errorMessage: description },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tpushFailure(error);\n\t\t\t} finally {\n\t\t\t\twatchdog.disarm();\n\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t}\n\t\t})();\n\n\t\treturn ready;\n\t};\n}\n\nfunction eventHasMeaningfulProgress(event: AssistantMessageEvent): boolean {\n\tswitch (event.type) {\n\t\tcase \"text_delta\":\n\t\tcase \"thinking_delta\":\n\t\tcase \"toolcall_delta\":\n\t\t\treturn event.delta.length > 0;\n\t\tcase \"text_end\":\n\t\tcase \"thinking_end\":\n\t\t\treturn event.content.length > 0;\n\t\tcase \"toolcall_end\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"watchdogs.js","sourceRoot":"","sources":["../../src/reliability/watchdogs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Silence/idle watchdogs for the reliability kernel.\n *\n * A silence watchdog bounds \"running but mute\" — it never bounds total runtime,\n * so long tasks that produce output are never killed (autonomy constraint).\n */\n\nexport interface SilenceWatchdog {\n\t/** Report activity (output chunk / stream event); resets the countdown.\n\t * Pass silenceMs to also change the bound for this and subsequent countdowns. */\n\ttouch(silenceMs?: number): void;\n\t/** Stop permanently (normal completion). Idempotent. */\n\tdisarm(): void;\n}\n\nexport interface SilenceWatchdogOptions {\n\tsilenceMs: number;\n\t/** Fired at most once, after silenceMs with no touch(). The watchdog self-disarms. */\n\tonSilence: () => void;\n}\n\nexport function createSilenceWatchdog(opts: SilenceWatchdogOptions): SilenceWatchdog {\n\tlet timer: NodeJS.Timeout | undefined;\n\tlet disarmed = false;\n\tlet currentSilenceMs = opts.silenceMs;\n\n\tconst arm = () => {\n\t\ttimer = setTimeout(() => {\n\t\t\tdisarmed = true;\n\t\t\ttimer = undefined;\n\t\t\topts.onSilence();\n\t\t}, currentSilenceMs);\n\t\t// Never keep the host process alive just for a watchdog.\n\t\ttimer.unref?.();\n\t};\n\n\tarm();\n\n\treturn {\n\t\ttouch(silenceMs?: number): void {\n\t\t\tif (disarmed) return;\n\t\t\tif (silenceMs !== undefined) currentSilenceMs = silenceMs;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tarm();\n\t\t},\n\t\tdisarm(): void {\n\t\t\tdisarmed = true;\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\ttimer = undefined;\n\t\t},\n\t};\n}\n\n// --- Stream-idle watchdog (wraps a StreamFn) -------------------------------\n\nimport { createAssistantMessageEventStream } from \"@caupulican/pi-ai/event-stream\";\nimport type { Api, AssistantMessage, AssistantMessageEvent, Model, ProviderResponse } from \"@caupulican/pi-ai/types\";\nimport { createEmptyUsage } from \"@caupulican/pi-ai/usage\";\nimport type { StreamFn } from \"../types.ts\";\n\nexport type StallPhase = \"connect\" | \"first-progress\" | \"quiet\" | \"active\";\n\n/** Structured abort reason shared with profilers; the user-facing message remains stable. */\nexport class StreamStallError extends Error {\n\treadonly phase: StallPhase;\n\treadonly elapsedMs: number;\n\n\tconstructor(phase: StallPhase, elapsedMs: number) {\n\t\tsuper(`stream stalled: no events for ${elapsedMs}ms (${phase} phase)`);\n\t\tthis.name = \"StreamStallError\";\n\t\tthis.phase = phase;\n\t\tthis.elapsedMs = elapsedMs;\n\t}\n}\n\nexport function isStreamStallError(value: unknown): value is StreamStallError {\n\treturn value instanceof StreamStallError;\n}\n\nexport interface StreamIdleOptions {\n\t/** Max ms to wait for the FIRST event (connection/first-token allowance). */\n\tconnectMs: number;\n\t/** Max ms between events while content is flowing — the latest content block is\n\t * text or toolCall. A flowing stream that goes silent this long is presumed dead. */\n\tactiveIdleMs: number;\n\t/** Max ms after transport confirmation until the first non-empty model delta.\n\t * Headers and structural start frames prove connectivity, not generation progress. */\n\tfirstProgressMs: number;\n\t/** Max ms between events while the model is quietly working — no content blocks\n\t * after it has emitted non-empty thinking. Deep-thinking models legitimately sit\n\t * here for minutes, so this bound is deliberately generous. */\n\tquietIdleMs: number;\n\t/** Fired once when a stall is detected, before the inner request is aborted. */\n\tonStall?: (info: { phase: StallPhase; elapsedMs: number }) => void;\n}\n\n/** Defaults: connect 120s / first progress 120s / active 180s / quiet 600s. The quiet bound must stay\n * below the HTTP dispatcher idle timeout (see coding-agent http-dispatcher.ts, 660s) or the\n * HTTP layer would kill quiet-but-healthy streams before this watchdog ever sees the gap. */\nexport const DEFAULT_STREAM_IDLE: StreamIdleOptions = {\n\tconnectMs: 120_000,\n\tfirstProgressMs: 120_000,\n\tactiveIdleMs: 180_000,\n\tquietIdleMs: 600_000,\n};\n\n/** Re-resolved at the start of every request, so hosts can wire live-tunable settings. */\nexport type StreamIdleOptionsResolver = (...args: Parameters<StreamFn>) => Partial<StreamIdleOptions>;\n\n/** Extracts the current AssistantMessage snapshot carried by any stream event variant. */\nfunction partialFromEvent(event: AssistantMessageEvent): AssistantMessage {\n\tif (event.type === \"done\") return event.message;\n\tif (event.type === \"error\") return event.error;\n\treturn event.partial;\n}\n\n/**\n * Wrap a StreamFn so a silently dead connection cannot wedge a turn forever.\n *\n * Phase-aware: `connectMs` bounds the wait for transport confirmation,\n * `firstProgressMs` bounds headers and structural frames that contain no model output,\n * `quietIdleMs` covers silence after non-empty thinking, and `activeIdleMs` covers\n * silence after text or tool-call output. This keeps detection fast where silence is\n * anomalous without killing healthy deep-thinking or compaction-sized requests.\n * No bound ever limits total runtime (autonomy constraint).\n *\n * On stall, the inner request is aborted and the returned stream resolves immediately\n * with a synthetic `AssistantMessage` (`stopReason: \"error\"`, `errorMessage: \"stream\n * stalled: no events for <n>ms (<phase> phase)\"`) — the `stream stalled` phrasing is\n * what `classifyFailure` maps to a retryable `stream_stall`, so the host's\n * retry/failover path takes it from there.\n *\n * Options may be a resolver function; it is re-invoked at the start of every request,\n * so settings changes apply without rewrapping.\n *\n * A caller-initiated abort (via the options `signal`) is never treated as a stall: it\n * is chained into the wrapper's own controller. The inner stream's abort result is\n * forwarded untouched when it provides one; if it just ends, the wrapper synthesizes\n * an aborted terminal event so the returned stream always settles.\n */\nexport function withStreamIdleWatchdog(\n\tstreamFn: StreamFn,\n\toptions?: Partial<StreamIdleOptions> | StreamIdleOptionsResolver,\n): StreamFn {\n\treturn (model, context, streamOptions) => {\n\t\tconst resolved = typeof options === \"function\" ? options(model, context, streamOptions) : options;\n\t\tconst cleaned: Partial<StreamIdleOptions> = {};\n\t\tif (resolved) {\n\t\t\tfor (const [key, val] of Object.entries(resolved)) {\n\t\t\t\tif (val !== undefined) {\n\t\t\t\t\t(cleaned as any)[key] = val;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst opts = { ...DEFAULT_STREAM_IDLE, ...cleaned };\n\n\t\tconst controller = new AbortController();\n\t\tconst callerSignal = streamOptions?.signal;\n\t\tlet callerAborted = callerSignal?.aborted ?? false;\n\t\tconst outer = createAssistantMessageEventStream();\n\t\tlet readySettled = false;\n\t\tlet resolveReady: (stream: typeof outer) => void = () => {};\n\t\tconst ready = new Promise<typeof outer>((resolve) => {\n\t\t\tresolveReady = resolve;\n\t\t});\n\t\tconst settleReady = () => {\n\t\t\tif (readySettled) return;\n\t\t\treadySettled = true;\n\t\t\tresolveReady(outer);\n\t\t};\n\n\t\t// Seeded so a connect-phase stall (no event ever arrived) still has a base message\n\t\t// to report on; overwritten with the latest real snapshot once events start flowing.\n\t\tlet latest: AssistantMessage = {\n\t\t\trole: \"assistant\",\n\t\t\tcontent: [],\n\t\t\tapi: model.api,\n\t\t\tprovider: model.provider,\n\t\t\tmodel: model.id,\n\t\t\tusage: createEmptyUsage(),\n\t\t\tstopReason: \"stop\",\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tlet stalled = false;\n\t\tlet meaningfulProgressSeen = false;\n\t\tlet transportConfirmed = false;\n\t\tlet streamSetupSettled = false;\n\t\tlet terminalPushed = false;\n\t\tconst pushFailure = (error?: unknown) => {\n\t\t\tif (terminalPushed || stalled) return;\n\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\tconst detail = error instanceof Error ? `: ${error.message}` : error === undefined ? \"\" : `: ${String(error)}`;\n\t\t\tterminalPushed = true;\n\t\t\touter.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: stopReason,\n\t\t\t\terror: {\n\t\t\t\t\t...latest,\n\t\t\t\t\tstopReason,\n\t\t\t\t\terrorMessage: callerAborted\n\t\t\t\t\t\t? `stream aborted before terminal event${detail}`\n\t\t\t\t\t\t: `stream ended without terminal event${detail}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\tsettleReady();\n\t\t};\n\n\t\t// The idle bound adapts per event: quiet while nothing/thinking, active while\n\t\t// text/toolCall content is flowing. Mutable so the onSilence closure always\n\t\t// reports the phase/bound that actually elapsed.\n\t\tlet currentPhase: StallPhase = \"connect\";\n\t\tlet currentBoundMs = opts.connectMs;\n\t\tconst idleBoundFor = (message: AssistantMessage): { phase: StallPhase; ms: number } => {\n\t\t\tconst lastBlock = message.content[message.content.length - 1];\n\t\t\treturn !lastBlock || lastBlock.type === \"thinking\"\n\t\t\t\t? { phase: \"quiet\", ms: opts.quietIdleMs }\n\t\t\t\t: { phase: \"active\", ms: opts.activeIdleMs };\n\t\t};\n\n\t\t// Emits the stall result directly (rather than after the inner loop finishes) so a\n\t\t// connection that never resolves at all still yields a result promptly — providers\n\t\t// are contractually expected to end their stream after abort, but the watchdog does\n\t\t// not depend on that to report the stall itself.\n\t\tconst stall = (phase: StallPhase, elapsedMs: number) => {\n\t\t\tif (callerAborted || stalled || terminalPushed) return;\n\t\t\tstalled = true;\n\t\t\topts.onStall?.({ phase, elapsedMs });\n\t\t\tconst stallError = new StreamStallError(phase, elapsedMs);\n\t\t\tconst description = stallError.message;\n\t\t\tcontroller.abort(stallError);\n\t\t\tconst message: AssistantMessage = {\n\t\t\t\t...latest,\n\t\t\t\tstopReason: \"error\",\n\t\t\t\terrorMessage: description,\n\t\t\t};\n\t\t\tterminalPushed = true;\n\t\t\touter.push({ type: \"error\", reason: \"error\", error: message });\n\t\t\tsettleReady();\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t};\n\n\t\tconst watchdog = createSilenceWatchdog({\n\t\t\tsilenceMs: opts.connectMs,\n\t\t\tonSilence: () => stall(currentPhase, currentBoundMs),\n\t\t});\n\t\tconst markTransportConfirmed = () => {\n\t\t\tif (callerAborted || stalled || meaningfulProgressSeen || transportConfirmed) return;\n\t\t\ttransportConfirmed = true;\n\t\t\tcurrentPhase = \"first-progress\";\n\t\t\tcurrentBoundMs = opts.firstProgressMs;\n\t\t\twatchdog.touch(opts.firstProgressMs);\n\t\t};\n\t\tconst originalOnResponse = streamOptions?.onResponse;\n\t\tconst pushSetupAbort = () => {\n\t\t\tif (streamSetupSettled || terminalPushed || stalled) return;\n\t\t\tterminalPushed = true;\n\t\t\twatchdog.disarm();\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\touter.push({\n\t\t\t\ttype: \"error\",\n\t\t\t\treason: \"aborted\",\n\t\t\t\terror: {\n\t\t\t\t\t...latest,\n\t\t\t\t\tstopReason: \"aborted\",\n\t\t\t\t\terrorMessage: \"stream aborted before terminal event during stream setup\",\n\t\t\t\t},\n\t\t\t});\n\t\t\tsettleReady();\n\t\t};\n\t\tconst onCallerAbort = () => {\n\t\t\tcallerAborted = true;\n\t\t\tcontroller.abort(callerSignal?.reason);\n\t\t\tpushSetupAbort();\n\t\t};\n\t\tif (callerAborted) {\n\t\t\tonCallerAbort();\n\t\t\treturn ready;\n\t\t}\n\t\tcallerSignal?.addEventListener(\"abort\", onCallerAbort, { once: true });\n\n\t\tvoid (async () => {\n\t\t\ttry {\n\t\t\t\tlet inner: Awaited<ReturnType<StreamFn>>;\n\t\t\t\ttry {\n\t\t\t\t\tinner = await streamFn(model, context, {\n\t\t\t\t\t\t...streamOptions,\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t\tonResponse: async (response: ProviderResponse, responseModel: Model<Api>) => {\n\t\t\t\t\t\t\tmarkTransportConfirmed();\n\t\t\t\t\t\t\tawait originalOnResponse?.(response, responseModel);\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t\tstreamSetupSettled = true;\n\t\t\t\t\tsettleReady();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tstreamSetupSettled = true;\n\t\t\t\t\tif (terminalPushed || stalled) return;\n\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\tpushFailure(error);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A setup promise may resolve after a connect stall or caller abort. The outer\n\t\t\t\t// stream is already terminal in that case, so never attach a late event pump.\n\t\t\t\tif (terminalPushed || stalled || callerAborted) return;\n\t\t\t\tfor await (const event of inner) {\n\t\t\t\t\tif (stalled) break;\n\t\t\t\t\tlatest = partialFromEvent(event);\n\t\t\t\t\tconst meaningfulProgress = eventHasMeaningfulProgress(event);\n\t\t\t\t\tif (meaningfulProgress) meaningfulProgressSeen = true;\n\t\t\t\t\tif (!transportConfirmed && !meaningfulProgress) {\n\t\t\t\t\t\tmarkTransportConfirmed();\n\t\t\t\t\t} else if (meaningfulProgressSeen) {\n\t\t\t\t\t\tconst bound = idleBoundFor(latest);\n\t\t\t\t\t\tcurrentPhase = bound.phase;\n\t\t\t\t\t\tcurrentBoundMs = bound.ms;\n\t\t\t\t\t\twatchdog.touch(bound.ms);\n\t\t\t\t\t}\n\t\t\t\t\t// A terminal event ends the turn: disarm synchronously, in the same tick as\n\t\t\t\t\t// the push below, so no watchdog can fire after the consumer's `result()`\n\t\t\t\t\t// promise resolves — a disarm that only happened once the loop later notices\n\t\t\t\t\t// `inner` is done would race with that resolution (it runs a tick or more\n\t\t\t\t\t// later) and could fire a spurious stall on an already-finished stream.\n\t\t\t\t\tconst terminal = event.type === \"done\" || event.type === \"error\";\n\t\t\t\t\tif (terminal) {\n\t\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\t\twatchdog.disarm();\n\t\t\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t\t\t}\n\t\t\t\t\touter.push(event);\n\t\t\t\t\tif (terminal) return;\n\t\t\t\t}\n\t\t\t\tif (!terminalPushed && !stalled) {\n\t\t\t\t\tconst stopReason = callerAborted ? \"aborted\" : \"error\";\n\t\t\t\t\tconst description = callerAborted\n\t\t\t\t\t\t? \"stream aborted before terminal event\"\n\t\t\t\t\t\t: \"stream ended without terminal event\";\n\t\t\t\t\tterminalPushed = true;\n\t\t\t\t\touter.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\treason: stopReason,\n\t\t\t\t\t\terror: { ...latest, stopReason, errorMessage: description },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tpushFailure(error);\n\t\t\t} finally {\n\t\t\t\twatchdog.disarm();\n\t\t\t\tcallerSignal?.removeEventListener(\"abort\", onCallerAbort);\n\t\t\t}\n\t\t})();\n\n\t\treturn ready;\n\t};\n}\n\nfunction eventHasMeaningfulProgress(event: AssistantMessageEvent): boolean {\n\tswitch (event.type) {\n\t\tcase \"text_delta\":\n\t\tcase \"thinking_delta\":\n\t\tcase \"toolcall_delta\":\n\t\t\treturn event.delta.length > 0;\n\t\tcase \"text_end\":\n\t\tcase \"thinking_end\":\n\t\t\treturn event.content.length > 0;\n\t\tcase \"toolcall_end\":\n\t\t\treturn true;\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caupulican/pi-agent-core",
|
|
3
|
-
"version": "0.93.
|
|
3
|
+
"version": "0.93.17",
|
|
4
4
|
"description": "General-purpose agent with transport abstraction, state management, and attachment support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
"prepublishOnly": "npm run clean && npm run build"
|
|
147
147
|
},
|
|
148
148
|
"dependencies": {
|
|
149
|
-
"@caupulican/pi-ai": "^0.93.
|
|
149
|
+
"@caupulican/pi-ai": "^0.93.17",
|
|
150
150
|
"ignore": "7.0.5",
|
|
151
151
|
"typebox": "1.1.38",
|
|
152
152
|
"yaml": "2.9.0"
|