@agent-native/core 0.203.0 → 0.203.1-nightly-20261007183952
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/action.d.ts +34 -0
- package/dist/agent/clip-text.d.ts +4 -0
- package/dist/agent/clip-text.js +17 -0
- package/dist/agent/follow-up-suggestions.d.ts +16 -2
- package/dist/agent/follow-up-suggestions.js +69 -1
- package/dist/agent/production-agent.d.ts +20 -2
- package/dist/agent/production-agent.js +322 -132
- package/dist/agent/provider-model-selection.d.ts +21 -2
- package/dist/agent/provider-model-selection.js +19 -2
- package/dist/agent/run-loop-with-resume.js +2 -16
- package/dist/agent/tool-search.d.ts +31 -1
- package/dist/agent/tool-search.js +196 -15
- package/dist/agent/write-receipts.d.ts +27 -0
- package/dist/agent/write-receipts.js +181 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +6 -6
- package/dist/observability/trace-error.d.ts +5 -3
- package/dist/observability/trace-error.js +29 -4
- package/dist/observability/traces.js +6 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +11 -11
- package/dist/server/action-routes.js +3 -0
- package/dist/server/agent-chat/prompt-resources.d.ts +13 -0
- package/dist/server/agent-chat/prompt-resources.js +21 -12
- package/dist/server/agent-chat-plugin.js +25 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/email-auth-links.js +98 -2
- package/dist/server/embed-route.js +2 -1
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +3 -3
- package/dist/server/realtime-voice.js +2 -4
- package/dist/shared/embed-auth.d.ts +1 -0
- package/dist/shared/embed-auth.js +1 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/actions/SKILL.md +25 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +25 -0
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -39
- package/dist/templates/headless/.agents/skills/actions/SKILL.md +25 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +25 -0
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/actions/SKILL.md +25 -0
- package/src/templates/default/.agents/skills/actions/SKILL.md +25 -0
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -39
- package/src/templates/headless/.agents/skills/actions/SKILL.md +25 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +25 -0
|
@@ -44,6 +44,7 @@ import { isReasoningEffort, normalizeReasoningEffortForRequest, stepDownReasonin
|
|
|
44
44
|
import { SYNTHETIC_TRAFFIC_BETA_E2E, SYNTHETIC_TRAFFIC_HEADER, } from "../shared/test-traffic.js";
|
|
45
45
|
import { actionPreparationContinuationNote } from "./action-continuation-guidance.js";
|
|
46
46
|
import { drainAgentWarnings, formatAgentWarningsForToolResult, } from "./action-warnings.js";
|
|
47
|
+
import { clipHead } from "./clip-text.js";
|
|
47
48
|
import { buildSystemManifestSections, readContextXraySystemSections, } from "./context-xray/manifest.js";
|
|
48
49
|
import { AGENT_CHAT_BACKGROUND_RUN_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, backgroundRuntimeDiagnosticDetail, dispatchPathTargetsNetlifyBackgroundFunction, isAgentChatDurableBackgroundEnabled, isAgentChatForegroundSelfChainEnabled, isInBackgroundFunctionRuntime, resolveAgentChatProcessRunDispatchPath, shouldUseBackgroundFunctionTimeoutForWorker, } from "./durable-background.js";
|
|
49
50
|
import { applyContextXrayTransformForIteration } from "./engine/context-directives-transform.js";
|
|
@@ -51,19 +52,20 @@ import { attemptContinuationDispatch } from "./engine/continuation-dispatch-retr
|
|
|
51
52
|
import { formatLlmCredentialErrorMessage, LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, userFacingLlmCredentialError, } from "./engine/credential-errors.js";
|
|
52
53
|
import { BUILDER_GATEWAY_INTERNAL_ERROR_CODE, isContextOverflowCode, isContextOverflowMessage, isProviderConnectionErrorMessage, PROVIDER_RATE_LIMITED_ERROR_CODE, PROVIDER_TRANSIENT_REJECTION_ERROR_CODE, } from "./engine/error-detail.js";
|
|
53
54
|
import { resolveEngine, explicitEngineName, registerBuiltinEngines, getStoredModelForEngine, normalizeModelForEngine, isResolvedEngineUsableForRequest, } from "./engine/index.js";
|
|
55
|
+
import { limitProviderTools, MAX_PROVIDER_TOOLS, } from "./engine/limit-provider-tools.js";
|
|
54
56
|
import { resolveEmptyResponseRetryMaxOutputTokens, resolveMainChatMaxOutputTokens, resolveMaxOutputTokensForEngine, } from "./engine/output-tokens.js";
|
|
55
57
|
import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
|
|
56
58
|
import { JOURNALED_TOOL_REPLAY_PREFIX, RECOVERED_TOOL_REPLAY_PREFIX, loadedSkillPagesContext, loadPriorTurnToolCallJournal, seedRepeatedToolErrorCountsFromJournal, seedRepeatedToolCallCountsFromJournal, } from "./engine/tool-call-journal-seed.js";
|
|
57
59
|
import { backfillEngineMessagesToolResults, stringifyToolUseInputForGateway, unmatchedToolResultReplayText, } from "./engine/translate-anthropic.js";
|
|
58
60
|
import { EngineError } from "./engine/types.js";
|
|
59
|
-
import {
|
|
61
|
+
import { FOLLOW_UP_SUGGESTIONS_COMPLETION_SYSTEM_PROMPT, FOLLOW_UP_SUGGESTIONS_INSTRUCTION, FOLLOW_UP_SUGGESTIONS_MAX_OUTPUT_TOKENS, FOLLOW_UP_SUGGESTIONS_TOOL_NAME, buildFollowUpCompletionMessages, followUpSuggestionsTool, identifyFollowUpSuggestions, parseFollowUpSuggestions, } from "./follow-up-suggestions.js";
|
|
60
62
|
import { filterHostedHarnessToolNames, normalizeHostedHarnessRuntime, } from "./harness/hosted.js";
|
|
61
63
|
import { buildRecentUserRequestContext, buildJevRequestContext, preloadJevTools, } from "./jev-tool-prefetch.js";
|
|
62
64
|
import { getDefaultMaxIterations, getDefaultMaxRunInputTokens, MAX_AGENT_MAX_ITERATIONS, MIN_AGENT_MAX_ITERATIONS, normalizeMaxIterations, normalizeMaxRunInputTokens, readAgentLoopSettings, } from "./loop-settings.js";
|
|
63
65
|
import { getContextWindowForModel, resolveFallbackModel, } from "./model-config.js";
|
|
64
66
|
import { maybeCompactThread, buildObservationalContext, hasObservationalMemory, serializeObservationalMemoryBlock, } from "./observational-memory/index.js";
|
|
65
67
|
import { ProcessorChain, TripWire, toolCallsFromContent, } from "./processors.js";
|
|
66
|
-
import {
|
|
68
|
+
import { applyUncheckedDefaultModelReplacement } from "./provider-model-selection.js";
|
|
67
69
|
import { startRun, subscribeToRun, replayCompletedTurn, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, abortRunDurably, abortTurnByRefDurably, abortTurnDurably, getSlotHoldingRunId, tryClaimRunSlot, isHostedRuntime, resolveRunSoftTimeoutMs, resolveRunToolTimeoutCeilingMs, endsAfterToolResultWithoutAssistantFinal, endsDuringActionPreparation, } from "./run-manager.js";
|
|
68
70
|
import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, AgentTurnInitiatorMismatchError, AgentTurnInitiatorUnavailableError, insertRun, insertRunEvent, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, turnRunLedgerExhausted, } from "./run-store.js";
|
|
69
71
|
import { buildCurrentTimeUserContext } from "./runtime-context.js";
|
|
@@ -72,8 +74,9 @@ import { consumeAgentToolApproval, createAgentToolApproval, isAgentToolAlwaysAll
|
|
|
72
74
|
import { buildResumeJournalNote, findCompletedJournalEntry, } from "./tool-call-journal.js";
|
|
73
75
|
import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
|
|
74
76
|
import { describeToolResultImages, extractAgentImagesFromActionResult, } from "./tool-result-images.js";
|
|
75
|
-
import { createToolSearchEntry, filterActionsForAgentDiscovery, searchToolRegistry, TOOL_SEARCH_ACTION_NAME, } from "./tool-search.js";
|
|
77
|
+
import { createToolSearchEntry, extractToolSearchResultNames, filterActionsForAgentDiscovery, readLoadedToolNames, searchToolRegistry, TOOL_SEARCH_ACTION_NAME, withLoadedToolNames, } from "./tool-search.js";
|
|
76
78
|
import { normalizeAgentActionScope, } from "./types.js";
|
|
79
|
+
import { mergeFinalResponseGuards, readWriteReceipt, writeReceiptGuard, } from "./write-receipts.js";
|
|
77
80
|
registerBuiltinEngines();
|
|
78
81
|
export { PROVIDER_TO_ENV };
|
|
79
82
|
/**
|
|
@@ -1081,6 +1084,11 @@ export function resolveSourceSweepToolCallThreshold() {
|
|
|
1081
1084
|
return getAppConfig().agent.sourceSweepToolCallThreshold;
|
|
1082
1085
|
}
|
|
1083
1086
|
const EXPANDED_TOOL_SCHEMA_WARN_BYTES = 32_000;
|
|
1087
|
+
const MAX_TOOL_SEARCH_OVERFLOW_NAMES_SHOWN = 5;
|
|
1088
|
+
const PROVIDER_CORE_TOOL_NAMES = [
|
|
1089
|
+
TOOL_SEARCH_ACTION_NAME,
|
|
1090
|
+
FOLLOW_UP_SUGGESTIONS_TOOL_NAME,
|
|
1091
|
+
];
|
|
1084
1092
|
const MAX_SCREEN_CONTEXT_CHARS = 10_000;
|
|
1085
1093
|
function capScreenContext(text) {
|
|
1086
1094
|
if (text.length <= MAX_SCREEN_CONTEXT_CHARS)
|
|
@@ -1841,6 +1849,36 @@ async function enrichMessage(message, references) {
|
|
|
1841
1849
|
}
|
|
1842
1850
|
return `${parts.join("\n\n")}\n\n${message}`;
|
|
1843
1851
|
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Folds one loop attempt's usage into a running total. Every wrapper that
|
|
1854
|
+
* re-enters `runAgentLoop` merges through this, so a field added to
|
|
1855
|
+
* `AgentLoopUsage` cannot be reported by the loop and dropped by a wrapper.
|
|
1856
|
+
*/
|
|
1857
|
+
export function mergeAgentLoopUsage(total, next) {
|
|
1858
|
+
total.inputTokens += next.inputTokens;
|
|
1859
|
+
total.outputTokens += next.outputTokens;
|
|
1860
|
+
total.cacheReadTokens += next.cacheReadTokens;
|
|
1861
|
+
total.cacheWriteTokens += next.cacheWriteTokens;
|
|
1862
|
+
for (const key of [
|
|
1863
|
+
"builderCreditsUsed",
|
|
1864
|
+
"llmCalls",
|
|
1865
|
+
"followUpMs",
|
|
1866
|
+
"followUpInputTokens",
|
|
1867
|
+
"receiptUnverifiedCount",
|
|
1868
|
+
"receiptChangedFalseCount",
|
|
1869
|
+
]) {
|
|
1870
|
+
if (typeof next[key] === "number") {
|
|
1871
|
+
total[key] = (total[key] ?? 0) + next[key];
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
total.engineName = next.engineName ?? total.engineName;
|
|
1875
|
+
total.model = next.model;
|
|
1876
|
+
if (next.usageReported)
|
|
1877
|
+
total.usageReported = true;
|
|
1878
|
+
// Keep the earliest attempt's first event — a later continuation
|
|
1879
|
+
// attempt starting fresh must not overwrite genuine first-token timing.
|
|
1880
|
+
total.firstEngineEventAtMs ??= next.firstEngineEventAtMs;
|
|
1881
|
+
}
|
|
1844
1882
|
function collectTextParts(parts) {
|
|
1845
1883
|
return parts
|
|
1846
1884
|
.filter((part) => part.type === "text")
|
|
@@ -2255,6 +2293,51 @@ const INTERRUPTED_TOOL_LEDGER_RECOVERY_TIMEOUT_MS = 5_000;
|
|
|
2255
2293
|
const MAX_IDENTICAL_TOOL_ERRORS = 3;
|
|
2256
2294
|
export const MAX_SAME_ERROR_ACROSS_ARGUMENTS = 3;
|
|
2257
2295
|
export const MAX_IDENTICAL_TOOL_CALLS = 8;
|
|
2296
|
+
const MAX_ALREADY_LOADED_TOOL_SEARCHES = 4;
|
|
2297
|
+
const MAX_STOP_ERROR_LINE_CHARS = 300;
|
|
2298
|
+
/** `error: message` for a JSON `{ error, message }` result; `undefined` when the text is not one. */
|
|
2299
|
+
function jsonErrorCause(text) {
|
|
2300
|
+
if (!text.startsWith("{"))
|
|
2301
|
+
return undefined;
|
|
2302
|
+
let parsed;
|
|
2303
|
+
try {
|
|
2304
|
+
parsed = JSON.parse(text);
|
|
2305
|
+
// coercion-ok: unparseable text is not a JSON error result; the caller reads its first line
|
|
2306
|
+
}
|
|
2307
|
+
catch {
|
|
2308
|
+
return undefined;
|
|
2309
|
+
}
|
|
2310
|
+
const { error, message } = (parsed ?? {});
|
|
2311
|
+
if (typeof error !== "string" || !error.trim())
|
|
2312
|
+
return undefined;
|
|
2313
|
+
return typeof message === "string" && message.trim()
|
|
2314
|
+
? `${error}: ${message}`
|
|
2315
|
+
: error;
|
|
2316
|
+
}
|
|
2317
|
+
function errorCauseLine(text) {
|
|
2318
|
+
const body = text.trim().replace(/^Error running [^\s:]+:\s*/, "");
|
|
2319
|
+
return ((jsonErrorCause(body) ?? body)
|
|
2320
|
+
.split(/\r?\n/)
|
|
2321
|
+
.find((line) => line.trim())
|
|
2322
|
+
?.trim() ?? "");
|
|
2323
|
+
}
|
|
2324
|
+
/**
|
|
2325
|
+
* The line of a failed tool result that names the cause, for a message the
|
|
2326
|
+
* user reads. It takes the credential-sanitized text and keeps identifiers and
|
|
2327
|
+
* emails, since a column or account name is the diagnosis; the trace-grade
|
|
2328
|
+
* `toolErrorSignature` redacts those. A pretty-printed JSON error opens with
|
|
2329
|
+
* `Error running x: {`, and a `(errorCode: ...)` suffix keeps it from parsing,
|
|
2330
|
+
* so a bare first line can be only a bracket.
|
|
2331
|
+
*/
|
|
2332
|
+
function stopErrorLine(sanitizedResult) {
|
|
2333
|
+
const lines = sanitizedResult.split(/\r?\n/).filter((line) => line.trim());
|
|
2334
|
+
for (const candidate of [sanitizedResult, ...lines]) {
|
|
2335
|
+
const cause = errorCauseLine(candidate);
|
|
2336
|
+
if (/[\p{L}\p{N}]/u.test(cause))
|
|
2337
|
+
return cause;
|
|
2338
|
+
}
|
|
2339
|
+
return errorCauseLine(sanitizedResult) || "(no error text)";
|
|
2340
|
+
}
|
|
2258
2341
|
function isToolCallTimeoutResult(content) {
|
|
2259
2342
|
return /tool call timed out after \d+(?:\.\d+)? seconds?/i.test(content);
|
|
2260
2343
|
}
|
|
@@ -2500,30 +2583,9 @@ const DEFAULT_INITIAL_TOOL_NAMES = new Set([
|
|
|
2500
2583
|
function isDefaultInitialToolName(name) {
|
|
2501
2584
|
return DEFAULT_INITIAL_TOOL_NAMES.has(name);
|
|
2502
2585
|
}
|
|
2503
|
-
function
|
|
2504
|
-
if (!value || typeof value !== "object")
|
|
2505
|
-
return [];
|
|
2506
|
-
const result = value;
|
|
2507
|
-
if (typeof result.query !== "string" || result.query.trim().length === 0) {
|
|
2508
|
-
return [];
|
|
2509
|
-
}
|
|
2510
|
-
if (!Array.isArray(result.results))
|
|
2511
|
-
return [];
|
|
2512
|
-
const names = [];
|
|
2513
|
-
for (const item of result.results) {
|
|
2514
|
-
if (!item || typeof item !== "object")
|
|
2515
|
-
continue;
|
|
2516
|
-
const record = item;
|
|
2517
|
-
if (record.callable === false)
|
|
2518
|
-
continue;
|
|
2519
|
-
const name = record.name;
|
|
2520
|
-
if (typeof name === "string" && name.trim())
|
|
2521
|
-
names.push(name);
|
|
2522
|
-
}
|
|
2523
|
-
return names;
|
|
2524
|
-
}
|
|
2525
|
-
function extractToolSearchResultNamesFromMessages(messages) {
|
|
2586
|
+
function readToolSearchHistory(messages) {
|
|
2526
2587
|
const names = [];
|
|
2588
|
+
let unreadable = 0;
|
|
2527
2589
|
for (const message of messages) {
|
|
2528
2590
|
if (message.role !== "user")
|
|
2529
2591
|
continue;
|
|
@@ -2533,15 +2595,14 @@ function extractToolSearchResultNamesFromMessages(messages) {
|
|
|
2533
2595
|
typeof part.content !== "string") {
|
|
2534
2596
|
continue;
|
|
2535
2597
|
}
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
}
|
|
2598
|
+
const loaded = readLoadedToolNames(part.content);
|
|
2599
|
+
if (loaded)
|
|
2600
|
+
names.push(...loaded);
|
|
2601
|
+
else
|
|
2602
|
+
unreadable += 1;
|
|
2542
2603
|
}
|
|
2543
2604
|
}
|
|
2544
|
-
return names;
|
|
2605
|
+
return { names, unreadable };
|
|
2545
2606
|
}
|
|
2546
2607
|
function normalizeToolInputSchema(schema) {
|
|
2547
2608
|
if (!schema)
|
|
@@ -3347,6 +3408,8 @@ export async function runAgentLoop(opts) {
|
|
|
3347
3408
|
: providedAvailableTools;
|
|
3348
3409
|
let completedFollowUpSuggestions;
|
|
3349
3410
|
let completingFollowUpSuggestions = false;
|
|
3411
|
+
let followUpCompletionContext = [];
|
|
3412
|
+
let followUpStartedAt;
|
|
3350
3413
|
let followUpCompletionFailed = false;
|
|
3351
3414
|
const failFollowUpCompletion = (code) => {
|
|
3352
3415
|
if (followUpCompletionFailed)
|
|
@@ -3382,8 +3445,8 @@ export async function runAgentLoop(opts) {
|
|
|
3382
3445
|
const finalResponseGuardRequestText = opts.finalResponseGuardRequestText ??
|
|
3383
3446
|
resolveFinalResponseGuardRequestText(messages);
|
|
3384
3447
|
const availableToolMap = new Map((availableTools ?? tools).map((tool) => [tool.name, tool]));
|
|
3385
|
-
|
|
3386
|
-
|
|
3448
|
+
let activeTools = limitProviderTools(tools, PROVIDER_CORE_TOOL_NAMES);
|
|
3449
|
+
const activeToolNames = new Set(activeTools.map((tool) => tool.name));
|
|
3387
3450
|
let appAuthorizationPromise;
|
|
3388
3451
|
const resolveTurnAppAuthorization = (userEmail, orgId) => {
|
|
3389
3452
|
if (!appAuthorizationPromise) {
|
|
@@ -3401,36 +3464,85 @@ export async function runAgentLoop(opts) {
|
|
|
3401
3464
|
};
|
|
3402
3465
|
let expandedToolSchemaBytes = 0;
|
|
3403
3466
|
let reportedExpandedToolSchemaBytes = false;
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3467
|
+
let alreadyLoadedToolSearches = 0;
|
|
3468
|
+
// Tools loaded after the first request, least recently needed first.
|
|
3469
|
+
const loadedToolNames = new Set();
|
|
3470
|
+
const noteToolNeeded = (name) => {
|
|
3471
|
+
if (loadedToolNames.delete(name))
|
|
3472
|
+
loadedToolNames.add(name);
|
|
3473
|
+
};
|
|
3474
|
+
// `names` lead with what the model needs most. Below MAX_PROVIDER_TOOLS they
|
|
3475
|
+
// append in registry order, so the tools prefix of the prompt cache survives.
|
|
3476
|
+
// Past it the request keeps, in this order: tool-search and the follow-up
|
|
3477
|
+
// tool, `pinned` (already promised this step) and `names`, the initial
|
|
3478
|
+
// tools, then the other loaded tools most recently needed first, so the least
|
|
3479
|
+
// recently needed loaded tools are evicted before any initial one. A tool
|
|
3480
|
+
// that still does not fit is returned as overflow and never becomes active,
|
|
3481
|
+
// so `activeTools` is exactly what the engine sends and nothing it dropped is
|
|
3482
|
+
// reported as loaded.
|
|
3483
|
+
const expandActiveTools = (names, pinned = new Set()) => {
|
|
3484
|
+
const requested = new Set(names);
|
|
3485
|
+
for (const name of requested)
|
|
3486
|
+
noteToolNeeded(name);
|
|
3487
|
+
const wanted = [...requested].flatMap((name) => {
|
|
3409
3488
|
const tool = availableToolMap.get(name);
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
...
|
|
3424
|
-
|
|
3489
|
+
return tool && !activeToolNames.has(name) ? [tool] : [];
|
|
3490
|
+
});
|
|
3491
|
+
const added = [];
|
|
3492
|
+
const overflow = [];
|
|
3493
|
+
if (wanted.length > 0) {
|
|
3494
|
+
if (activeTools.length + wanted.length <= MAX_PROVIDER_TOOLS) {
|
|
3495
|
+
const wantedNames = new Set(wanted.map((tool) => tool.name));
|
|
3496
|
+
activeTools = [
|
|
3497
|
+
...activeTools,
|
|
3498
|
+
...(availableTools ?? tools).filter((tool) => wantedNames.has(tool.name)),
|
|
3499
|
+
];
|
|
3500
|
+
}
|
|
3501
|
+
else {
|
|
3502
|
+
const byName = new Map([...activeTools, ...wanted].map((tool) => [tool.name, tool]));
|
|
3503
|
+
const priority = new Set([
|
|
3504
|
+
...PROVIDER_CORE_TOOL_NAMES,
|
|
3505
|
+
...pinned,
|
|
3506
|
+
...names,
|
|
3507
|
+
...activeTools
|
|
3508
|
+
.map((tool) => tool.name)
|
|
3509
|
+
.filter((name) => !loadedToolNames.has(name)),
|
|
3510
|
+
...[...loadedToolNames].reverse(),
|
|
3511
|
+
]);
|
|
3512
|
+
activeTools = [...priority]
|
|
3513
|
+
.flatMap((name) => byName.get(name) ?? [])
|
|
3514
|
+
.slice(0, MAX_PROVIDER_TOOLS);
|
|
3515
|
+
}
|
|
3516
|
+
activeToolNames.clear();
|
|
3517
|
+
for (const tool of activeTools)
|
|
3518
|
+
activeToolNames.add(tool.name);
|
|
3519
|
+
for (const name of loadedToolNames) {
|
|
3520
|
+
if (!activeToolNames.has(name))
|
|
3521
|
+
loadedToolNames.delete(name);
|
|
3522
|
+
}
|
|
3523
|
+
for (const tool of wanted) {
|
|
3524
|
+
if (!activeToolNames.has(tool.name)) {
|
|
3525
|
+
overflow.push(tool.name);
|
|
3526
|
+
continue;
|
|
3527
|
+
}
|
|
3528
|
+
added.push(tool.name);
|
|
3529
|
+
loadedToolNames.add(tool.name);
|
|
3530
|
+
expandedToolSchemaBytes += JSON.stringify(tool).length;
|
|
3531
|
+
}
|
|
3425
3532
|
}
|
|
3426
3533
|
if (!reportedExpandedToolSchemaBytes &&
|
|
3427
3534
|
expandedToolSchemaBytes > EXPANDED_TOOL_SCHEMA_WARN_BYTES) {
|
|
3428
3535
|
reportedExpandedToolSchemaBytes = true;
|
|
3429
3536
|
console.warn(`[agent-loop] expanded tool schemas reached ${expandedToolSchemaBytes} bytes across ${activeToolNames.size} active tools (runId=${opts.runId ?? "none"})`);
|
|
3430
3537
|
}
|
|
3431
|
-
return added;
|
|
3538
|
+
return { added, overflow };
|
|
3432
3539
|
};
|
|
3433
|
-
|
|
3540
|
+
const priorSearches = readToolSearchHistory(messages);
|
|
3541
|
+
if (priorSearches.unreadable > 0) {
|
|
3542
|
+
console.warn(`[agent-loop] ${priorSearches.unreadable} earlier tool-search result(s) could not be read, so the tools they loaded were not restored (runId=${opts.runId ?? "none"})`);
|
|
3543
|
+
}
|
|
3544
|
+
// Newest searches lead, so they win if history alone overflows the cap.
|
|
3545
|
+
expandActiveTools(priorSearches.names.reverse());
|
|
3434
3546
|
const processorChain = opts.processors && opts.processors.length > 0
|
|
3435
3547
|
? new ProcessorChain(opts.processors)
|
|
3436
3548
|
: null;
|
|
@@ -3460,6 +3572,8 @@ export async function runAgentLoop(opts) {
|
|
|
3460
3572
|
actions,
|
|
3461
3573
|
});
|
|
3462
3574
|
const toolResultHistory = [];
|
|
3575
|
+
const turnReceipts = () => toolResultHistory.flatMap((result) => result.receipt ? [{ tool: result.name, ...result.receipt }] : []);
|
|
3576
|
+
let receiptGuardRetried = false;
|
|
3463
3577
|
const runCtx = getRequestRunContext();
|
|
3464
3578
|
if (runCtx) {
|
|
3465
3579
|
runCtx.toolCalls = toolCallHistory;
|
|
@@ -3618,44 +3732,28 @@ export async function runAgentLoop(opts) {
|
|
|
3618
3732
|
let terminalStopReason;
|
|
3619
3733
|
const toolCallErrors = new Map();
|
|
3620
3734
|
let contextMessages = completingFollowUpSuggestions
|
|
3621
|
-
?
|
|
3622
|
-
...messages,
|
|
3623
|
-
{
|
|
3624
|
-
role: "user",
|
|
3625
|
-
content: [
|
|
3626
|
-
{
|
|
3627
|
-
type: "text",
|
|
3628
|
-
text: FOLLOW_UP_SUGGESTIONS_COMPLETION_INSTRUCTION,
|
|
3629
|
-
},
|
|
3630
|
-
],
|
|
3631
|
-
},
|
|
3632
|
-
]
|
|
3735
|
+
? followUpCompletionContext
|
|
3633
3736
|
: messages;
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3737
|
+
// The completion call carries a digest, not the conversation: the manifest
|
|
3738
|
+
// and memory window describe the full thread, and the transform would
|
|
3739
|
+
// overwrite this turn's manifest with the digest.
|
|
3740
|
+
if (opts.threadId && !completingFollowUpSuggestions) {
|
|
3741
|
+
contextMessages = await applyContextXrayTransformForIteration({
|
|
3742
|
+
threadId: opts.threadId,
|
|
3743
|
+
ownerEmail: opts.ownerEmail,
|
|
3744
|
+
turnId: opts.turnId,
|
|
3745
|
+
model,
|
|
3746
|
+
messages: contextMessages,
|
|
3747
|
+
systemSections: opts.systemSections,
|
|
3748
|
+
});
|
|
3749
|
+
if (opts.ownerEmail) {
|
|
3750
|
+
contextMessages = await applyObservationalMemoryToContext(contextMessages, {
|
|
3637
3751
|
threadId: opts.threadId,
|
|
3638
3752
|
ownerEmail: opts.ownerEmail,
|
|
3639
|
-
|
|
3640
|
-
model,
|
|
3641
|
-
messages: contextMessages,
|
|
3642
|
-
systemSections: opts.systemSections,
|
|
3753
|
+
orgId: opts.orgId ?? null,
|
|
3643
3754
|
});
|
|
3644
|
-
if (opts.ownerEmail) {
|
|
3645
|
-
contextMessages = await applyObservationalMemoryToContext(contextMessages, {
|
|
3646
|
-
threadId: opts.threadId,
|
|
3647
|
-
ownerEmail: opts.ownerEmail,
|
|
3648
|
-
orgId: opts.orgId ?? null,
|
|
3649
|
-
});
|
|
3650
|
-
}
|
|
3651
3755
|
}
|
|
3652
3756
|
}
|
|
3653
|
-
catch (error) {
|
|
3654
|
-
if (signal.aborted || !completingFollowUpSuggestions)
|
|
3655
|
-
throw error;
|
|
3656
|
-
failFollowUpCompletion("context_error");
|
|
3657
|
-
break;
|
|
3658
|
-
}
|
|
3659
3757
|
if (signal.aborted)
|
|
3660
3758
|
break;
|
|
3661
3759
|
for (let retry = 0;; retry++) {
|
|
@@ -3674,7 +3772,9 @@ export async function runAgentLoop(opts) {
|
|
|
3674
3772
|
}
|
|
3675
3773
|
const streamOpts = {
|
|
3676
3774
|
model,
|
|
3677
|
-
systemPrompt:
|
|
3775
|
+
systemPrompt: completingFollowUpSuggestions
|
|
3776
|
+
? FOLLOW_UP_SUGGESTIONS_COMPLETION_SYSTEM_PROMPT
|
|
3777
|
+
: continuationSystemPrompt,
|
|
3678
3778
|
messages: contextMessages,
|
|
3679
3779
|
tools: loopBreakerCloseout
|
|
3680
3780
|
? []
|
|
@@ -3949,6 +4049,10 @@ export async function runAgentLoop(opts) {
|
|
|
3949
4049
|
usage.outputTokens += eventUsage.outputTokens;
|
|
3950
4050
|
usage.cacheReadTokens += eventUsage.cacheReadTokens;
|
|
3951
4051
|
usage.cacheWriteTokens += eventUsage.cacheWriteTokens;
|
|
4052
|
+
if (completingFollowUpSuggestions) {
|
|
4053
|
+
usage.followUpInputTokens =
|
|
4054
|
+
(usage.followUpInputTokens ?? 0) + eventUsage.inputTokens;
|
|
4055
|
+
}
|
|
3952
4056
|
if (eventUsage.builderCreditsUsed !== undefined) {
|
|
3953
4057
|
usage.builderCreditsUsed =
|
|
3954
4058
|
(usage.builderCreditsUsed ?? 0) +
|
|
@@ -4070,6 +4174,9 @@ export async function runAgentLoop(opts) {
|
|
|
4070
4174
|
throw err;
|
|
4071
4175
|
}
|
|
4072
4176
|
}
|
|
4177
|
+
if (completingFollowUpSuggestions && followUpStartedAt !== undefined) {
|
|
4178
|
+
usage.followUpMs = Date.now() - followUpStartedAt;
|
|
4179
|
+
}
|
|
4073
4180
|
if (tripwire || followUpCompletionFailed)
|
|
4074
4181
|
break;
|
|
4075
4182
|
if (!assistantContent && toolCallErrors.size > 0) {
|
|
@@ -4252,6 +4359,8 @@ export async function runAgentLoop(opts) {
|
|
|
4252
4359
|
}
|
|
4253
4360
|
let guard = null;
|
|
4254
4361
|
const finalResponseDraftText = collectTextParts(assistantContentForHistory);
|
|
4362
|
+
const receipts = turnReceipts();
|
|
4363
|
+
const receiptGuard = writeReceiptGuard(receipts, receiptGuardRetried);
|
|
4255
4364
|
if (opts.finalResponseGuard) {
|
|
4256
4365
|
try {
|
|
4257
4366
|
guard = await opts.finalResponseGuard({
|
|
@@ -4261,6 +4370,7 @@ export async function runAgentLoop(opts) {
|
|
|
4261
4370
|
text: finalResponseDraftText,
|
|
4262
4371
|
toolCalls: [...toolCallHistory],
|
|
4263
4372
|
toolResults: [...toolResultHistory],
|
|
4373
|
+
receipts,
|
|
4264
4374
|
retryCount: finalGuardRetries,
|
|
4265
4375
|
executionMode: opts.executionMode ?? "act",
|
|
4266
4376
|
});
|
|
@@ -4270,6 +4380,11 @@ export async function runAgentLoop(opts) {
|
|
|
4270
4380
|
throw err;
|
|
4271
4381
|
}
|
|
4272
4382
|
}
|
|
4383
|
+
if (receiptGuard) {
|
|
4384
|
+
guard = guard
|
|
4385
|
+
? mergeFinalResponseGuards(receiptGuard, guard)
|
|
4386
|
+
: receiptGuard;
|
|
4387
|
+
}
|
|
4273
4388
|
if (guard) {
|
|
4274
4389
|
completedFollowUpSuggestions = undefined;
|
|
4275
4390
|
const retryMessage = typeof guard === "string" ? guard : guard.retryMessage;
|
|
@@ -4279,7 +4394,10 @@ export async function runAgentLoop(opts) {
|
|
|
4279
4394
|
: Math.max(0, Math.min(3, Math.trunc(guard.maxRetries ?? 1)));
|
|
4280
4395
|
if (finalGuardRetries < maxGuardRetries) {
|
|
4281
4396
|
if (typeof guard !== "string" && guard.expandToolSurface) {
|
|
4282
|
-
expandActiveTools([...availableToolMap.keys()]);
|
|
4397
|
+
expandActiveTools([...activeToolNames, ...availableToolMap.keys()]);
|
|
4398
|
+
}
|
|
4399
|
+
if (receiptGuard && receiptGuard.maxRetries > 0) {
|
|
4400
|
+
receiptGuardRetried = true;
|
|
4283
4401
|
}
|
|
4284
4402
|
finalGuardRetries += 1;
|
|
4285
4403
|
send({ type: "clear" });
|
|
@@ -4309,6 +4427,12 @@ export async function runAgentLoop(opts) {
|
|
|
4309
4427
|
completedFollowUpSuggestions === undefined &&
|
|
4310
4428
|
terminalStopReason === "end_turn") {
|
|
4311
4429
|
completingFollowUpSuggestions = true;
|
|
4430
|
+
followUpStartedAt = Date.now();
|
|
4431
|
+
followUpCompletionContext = buildFollowUpCompletionMessages({
|
|
4432
|
+
requestText: finalResponseGuardRequestText,
|
|
4433
|
+
replyText: finalResponseDraftText,
|
|
4434
|
+
toolNames: toolCallHistory.map((call) => call.name),
|
|
4435
|
+
});
|
|
4312
4436
|
continue;
|
|
4313
4437
|
}
|
|
4314
4438
|
}
|
|
@@ -4370,7 +4494,12 @@ export async function runAgentLoop(opts) {
|
|
|
4370
4494
|
return requestedActionStop === stop ? stop : null;
|
|
4371
4495
|
};
|
|
4372
4496
|
const approvedToolCallKeys = new Set(opts.approvedToolCalls ?? []);
|
|
4497
|
+
// A tool-search in this step loads schemas the model can call only on the
|
|
4498
|
+
// next step, so a sibling search must not read them as already callable.
|
|
4499
|
+
const toolNamesLoadedThisStep = new Set();
|
|
4500
|
+
let countedAlreadyLoadedSearchThisStep = false;
|
|
4373
4501
|
const runToolCall = async (toolCall) => {
|
|
4502
|
+
noteToolNeeded(toolCall.name);
|
|
4374
4503
|
const actionEntry = actions[toolCall.name];
|
|
4375
4504
|
const placeholderNormalization = actionEntry
|
|
4376
4505
|
? normalizeOptionalToolPlaceholders(actionEntry.tool.parameters, toolCall.input)
|
|
@@ -4442,12 +4571,13 @@ export async function runAgentLoop(opts) {
|
|
|
4442
4571
|
name: toolCall.name,
|
|
4443
4572
|
input: normalizedToolInput,
|
|
4444
4573
|
});
|
|
4445
|
-
const recordToolResult = (content, isError, artifacts) => {
|
|
4574
|
+
const recordToolResult = (content, isError, artifacts, receipt) => {
|
|
4446
4575
|
toolResultHistory.push({
|
|
4447
4576
|
name: toolCall.name,
|
|
4448
4577
|
content,
|
|
4449
4578
|
isError,
|
|
4450
4579
|
...(artifacts?.length ? { artifacts } : {}),
|
|
4580
|
+
...(receipt ? { receipt } : {}),
|
|
4451
4581
|
});
|
|
4452
4582
|
};
|
|
4453
4583
|
let directStop = null;
|
|
@@ -4483,9 +4613,12 @@ export async function runAgentLoop(opts) {
|
|
|
4483
4613
|
anyArgsCount >= MAX_SAME_ERROR_ACROSS_ARGUMENTS) {
|
|
4484
4614
|
const result = `Stopped after ${anyArgsCount} attempts at ${toolCall.name} that all failed the same way ` +
|
|
4485
4615
|
`with different arguments. Last error: ${sanitizedResult}`;
|
|
4616
|
+
const lastErrorLine = stopErrorLine(sanitizedResult);
|
|
4486
4617
|
requestedActionStop ??= {
|
|
4487
|
-
message: `I stopped because the ${toolCall.name} action rejected ${anyArgsCount} different attempts the same way
|
|
4488
|
-
|
|
4618
|
+
message: `I stopped because the ${toolCall.name} action rejected ${anyArgsCount} different attempts the same way. ` +
|
|
4619
|
+
`Last error: ${lastErrorLine.length > MAX_STOP_ERROR_LINE_CHARS
|
|
4620
|
+
? `${clipHead(lastErrorLine, MAX_STOP_ERROR_LINE_CHARS)}…`
|
|
4621
|
+
: lastErrorLine}\n\nAnything completed before this is saved.`,
|
|
4489
4622
|
errorCode: "repeated_tool_error_across_arguments",
|
|
4490
4623
|
details: sanitizedResult,
|
|
4491
4624
|
};
|
|
@@ -4955,6 +5088,7 @@ export async function runAgentLoop(opts) {
|
|
|
4955
5088
|
let toolResultImages;
|
|
4956
5089
|
let toolArtifacts = [];
|
|
4957
5090
|
let fileMutation;
|
|
5091
|
+
let receipt;
|
|
4958
5092
|
try {
|
|
4959
5093
|
// The run may have been aborted while we waited above for an
|
|
4960
5094
|
// interrupted tool's ledger result (the wait can poll for minutes).
|
|
@@ -5083,6 +5217,8 @@ export async function runAgentLoop(opts) {
|
|
|
5083
5217
|
}
|
|
5084
5218
|
}
|
|
5085
5219
|
chatUIResult = resultForAgent;
|
|
5220
|
+
// Before stringify and truncation: a large result must not lose it.
|
|
5221
|
+
receipt = readWriteReceipt(resultForAgent);
|
|
5086
5222
|
toolArtifacts = detectArtifactReceipts(resultForAgent, toolCall.name);
|
|
5087
5223
|
if (toolResultImages) {
|
|
5088
5224
|
imageNotes = [
|
|
@@ -5090,6 +5226,18 @@ export async function runAgentLoop(opts) {
|
|
|
5090
5226
|
...imageNotes,
|
|
5091
5227
|
];
|
|
5092
5228
|
}
|
|
5229
|
+
let toolSearch;
|
|
5230
|
+
if (toolCall.name === TOOL_SEARCH_ACTION_NAME && !isError) {
|
|
5231
|
+
const matched = extractToolSearchResultNames(rawForAgent);
|
|
5232
|
+
const { added, overflow } = expandActiveTools(matched, toolNamesLoadedThisStep);
|
|
5233
|
+
for (const name of added)
|
|
5234
|
+
toolNamesLoadedThisStep.add(name);
|
|
5235
|
+
const loadedThisStep = matched.filter((name) => toolNamesLoadedThisStep.has(name));
|
|
5236
|
+
toolSearch = { matched, overflow, loadedThisStep };
|
|
5237
|
+
if (matched.length > 0) {
|
|
5238
|
+
resultForAgent = withLoadedToolNames(rawForAgent, loadedThisStep);
|
|
5239
|
+
}
|
|
5240
|
+
}
|
|
5093
5241
|
let resultStr = typeof resultForAgent === "string"
|
|
5094
5242
|
? resultForAgent
|
|
5095
5243
|
: JSON.stringify(resultForAgent, null, 2);
|
|
@@ -5101,10 +5249,63 @@ export async function runAgentLoop(opts) {
|
|
|
5101
5249
|
resultStr = `${resultStr}\n\n${imageNotes.join("\n")}`;
|
|
5102
5250
|
}
|
|
5103
5251
|
result = resultStr;
|
|
5104
|
-
if (
|
|
5105
|
-
const
|
|
5106
|
-
|
|
5107
|
-
|
|
5252
|
+
if (toolSearch) {
|
|
5253
|
+
const { matched, overflow, loadedThisStep } = toolSearch;
|
|
5254
|
+
const loadedNote = `Loaded matching tool schemas for the next step, not this one: ${loadedThisStep.join(", ")}`;
|
|
5255
|
+
if (loadedThisStep.length > 0) {
|
|
5256
|
+
result += `\n\n${loadedNote}`;
|
|
5257
|
+
alreadyLoadedToolSearches = 0;
|
|
5258
|
+
}
|
|
5259
|
+
else if (matched.length > 0 &&
|
|
5260
|
+
matched.every((name) => activeToolNames.has(name))) {
|
|
5261
|
+
const priorMessage = rawForAgent
|
|
5262
|
+
.message;
|
|
5263
|
+
result = JSON.stringify({
|
|
5264
|
+
...resultForAgent,
|
|
5265
|
+
alreadyLoaded: true,
|
|
5266
|
+
message: [
|
|
5267
|
+
`All ${matched.length} matches are already callable; call them directly.`,
|
|
5268
|
+
typeof priorMessage === "string" ? priorMessage : "",
|
|
5269
|
+
]
|
|
5270
|
+
.filter(Boolean)
|
|
5271
|
+
.join(" "),
|
|
5272
|
+
}, null, 2);
|
|
5273
|
+
// One count per step, reset by any progress: parallel searches
|
|
5274
|
+
// are one repeat, and a run that searches between writes is not
|
|
5275
|
+
// repeating.
|
|
5276
|
+
if (!countedAlreadyLoadedSearchThisStep) {
|
|
5277
|
+
countedAlreadyLoadedSearchThisStep = true;
|
|
5278
|
+
alreadyLoadedToolSearches += 1;
|
|
5279
|
+
}
|
|
5280
|
+
if (alreadyLoadedToolSearches >= MAX_ALREADY_LOADED_TOOL_SEARCHES) {
|
|
5281
|
+
requestedActionStop ??= {
|
|
5282
|
+
message: `Stopped because tool-search was used ${alreadyLoadedToolSearches} times in a row to look for tools that were already callable, ` +
|
|
5283
|
+
"which means the same step is repeating rather than making progress. " +
|
|
5284
|
+
"Everything completed before this point is preserved above.",
|
|
5285
|
+
errorCode: "repeated_tool_call",
|
|
5286
|
+
};
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
if (overflow.length > 0) {
|
|
5290
|
+
const shown = overflow.slice(0, MAX_TOOL_SEARCH_OVERFLOW_NAMES_SHOWN);
|
|
5291
|
+
const more = overflow.length > shown.length
|
|
5292
|
+
? `, and ${overflow.length - shown.length} more`
|
|
5293
|
+
: "";
|
|
5294
|
+
const overflowNote = `Could not load ${overflow.length} matched ${overflow.length === 1 ? "tool" : "tools"} because the per-request tool limit (${MAX_PROVIDER_TOOLS}) was reached: ${shown.join(", ")}${more}. They are not callable; use tools you already have.`;
|
|
5295
|
+
// A repeated search's own message says its matches are
|
|
5296
|
+
// available, which these are not.
|
|
5297
|
+
result =
|
|
5298
|
+
rawForAgent.repeated === true
|
|
5299
|
+
? JSON.stringify({
|
|
5300
|
+
...resultForAgent,
|
|
5301
|
+
message: [
|
|
5302
|
+
loadedThisStep.length > 0 ? loadedNote : "",
|
|
5303
|
+
overflowNote,
|
|
5304
|
+
]
|
|
5305
|
+
.filter(Boolean)
|
|
5306
|
+
.join(" "),
|
|
5307
|
+
}, null, 2)
|
|
5308
|
+
: `${result}\n\n${overflowNote}`;
|
|
5108
5309
|
}
|
|
5109
5310
|
}
|
|
5110
5311
|
}
|
|
@@ -5168,12 +5369,25 @@ export async function runAgentLoop(opts) {
|
|
|
5168
5369
|
writeToolInterruptions.set(key, (writeToolInterruptions.get(key) ?? 0) + 1);
|
|
5169
5370
|
}
|
|
5170
5371
|
if (isError) {
|
|
5372
|
+
receipt = undefined;
|
|
5171
5373
|
if (result !== INTERRUPTED_TOOL_RESULT_MARKER) {
|
|
5172
5374
|
result = finalizeToolErrorResult(result);
|
|
5173
5375
|
}
|
|
5174
5376
|
}
|
|
5175
5377
|
else {
|
|
5176
5378
|
fileMutation = actionEntry.fileMutationProof?.(toolCall.input);
|
|
5379
|
+
// Only a write or a receipt is progress; a read-only call between
|
|
5380
|
+
// redundant searches is the same repeating step.
|
|
5381
|
+
if (!actionIsReadOnly || receipt)
|
|
5382
|
+
alreadyLoadedToolSearches = 0;
|
|
5383
|
+
if (receipt && receipt.verified !== true) {
|
|
5384
|
+
usage.receiptUnverifiedCount =
|
|
5385
|
+
(usage.receiptUnverifiedCount ?? 0) + 1;
|
|
5386
|
+
}
|
|
5387
|
+
if (receipt && !receipt.changed) {
|
|
5388
|
+
usage.receiptChangedFalseCount =
|
|
5389
|
+
(usage.receiptChangedFalseCount ?? 0) + 1;
|
|
5390
|
+
}
|
|
5177
5391
|
}
|
|
5178
5392
|
const agentWarnings = drainAgentWarnings();
|
|
5179
5393
|
if (agentWarnings.length > 0) {
|
|
@@ -5207,16 +5421,18 @@ export async function runAgentLoop(opts) {
|
|
|
5207
5421
|
...(isError ? { isError: true } : {}),
|
|
5208
5422
|
...(isError
|
|
5209
5423
|
? { completedSideEffect: false }
|
|
5210
|
-
:
|
|
5211
|
-
? { completedSideEffect:
|
|
5212
|
-
:
|
|
5424
|
+
: receipt
|
|
5425
|
+
? { completedSideEffect: receipt.changed }
|
|
5426
|
+
: !actionIsReadOnly
|
|
5427
|
+
? { completedSideEffect: true }
|
|
5428
|
+
: {}),
|
|
5213
5429
|
...(mcpApp ? { mcpApp } : {}),
|
|
5214
5430
|
...(resolvedChatUI ? { chatUI: resolvedChatUI.chatUI } : {}),
|
|
5215
5431
|
...(resolvedChatUI ? { chatUIResult: resolvedChatUI.result } : {}),
|
|
5216
5432
|
...(fileMutation ? { fileMutation } : {}),
|
|
5217
5433
|
...(toolArtifacts.length > 0 ? { artifacts: toolArtifacts } : {}),
|
|
5218
5434
|
});
|
|
5219
|
-
recordToolResult(result, isError, toolArtifacts);
|
|
5435
|
+
recordToolResult(result, isError, toolArtifacts, receipt);
|
|
5220
5436
|
if (!isError) {
|
|
5221
5437
|
noteToolCallSucceeded(actionEntry);
|
|
5222
5438
|
if (cacheKey) {
|
|
@@ -5675,26 +5891,7 @@ export async function runAgentLoopWithMainChatInternalContinuations(opts) {
|
|
|
5675
5891
|
engineName: opts.engine.name,
|
|
5676
5892
|
model: opts.model,
|
|
5677
5893
|
};
|
|
5678
|
-
const addUsage = (next) =>
|
|
5679
|
-
usage.inputTokens += next.inputTokens;
|
|
5680
|
-
usage.outputTokens += next.outputTokens;
|
|
5681
|
-
usage.cacheReadTokens += next.cacheReadTokens;
|
|
5682
|
-
usage.cacheWriteTokens += next.cacheWriteTokens;
|
|
5683
|
-
if (next.builderCreditsUsed !== undefined) {
|
|
5684
|
-
usage.builderCreditsUsed =
|
|
5685
|
-
(usage.builderCreditsUsed ?? 0) + next.builderCreditsUsed;
|
|
5686
|
-
}
|
|
5687
|
-
usage.engineName = next.engineName ?? usage.engineName;
|
|
5688
|
-
usage.model = next.model;
|
|
5689
|
-
if (typeof next.llmCalls === "number") {
|
|
5690
|
-
usage.llmCalls = (usage.llmCalls ?? 0) + next.llmCalls;
|
|
5691
|
-
}
|
|
5692
|
-
if (next.usageReported)
|
|
5693
|
-
usage.usageReported = true;
|
|
5694
|
-
// Keep the earliest attempt's first event — a later continuation
|
|
5695
|
-
// attempt starting fresh must not overwrite genuine first-token timing.
|
|
5696
|
-
usage.firstEngineEventAtMs ??= next.firstEngineEventAtMs;
|
|
5697
|
-
};
|
|
5894
|
+
const addUsage = (next) => mergeAgentLoopUsage(usage, next);
|
|
5698
5895
|
const budgetStartedAt = opts.budgetStartedAt ?? Date.now();
|
|
5699
5896
|
const resumeResumableErrorsInProcess = opts.resumeResumableErrorsInProcess === true;
|
|
5700
5897
|
const maxContinuations = typeof opts.maxContinuations === "number" && opts.maxContinuations > 0
|
|
@@ -6832,21 +7029,14 @@ export function createProductionAgentHandler(options) {
|
|
|
6832
7029
|
const storedModel = !requestModelIsExplicit && !configuredModelIsExplicit
|
|
6833
7030
|
? await getStoredModelForEngine(engine, { appId: options.appId })
|
|
6834
7031
|
: undefined;
|
|
6835
|
-
const modelSelection = resolveAgentModelSelection({
|
|
7032
|
+
const modelSelection = await applyUncheckedDefaultModelReplacement(engine, resolveAgentModelSelection({
|
|
6836
7033
|
requestModel,
|
|
6837
7034
|
configuredModel,
|
|
6838
7035
|
storedModel,
|
|
6839
7036
|
defaultModel: engine.defaultModel,
|
|
6840
|
-
});
|
|
6841
|
-
// Only the engine default yields to the provider's checked models. A model
|
|
6842
|
-
// the request or a stored default names still runs after it is unchecked,
|
|
6843
|
-
// so chats already on it keep working.
|
|
6844
|
-
const modelCandidate = modelSelection.source === "default"
|
|
6845
|
-
? ((await resolveUncheckedDefaultModelReplacement(engine)) ??
|
|
6846
|
-
modelSelection.model)
|
|
6847
|
-
: modelSelection.model;
|
|
7037
|
+
}));
|
|
6848
7038
|
workerStep("model_done");
|
|
6849
|
-
const model = normalizeModelForEngine(engine,
|
|
7039
|
+
const model = normalizeModelForEngine(engine, modelSelection.model);
|
|
6850
7040
|
let effectiveModel = model;
|
|
6851
7041
|
let modelSelectionSource = modelSelection.source;
|
|
6852
7042
|
const turnUsageLabel = normalizeUsageLabel(body.usageLabel);
|