@inhuman.tools/harness 0.2.0 → 0.3.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.
Files changed (299) hide show
  1. package/compact.prompt +12 -2
  2. package/dist/builtin-tools/catalog-entry.d.ts.map +1 -1
  3. package/dist/builtin-tools/catalog-entry.js +1 -0
  4. package/dist/builtin-tools/catalog-entry.js.map +1 -1
  5. package/dist/builtin-tools/index.d.ts +1 -2
  6. package/dist/builtin-tools/index.d.ts.map +1 -1
  7. package/dist/builtin-tools/index.js +1 -2
  8. package/dist/builtin-tools/index.js.map +1 -1
  9. package/dist/builtin-tools/metadata/context-need.d.ts +1 -0
  10. package/dist/builtin-tools/metadata/context-need.d.ts.map +1 -1
  11. package/dist/builtin-tools/metadata/context-need.js +13 -6
  12. package/dist/builtin-tools/metadata/context-need.js.map +1 -1
  13. package/dist/builtin-tools/metadata/context-recall.d.ts +1 -5
  14. package/dist/builtin-tools/metadata/context-recall.d.ts.map +1 -1
  15. package/dist/builtin-tools/metadata/context-recall.js +10 -7
  16. package/dist/builtin-tools/metadata/context-recall.js.map +1 -1
  17. package/dist/builtin-tools/metadata/update-metadata.d.ts +1 -0
  18. package/dist/builtin-tools/metadata/update-metadata.d.ts.map +1 -1
  19. package/dist/builtin-tools/metadata/update-metadata.js +12 -12
  20. package/dist/builtin-tools/metadata/update-metadata.js.map +1 -1
  21. package/dist/builtin-tools/named-agents/agent-catalog.d.ts.map +1 -1
  22. package/dist/builtin-tools/named-agents/agent-catalog.js +9 -9
  23. package/dist/builtin-tools/named-agents/agent-catalog.js.map +1 -1
  24. package/dist/builtin-tools/named-agents/agent-stdlib.d.ts.map +1 -1
  25. package/dist/builtin-tools/named-agents/agent-stdlib.js +2 -0
  26. package/dist/builtin-tools/named-agents/agent-stdlib.js.map +1 -1
  27. package/dist/builtin-tools/schemas/tool-result.d.ts +2 -0
  28. package/dist/builtin-tools/schemas/tool-result.d.ts.map +1 -1
  29. package/dist/builtin-tools/schemas/tool-result.js +2 -0
  30. package/dist/builtin-tools/schemas/tool-result.js.map +1 -1
  31. package/dist/builtin-tools/shell/job-stop.d.ts +1 -0
  32. package/dist/builtin-tools/shell/job-stop.d.ts.map +1 -1
  33. package/dist/builtin-tools/shell/shell-tool.d.ts +1 -0
  34. package/dist/builtin-tools/shell/shell-tool.d.ts.map +1 -1
  35. package/dist/builtin-tools/toolkit/dist-tools.d.ts +1 -0
  36. package/dist/builtin-tools/toolkit/dist-tools.d.ts.map +1 -1
  37. package/dist/builtin-tools/toolkit/dist-tools.js +1 -0
  38. package/dist/builtin-tools/toolkit/dist-tools.js.map +1 -1
  39. package/dist/connections/adopt.d.ts +2 -0
  40. package/dist/connections/adopt.d.ts.map +1 -1
  41. package/dist/connections/adopt.js.map +1 -1
  42. package/dist/connections/index.d.ts +1 -1
  43. package/dist/connections/index.d.ts.map +1 -1
  44. package/dist/extensions/behavior.d.ts.map +1 -1
  45. package/dist/extensions/behavior.js +17 -4
  46. package/dist/extensions/behavior.js.map +1 -1
  47. package/dist/extensions/consent.d.ts +0 -68
  48. package/dist/extensions/consent.d.ts.map +1 -1
  49. package/dist/extensions/consent.js +0 -69
  50. package/dist/extensions/consent.js.map +1 -1
  51. package/dist/extensions/contributions.d.ts.map +1 -1
  52. package/dist/extensions/contributions.js +1 -0
  53. package/dist/extensions/contributions.js.map +1 -1
  54. package/dist/extensions/dispatch.d.ts +1 -9
  55. package/dist/extensions/dispatch.d.ts.map +1 -1
  56. package/dist/extensions/dispatch.js +5 -7
  57. package/dist/extensions/dispatch.js.map +1 -1
  58. package/dist/extensions/eval.d.ts.map +1 -1
  59. package/dist/extensions/eval.js +2 -0
  60. package/dist/extensions/eval.js.map +1 -1
  61. package/dist/extensions/hook-deadline.d.ts +28 -0
  62. package/dist/extensions/hook-deadline.d.ts.map +1 -0
  63. package/dist/extensions/hook-deadline.js +72 -0
  64. package/dist/extensions/hook-deadline.js.map +1 -0
  65. package/dist/extensions/host.d.ts +2 -0
  66. package/dist/extensions/host.d.ts.map +1 -1
  67. package/dist/extensions/host.js +15 -0
  68. package/dist/extensions/host.js.map +1 -1
  69. package/dist/extensions/inventory.d.ts +9 -0
  70. package/dist/extensions/inventory.d.ts.map +1 -1
  71. package/dist/extensions/inventory.js +15 -1
  72. package/dist/extensions/inventory.js.map +1 -1
  73. package/dist/extensions/notes.d.ts +1 -0
  74. package/dist/extensions/notes.d.ts.map +1 -1
  75. package/dist/extensions/notes.js +1 -0
  76. package/dist/extensions/notes.js.map +1 -1
  77. package/dist/extensions/schemas/bot-definition.d.ts +1 -1
  78. package/dist/extensions/schemas/bot-definition.d.ts.map +1 -1
  79. package/dist/extensions/schemas/plugin-manifest.d.ts +4 -4
  80. package/dist/extensions/schemas/plugin-manifest.d.ts.map +1 -1
  81. package/dist/extensions/schemas/plugin-manifest.js +13 -1
  82. package/dist/extensions/schemas/plugin-manifest.js.map +1 -1
  83. package/dist/extensions/state.d.ts.map +1 -1
  84. package/dist/extensions/state.js +5 -0
  85. package/dist/extensions/state.js.map +1 -1
  86. package/dist/extensions/value.d.ts +4 -0
  87. package/dist/extensions/value.d.ts.map +1 -1
  88. package/dist/extensions/value.js.map +1 -1
  89. package/dist/protocol/schemas/health.d.ts +1 -3
  90. package/dist/protocol/schemas/health.d.ts.map +1 -1
  91. package/dist/protocol/schemas/health.js.map +1 -1
  92. package/dist/protocol/schemas/identity.d.ts +0 -4
  93. package/dist/protocol/schemas/identity.d.ts.map +1 -1
  94. package/dist/protocol/schemas/identity.js.map +1 -1
  95. package/dist/protocol/schemas/loss.d.ts +0 -2
  96. package/dist/protocol/schemas/loss.d.ts.map +1 -1
  97. package/dist/protocol/schemas/loss.js.map +1 -1
  98. package/dist/protocol/schemas/message.d.ts +0 -1
  99. package/dist/protocol/schemas/message.d.ts.map +1 -1
  100. package/dist/protocol/supervision/loss.d.ts +0 -1
  101. package/dist/protocol/supervision/loss.d.ts.map +1 -1
  102. package/dist/protocol/supervision/loss.js +0 -3
  103. package/dist/protocol/supervision/loss.js.map +1 -1
  104. package/dist/providers/auth/index.d.ts +2 -2
  105. package/dist/providers/auth/index.d.ts.map +1 -1
  106. package/dist/providers/auth/index.js +2 -2
  107. package/dist/providers/auth/index.js.map +1 -1
  108. package/dist/providers/auth/loopback.d.ts +0 -2
  109. package/dist/providers/auth/loopback.d.ts.map +1 -1
  110. package/dist/providers/auth/loopback.js.map +1 -1
  111. package/dist/providers/chatgpt/connection.d.ts +1 -0
  112. package/dist/providers/chatgpt/connection.d.ts.map +1 -1
  113. package/dist/providers/chatgpt/connection.js +1 -1
  114. package/dist/providers/chatgpt/connection.js.map +1 -1
  115. package/dist/providers/grok/connection.d.ts +2 -1
  116. package/dist/providers/grok/connection.d.ts.map +1 -1
  117. package/dist/providers/grok/connection.js +1 -1
  118. package/dist/providers/grok/connection.js.map +1 -1
  119. package/dist/providers/mcp/fetch.d.ts.map +1 -1
  120. package/dist/providers/mcp/fetch.js +2 -6
  121. package/dist/providers/mcp/fetch.js.map +1 -1
  122. package/dist/providers/nous/driver.d.ts +1 -0
  123. package/dist/providers/nous/driver.d.ts.map +1 -1
  124. package/dist/providers/openai-compat/actor.d.ts.map +1 -1
  125. package/dist/providers/openai-compat/actor.js +167 -39
  126. package/dist/providers/openai-compat/actor.js.map +1 -1
  127. package/dist/providers/openai-compat/adopt.d.ts +2 -0
  128. package/dist/providers/openai-compat/adopt.d.ts.map +1 -1
  129. package/dist/providers/openai-compat/adopt.js.map +1 -1
  130. package/dist/providers/openai-compat/connection.d.ts +9 -2
  131. package/dist/providers/openai-compat/connection.d.ts.map +1 -1
  132. package/dist/providers/openai-compat/connection.js +35 -27
  133. package/dist/providers/openai-compat/connection.js.map +1 -1
  134. package/dist/providers/openai-compat/fetch-types.d.ts.map +1 -1
  135. package/dist/providers/openai-compat/fetch-types.js +2 -6
  136. package/dist/providers/openai-compat/fetch-types.js.map +1 -1
  137. package/dist/providers/openai-compat/index.d.ts +2 -2
  138. package/dist/providers/openai-compat/index.d.ts.map +1 -1
  139. package/dist/providers/openai-compat/index.js +2 -2
  140. package/dist/providers/openai-compat/index.js.map +1 -1
  141. package/dist/providers/openai-compat/model-capabilities.d.ts +7 -0
  142. package/dist/providers/openai-compat/model-capabilities.d.ts.map +1 -0
  143. package/dist/providers/openai-compat/model-capabilities.js +24 -0
  144. package/dist/providers/openai-compat/model-capabilities.js.map +1 -0
  145. package/dist/providers/openai-compat/schema.d.ts +1 -1
  146. package/dist/providers/web/registrations/ids.d.ts +0 -2
  147. package/dist/providers/web/registrations/ids.d.ts.map +1 -1
  148. package/dist/providers/web/registrations/ids.js +0 -1
  149. package/dist/providers/web/registrations/ids.js.map +1 -1
  150. package/dist/runtime/bot-mcp.d.ts.map +1 -1
  151. package/dist/runtime/bot-mcp.js +1 -4
  152. package/dist/runtime/bot-mcp.js.map +1 -1
  153. package/dist/runtime/intent-recovery.d.ts +3 -20
  154. package/dist/runtime/intent-recovery.d.ts.map +1 -1
  155. package/dist/runtime/intent-recovery.js +1 -122
  156. package/dist/runtime/intent-recovery.js.map +1 -1
  157. package/dist/server/ask-answer.d.ts.map +1 -1
  158. package/dist/server/ask-answer.js +3 -3
  159. package/dist/server/ask-answer.js.map +1 -1
  160. package/dist/server/daemon/kernel/KernelDaemon.d.ts +4 -0
  161. package/dist/server/daemon/kernel/KernelDaemon.d.ts.map +1 -1
  162. package/dist/server/daemon/kernel/KernelDaemon.js +100 -40
  163. package/dist/server/daemon/kernel/KernelDaemon.js.map +1 -1
  164. package/dist/server/daemon/kernel/completion-catalog.d.ts +8 -0
  165. package/dist/server/daemon/kernel/completion-catalog.d.ts.map +1 -1
  166. package/dist/server/daemon/kernel/completion-catalog.js +16 -0
  167. package/dist/server/daemon/kernel/completion-catalog.js.map +1 -1
  168. package/dist/server/daemon/kernel/fs-reflection/reflectKernel.d.ts.map +1 -1
  169. package/dist/server/daemon/kernel/fs-reflection/reflectKernel.js +47 -1
  170. package/dist/server/daemon/kernel/fs-reflection/reflectKernel.js.map +1 -1
  171. package/dist/server/daemon/kernel/glance-ask-line.d.ts +1 -6
  172. package/dist/server/daemon/kernel/glance-ask-line.d.ts.map +1 -1
  173. package/dist/server/daemon/kernel/glance-ask-line.js +1 -35
  174. package/dist/server/daemon/kernel/glance-ask-line.js.map +1 -1
  175. package/dist/server/daemon/kernel/settings-files.d.ts +1 -1
  176. package/dist/server/daemon/kernel/settings-files.d.ts.map +1 -1
  177. package/dist/server/daemon/kernel/settings-files.js +3 -6
  178. package/dist/server/daemon/kernel/settings-files.js.map +1 -1
  179. package/dist/server/daemon/project/ProjectDaemon.d.ts +4 -0
  180. package/dist/server/daemon/project/ProjectDaemon.d.ts.map +1 -1
  181. package/dist/server/daemon/project/ProjectDaemon.js +11 -0
  182. package/dist/server/daemon/project/ProjectDaemon.js.map +1 -1
  183. package/dist/server/daemon/session/SessionDaemon.d.ts +5 -3
  184. package/dist/server/daemon/session/SessionDaemon.d.ts.map +1 -1
  185. package/dist/server/daemon/session/SessionDaemon.js +54 -37
  186. package/dist/server/daemon/session/SessionDaemon.js.map +1 -1
  187. package/dist/server/daemon/session/SessionOrchestrator.d.ts +1 -0
  188. package/dist/server/daemon/session/SessionOrchestrator.d.ts.map +1 -1
  189. package/dist/server/daemon/session/SessionOrchestrator.js +1371 -1146
  190. package/dist/server/daemon/session/SessionOrchestrator.js.map +1 -1
  191. package/dist/server/daemon/session/agent-runtime.d.ts +2 -0
  192. package/dist/server/daemon/session/agent-runtime.d.ts.map +1 -1
  193. package/dist/server/daemon/session/agent-runtime.js +55 -6
  194. package/dist/server/daemon/session/agent-runtime.js.map +1 -1
  195. package/dist/server/daemon/session/ambient-compact.d.ts +3 -21
  196. package/dist/server/daemon/session/ambient-compact.d.ts.map +1 -1
  197. package/dist/server/daemon/session/ambient-compact.js +7 -116
  198. package/dist/server/daemon/session/ambient-compact.js.map +1 -1
  199. package/dist/server/daemon/session/behavior-host.d.ts +9 -3
  200. package/dist/server/daemon/session/behavior-host.d.ts.map +1 -1
  201. package/dist/server/daemon/session/behavior-host.js +222 -47
  202. package/dist/server/daemon/session/behavior-host.js.map +1 -1
  203. package/dist/server/daemon/session/compact.d.ts +0 -1
  204. package/dist/server/daemon/session/compact.d.ts.map +1 -1
  205. package/dist/server/daemon/session/compact.js +7 -1
  206. package/dist/server/daemon/session/compact.js.map +1 -1
  207. package/dist/server/daemon/session/plugin-abi.d.ts.map +1 -1
  208. package/dist/server/daemon/session/plugin-abi.js +7 -11
  209. package/dist/server/daemon/session/plugin-abi.js.map +1 -1
  210. package/dist/server/daemon/session/prompt-message-of.d.ts +1 -8
  211. package/dist/server/daemon/session/prompt-message-of.d.ts.map +1 -1
  212. package/dist/server/daemon/session/prompt-message-of.js +2 -12
  213. package/dist/server/daemon/session/prompt-message-of.js.map +1 -1
  214. package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.d.ts +3 -42
  215. package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.d.ts.map +1 -1
  216. package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.js +4 -145
  217. package/dist/server/daemon/session/re-act-loop-intent-detected-trigger.js.map +1 -1
  218. package/dist/server/daemon/session/round-boundary.d.ts +13 -0
  219. package/dist/server/daemon/session/round-boundary.d.ts.map +1 -0
  220. package/dist/server/daemon/session/round-boundary.js +196 -0
  221. package/dist/server/daemon/session/round-boundary.js.map +1 -0
  222. package/dist/server/daemon/session/round-stop.d.ts +1 -25
  223. package/dist/server/daemon/session/round-stop.d.ts.map +1 -1
  224. package/dist/server/daemon/session/round-stop.js +1 -75
  225. package/dist/server/daemon/session/round-stop.js.map +1 -1
  226. package/dist/server/daemon/session/turn-iterator.d.ts +1 -44
  227. package/dist/server/daemon/session/turn-iterator.d.ts.map +1 -1
  228. package/dist/server/daemon/session/turn-iterator.js +1 -435
  229. package/dist/server/daemon/session/turn-iterator.js.map +1 -1
  230. package/dist/server/daemon/session/turn-resolvers.d.ts +1 -16
  231. package/dist/server/daemon/session/turn-resolvers.d.ts.map +1 -1
  232. package/dist/server/daemon/session/turn-resolvers.js +1 -28
  233. package/dist/server/daemon/session/turn-resolvers.js.map +1 -1
  234. package/dist/server/daemon/session/visual-store.d.ts +2 -2
  235. package/dist/server/daemon/session/visual-store.d.ts.map +1 -1
  236. package/dist/server/daemon/session/visual-store.js +19 -20
  237. package/dist/server/daemon/session/visual-store.js.map +1 -1
  238. package/dist/server/drivers/request-timeout.d.ts +1 -0
  239. package/dist/server/drivers/request-timeout.d.ts.map +1 -1
  240. package/dist/server/drivers/request-timeout.js +2 -1
  241. package/dist/server/drivers/request-timeout.js.map +1 -1
  242. package/dist/server/runtime-bin.js +5 -8
  243. package/dist/server/runtime-bin.js.map +1 -1
  244. package/dist/server/schemas/index.d.ts +0 -1
  245. package/dist/server/schemas/index.d.ts.map +1 -1
  246. package/dist/server/schemas/index.js +0 -1
  247. package/dist/server/schemas/index.js.map +1 -1
  248. package/dist/server/schemas/json-record.d.ts +0 -6
  249. package/dist/server/schemas/json-record.d.ts.map +1 -1
  250. package/dist/server/schemas/json-record.js +0 -16
  251. package/dist/server/schemas/json-record.js.map +1 -1
  252. package/dist/server/schemas/well-known.d.ts +0 -2
  253. package/dist/server/schemas/well-known.d.ts.map +1 -1
  254. package/dist/server/schemas/well-known.js +0 -1
  255. package/dist/server/schemas/well-known.js.map +1 -1
  256. package/dist/server/testkit/scripted-model.d.ts +11 -0
  257. package/dist/server/testkit/scripted-model.d.ts.map +1 -1
  258. package/dist/server/testkit/scripted-model.js +42 -3
  259. package/dist/server/testkit/scripted-model.js.map +1 -1
  260. package/dist/server/toolkit/actor.d.ts +0 -1
  261. package/dist/server/toolkit/actor.d.ts.map +1 -1
  262. package/dist/server/toolkit/actor.js +2 -5
  263. package/dist/server/toolkit/actor.js.map +1 -1
  264. package/dist/server/toolkit/catalog.d.ts +0 -2
  265. package/dist/server/toolkit/catalog.d.ts.map +1 -1
  266. package/dist/server/toolkit/catalog.js +0 -81
  267. package/dist/server/toolkit/catalog.js.map +1 -1
  268. package/dist/server/toolkit/pack-catalog.d.ts +2 -1
  269. package/dist/server/toolkit/pack-catalog.d.ts.map +1 -1
  270. package/dist/server/toolkit/pack-catalog.js +1 -0
  271. package/dist/server/toolkit/pack-catalog.js.map +1 -1
  272. package/dist/server/toolkit/tool-entry.d.ts.map +1 -1
  273. package/dist/server/toolkit/tool-entry.js +1 -0
  274. package/dist/server/toolkit/tool-entry.js.map +1 -1
  275. package/dist/server/toolkit/tool-table.d.ts +0 -1
  276. package/dist/server/toolkit/tool-table.d.ts.map +1 -1
  277. package/dist/server/toolkit/tool-table.js +0 -3
  278. package/dist/server/toolkit/tool-table.js.map +1 -1
  279. package/dist/server/util/read-field.d.ts +0 -2
  280. package/dist/server/util/read-field.d.ts.map +1 -1
  281. package/dist/server/util/read-field.js +0 -9
  282. package/dist/server/util/read-field.js.map +1 -1
  283. package/dist/server/util/tool-call-json.d.ts +0 -1
  284. package/dist/server/util/tool-call-json.d.ts.map +1 -1
  285. package/dist/server/util/tool-call-json.js +0 -25
  286. package/dist/server/util/tool-call-json.js.map +1 -1
  287. package/dist/session/index.d.ts +4 -1
  288. package/dist/session/index.d.ts.map +1 -1
  289. package/dist/session/index.js +10 -1
  290. package/dist/session/index.js.map +1 -1
  291. package/dist/tool-runtime/guard-tool.d.ts +3 -0
  292. package/dist/tool-runtime/guard-tool.d.ts.map +1 -1
  293. package/dist/tool-runtime/guard-tool.js +37 -3
  294. package/dist/tool-runtime/guard-tool.js.map +1 -1
  295. package/dist/yjs/document-service.d.ts +2 -4
  296. package/dist/yjs/document-service.d.ts.map +1 -1
  297. package/dist/yjs/document-service.js +0 -6
  298. package/dist/yjs/document-service.js.map +1 -1
  299. package/package.json +40 -40
