@auvira.ai/sdk 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +105 -0
  2. package/dist/agent/Agent.d.ts.map +1 -1
  3. package/dist/agent/Agent.js +9 -0
  4. package/dist/agent/Agent.js.map +1 -1
  5. package/dist/agent/attachmentContext.d.ts +9 -0
  6. package/dist/agent/attachmentContext.d.ts.map +1 -0
  7. package/dist/agent/attachmentContext.js +72 -0
  8. package/dist/agent/attachmentContext.js.map +1 -0
  9. package/dist/agent/editCompletion.d.ts +18 -3
  10. package/dist/agent/editCompletion.d.ts.map +1 -1
  11. package/dist/agent/editCompletion.js +113 -1
  12. package/dist/agent/editCompletion.js.map +1 -1
  13. package/dist/agent/evaluateTaskCompletion.d.ts +2 -0
  14. package/dist/agent/evaluateTaskCompletion.d.ts.map +1 -1
  15. package/dist/agent/evaluateTaskCompletion.js +4 -1
  16. package/dist/agent/evaluateTaskCompletion.js.map +1 -1
  17. package/dist/agent/events.d.ts +2 -2
  18. package/dist/agent/events.d.ts.map +1 -1
  19. package/dist/agent/events.js +2 -0
  20. package/dist/agent/events.js.map +1 -1
  21. package/dist/agent/hostTools.d.ts +46 -0
  22. package/dist/agent/hostTools.d.ts.map +1 -0
  23. package/dist/agent/hostTools.js +98 -0
  24. package/dist/agent/hostTools.js.map +1 -0
  25. package/dist/agent/parseSendPayload.d.ts +3 -1
  26. package/dist/agent/parseSendPayload.d.ts.map +1 -1
  27. package/dist/agent/parseSendPayload.js +16 -0
  28. package/dist/agent/parseSendPayload.js.map +1 -1
  29. package/dist/agent/runAgentTask.d.ts +4 -1
  30. package/dist/agent/runAgentTask.d.ts.map +1 -1
  31. package/dist/agent/runAgentTask.js +16 -0
  32. package/dist/agent/runAgentTask.js.map +1 -1
  33. package/dist/agent/runValidationWithRepair.d.ts.map +1 -1
  34. package/dist/agent/runValidationWithRepair.js +6 -0
  35. package/dist/agent/runValidationWithRepair.js.map +1 -1
  36. package/dist/agent/tools/assetPathValidation.d.ts +19 -0
  37. package/dist/agent/tools/assetPathValidation.d.ts.map +1 -0
  38. package/dist/agent/tools/assetPathValidation.js +124 -0
  39. package/dist/agent/tools/assetPathValidation.js.map +1 -0
  40. package/dist/agent/tools/executeTool.d.ts +1 -1
  41. package/dist/agent/tools/executeTool.d.ts.map +1 -1
  42. package/dist/agent/tools/executeTool.js +31 -1
  43. package/dist/agent/tools/executeTool.js.map +1 -1
  44. package/dist/agent/tools/generateImage.d.ts +11 -0
  45. package/dist/agent/tools/generateImage.d.ts.map +1 -0
  46. package/dist/agent/tools/generateImage.js +98 -0
  47. package/dist/agent/tools/generateImage.js.map +1 -0
  48. package/dist/agent/tools/publishReferenceImage.d.ts +9 -0
  49. package/dist/agent/tools/publishReferenceImage.d.ts.map +1 -0
  50. package/dist/agent/tools/publishReferenceImage.js +61 -0
  51. package/dist/agent/tools/publishReferenceImage.js.map +1 -0
  52. package/dist/agent/tools/saveAsset.d.ts +11 -0
  53. package/dist/agent/tools/saveAsset.d.ts.map +1 -0
  54. package/dist/agent/tools/saveAsset.js +30 -0
  55. package/dist/agent/tools/saveAsset.js.map +1 -0
  56. package/dist/agent/tools/types.d.ts +7 -0
  57. package/dist/agent/tools/types.d.ts.map +1 -1
  58. package/dist/agent/types.d.ts +37 -0
  59. package/dist/agent/types.d.ts.map +1 -1
  60. package/dist/agent/types.js.map +1 -1
  61. package/dist/agent/validateOptions.d.ts.map +1 -1
  62. package/dist/agent/validateOptions.js +10 -1
  63. package/dist/agent/validateOptions.js.map +1 -1
  64. package/dist/index.d.ts +4 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/providers/agentic/AgenticCustomProvider.d.ts.map +1 -1
  69. package/dist/providers/agentic/AgenticCustomProvider.js +26 -0
  70. package/dist/providers/agentic/AgenticCustomProvider.js.map +1 -1
  71. package/dist/providers/agentic/agentLoop.d.ts +4 -1
  72. package/dist/providers/agentic/agentLoop.d.ts.map +1 -1
  73. package/dist/providers/agentic/agentLoop.js +295 -256
  74. package/dist/providers/agentic/agentLoop.js.map +1 -1
  75. package/dist/providers/agentic/toolSchemas.d.ts +438 -0
  76. package/dist/providers/agentic/toolSchemas.d.ts.map +1 -1
  77. package/dist/providers/agentic/toolSchemas.js +62 -0
  78. package/dist/providers/agentic/toolSchemas.js.map +1 -1
  79. package/dist/providers/types.d.ts +4 -1
  80. package/dist/providers/types.d.ts.map +1 -1
  81. package/dist/providers/types.js.map +1 -1
  82. package/dist/runner/jobTypes.d.ts +3 -2
  83. package/dist/runner/jobTypes.d.ts.map +1 -1
  84. package/dist/runner/run.d.ts.map +1 -1
  85. package/dist/runner/run.js +6 -2
  86. package/dist/runner/run.js.map +1 -1
  87. package/dist/runner/validateJob.d.ts.map +1 -1
  88. package/dist/runner/validateJob.js +83 -0
  89. package/dist/runner/validateJob.js.map +1 -1
  90. package/dist/util/minimaxImageClient.d.ts +25 -0
  91. package/dist/util/minimaxImageClient.d.ts.map +1 -0
  92. package/dist/util/minimaxImageClient.js +114 -0
  93. package/dist/util/minimaxImageClient.js.map +1 -0
  94. package/dist/workspace/referenceImages.d.ts +14 -0
  95. package/dist/workspace/referenceImages.d.ts.map +1 -0
  96. package/dist/workspace/referenceImages.js +60 -0
  97. package/dist/workspace/referenceImages.js.map +1 -0
  98. package/dist/workspace/workspaceCheckpoint.d.ts.map +1 -1
  99. package/dist/workspace/workspaceCheckpoint.js +148 -16
  100. package/dist/workspace/workspaceCheckpoint.js.map +1 -1
  101. package/dist/workspace/writeScreenshot.d.ts.map +1 -1
  102. package/dist/workspace/writeScreenshot.js +15 -7
  103. package/dist/workspace/writeScreenshot.js.map +1 -1
  104. package/docs/host-integration-image-placement.md +141 -0
  105. package/docs/sandbox-runner.md +43 -1
  106. package/package.json +6 -1
