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.
- package/dist/conversation-utility-host.bundle.cjs +159 -40
- package/dist/core/agent.d.ts +1 -1
- package/dist/core/agent.js +7 -1
- package/dist/core/agentKernelRunner.js +43 -8
- package/dist/core/emptyResponseRetry.d.ts +16 -0
- package/dist/core/emptyResponseRetry.js +25 -0
- package/dist/core/terminalOutputBuffer.d.ts +24 -0
- package/dist/core/terminalOutputBuffer.js +92 -0
- package/dist/llm/provider.d.ts +4 -4
- package/dist/llm/provider.js +55 -21
- package/dist/main.js +12 -3
- package/dist/providers/chat-completions.adapter.js +17 -4
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +40 -0
- package/dist/providers/responses.adapter.js +23 -9
- package/dist/ui/index.html +68 -17
- package/dist/wsl-agent-host.bundle.cjs +159 -40
- package/package.json +3 -1
|
@@ -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]")
|
|
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
|
|
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)
|
|
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
|
|
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
|
-
|
|
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 || ""),
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
329278
|
-
yield { type: "tool_call.arguments.delta", id: call.id, delta:
|
|
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:
|
|
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]
|
|
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
|
-
*
|
|
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
|
|
330323
|
-
*
|
|
330324
|
-
*
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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]")
|
|
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
|
-
|
|
330545
|
-
|
|
330546
|
-
|
|
330547
|
-
|
|
330548
|
-
|
|
330549
|
-
|
|
330550
|
-
|
|
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 (
|
|
330559
|
-
|
|
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
|
|
340869
|
-
|
|
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
|
-
|
|
340872
|
-
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${
|
|
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;
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/agent.js
CHANGED
|
@@ -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
|
|
452
|
-
|
|
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
|
-
|
|
455
|
-
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${
|
|
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
|