newmark-agent 0.5.7 → 0.5.8

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.
@@ -328709,6 +328709,30 @@ function parseProviderSse(raw) {
328709
328709
  }
328710
328710
  return events;
328711
328711
  }
328712
+ function assembleCompatibleToolArguments(parts) {
328713
+ const nonEmpty = (parts || []).map(String).filter((part) => part && part !== "null");
328714
+ if (!nonEmpty.length) return "{}";
328715
+ const isJsonObject = (value) => {
328716
+ try {
328717
+ const parsed = JSON.parse(value);
328718
+ return !!parsed && typeof parsed === "object" && !Array.isArray(parsed);
328719
+ } catch {
328720
+ return false;
328721
+ }
328722
+ };
328723
+ const incremental = nonEmpty.join("");
328724
+ if (isJsonObject(incremental)) return incremental;
328725
+ let compatible = "";
328726
+ for (const incoming of nonEmpty) {
328727
+ if (!compatible) compatible = incoming;
328728
+ else if (incoming === compatible) continue;
328729
+ else if (incoming.startsWith(compatible)) compatible = incoming;
328730
+ else if (compatible.startsWith(incoming)) continue;
328731
+ else compatible += incoming;
328732
+ }
328733
+ if (isJsonObject(compatible)) return compatible;
328734
+ return [...nonEmpty].reverse().find(isJsonObject) || compatible;
328735
+ }
328712
328736
  function isContentPolicyBlocked(json) {
328713
328737
  const choices = Array.isArray(json.choices) ? json.choices : [];
328714
328738
  const choice = choices[0] || {};
@@ -328937,6 +328961,8 @@ var ChatCompletionsAdapter = class {
328937
328961
  let contentPolicyBlocked = false;
328938
328962
  let emittedContent = false;
328939
328963
  let emittedTool = false;
328964
+ let emittedReasoning = false;
328965
+ let explicitCompletion = false;
328940
328966
  try {
328941
328967
  while (true) {
328942
328968
  const { done, value } = await readProviderStreamChunk(reader, signal);
@@ -328948,7 +328974,10 @@ var ChatCompletionsAdapter = class {
328948
328974
  const trimmed = line.trim();
328949
328975
  if (!trimmed.startsWith("data: ")) continue;
328950
328976
  const data = trimmed.slice(6);
328951
- if (data === "[DONE]") continue;
328977
+ if (data === "[DONE]") {
328978
+ explicitCompletion = true;
328979
+ continue;
328980
+ }
328952
328981
  let json;
328953
328982
  try {
328954
328983
  json = JSON.parse(data);
@@ -328961,11 +328990,16 @@ var ChatCompletionsAdapter = class {
328961
328990
  }
328962
328991
  if (isContentPolicyBlocked(json)) contentPolicyBlocked = true;
328963
328992
  const choices = Array.isArray(json.choices) ? json.choices : [];
328964
- const delta = choices[0]?.delta;
328993
+ const choice = choices[0];
328994
+ if (choice?.finish_reason !== void 0 && choice.finish_reason !== null) explicitCompletion = true;
328995
+ const delta = choice?.delta;
328965
328996
  if (!delta) continue;
328966
328997
  if (delta.reasoning_content) {
328967
328998
  const reasoning = this.extractText(delta.reasoning_content);
328968
- if (reasoning) yield { type: "reasoning.summary.delta", delta: reasoning };
328999
+ if (reasoning) {
329000
+ emittedReasoning = true;
329001
+ yield { type: "reasoning.summary.delta", delta: reasoning };
329002
+ }
328969
329003
  }
328970
329004
  const textDelta = this.extractText(delta.content);
328971
329005
  if (textDelta) {
@@ -329010,13 +329044,17 @@ var ChatCompletionsAdapter = class {
329010
329044
  type: "tool_call.completed",
329011
329045
  id: currentToolCall.id,
329012
329046
  name: currentToolCall.name,
329013
- arguments: currentToolCall.argumentParts.join("")
329047
+ arguments: assembleCompatibleToolArguments(currentToolCall.argumentParts)
329014
329048
  };
329015
329049
  }
329016
329050
  } else if (!emittedContent && !emittedTool && contentPolicyBlocked) {
329017
329051
  yield { type: "response.failed", error: "[Error] Content policy refusal (content_filter)." };
329018
329052
  return;
329019
329053
  }
329054
+ if (!explicitCompletion && !emittedContent && !emittedTool && !emittedReasoning) {
329055
+ yield { type: "response.failed", error: "[LLM Error] Chat stream ended before an explicit completion." };
329056
+ return;
329057
+ }
329020
329058
  yield { type: "response.completed" };
329021
329059
  } finally {
329022
329060
  reader.releaseLock();
@@ -329213,6 +329251,7 @@ var ResponsesAdapter = class {
329213
329251
  const key3 = `${String(payload.item_id || "")}:${String(payload.summary_index || 0)}`;
329214
329252
  const delta = this.extractText(payload.delta);
329215
329253
  if (delta) {
329254
+ emittedContent = true;
329216
329255
  reasoningSummaries.set(key3, (reasoningSummaries.get(key3) || "") + delta);
329217
329256
  yield { type: "reasoning.summary.delta", delta };
329218
329257
  }
@@ -329243,7 +329282,7 @@ var ResponsesAdapter = class {
329243
329282
  calls.set(key3, {
329244
329283
  id: String(item.call_id || item.id || key3),
329245
329284
  name: String(item.name || ""),
329246
- arguments: String(item.arguments || ""),
329285
+ argumentParts: item.arguments ? [String(item.arguments)] : [],
329247
329286
  emitted: false
329248
329287
  });
329249
329288
  }
@@ -329251,9 +329290,9 @@ var ResponsesAdapter = class {
329251
329290
  }
329252
329291
  if (eventType === "response.function_call_arguments.delta") {
329253
329292
  const key3 = String(payload.item_id || payload.call_id || payload.output_index || "");
329254
- const call = calls.get(key3) || { id: String(payload.call_id || key3), name: String(payload.name || ""), arguments: "", emitted: false };
329293
+ const call = calls.get(key3) || { id: String(payload.call_id || key3), name: String(payload.name || ""), argumentParts: [], emitted: false };
329255
329294
  const delta = String(payload.delta || "");
329256
- call.arguments += delta;
329295
+ if (delta) call.argumentParts.push(delta);
329257
329296
  calls.set(key3, call);
329258
329297
  yield { type: "tool_call.arguments.delta", id: call.id, delta };
329259
329298
  continue;
@@ -329265,19 +329304,20 @@ var ResponsesAdapter = class {
329265
329304
  const call = calls.get(key3) || {
329266
329305
  id: String(item.call_id || item.id || key3),
329267
329306
  name: String(item.name || ""),
329268
- arguments: String(item.arguments || ""),
329307
+ argumentParts: item.arguments ? [String(item.arguments)] : [],
329269
329308
  emitted: false
329270
329309
  };
329271
329310
  call.id = String(item.call_id || call.id);
329272
329311
  call.name = String(item.name || call.name);
329273
- call.arguments = typeof item.arguments === "string" ? item.arguments : call.arguments;
329312
+ if (typeof item.arguments === "string" && item.arguments) call.argumentParts.push(item.arguments);
329274
329313
  if (!call.emitted) {
329275
329314
  call.emitted = true;
329315
+ const argumentsJson = assembleCompatibleToolArguments(call.argumentParts);
329276
329316
  yield { type: "tool_call.started", id: call.id, name: call.name };
329277
- if (call.arguments && call.arguments !== "{}") {
329278
- yield { type: "tool_call.arguments.delta", id: call.id, delta: call.arguments };
329317
+ if (argumentsJson !== "{}") {
329318
+ yield { type: "tool_call.arguments.delta", id: call.id, delta: argumentsJson };
329279
329319
  }
329280
- yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: call.arguments };
329320
+ yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: argumentsJson };
329281
329321
  }
329282
329322
  calls.set(key3, call);
329283
329323
  }
@@ -329302,8 +329342,14 @@ var ResponsesAdapter = class {
329302
329342
  } else if (!completed) {
329303
329343
  yield { type: "response.failed", error: "[LLM Error] Responses stream ended before response.completed." };
329304
329344
  } else if (!emittedContent && calls.size === 0) {
329305
- yield { type: "response.failed", error: "[Error] Empty Responses stream." };
329345
+ yield { type: "response.failed", error: "[Error] Provider returned an empty response." };
329306
329346
  } else {
329347
+ for (const call of calls.values()) {
329348
+ if (call.emitted) continue;
329349
+ const argumentsJson = assembleCompatibleToolArguments(call.argumentParts);
329350
+ yield { type: "tool_call.started", id: call.id, name: call.name };
329351
+ yield { type: "tool_call.completed", id: call.id, name: call.name, arguments: argumentsJson };
329352
+ }
329307
329353
  yield { type: "response.completed" };
329308
329354
  }
329309
329355
  } finally {
@@ -330182,7 +330228,7 @@ ${responsePath}
330182
330228
  * `provider_adapters_v2` context flag. Request serialization and SSE
330183
330229
  * normalization are delegated to the shared provider adapters while the
330184
330230
  * transport orchestration (loopback node-http, fetch -> node-http fallback,
330185
- * 120s/30s timeouts) and the 4xx Chat -> Responses downgrade stay here.
330231
+ * cancellation-only streaming and the 4xx Chat -> Responses downgrade) stay here.
330186
330232
  * The emitted request body and StreamToken stream are byte-equivalent to
330187
330233
  * the legacy inlined path.
330188
330234
  */
@@ -330319,9 +330365,9 @@ ${responsePath}
330319
330365
  }
330320
330366
  /**
330321
330367
  * Loopback-aware transport injected into adapter `execute`. Streaming
330322
- * requests retain the fetch-to-node fallback for transport failures, while
330323
- * a local deadline is returned directly so one request cannot become a
330324
- * second Windows fallback request.
330368
+ * requests retain the fetch-to-node fallback for transport failures. They
330369
+ * have no response deadline; only caller cancellation or a concrete
330370
+ * transport/provider failure may end the request.
330325
330371
  */
330326
330372
  buildProviderAdapterTransport() {
330327
330373
  return async (request, signal) => {
@@ -330331,7 +330377,7 @@ ${responsePath}
330331
330377
  const forwardAbort = () => abort.abort(signal?.reason);
330332
330378
  if (signal?.aborted) forwardAbort();
330333
330379
  else signal?.addEventListener("abort", forwardAbort, { once: true });
330334
- const effectiveTimeout = this.effectiveRequestTimeout(12e4);
330380
+ const effectiveTimeout = 0;
330335
330381
  const timer = effectiveTimeout > 0 ? setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout) : void 0;
330336
330382
  try {
330337
330383
  try {
@@ -330476,7 +330522,7 @@ ${responsePath}
330476
330522
  const forwardAbort = () => abort.abort(signal?.reason);
330477
330523
  if (signal?.aborted) forwardAbort();
330478
330524
  else signal?.addEventListener("abort", forwardAbort, { once: true });
330479
- const effectiveTimeout = this.effectiveRequestTimeout(12e4);
330525
+ const effectiveTimeout = 0;
330480
330526
  const timeout = effectiveTimeout > 0 ? setTimeout(() => abort.abort(providerTimeoutError(effectiveTimeout)), effectiveTimeout) : void 0;
330481
330527
  let reader = null;
330482
330528
  try {
@@ -330508,10 +330554,14 @@ ${responsePath}
330508
330554
  }
330509
330555
  const decoder = new TextDecoder();
330510
330556
  let buffer = "";
330511
- let currentToolCall = null;
330557
+ const toolCalls = /* @__PURE__ */ new Map();
330558
+ const toolCallOrder = [];
330559
+ let syntheticToolIndex = 0;
330560
+ let lastToolIndex = 0;
330512
330561
  let currentReasoningContent = "";
330513
330562
  let contentPolicyBlocked = false;
330514
330563
  let emittedContent = false;
330564
+ let explicitCompletion = false;
330515
330565
  const streamSignal = signal || new AbortController().signal;
330516
330566
  while (true) {
330517
330567
  const { done, value } = await readProviderStreamChunk(reader, streamSignal);
@@ -330523,7 +330573,10 @@ ${responsePath}
330523
330573
  const trimmed = line.trim();
330524
330574
  if (!trimmed.startsWith("data: ")) continue;
330525
330575
  const data = trimmed.slice(6);
330526
- if (data === "[DONE]") continue;
330576
+ if (data === "[DONE]") {
330577
+ explicitCompletion = true;
330578
+ continue;
330579
+ }
330527
330580
  try {
330528
330581
  const json = JSON.parse(data);
330529
330582
  if (json.usage) yield { type: "usage", text: "", usage: extractProviderUsage(json) };
@@ -330541,24 +330594,44 @@ ${responsePath}
330541
330594
  }
330542
330595
  if (delta.tool_calls) {
330543
330596
  for (const tc of delta.tool_calls) {
330544
- if (tc.id) {
330545
- if (currentToolCall) {
330546
- yield { type: "tool_call", text: "", toolCall: currentToolCall, reasoningContent: currentReasoningContent || void 0 };
330547
- }
330548
- currentToolCall = { id: tc.id, name: tc.function?.name || "", arguments: tc.function?.arguments || "" };
330549
- } else if (tc.function?.arguments && currentToolCall) {
330550
- currentToolCall.arguments += tc.function.arguments;
330597
+ const rawIndex = Number(tc.index);
330598
+ const index = Number.isInteger(rawIndex) && rawIndex >= 0 ? rawIndex : tc.id ? syntheticToolIndex++ : lastToolIndex;
330599
+ lastToolIndex = index;
330600
+ let call = toolCalls.get(index);
330601
+ if (!call && (tc.id || tc.function?.name)) {
330602
+ call = { id: tc.id || "", name: tc.function?.name || "", argumentParts: [] };
330603
+ toolCalls.set(index, call);
330604
+ toolCallOrder.push(index);
330551
330605
  }
330606
+ if (!call) continue;
330607
+ if (tc.id && !call.id) call.id = tc.id;
330608
+ if (tc.function?.name && !call.name) call.name = tc.function.name;
330609
+ if (tc.function?.arguments) call.argumentParts.push(tc.function.arguments);
330552
330610
  }
330553
330611
  }
330554
330612
  } catch {
330555
330613
  }
330556
330614
  }
330557
330615
  }
330558
- if (currentToolCall && currentToolCall.arguments) {
330559
- yield { type: "tool_call", text: "", toolCall: currentToolCall, reasoningContent: currentReasoningContent || void 0 };
330616
+ if (toolCallOrder.length) {
330617
+ for (const index of toolCallOrder) {
330618
+ const call = toolCalls.get(index);
330619
+ if (!call) continue;
330620
+ yield {
330621
+ type: "tool_call",
330622
+ text: "",
330623
+ toolCall: {
330624
+ id: call.id,
330625
+ name: call.name,
330626
+ arguments: assembleCompatibleToolArguments(call.argumentParts)
330627
+ },
330628
+ reasoningContent: currentReasoningContent || void 0
330629
+ };
330630
+ }
330560
330631
  } else if (!emittedContent && contentPolicyBlocked) {
330561
330632
  yield { type: "text", text: "[Error] Content policy refusal (content_filter)." };
330633
+ } else if (!explicitCompletion && !emittedContent && !currentReasoningContent) {
330634
+ yield { type: "text", text: "[LLM Error] GitHub Models stream ended before an explicit completion." };
330562
330635
  }
330563
330636
  } finally {
330564
330637
  reader?.releaseLock();
@@ -340511,6 +340584,22 @@ function createToolchainCore() {
340511
340584
  return { registry: new ToolRegistry(), catalog: new CapabilityCatalog() };
340512
340585
  }
340513
340586
 
340587
+ // src/core/emptyResponseRetry.ts
340588
+ var EMPTY_RESPONSE_RETRY_DELAYS_MS = [200, 800, 2e3, 1e4, 6e4];
340589
+ var MAX_EMPTY_RESPONSE_RETRIES = EMPTY_RESPONSE_RETRY_DELAYS_MS.length;
340590
+ var MAX_CONSECUTIVE_EMPTY_RESPONSES = MAX_EMPTY_RESPONSE_RETRIES + 1;
340591
+ function emptyResponseRetryDelayMs(consecutiveEmptyResponses) {
340592
+ return EMPTY_RESPONSE_RETRY_DELAYS_MS[Math.max(0, consecutiveEmptyResponses - 1)] ?? 0;
340593
+ }
340594
+ function observeEmptyResponseOutcome(consecutiveEmptyResponses, emptyResponse) {
340595
+ const nextCount = emptyResponse ? consecutiveEmptyResponses + 1 : 0;
340596
+ return {
340597
+ consecutiveEmptyResponses: nextCount,
340598
+ retry: emptyResponse && nextCount <= MAX_EMPTY_RESPONSE_RETRIES,
340599
+ terminate: emptyResponse && nextCount > MAX_EMPTY_RESPONSE_RETRIES
340600
+ };
340601
+ }
340602
+
340514
340603
  // src/core/agentKernelRunner.ts
340515
340604
  var publicStreamFilters = /* @__PURE__ */ new WeakMap();
340516
340605
  var brokerOnlyAssistantBuffers = /* @__PURE__ */ new WeakMap();
@@ -340662,7 +340751,7 @@ function kernelTurnFailed(agent, turn) {
340662
340751
  return turn.stopReason === "error" || agent.isLlmErrorText(turn.text);
340663
340752
  }
340664
340753
  function providerTurnIsEmpty(turn) {
340665
- return /provider returned an empty response/i.test(`${turn.errorMessage}
340754
+ return !turn.activity && /provider returned an empty response/i.test(`${turn.errorMessage}
340666
340755
  ${turn.text}`);
340667
340756
  }
340668
340757
  function removeTrailingFailedAssistant(agent, messages) {
@@ -340671,6 +340760,12 @@ function removeTrailingFailedAssistant(agent, messages) {
340671
340760
  const text = KernelMessageText(last);
340672
340761
  if (last.stopReason === "error" || agent.isLlmErrorText(text)) messages.pop();
340673
340762
  }
340763
+ function removeTrailingThoughtOnlyAssistant(messages) {
340764
+ const last = messages[messages.length - 1];
340765
+ if (last?.role !== "assistant") return;
340766
+ const hasToolCall = last.content.some((content) => content.type === "toolCall");
340767
+ if (!KernelMessageText(last).trim() && !hasToolCall) messages.pop();
340768
+ }
340674
340769
  function normalizePublicProviderError(error, secrets = []) {
340675
340770
  let raw = "";
340676
340771
  if (error instanceof Error) {
@@ -340796,8 +340891,17 @@ async function runAgentKernel(agent) {
340796
340891
  const tokens = [];
340797
340892
  const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
340798
340893
  let lastAssistant = null;
340894
+ let observedActivity = false;
340895
+ let observedThought = false;
340799
340896
  const unsubscribe = kernel2.subscribe(async (event) => {
340800
340897
  await handleKernelEvent(agent, event, tokens);
340898
+ if (event.type === "message_update") {
340899
+ const delta = event.assistantMessageEvent;
340900
+ const deltaText = typeof delta.delta === "string" ? delta.delta : "";
340901
+ const thoughtDelta = delta.type === "thinking_delta" && !!deltaText.trim();
340902
+ observedThought = observedThought || thoughtDelta;
340903
+ observedActivity = observedActivity || thoughtDelta || delta.type === "text_delta" && !!deltaText.trim() || delta.type === "toolcall_end";
340904
+ }
340801
340905
  if (event.type === "message_end" && event.message.role === "assistant") {
340802
340906
  lastAssistant = event.message;
340803
340907
  }
@@ -340815,11 +340919,13 @@ async function runAgentKernel(agent) {
340815
340919
  const assistant = lastAssistant;
340816
340920
  const text = assistant ? KernelMessageText(assistant) : "";
340817
340921
  const hasToolCall = !!assistant?.content?.some((content) => content.type === "toolCall");
340818
- const emptyResponse = !assistant || !text.trim() && !hasToolCall && String(assistant?.stopReason || "") !== "aborted";
340922
+ const emptyResponse = !assistant || !text.trim() && !hasToolCall && !observedActivity && String(assistant?.stopReason || "") !== "aborted";
340819
340923
  return {
340820
340924
  text: emptyResponse ? "[Error] Provider returned an empty response." : text,
340821
340925
  stopReason: String(assistant?.stopReason || ""),
340822
- errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : ""))
340926
+ errorMessage: String(assistant?.errorMessage || (emptyResponse ? "Provider returned an empty response." : "")),
340927
+ activity: observedActivity || !!text.trim() || hasToolCall,
340928
+ thoughtOnly: observedThought && !text.trim() && !hasToolCall && !["error", "aborted"].includes(String(assistant?.stopReason || ""))
340823
340929
  };
340824
340930
  } finally {
340825
340931
  unsubscribe();
@@ -340865,14 +340971,22 @@ async function runAgentKernel(agent) {
340865
340971
  fallback: { from: modelBeforeKernelRun, to: agent.model, providerId: agent.activeDeployment()?.providerId }
340866
340972
  });
340867
340973
  }
340868
- let emptyResponseRetries = 0;
340869
- while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
340974
+ let consecutiveEmptyResponses = 0;
340975
+ for (; ; ) {
340976
+ const emptyResponseState = observeEmptyResponseOutcome(consecutiveEmptyResponses, providerTurnIsEmpty(lastTurn));
340977
+ consecutiveEmptyResponses = emptyResponseState.consecutiveEmptyResponses;
340978
+ if (lastTurn.thoughtOnly) {
340979
+ removeTrailingThoughtOnlyAssistant(kernel2.state.messages);
340980
+ lastTurn = await runWithCompressionResume([], false);
340981
+ continue;
340982
+ }
340983
+ if (!emptyResponseState.retry) break;
340870
340984
  removeTrailingFailedAssistant(agent, kernel2.state.messages);
340871
- emptyResponseRetries += 1;
340872
- const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
340985
+ const retryNumber = consecutiveEmptyResponses;
340986
+ const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${retryNumber}/${MAX_EMPTY_RESPONSE_RETRIES}) after ${emptyResponseRetryDelayMs(consecutiveEmptyResponses)}ms.`;
340873
340987
  tokens.push({ type: "text", text: notice });
340874
340988
  agent.recordWorkStatus(notice);
340875
- await agent.waitForPlannedRouteRetry();
340989
+ await agent.waitForPlannedRouteRetry(emptyResponseRetryDelayMs(consecutiveEmptyResponses));
340876
340990
  lastTurn = await runWithCompressionResume([], false);
340877
340991
  }
340878
340992
  let routeRetries = 0;
@@ -341068,7 +341182,7 @@ async function runAgentKernel(agent) {
341068
341182
  return;
341069
341183
  }
341070
341184
  if (textStarted) finalContent.push({ type: "text", text });
341071
- if (!finalContent.length) {
341185
+ if (!finalContent.length && !thinking.trim()) {
341072
341186
  text = "[Error] Provider returned an empty response.";
341073
341187
  finalContent.push({ type: "text", text });
341074
341188
  }
@@ -345652,7 +345766,12 @@ var Agent4 = class _Agent {
345652
345766
  beginRouteAttempt() {
345653
345767
  this.routeAttemptStartedAt = Date.now();
345654
345768
  }
345655
- async waitForPlannedRouteRetry() {
345769
+ async waitForPlannedRouteRetry(explicitDelayMs) {
345770
+ if (explicitDelayMs !== void 0) {
345771
+ if (explicitDelayMs <= 0) return;
345772
+ await new Promise((resolve16) => setTimeout(resolve16, explicitDelayMs));
345773
+ return;
345774
+ }
345656
345775
  const waitBudgetMs = Math.max(0, Math.min(15e3, this.lastRouteDecision?.retryBudgetMs ?? 5e3));
345657
345776
  const delay = Math.max(0, Math.min(waitBudgetMs, this.lastRouteRetryDelayMs));
345658
345777
  this.lastRouteRetryDelayMs = 0;
@@ -464,7 +464,7 @@ export declare class Agent {
464
464
  markRouteToolExecuted(name: string, rawArgs?: string): void;
465
465
  routeTransitionKind(): PlannedRouteAttempt['kind'] | '';
466
466
  beginRouteAttempt(): void;
467
- waitForPlannedRouteRetry(): Promise<void>;
467
+ waitForPlannedRouteRetry(explicitDelayMs?: number): Promise<void>;
468
468
  recordRouteSuccess(latencyMs?: number, throughput?: number): void;
469
469
  recordRouteToolOutcome(valid: boolean): void;
470
470
  updateProviders(value: unknown): void;
@@ -1016,7 +1016,13 @@ class Agent {
1016
1016
  beginRouteAttempt() {
1017
1017
  this.routeAttemptStartedAt = Date.now();
1018
1018
  }
1019
- async waitForPlannedRouteRetry() {
1019
+ async waitForPlannedRouteRetry(explicitDelayMs) {
1020
+ if (explicitDelayMs !== undefined) {
1021
+ if (explicitDelayMs <= 0)
1022
+ return;
1023
+ await new Promise(resolve => setTimeout(resolve, explicitDelayMs));
1024
+ return;
1025
+ }
1020
1026
  const waitBudgetMs = Math.max(0, Math.min(15_000, this.lastRouteDecision?.retryBudgetMs ?? 5_000));
1021
1027
  const delay = Math.max(0, Math.min(waitBudgetMs, this.lastRouteRetryDelayMs));
1022
1028
  this.lastRouteRetryDelayMs = 0;
@@ -46,6 +46,7 @@ const toolPolicy_1 = require("./toolPolicy");
46
46
  const performanceDiagnostics_1 = require("./performanceDiagnostics");
47
47
  const agentKernelDiagnostics_1 = require("./agentKernelDiagnostics");
48
48
  const toolchain_1 = require("../toolchain");
49
+ const emptyResponseRetry_1 = require("./emptyResponseRetry");
49
50
  const publicStreamFilters = new WeakMap();
50
51
  const brokerOnlyAssistantBuffers = new WeakMap();
51
52
  const BROKER_PREFACE_BUFFER_CHARS = 96;
@@ -206,7 +207,7 @@ function kernelTurnFailed(agent, turn) {
206
207
  return turn.stopReason === 'error' || agent.isLlmErrorText(turn.text);
207
208
  }
208
209
  function providerTurnIsEmpty(turn) {
209
- return /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
210
+ return !turn.activity && /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
210
211
  }
211
212
  function removeTrailingFailedAssistant(agent, messages) {
212
213
  const last = messages[messages.length - 1];
@@ -216,6 +217,14 @@ function removeTrailingFailedAssistant(agent, messages) {
216
217
  if (last.stopReason === 'error' || agent.isLlmErrorText(text))
217
218
  messages.pop();
218
219
  }
220
+ function removeTrailingThoughtOnlyAssistant(messages) {
221
+ const last = messages[messages.length - 1];
222
+ if (last?.role !== 'assistant')
223
+ return;
224
+ const hasToolCall = last.content.some(content => content.type === 'toolCall');
225
+ if (!KernelMessageText(last).trim() && !hasToolCall)
226
+ messages.pop();
227
+ }
219
228
  function normalizePublicProviderError(error, secrets = []) {
220
229
  let raw = '';
221
230
  if (error instanceof Error) {
@@ -369,8 +378,22 @@ async function runAgentKernel(agent) {
369
378
  const tokens = [];
370
379
  const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
371
380
  let lastAssistant = null;
381
+ let observedActivity = false;
382
+ let observedThought = false;
372
383
  const unsubscribe = kernel.subscribe(async (event) => {
373
384
  await handleKernelEvent(agent, event, tokens);
385
+ if (event.type === 'message_update') {
386
+ const delta = event.assistantMessageEvent;
387
+ const deltaText = typeof delta.delta === 'string'
388
+ ? delta.delta
389
+ : '';
390
+ const thoughtDelta = delta.type === 'thinking_delta' && !!deltaText.trim();
391
+ observedThought = observedThought || thoughtDelta;
392
+ observedActivity = observedActivity ||
393
+ thoughtDelta ||
394
+ (delta.type === 'text_delta' && !!deltaText.trim()) ||
395
+ (delta.type === 'toolcall_end');
396
+ }
374
397
  if (event.type === 'message_end' && event.message.role === 'assistant') {
375
398
  lastAssistant = event.message;
376
399
  }
@@ -390,11 +413,14 @@ async function runAgentKernel(agent) {
390
413
  const text = assistant ? KernelMessageText(assistant) : '';
391
414
  const hasToolCall = !!assistant?.content?.some(content => content.type === 'toolCall');
392
415
  const emptyResponse = !assistant
393
- || (!text.trim() && !hasToolCall && String(assistant?.stopReason || '') !== 'aborted');
416
+ || (!text.trim() && !hasToolCall && !observedActivity && String(assistant?.stopReason || '') !== 'aborted');
394
417
  return {
395
418
  text: emptyResponse ? '[Error] Provider returned an empty response.' : text,
396
419
  stopReason: String(assistant?.stopReason || ''),
397
420
  errorMessage: String(assistant?.errorMessage || (emptyResponse ? 'Provider returned an empty response.' : '')),
421
+ activity: observedActivity || !!text.trim() || hasToolCall,
422
+ thoughtOnly: observedThought && !text.trim() && !hasToolCall
423
+ && !['error', 'aborted'].includes(String(assistant?.stopReason || '')),
398
424
  };
399
425
  }
400
426
  finally {
@@ -448,14 +474,23 @@ async function runAgentKernel(agent) {
448
474
  fallback: { from: modelBeforeKernelRun, to: agent.model, providerId: agent.activeDeployment()?.providerId },
449
475
  });
450
476
  }
451
- let emptyResponseRetries = 0;
452
- while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
477
+ let consecutiveEmptyResponses = 0;
478
+ for (;;) {
479
+ const emptyResponseState = (0, emptyResponseRetry_1.observeEmptyResponseOutcome)(consecutiveEmptyResponses, providerTurnIsEmpty(lastTurn));
480
+ consecutiveEmptyResponses = emptyResponseState.consecutiveEmptyResponses;
481
+ if (lastTurn.thoughtOnly) {
482
+ removeTrailingThoughtOnlyAssistant(kernel.state.messages);
483
+ lastTurn = await runWithCompressionResume([], false);
484
+ continue;
485
+ }
486
+ if (!emptyResponseState.retry)
487
+ break;
453
488
  removeTrailingFailedAssistant(agent, kernel.state.messages);
454
- emptyResponseRetries += 1;
455
- const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
489
+ const retryNumber = consecutiveEmptyResponses;
490
+ const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${retryNumber}/${emptyResponseRetry_1.MAX_EMPTY_RESPONSE_RETRIES}) after ${(0, emptyResponseRetry_1.emptyResponseRetryDelayMs)(consecutiveEmptyResponses)}ms.`;
456
491
  tokens.push({ type: 'text', text: notice });
457
492
  agent.recordWorkStatus(notice);
458
- await agent.waitForPlannedRouteRetry();
493
+ await agent.waitForPlannedRouteRetry((0, emptyResponseRetry_1.emptyResponseRetryDelayMs)(consecutiveEmptyResponses));
459
494
  lastTurn = await runWithCompressionResume([], false);
460
495
  }
461
496
  let routeRetries = 0;
@@ -667,7 +702,7 @@ async function runAgentKernel(agent) {
667
702
  }
668
703
  if (textStarted)
669
704
  finalContent.push({ type: 'text', text });
670
- if (!finalContent.length) {
705
+ if (!finalContent.length && !thinking.trim()) {
671
706
  text = '[Error] Provider returned an empty response.';
672
707
  finalContent.push({ type: 'text', text });
673
708
  }
@@ -0,0 +1,16 @@
1
+ export declare const EMPTY_RESPONSE_RETRY_DELAYS_MS: readonly [200, 800, 2000, 10000, 60000];
2
+ /**
3
+ * A retry is scheduled only after an explicit provider empty-response
4
+ * failure. The initial failed request is not called a retry, so five retries
5
+ * means six consecutive explicit failures before termination.
6
+ */
7
+ export declare const MAX_EMPTY_RESPONSE_RETRIES: 5;
8
+ export declare const MAX_CONSECUTIVE_EMPTY_RESPONSES: number;
9
+ export declare function emptyResponseRetryDelayMs(consecutiveEmptyResponses: number): number;
10
+ export interface EmptyResponseRetryState {
11
+ consecutiveEmptyResponses: number;
12
+ retry: boolean;
13
+ terminate: boolean;
14
+ }
15
+ export declare function observeEmptyResponseOutcome(consecutiveEmptyResponses: number, emptyResponse: boolean): EmptyResponseRetryState;
16
+ //# sourceMappingURL=emptyResponseRetry.d.ts.map
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_CONSECUTIVE_EMPTY_RESPONSES = exports.MAX_EMPTY_RESPONSE_RETRIES = exports.EMPTY_RESPONSE_RETRY_DELAYS_MS = void 0;
4
+ exports.emptyResponseRetryDelayMs = emptyResponseRetryDelayMs;
5
+ exports.observeEmptyResponseOutcome = observeEmptyResponseOutcome;
6
+ exports.EMPTY_RESPONSE_RETRY_DELAYS_MS = [200, 800, 2_000, 10_000, 60_000];
7
+ /**
8
+ * A retry is scheduled only after an explicit provider empty-response
9
+ * failure. The initial failed request is not called a retry, so five retries
10
+ * means six consecutive explicit failures before termination.
11
+ */
12
+ exports.MAX_EMPTY_RESPONSE_RETRIES = exports.EMPTY_RESPONSE_RETRY_DELAYS_MS.length;
13
+ exports.MAX_CONSECUTIVE_EMPTY_RESPONSES = exports.MAX_EMPTY_RESPONSE_RETRIES + 1;
14
+ function emptyResponseRetryDelayMs(consecutiveEmptyResponses) {
15
+ return exports.EMPTY_RESPONSE_RETRY_DELAYS_MS[Math.max(0, consecutiveEmptyResponses - 1)] ?? 0;
16
+ }
17
+ function observeEmptyResponseOutcome(consecutiveEmptyResponses, emptyResponse) {
18
+ const nextCount = emptyResponse ? consecutiveEmptyResponses + 1 : 0;
19
+ return {
20
+ consecutiveEmptyResponses: nextCount,
21
+ retry: emptyResponse && nextCount <= exports.MAX_EMPTY_RESPONSE_RETRIES,
22
+ terminate: emptyResponse && nextCount > exports.MAX_EMPTY_RESPONSE_RETRIES,
23
+ };
24
+ }
25
+ //# sourceMappingURL=emptyResponseRetry.js.map
@@ -0,0 +1,24 @@
1
+ export declare const TERMINAL_HISTORY_LIMIT: number;
2
+ export interface TerminalOutputBufferOptions {
3
+ flushIntervalMs?: number;
4
+ historyLimit?: number;
5
+ }
6
+ export declare class TerminalOutputBuffer {
7
+ private readonly send;
8
+ private readonly sessions;
9
+ private timer;
10
+ private readonly flushIntervalMs;
11
+ private readonly historyLimit;
12
+ constructor(send: (sessionId: string, text: string) => void, options?: TerminalOutputBufferOptions);
13
+ push(sessionId: string, text: string): void;
14
+ flush(sessionId: string): void;
15
+ flushAll(): void;
16
+ close(sessionId: string): string;
17
+ history(sessionId: string): string;
18
+ pendingChunkCount(): number;
19
+ hasScheduledFlush(): boolean;
20
+ private bound;
21
+ private schedule;
22
+ private clearTimerWhenIdle;
23
+ }
24
+ //# sourceMappingURL=terminalOutputBuffer.d.ts.map