@@ -2,7 +2,8 @@ import { buildAssistantEvent } from "../../agent/assistantEvent.js";
2
2
  import { throwIfCancelled } from "../../agent/cancellation.js";
3
3
  import { emitAgentCompletionCheckThinking, emitAgentPlanningThinking, emitAgentRawModelTrace, emitAgentReflectionThinking, emitAgentToolSelectionThinking, } from "../../agent/emitAgentThinking.js";
4
4
  import { buildIncompleteContinuationNudge, computeCompletionEvaluationKey, evaluateTaskCompletion, getCompletionMaxContinues, getTaskCompletionConfidenceThreshold, } from "../../agent/evaluateTaskCompletion.js";
5
- import { evaluateHarnessCompletionGate, getWiringIncompleteNudge, isStyleOnlyDiff, } from "../../agent/editCompletion.js";
5
+ import { evaluateHarnessCompletionGate, getAssetPublishIncompleteNudge, getCompletionRulesIncompleteNudge, getWiringIncompleteNudge, isStyleOnlyDiff, } from "../../agent/editCompletion.js";
6
+ import { clearHostToolsForRun, hostToolsToOpenAiDefinitions, registerHostToolsForRun, resolveHostToolsForRun, } from "../../agent/hostTools.js";
6
7
  import { executeAgentTool, parseToolArguments, } from "../../agent/tools/executeTool.js";
7
8
  import { filterAllowlistedChangedFiles, } from "../../agent/tools/pathGuard.js";
8
9
  import { gitDiff } from "../../git/gitDiff.js";
@@ -12,7 +13,7 @@ import { formatRateLimitReport, formatTimingReport, postJsonWithRetry, } from ".
12
13
  import { applyCustomModelRequestDefaults, bumpMaxCompletionTokensForLengthRetry, getMaxCompletionTokens, } from "../custom/modelRequestDefaults.js";
13
14
  import { evaluateToolPolicy, getPrimaryMaxToolCalls, } from "./toolPolicy.js";
14
15
  import { trimAgentHistory, } from "./trimAgentHistory.js";
15
- import { AGENT_TOOL_DEFINITIONS } from "./toolSchemas.js";
16
+ import { buildAgentToolDefinitions } from "./toolSchemas.js";
16
17
  const MAX_LOOP_TURNS = 25;