@@ -40,7 +40,7 @@ const COMPLETION_FAILED_STATUS = /(?:openai-compat completion failed: |\bHTTP )(
40
40
  const TRANSIENT_CODE = /^(?:ECONNRESET|ETIMEDOUT|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|EPIPE|EHOSTUNREACH|ENETUNREACH|EPROTO|UND_ERR_)/;
41
41
  const TRANSIENT_TEXT = /fetch failed|Failed to fetch|ECONNRESET|ETIMEDOUT|ENOTFOUND|ECONNREFUSED|EAI_AGAIN|Connect Timeout|Headers Timeout|other side closed|UND_ERR_|socket hang up/i;
42
42
  /** Parsed SSE `data:` only. Socket bytes reset undici's body timer, including pings (`request-timeout.ts`). */
43
- const STREAM_STALL_MS = 60_000;
43
+ const STREAM_STALL_MS = 180_000;
44
44
  /** Longest wait between attempts, whether it comes from our backoff or from the server. */
45
45
  const BACKOFF_CAP_MS = 5 * 60 * 1000;
46
46
  /** A 429 or 503 that named how long to wait. The message is the ordinary HTTP failure text. */
@@ -336,20 +336,6 @@ function visibleThought(raw, summary) {
336
336
  return { thought: summary, kind: "summary" };
337
337
  return { thought: "", kind: null };
338
338
  }
339
- /**
340
- * Responses `incomplete` + `max_output_tokens` after a reasoning item is the
341
- * model spending the budget on thought and stopping before a message. Not a
342
- * failed generation — ReACT continues. Other incomplete reasons stay fatal.
343
- */
344
- function incompleteThoughtExhausted(reason, thought) {
345
- if (reason !== "max_output_tokens")
346
- return false;
347
- return (thought.raw.trim() !== "" ||
348
- thought.summary.trim() !== "" ||
349
- thought.encrypted !== "" ||
350
- thought.kind != null ||
351
- (thought.reasoningTokens ?? 0) > 0);
352
- }
353
339
  const THINKING_PART_TYPES = new Set(["thinking", "reasoning", "redacted_thinking", "reasoning_text"]);
354
340
  function isThinkingPart(part) {
355
341
  const type = readField(part, "type");
@@ -424,7 +410,107 @@ function chunkMessageReasoning(raw) {
424
410
  const alt = readField(message, "reasoning");
425
411
  return typeof alt === "string" && alt !== "" ? alt : "";
426
412
  }
413
+ /** A no-progress deadline is terminal, unlike explicitly configured transient retries. */
414
+ async function withProgressDeadline(pending, signal, ms, phase, cancel) {
415
+ let timer;
416
+ let onAbort;
417
+ const deadline = new Promise((_resolve, reject) => {
418
+ onAbort = () => reject(signal.reason ?? new Error("completion aborted"));
419
+ if (signal.aborted) {
420
+ onAbort();
421
+ return;
422
+ }
423
+ signal.addEventListener("abort", onAbort, { once: true });
424
+ timer = setTimeout(() => {
425
+ const reason = new StreamStallError(ms);
426
+ reason.message = `${phase} stalled after ${ms}ms`;
427
+ reject(reason);
428
+ cancel(reason);
429
+ }, ms);
430
+ timer.unref();
431
+ });
432
+ try {
433
+ return await Promise.race([pending, deadline]);
434
+ }
435
+ finally {
436
+ clearTimeout(timer);
437
+ if (onAbort)
438
+ signal.removeEventListener("abort", onAbort);
439
+ }
440
+ }
441
+ function readBulkText(response, signal, ms) {
442
+ return withProgressDeadline(response.text(), signal, ms, "response body", (reason) => {
443
+ // The fetch signal owns network cancellation. A synthetic response may not
444
+ // observe it; deadline rejection must not depend on its cancel implementation.
445
+ void response.body?.cancel(reason).catch(() => undefined);
446
+ });
447
+ }
448
+ /** Only model output or an authoritative terminal can extend the no-progress deadline. */
449
+ function streamFrameProgress(frame) {
450
+ if (typeof frame !== "object" || frame === null)
451
+ return false;
452
+ const text = (value) => typeof value === "string" && value.length > 0;
453
+ const type = readField(frame, "type");
454
+ if (type === "error" ||
455
+ type === "response.completed" ||
456
+ type === "response.done" ||
457
+ type === "response.failed" ||
458
+ type === "response.incomplete")
459
+ return true;
460
+ if (typeof type === "string" && type.startsWith("response.") && type.endsWith(".delta"))
461
+ return text(readField(frame, "delta"));
462
+ if (type === "response.output_item.added" || type === "response.output_item.done") {
463
+ const item = readField(frame, "item");
464
+ if (typeof item !== "object" || item === null)
465
+ return false;
466
+ const content = splitMessageContent(readField(item, "content"));
467
+ return (text(readField(item, "name")) ||
468
+ text(readField(item, "arguments")) ||
469
+ text(readField(item, "encrypted_content")) ||
470
+ text(reasoningPartText(readField(item, "summary"))) ||
471
+ text(reasoningPartText(readField(item, "content"))) ||
472
+ text(content.text) ||
473
+ text(content.thought));
474
+ }
475
+ const choices = readField(frame, "choices");
476
+ if (!Array.isArray(choices))
477
+ return false;
478
+ return choices.some((choice) => {
479
+ if (typeof choice !== "object" || choice === null)
480
+ return false;
481
+ if (text(readField(choice, "finish_reason")))
482
+ return true;
483
+ const delta = readField(choice, "delta");
484
+ if (typeof delta !== "object" || delta === null)
485
+ return false;
486
+ const content = splitMessageContent(readField(delta, "content"));
487
+ if (text(content.text) ||
488
+ text(content.thought) ||
489
+ content.sawThinking ||
490
+ text(readField(delta, "reasoning_content")) ||
491
+ text(readField(delta, "reasoning")) ||
492
+ text(readField(delta, "thinking")))
493
+ return true;
494
+ const reasoningDetails = readField(delta, "reasoning_details");
495
+ if (Array.isArray(reasoningDetails) &&
496
+ reasoningDetails.some((entry) => typeof entry === "object" &&
497
+ entry !== null &&
498
+ (text(readField(entry, "text")) || text(readField(entry, "summary")) || text(readField(entry, "data")))))
499
+ return true;
500
+ const calls = readField(delta, "tool_calls");
501
+ return (Array.isArray(calls) &&
502
+ calls.some((call) => {
503
+ if (typeof call !== "object" || call === null)
504
+ return false;
505
+ if (text(readField(call, "id")))
506
+ return true;
507
+ const fn = readField(call, "function");
508
+ return (typeof fn === "object" && fn !== null && (text(readField(fn, "name")) || text(readField(fn, "arguments"))));
509
+ }));
510
+ });
511
+ }
427
512
  export async function* readSseJson(body, signal, stallMs) {
513
+ signal.throwIfAborted();
428
514
  if (!body)
429
515
  return;
430
516
  const reader = body.getReader();
@@ -432,6 +518,18 @@ export async function* readSseJson(body, signal, stallMs) {
432
518
  let buf = "";
433
519
  let armTimer;
434
520
  let stalled;
521
+ let rejectInterrupted;
522
+ const interrupted = new Promise((_resolve, reject) => {
523
+ rejectInterrupted = reject;
524
+ });
525
+ const onAbort = () => {
526
+ rejectInterrupted(signal.reason ?? new Error("completion aborted"));
527
+ void reader.cancel(signal.reason).catch(() => undefined);
528
+ };
529
+ if (signal.aborted)
530
+ onAbort();
531
+ else
532
+ signal.addEventListener("abort", onAbort, { once: true });
435
533
  const clearArm = () => {
436
534
  if (armTimer === undefined)
437
535
  return;
@@ -443,6 +541,7 @@ export async function* readSseJson(body, signal, stallMs) {
443
541
  clearArm();
444
542
  const timer = setTimeout(() => {
445
543
  stalled = new StreamStallError(stallMs);
544
+ rejectInterrupted(stalled);
446
545
  void reader.cancel(stalled).catch(() => undefined);
447
546
  }, stallMs);
448
547
  timer.unref();
@@ -450,7 +549,7 @@ export async function* readSseJson(body, signal, stallMs) {
450
549
  };
451
550
  const read = async () => {
452
551
  try {
453
- const next = await reader.read();
552
+ const next = await Promise.race([reader.read(), interrupted]);
454
553
  if (stalled !== undefined)
455
554
  throw stalled;
456
555
  return next;
@@ -470,11 +569,11 @@ export async function* readSseJson(body, signal, stallMs) {
470
569
  buf += decoder.decode(value, { stream: true });
471
570
  let yielded = false;
472
571
  for (;;) {
473
- const sep = buf.indexOf("\n\n");
474
- if (sep === -1)
572
+ const separator = /\r?\n\r?\n/.exec(buf);
573
+ if (separator === null)
475
574
  break;
476
- const event = buf.slice(0, sep);
477
- buf = buf.slice(sep + 2);
575
+ const event = buf.slice(0, separator.index);
576
+ buf = buf.slice(separator.index + separator[0].length);
478
577
  for (const line of event.split(/\r?\n/)) {
479
578
  const trimmed = line.trimEnd();
480
579
  if (!trimmed.startsWith("data:"))
@@ -486,8 +585,10 @@ export async function* readSseJson(body, signal, stallMs) {
486
585
  continue;
487
586
  }
488
587
  try {
489
- yield parseJsonUnknown(data);
490
- yielded = true;
588
+ const parsed = parseJsonUnknown(data);
589
+ yield parsed;
590
+ if (streamFrameProgress(parsed))
591
+ yielded = true;
491
592
  }
492
593
  catch {
493
594
  // skip malformed line
@@ -516,6 +617,8 @@ export async function* readSseJson(body, signal, stallMs) {
516
617
  }
517
618
  finally {
518
619
  clearArm();
620
+ signal.removeEventListener("abort", onAbort);
621
+ void reader.cancel().catch(() => undefined);
519
622
  try {
520
623
  reader.releaseLock();
521
624
  }
@@ -621,14 +724,14 @@ export class OpenAiCompatActor extends ExpectationActor {
621
724
  const posted = Date.now();
622
725
  trace("harness:complete", `post ${this.expectation.uuid} connection=${this.driverConnection.connectionId} model=${this.effectiveModel()} attempt=${n}`);
623
726
  try {
624
- response = await this.driverConnection.authorizedFetch(url, {
727
+ response = await withProgressDeadline(this.driverConnection.authorizedFetch(url, {
625
728
  method: "POST",
626
729
  headers: affinity.headers,
627
730
  body: JSON.stringify(payload),
628
- }, abort.signal);
731
+ }, abort.signal), abort.signal, this.stallMs, "completion request", (reason) => abort.abort(reason));
629
732
  }
630
733
  catch (error) {
631
- if (error instanceof ConnectionMiss)
734
+ if (error instanceof ConnectionMiss || error instanceof StreamStallError)
632
735
  throw error;
633
736
  throw wrapFetchFailure(error, {
634
737
  why: "completion",
@@ -637,7 +740,7 @@ export class OpenAiCompatActor extends ExpectationActor {
637
740
  });
638
741
  }
639
742
  if (!response.ok) {
640
- const errorBody = await response.text();
743
+ const errorBody = await withProgressDeadline(response.text(), abort.signal, this.stallMs, "error response body", (reason) => abort.abort(reason));
641
744
  const detail = errorBody.trim().slice(0, 240);
642
745
  const message = describeHttpFailure({
643
746
  why: "completion",
@@ -658,6 +761,8 @@ export class OpenAiCompatActor extends ExpectationActor {
658
761
  throw new Error(message);
659
762
  }
660
763
  const answer = await this.readAnswer(response, abort.signal);
764
+ if (this.signal.aborted)
765
+ return;
661
766
  trace("harness:complete", `answer ${this.expectation.uuid} ms=${Date.now() - posted} status=${response.status} chars=${answer?.content.length ?? 0}`);
662
767
  invariant(answer !== null, "openai-compat completion failed");
663
768
  this.expectation.stampAnswer({
@@ -692,6 +797,8 @@ export class OpenAiCompatActor extends ExpectationActor {
692
797
  return;
693
798
  if (error instanceof ExpectationInvariant)
694
799
  throw error;
800
+ if (error instanceof StreamStallError)
801
+ abort.abort(error);
695
802
  if (!isTransientCompletionError(error)) {
696
803
  const reason = error instanceof Error ? error.message : String(error);
697
804
  this.expectation.failReason = reason;
@@ -968,12 +1075,12 @@ export class OpenAiCompatChatActor extends OpenAiCompatActor {
968
1075
  async readAnswer(response, signal) {
969
1076
  const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
970
1077
  if (contentType.includes("application/json")) {
971
- return this.#roundFromBulk(await response.json());
1078
+ return this.#roundFromBulk(parseJsonUnknown(await readBulkText(response, signal, this.stallMs)));
972
1079
  }
973
1080
  if (contentType.includes("text/event-stream") || contentType.includes("event-stream")) {
974
1081
  return this.#consumeStream(readSseJson(response.body, signal, this.stallMs));
975
1082
  }
976
- const text = await response.text();
1083
+ const text = await readBulkText(response, signal, this.stallMs);
977
1084
  const head = text.trimStart();
978
1085
  if (head.startsWith("{") || head.startsWith("[")) {
979
1086
  try {
@@ -997,6 +1104,9 @@ export class OpenAiCompatChatActor extends OpenAiCompatActor {
997
1104
  const choice = Array.isArray(choices) && typeof choices[0] === "object" && choices[0] !== null ? choices[0] : undefined;
998
1105
  const message = choice == null ? undefined : readField(choice, "message");
999
1106
  const messageRec = typeof message === "object" && message !== null ? message : undefined;
1107
+ const terminal = choice == null ? undefined : readField(choice, "finish_reason");
1108
+ if (messageRec == null || typeof terminal !== "string" || terminal === "")
1109
+ throw new Error("Chat completion response is missing its message or terminal finish reason");
1000
1110
  const reasoningContent = messageRec == null ? undefined : readField(messageRec, "reasoning_content");
1001
1111
  const reasoningAlt = messageRec == null ? undefined : readField(messageRec, "reasoning");
1002
1112
  const reasoning = typeof reasoningContent === "string" && reasoningContent.length > 0
@@ -1054,6 +1164,7 @@ export class OpenAiCompatChatActor extends OpenAiCompatActor {
1054
1164
  const partial = new Set();
1055
1165
  const dropped = [];
1056
1166
  let finishReason;
1167
+ let sawTerminal = false;
1057
1168
  const onEvent = (ev) => {
1058
1169
  switch (ev.type) {
1059
1170
  case "reasoning_delta": {
@@ -1109,6 +1220,8 @@ export class OpenAiCompatChatActor extends OpenAiCompatActor {
1109
1220
  if (typeof raw === "object" && raw !== null) {
1110
1221
  const rec = raw;
1111
1222
  const choice = rec.choices?.[0];
1223
+ if (typeof choice?.finish_reason === "string" && choice.finish_reason !== "")
1224
+ sawTerminal = true;
1112
1225
  const delta = choice?.delta;
1113
1226
  let chunk = rec;
1114
1227
  if (delta != null) {
@@ -1144,7 +1257,11 @@ export class OpenAiCompatChatActor extends OpenAiCompatActor {
1144
1257
  }
1145
1258
  if (thoughtAcc === "")
1146
1259
  thoughtAcc = chunkMessageReasoning(raw);
1260
+ if (sawTerminal)
1261
+ break;
1147
1262
  }
1263
+ if (!sawTerminal)
1264
+ throw new Error("Chat completion stream ended without a terminal finish reason");
1148
1265
  for (const ev of session.close())
1149
1266
  onEvent(ev);
1150
1267
  const settled = settleCalls(calls, partial, dropped, finishReason);
@@ -1258,8 +1375,19 @@ export class OpenAiCompatResponsesActor extends OpenAiCompatActor {
1258
1375
  async readAnswer(response, signal) {
1259
1376
  const contentType = response.headers.get("content-type") ?? "";
1260
1377
  if (contentType.toLowerCase().includes("application/json") && !contentType.toLowerCase().includes("event-stream")) {
1261
- const payload = await response.json();
1378
+ const payload = parseJsonUnknown(await readBulkText(response, signal, this.stallMs));
1262
1379
  this.stampConsumed(payload);
1380
+ const status = typeof payload === "object" && payload !== null ? readField(payload, "status") : undefined;
1381
+ if (status !== "completed" && status !== "incomplete") {
1382
+ const error = readField(payload, "error");
1383
+ const message = typeof error === "object" && error !== null ? readField(error, "message") : undefined;
1384
+ throw new Error(typeof message === "string" ? message : `Responses request ended with status ${status}`);
1385
+ }
1386
+ const incomplete = typeof payload === "object" && payload !== null ? readField(payload, "incomplete_details") : undefined;
1387
+ const incompleteReason = typeof incomplete === "object" && incomplete !== null ? readField(incomplete, "reason") : undefined;
1388
+ if (status === "incomplete" && incompleteReason !== "max_output_tokens" && incompleteReason !== "content_filter")
1389
+ throw new Error(`Responses request incomplete: ${String(incompleteReason)}`);
1390
+ const finishReason = status === "incomplete" ? (incompleteReason === "content_filter" ? "content_filter" : "length") : undefined;
1263
1391
  const parts = [];
1264
1392
  const rawParts = [];
1265
1393
  const summaryParts = [];
@@ -1319,7 +1447,13 @@ export class OpenAiCompatResponsesActor extends OpenAiCompatActor {
1319
1447
  this.#stampReasoningEncrypted(encrypted);
1320
1448
  this.stampThoughtKind(thoughtKind);
1321
1449
  this.speech(text, visible.thought);
1322
- return { content: text, reasoning: visible.thought, thoughtKind, calls };
1450
+ return {
1451
+ content: text,
1452
+ reasoning: visible.thought,
1453
+ thoughtKind,
1454
+ calls,
1455
+ ...(finishReason == null ? {} : { finishReason }),
1456
+ };
1323
1457
  }
1324
1458
  let acc = {
1325
1459
  text: "",
@@ -1422,19 +1556,11 @@ export class OpenAiCompatResponsesActor extends OpenAiCompatActor {
1422
1556
  }
1423
1557
  }
1424
1558
  }
1425
- if (incompleteThoughtExhausted(details, {
1426
- raw: acc.raw,
1427
- summary: acc.summary,
1428
- encrypted,
1429
- kind: this.expectation.responseThoughtKind,
1430
- reasoningTokens: this.expectation.reasoningTokens,
1431
- })) {
1559
+ if (details === "max_output_tokens") {
1432
1560
  sawTerminal = true;
1433
1561
  finish = "length";
1434
1562
  break;
1435
1563
  }
1436
- // Speech cut off by max_output_tokens stays a failed round (chatgpt-complete.test); only
1437
- // the filter is a stop of its own.
1438
1564
  if (details === "content_filter") {
1439
1565
  sawTerminal = true;
1440
1566
  finish = "content_filter";
@@ -1493,6 +1619,8 @@ export class OpenAiCompatResponsesActor extends OpenAiCompatActor {
1493
1619
  emitSpeech();
1494
1620
  }
1495
1621
  }
1622
+ if (sawTerminal)
1623
+ break;
1496
1624
  }
1497
1625
  if (!sawTerminal)
1498
1626
  throw new Error("Responses stream ended before terminal response event");