17
18
  const LENGTH_RETRY_NUDGE = "Your previous response was truncated (length limit). Call one tool with minimal arguments.";
18
19
  function buildUserMessage(userPrompt, imageDataUrls) {
@@ -100,7 +101,7 @@ function buildEarlyFinishResult(input) {
100
101
  };
101
102
  }
102
103
  async function applyDeterministicCompletionGate(input) {
103
- const gate = evaluateHarnessCompletionGate({
104
+ const gate = await evaluateHarnessCompletionGate({
104
105
  changedFiles: input.allowlistedFiles,
105
106
  toolCallCount: input.policyState.toolCallCount,
106
107
  modelTurnCount: input.modelTurnCount,
@@ -108,12 +109,17 @@ async function applyDeterministicCompletionGate(input) {
108
109
  selectedDom: input.selectedDom,
109
110
  completion: input.completion,
110
111
  deferredFinishCount: input.completionState.deferredFinishCount,
112
+ repoPath: input.repoPath,
111
113
  });
112
114
  if (gate.action === "continue") {
113
115
  input.completionState.deferredFinishCount += 1;
114
- emitAgentReflectionThinking(input.bus, input.modelTurnCount, gate.continueReason === "style_only_incomplete"
115
- ? "Style changes are in place; continuing to wire components and config."
116
- : undefined);
116
+ emitAgentReflectionThinking(input.bus, input.modelTurnCount, gate.continueReason === "asset_publish_incomplete"
117
+ ? "Wiring references an image; continuing until publish_reference_image writes public/assets."
118
+ : gate.continueReason === "style_only_incomplete"
119
+ ? "Style changes are in place; continuing to wire components and config."
120
+ : gate.continueReason === "completion_callback"
121
+ ? "Completion rules not satisfied; continuing until required files change."
122
+ : undefined);
117
123
  input.bus?.emit({
118
124
  type: "run.continue",
119
125
  reason: gate.continueReason ?? "style_only_incomplete",
@@ -125,7 +131,11 @@ async function applyDeterministicCompletionGate(input) {
125
131
  });
126
132
  input.messages.push({
127
133
  role: "user",
128
- content: getWiringIncompleteNudge(),
134
+ content: gate.continueReason === "asset_publish_incomplete"
135
+ ? getAssetPublishIncompleteNudge()
136
+ : gate.continueReason === "completion_callback"
137
+ ? getCompletionRulesIncompleteNudge()
138
+ : getWiringIncompleteNudge(),
129
139
  });
130
140
  return undefined;
131
141
  }
@@ -172,7 +182,7 @@ async function tryHarnessEarlyFinish(input) {
172
182
  if (allowlistedFiles.length === 0) {
173
183
  return undefined;
174
184
  }
175
- const gateContext = evaluateHarnessCompletionGate({
185
+ const gateContext = await evaluateHarnessCompletionGate({
176
186
  changedFiles: allowlistedFiles,
177
187
  toolCallCount: input.policyState.toolCallCount,
178
188
  modelTurnCount: input.modelTurnCount,
@@ -180,6 +190,7 @@ async function tryHarnessEarlyFinish(input) {
180
190
  selectedDom: input.selectedDom,
181
191
  completion: input.completion,
182
192
  deferredFinishCount: input.completionState.deferredFinishCount,
193
+ repoPath: input.repoPath,
183
194
  });
184
195
  const useLlm = shouldUseLlmCompletionEvaluator({
185
196
  harnessMode: input.harnessMode,
@@ -316,6 +327,7 @@ async function tryHarnessEarlyFinish(input) {
316
327
  selectedDom: input.selectedDom,
317
328
  completion: input.completion,
318
329
  completionState: input.completionState,
330
+ repoPath: input.repoPath,
319
331
  });
320
332
  }
321
333
  /** Runs a multi-turn tool-calling loop against an OpenAI-compatible chat API. */
@@ -334,235 +346,295 @@ export async function runAgentToolLoop(input) {
334
346
  recentToolResults: [],
335
347
  evaluationCallCount: 0,
336
348
  };
349
+ const resolvedHostTools = resolveHostToolsForRun({
350
+ hostTools: input.hostTools,
351
+ includeAttachmentUrlsTool: Boolean(input.attachments?.length),
352
+ });
353
+ registerHostToolsForRun(resolvedHostTools);
354
+ const toolDefinitions = buildAgentToolDefinitions(hostToolsToOpenAiDefinitions(resolvedHostTools));
337
355
  const toolCtx = {
338
356
  repoPath: input.repoPath,
339
357
  allowedWritePaths: input.allowedWritePaths,
340
358
  enforceAllowlist: input.enforceAllowlist,
359
+ apiKey: input.apiKey,
360
+ signal: input.signal,
361
+ imageGenCount: 0,
362
+ attachments: input.attachments,
363
+ selectedDom: input.selectedDom,
341
364
  };
342
- for (let turn = 0; turn < MAX_LOOP_TURNS; turn += 1) {
343
- throwIfCancelled(input.signal);
344
- const modelTurnCount = turn + 1;
345
- if (policyState.abortReason) {
346
- const early = await tryHarnessEarlyFinish({
347
- harnessMode: input.harnessMode,
348
- allowedWritePaths: input.allowedWritePaths,
349
- repoPath: input.repoPath,
350
- workspaceCheckpoint: input.workspaceCheckpoint,
351
- bus: input.bus,
352
- policyState,
353
- editsApplied,
354
- lastAssistantText,
355
- loopStartMs,
356
- lastUsage,
357
- messages,
358
- modelTurnCount,
359
- hasVision: input.hasVision,
360
- selectedDom: input.selectedDom,
361
- completion: input.completion,
362
- completionAuto: input.completionAuto,
363
- completionEvaluationEnabled: input.completionEvaluationEnabled,
364
- completionConfidenceThreshold: input.completionConfidenceThreshold,
365
- referenceImages: input.referenceImages,
366
- ownerRequest: input.ownerRequest,
367
- model: input.model,
368
- timeoutMs: input.timeoutMs,
369
- signal: input.signal,
370
- completionState,
371
- });
372
- if (early) {
373
- return early;
365
+ try {
366
+ for (let turn = 0; turn < MAX_LOOP_TURNS; turn += 1) {
367
+ throwIfCancelled(input.signal);
368
+ const modelTurnCount = turn + 1;
369
+ if (policyState.abortReason) {
370
+ const early = await tryHarnessEarlyFinish({
371
+ harnessMode: input.harnessMode,
372
+ allowedWritePaths: input.allowedWritePaths,
373
+ repoPath: input.repoPath,
374
+ workspaceCheckpoint: input.workspaceCheckpoint,
375
+ bus: input.bus,
376
+ policyState,
377
+ editsApplied,
378
+ lastAssistantText,
379
+ loopStartMs,
380
+ lastUsage,
381
+ messages,
382
+ modelTurnCount,
383
+ hasVision: input.hasVision,
384
+ selectedDom: input.selectedDom,
385
+ completion: input.completion,
386
+ completionAuto: input.completionAuto,
387
+ completionEvaluationEnabled: input.completionEvaluationEnabled,
388
+ completionConfidenceThreshold: input.completionConfidenceThreshold,
389
+ referenceImages: input.referenceImages,
390
+ ownerRequest: input.ownerRequest,
391
+ model: input.model,
392
+ timeoutMs: input.timeoutMs,
393
+ signal: input.signal,
394
+ completionState,
395
+ });
396
+ if (early) {
397
+ return early;
398
+ }
399
+ break;
374
400
  }
375
- break;
376
- }
377
- const trimmedMessages = trimAgentHistory(messages);
378
- let maxCompletionTokensOverride;
379
- let lengthRetryUsed = false;
380
- input.bus?.emit({
381
- type: "model.attempt",
382
- attempt: turn + 1,
383
- maxAttempts: MAX_LOOP_TURNS,
384
- });
385
- emitAgentPlanningThinking(input.bus, turn + 1);
386
- const requestBody = {
387
- model: input.modelId,
388
- messages: trimmedMessages,
389
- tools: AGENT_TOOL_DEFINITIONS,
390
- tool_choice: "auto",
391
- temperature: 0.2,
392
- };
393
- applyCustomModelRequestDefaults(requestBody, {
394
- profile,
395
- modelId: input.modelId,
396
- maxCompletionTokensOverride,
397
- });
398
- const callModel = async () => {
401
+ const trimmedMessages = trimAgentHistory(messages);
402
+ let maxCompletionTokensOverride;
403
+ let lengthRetryUsed = false;
399
404
  input.bus?.emit({
400
- type: "model.request",
401
- provider: "custom",
402
- model: input.modelId,
403
- hasVision: Boolean(input.hasVision),
405
+ type: "model.attempt",
406
+ attempt: turn + 1,
407
+ maxAttempts: MAX_LOOP_TURNS,
404
408
  });
405
- const { value: result } = await withApiConcurrencyLimit((queueWaitMs) => postJsonWithRetry({
406
- url: `${input.baseURL}/chat/completions`,
407
- headers: {
408
- Authorization: `Bearer ${input.apiKey}`,
409
- "Content-Type": "application/json",
410
- },
411
- body: requestBody,
412
- timeoutMs: input.timeoutMs ?? 300_000,
413
- signal: input.signal,
414
- }, queueWaitMs));
415
- if (result.ok && result.data) {
416
- const content = result.data.choices?.[0]?.message?.content?.trim() ?? "";
417
- input.bus?.emit({
418
- type: "model.response",
419
- contentPreview: content.slice(0, 500),
420
- timingMs: result.totalMs,
421
- httpRetries: result.retries,
422
- });
423
- }
424
- return result;
425
- };
426
- let apiResult = input.bus
427
- ? await input.bus.withTool("model_completion", { turn: turn + 1, model: input.modelId }, callModel)
428
- : await callModel();
429
- throwIfCancelled(input.signal);
430
- if (!apiResult.ok || !apiResult.data) {
431
- const rateLimit = apiResult.rateLimit;
432
- return {
433
- ok: false,
434
- response: "",
435
- summary: rateLimit ? "MiniMax rate limit exceeded" : "Model API request failed",
436
- editsApplied,
437
- toolCallCount: policyState.toolCallCount,
438
- error: {
439
- message: rateLimit
440
- ? formatRateLimitReport(rateLimit)
441
- : `${apiResult.errorMessage ?? "Model API request failed"} | ${formatTimingReport(apiResult)}`,
442
- code: rateLimit ? "RATE_LIMIT" : "PROVIDER_ERROR",
443
- },
409
+ emitAgentPlanningThinking(input.bus, turn + 1);
410
+ const requestBody = {
411
+ model: input.modelId,
412
+ messages: trimmedMessages,
413
+ tools: toolDefinitions,
414
+ tool_choice: "auto",
415
+ temperature: 0.2,
444
416
  };
445
- }
446
- let payload = apiResult.data;
447
- let choice = payload.choices?.[0];
448
- let finishReason = choice?.finish_reason;
449
- if (finishReason === "length" && !lengthRetryUsed) {
450
- lengthRetryUsed = true;
451
- const currentCap = requestBody.max_completion_tokens ??
452
- getMaxCompletionTokens(profile);
453
- maxCompletionTokensOverride = bumpMaxCompletionTokensForLengthRetry(profile, currentCap);
454
- messages.push({ role: "user", content: LENGTH_RETRY_NUDGE });
455
417
  applyCustomModelRequestDefaults(requestBody, {
456
418
  profile,
457
419
  modelId: input.modelId,
458
420
  maxCompletionTokensOverride,
459
421
  });
460
- requestBody.messages = trimAgentHistory(messages);
461
- input.bus?.emit({
462
- type: "model.repair",
463
- reason: "length",
464
- priorOutputSnippet: (choice?.message?.content ?? "").slice(0, 200),
465
- });
466
- apiResult = input.bus
467
- ? await input.bus.withTool("model_completion", { turn: turn + 1, model: input.modelId, lengthRetry: true }, callModel)
422
+ const callModel = async () => {
423
+ input.bus?.emit({
424
+ type: "model.request",
425
+ provider: "custom",
426
+ model: input.modelId,
427
+ hasVision: Boolean(input.hasVision),
428
+ });
429
+ const { value: result } = await withApiConcurrencyLimit((queueWaitMs) => postJsonWithRetry({
430
+ url: `${input.baseURL}/chat/completions`,
431
+ headers: {
432
+ Authorization: `Bearer ${input.apiKey}`,
433
+ "Content-Type": "application/json",
434
+ },
435
+ body: requestBody,
436
+ timeoutMs: input.timeoutMs ?? 300_000,
437
+ signal: input.signal,
438
+ }, queueWaitMs));
439
+ if (result.ok && result.data) {
440
+ const content = result.data.choices?.[0]?.message?.content?.trim() ?? "";
441
+ input.bus?.emit({
442
+ type: "model.response",
443
+ contentPreview: content.slice(0, 500),
444
+ timingMs: result.totalMs,
445
+ httpRetries: result.retries,
446
+ });
447
+ }
448
+ return result;
449
+ };
450
+ let apiResult = input.bus
451
+ ? await input.bus.withTool("model_completion", { turn: turn + 1, model: input.modelId }, callModel)
468
452
  : await callModel();
453
+ throwIfCancelled(input.signal);
469
454
  if (!apiResult.ok || !apiResult.data) {
470
455
  const rateLimit = apiResult.rateLimit;
471
456
  return {
472
457
  ok: false,
473
- response: lastAssistantText,
474
- summary: "Model API request failed after length retry",
458
+ response: "",
459
+ summary: rateLimit ? "MiniMax rate limit exceeded" : "Model API request failed",
475
460
  editsApplied,
476
461
  toolCallCount: policyState.toolCallCount,
477
462
  error: {
478
- message: rateLimit?.message ??
479
- apiResult.errorMessage ??
480
- "Model API request failed",
463
+ message: rateLimit
464
+ ? formatRateLimitReport(rateLimit)
465
+ : `${apiResult.errorMessage ?? "Model API request failed"} | ${formatTimingReport(apiResult)}`,
481
466
  code: rateLimit ? "RATE_LIMIT" : "PROVIDER_ERROR",
482
467
  },
483
468
  };
484
469
  }
485
- payload = apiResult.data;
486
- choice = payload.choices?.[0];
487
- finishReason = choice?.finish_reason;
488
- }
489
- lastUsage = payload.usage;
490
- const message = choice?.message;
491
- if (!message) {
492
- return {
493
- ok: false,
494
- response: lastAssistantText,
495
- summary: "Model returned empty message",
496
- editsApplied,
497
- toolCallCount: policyState.toolCallCount,
498
- error: { message: "Empty model response", code: "PROVIDER_ERROR" },
499
- };
500
- }
501
- const toolCalls = message.tool_calls ?? [];
502
- const assistantContent = typeof message.content === "string" ? message.content.trim() : "";
503
- emitAgentRawModelTrace(input.bus, turn + 1, "custom", message);
504
- if (assistantContent) {
505
- lastAssistantText = assistantContent;
506
- input.bus?.emit(buildAssistantEvent(assistantContent));
507
- }
508
- messages.push(toAssistantHistoryMessage(message));
509
- if (toolCalls.length > 0) {
510
- emitAgentToolSelectionThinking(input.bus, turn + 1, toolCalls.map((tc) => tc.function.name));
511
- }
512
- if (toolCalls.length === 0) {
513
- const harnessOk = input.harnessMode && input.allowedWritePaths?.length
514
- ? editsApplied > 0
515
- : editsApplied > 0 || assistantContent.length > 0;
516
- return {
517
- ok: harnessOk,
518
- response: assistantContent || lastAssistantText || "Agent completed",
519
- summary: editsApplied > 0 ? `Applied ${editsApplied} edit(s)` : assistantContent.slice(0, 200),
520
- editsApplied,
521
- toolCallCount: policyState.toolCallCount,
522
- usage: lastUsage,
523
- };
524
- }
525
- let mutatingEditsThisTurn = 0;
526
- for (const toolCall of toolCalls) {
527
- throwIfCancelled(input.signal);
528
- const toolName = toolCall.function.name;
529
- policyState = evaluateToolPolicy(toolName, policyState, maxToolCalls);
530
- if (policyState.abortReason) {
470
+ let payload = apiResult.data;
471
+ let choice = payload.choices?.[0];
472
+ let finishReason = choice?.finish_reason;
473
+ if (finishReason === "length" && !lengthRetryUsed) {
474
+ lengthRetryUsed = true;
475
+ const currentCap = requestBody.max_completion_tokens ??
476
+ getMaxCompletionTokens(profile);
477
+ maxCompletionTokensOverride = bumpMaxCompletionTokensForLengthRetry(profile, currentCap);
478
+ messages.push({ role: "user", content: LENGTH_RETRY_NUDGE });
479
+ applyCustomModelRequestDefaults(requestBody, {
480
+ profile,
481
+ modelId: input.modelId,
482
+ maxCompletionTokensOverride,
483
+ });
484
+ requestBody.messages = trimAgentHistory(messages);
485
+ input.bus?.emit({
486
+ type: "model.repair",
487
+ reason: "length",
488
+ priorOutputSnippet: (choice?.message?.content ?? "").slice(0, 200),
489
+ });
490
+ apiResult = input.bus
491
+ ? await input.bus.withTool("model_completion", { turn: turn + 1, model: input.modelId, lengthRetry: true }, callModel)
492
+ : await callModel();
493
+ if (!apiResult.ok || !apiResult.data) {
494
+ const rateLimit = apiResult.rateLimit;
495
+ return {
496
+ ok: false,
497
+ response: lastAssistantText,
498
+ summary: "Model API request failed after length retry",
499
+ editsApplied,
500
+ toolCallCount: policyState.toolCallCount,
501
+ error: {
502
+ message: rateLimit?.message ??
503
+ apiResult.errorMessage ??
504
+ "Model API request failed",
505
+ code: rateLimit ? "RATE_LIMIT" : "PROVIDER_ERROR",
506
+ },
507
+ };
508
+ }
509
+ payload = apiResult.data;
510
+ choice = payload.choices?.[0];
511
+ finishReason = choice?.finish_reason;
512
+ }
513
+ lastUsage = payload.usage;
514
+ const message = choice?.message;
515
+ if (!message) {
516
+ return {
517
+ ok: false,
518
+ response: lastAssistantText,
519
+ summary: "Model returned empty message",
520
+ editsApplied,
521
+ toolCallCount: policyState.toolCallCount,
522
+ error: { message: "Empty model response", code: "PROVIDER_ERROR" },
523
+ };
524
+ }
525
+ const toolCalls = message.tool_calls ?? [];
526
+ const assistantContent = typeof message.content === "string" ? message.content.trim() : "";
527
+ emitAgentRawModelTrace(input.bus, turn + 1, "custom", message);
528
+ if (assistantContent) {
529
+ lastAssistantText = assistantContent;
530
+ input.bus?.emit(buildAssistantEvent(assistantContent));
531
+ }
532
+ messages.push(toAssistantHistoryMessage(message));
533
+ if (toolCalls.length > 0) {
534
+ emitAgentToolSelectionThinking(input.bus, turn + 1, toolCalls.map((tc) => tc.function.name));
535
+ }
536
+ if (toolCalls.length === 0) {
537
+ const harnessOk = input.harnessMode && input.allowedWritePaths?.length
538
+ ? editsApplied > 0
539
+ : editsApplied > 0 || assistantContent.length > 0;
540
+ return {
541
+ ok: harnessOk,
542
+ response: assistantContent || lastAssistantText || "Agent completed",
543
+ summary: editsApplied > 0 ? `Applied ${editsApplied} edit(s)` : assistantContent.slice(0, 200),
544
+ editsApplied,
545
+ toolCallCount: policyState.toolCallCount,
546
+ usage: lastUsage,
547
+ };
548
+ }
549
+ let mutatingEditsThisTurn = 0;
550
+ for (const toolCall of toolCalls) {
551
+ throwIfCancelled(input.signal);
552
+ const toolName = toolCall.function.name;
553
+ policyState = evaluateToolPolicy(toolName, policyState, maxToolCalls);
554
+ if (policyState.abortReason) {
555
+ messages.push({
556
+ role: "tool",
557
+ tool_call_id: toolCall.id,
558
+ content: `Tool blocked: ${policyState.abortReason}`,
559
+ });
560
+ break;
561
+ }
562
+ const args = parseToolArguments(toolCall.function.arguments);
563
+ const result = await executeAgentTool(toolName, args, toolCtx, input.bus);
564
+ const toolPath = typeof args.path === "string"
565
+ ? args.path
566
+ : typeof args.file_path === "string"
567
+ ? args.file_path
568
+ : typeof args.destPath === "string"
569
+ ? args.destPath
570
+ : typeof args.dest_path === "string"
571
+ ? args.dest_path
572
+ : typeof result.path === "string"
573
+ ? result.path
574
+ : undefined;
575
+ const meta = result.meta ?? {};
576
+ const publicUrl = typeof meta.publicUrl === "string" ? meta.publicUrl : undefined;
577
+ const destPath = typeof meta.destPath === "string" ? meta.destPath : toolPath;
578
+ completionState.recentToolResults.push({
579
+ name: toolName,
580
+ ok: result.ok,
581
+ output: result.output,
582
+ path: destPath,
583
+ publicUrl,
584
+ meta,
585
+ });
586
+ if (completionState.recentToolResults.length > 5) {
587
+ completionState.recentToolResults.shift();
588
+ }
589
+ if (result.applied) {
590
+ editsApplied += 1;
591
+ mutatingEditsThisTurn += 1;
592
+ invalidateWorkspaceCheckpointCache(input.workspaceCheckpoint);
593
+ }
531
594
  messages.push({
532
595
  role: "tool",
533
596
  tool_call_id: toolCall.id,
534
- content: `Tool blocked: ${policyState.abortReason}`,
597
+ content: result.output,
535
598
  });
536
- break;
537
599
  }
538
- const args = parseToolArguments(toolCall.function.arguments);
539
- const result = await executeAgentTool(toolName, args, toolCtx, input.bus);
540
- const toolPath = typeof args.path === "string"
541
- ? args.path
542
- : typeof args.file_path === "string"
543
- ? args.file_path
544
- : undefined;
545
- completionState.recentToolResults.push({
546
- name: toolName,
547
- ok: result.ok,
548
- output: result.output,
549
- path: toolPath,
550
- });
551
- if (completionState.recentToolResults.length > 5) {
552
- completionState.recentToolResults.shift();
600
+ if (mutatingEditsThisTurn > 0) {
601
+ const early = await tryHarnessEarlyFinish({
602
+ harnessMode: input.harnessMode,
603
+ allowedWritePaths: input.allowedWritePaths,
604
+ repoPath: input.repoPath,
605
+ workspaceCheckpoint: input.workspaceCheckpoint,
606
+ bus: input.bus,
607
+ policyState,
608
+ editsApplied,
609
+ lastAssistantText,
610
+ loopStartMs,
611
+ lastUsage,
612
+ messages,
613
+ modelTurnCount,
614
+ hasVision: input.hasVision,
615
+ selectedDom: input.selectedDom,
616
+ completion: input.completion,
617
+ completionAuto: input.completionAuto,
618
+ completionEvaluationEnabled: input.completionEvaluationEnabled,
619
+ completionConfidenceThreshold: input.completionConfidenceThreshold,
620
+ referenceImages: input.referenceImages,
621
+ ownerRequest: input.ownerRequest,
622
+ model: input.model,
623
+ timeoutMs: input.timeoutMs,
624
+ signal: input.signal,
625
+ completionState,
626
+ });
627
+ if (early) {
628
+ return early;
629
+ }
553
630
  }
554
- if (result.applied) {
555
- editsApplied += 1;
556
- mutatingEditsThisTurn += 1;
557
- invalidateWorkspaceCheckpointCache(input.workspaceCheckpoint);
631
+ if (editsApplied > 0 && toolCalls.every((call) => !isMutatingTool(call.function.name))) {
632
+ continue;
558
633
  }
559
- messages.push({
560
- role: "tool",
561
- tool_call_id: toolCall.id,
562
- content: result.output,
563
- });
564
634
  }
565
- if (mutatingEditsThisTurn > 0) {
635
+ if (input.harnessMode &&
636
+ input.allowedWritePaths?.length &&
637
+ editsApplied > 0) {
566
638
  const early = await tryHarnessEarlyFinish({
567
639
  harnessMode: input.harnessMode,
568
640
  allowedWritePaths: input.allowedWritePaths,
@@ -575,7 +647,7 @@ export async function runAgentToolLoop(input) {
575
647
  loopStartMs,
576
648
  lastUsage,
577
649
  messages,
578
- modelTurnCount,
650
+ modelTurnCount: MAX_LOOP_TURNS,
579
651
  hasVision: input.hasVision,
580
652
  selectedDom: input.selectedDom,
581
653
  completion: input.completion,
@@ -593,60 +665,27 @@ export async function runAgentToolLoop(input) {
593
665
  return early;
594
666
  }
595
667
  }
596
- if (editsApplied > 0 && toolCalls.every((call) => !isMutatingTool(call.function.name))) {
597
- continue;
598
- }
599
- }
600
- if (input.harnessMode &&
601
- input.allowedWritePaths?.length &&
602
- editsApplied > 0) {
603
- const early = await tryHarnessEarlyFinish({
604
- harnessMode: input.harnessMode,
605
- allowedWritePaths: input.allowedWritePaths,
606
- repoPath: input.repoPath,
607
- workspaceCheckpoint: input.workspaceCheckpoint,
608
- bus: input.bus,
609
- policyState,
668
+ return {
669
+ ok: editsApplied > 0,
670
+ response: lastAssistantText || "Agent loop ended",
671
+ summary: editsApplied > 0
672
+ ? `Applied ${editsApplied} edit(s) via tools`
673
+ : policyState.abortReason ?? "Agent loop ended without edits",
610
674
  editsApplied,
611
- lastAssistantText,
612
- loopStartMs,
613
- lastUsage,
614
- messages,
615
- modelTurnCount: MAX_LOOP_TURNS,
616
- hasVision: input.hasVision,
617
- selectedDom: input.selectedDom,
618
- completion: input.completion,
619
- completionAuto: input.completionAuto,
620
- completionEvaluationEnabled: input.completionEvaluationEnabled,
621
- completionConfidenceThreshold: input.completionConfidenceThreshold,
622
- referenceImages: input.referenceImages,
623
- ownerRequest: input.ownerRequest,
624
- model: input.model,
625
- timeoutMs: input.timeoutMs,
626
- signal: input.signal,
627
- completionState,
628
- });
629
- if (early) {
630
- return early;
631
- }
675
+ toolCallCount: policyState.toolCallCount,
676
+ abortReason: policyState.abortReason,
677
+ usage: lastUsage,
678
+ error: editsApplied === 0
679
+ ? {
680
+ message: policyState.abortReason ?? "No edits applied",
681
+ code: "EDIT_NOT_APPLIED",
682
+ }
683
+ : undefined,
684
+ };
685
+ }
686
+ finally {
687
+ clearHostToolsForRun();
632
688
  }
633
- return {
634
- ok: editsApplied > 0,
635
- response: lastAssistantText || "Agent loop ended",
636
- summary: editsApplied > 0
637
- ? `Applied ${editsApplied} edit(s) via tools`
638
- : policyState.abortReason ?? "Agent loop ended without edits",
639
- editsApplied,
640
- toolCallCount: policyState.toolCallCount,
641
- abortReason: policyState.abortReason,
642
- usage: lastUsage,
643
- error: editsApplied === 0
644
- ? {
645
- message: policyState.abortReason ?? "No edits applied",
646
- code: "EDIT_NOT_APPLIED",
647
- }
648
- : undefined,
649
- };
650
689
  }
651
690
  function isMutatingTool(toolName) {
652
691
  const name = toolName.toLowerCase();