@pentoshi/clai 3.11.3 → 3.11.5

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 (60) hide show
  1. package/dist/agent/runner.js +60 -16
  2. package/dist/agent/runner.js.map +1 -1
  3. package/dist/agent/stream-recovery.d.ts +15 -1
  4. package/dist/agent/stream-recovery.js +42 -2
  5. package/dist/agent/stream-recovery.js.map +1 -1
  6. package/dist/agent/tool-history.d.ts +6 -3
  7. package/dist/agent/tool-history.js +90 -11
  8. package/dist/agent/tool-history.js.map +1 -1
  9. package/dist/llm/agentrouter.js +2 -2
  10. package/dist/llm/agentrouter.js.map +1 -1
  11. package/dist/llm/bynara.js +2 -2
  12. package/dist/llm/bynara.js.map +1 -1
  13. package/dist/llm/http.d.ts +29 -3
  14. package/dist/llm/http.js +110 -25
  15. package/dist/llm/http.js.map +1 -1
  16. package/dist/llm/modal.js +7 -2
  17. package/dist/llm/modal.js.map +1 -1
  18. package/dist/llm/nvidia.js +6 -6
  19. package/dist/llm/nvidia.js.map +1 -1
  20. package/dist/llm/router.d.ts +1 -1
  21. package/dist/llm/router.js +59 -15
  22. package/dist/llm/router.js.map +1 -1
  23. package/dist/modes/ask.js +103 -32
  24. package/dist/modes/ask.js.map +1 -1
  25. package/dist/prompts/embedded.js +2 -2
  26. package/dist/prompts/embedded.js.map +1 -1
  27. package/dist/prompts/index.d.ts +7 -0
  28. package/dist/prompts/index.js +44 -4
  29. package/dist/prompts/index.js.map +1 -1
  30. package/dist/prompts/system.agent.md +2 -1
  31. package/dist/prompts/system.ask.md +1 -0
  32. package/dist/safety/classifier.js +3 -0
  33. package/dist/safety/classifier.js.map +1 -1
  34. package/dist/tools/definitions.js +15 -0
  35. package/dist/tools/definitions.js.map +1 -1
  36. package/dist/tools/image.d.ts +19 -1
  37. package/dist/tools/image.js +149 -1
  38. package/dist/tools/image.js.map +1 -1
  39. package/dist/tools/registry.js +13 -1
  40. package/dist/tools/registry.js.map +1 -1
  41. package/dist/tools/tool-types.d.ts +8 -1
  42. package/dist/tui-v2/bootstrap/capabilities.d.ts +1 -0
  43. package/dist/tui-v2/bootstrap/capabilities.js +1 -1
  44. package/dist/tui-v2/bootstrap/capabilities.js.map +1 -1
  45. package/dist/tui-v2/components/transcript/transcript-view.js +155 -28
  46. package/dist/tui-v2/components/transcript/transcript-view.js.map +1 -1
  47. package/dist/tui-v2/components/transcript/use-transcript-selection.d.ts +2 -2
  48. package/dist/tui-v2/components/transcript/use-transcript-selection.js +7 -8
  49. package/dist/tui-v2/components/transcript/use-transcript-selection.js.map +1 -1
  50. package/dist/tui-v2/rendering/streaming-markdown.js +11 -2
  51. package/dist/tui-v2/rendering/streaming-markdown.js.map +1 -1
  52. package/dist/types.d.ts +17 -0
  53. package/dist/ui/code-block.d.ts +40 -0
  54. package/dist/ui/code-block.js +380 -0
  55. package/dist/ui/code-block.js.map +1 -0
  56. package/dist/ui/markdown.js +55 -52
  57. package/dist/ui/markdown.js.map +1 -1
  58. package/dist/version.generated.d.ts +2 -2
  59. package/dist/version.generated.js +2 -2
  60. package/package.json +1 -1
@@ -3,7 +3,7 @@ import { join } from "node:path";
3
3
  import { completeWithProvider, streamWithProvider } from "../llm/router.js";
4
4
  import { streamAlreadyEmitted } from "../llm/stream-progress.js";
5
5
  import { classifyStreamFailure, planStreamRecovery, recordRecoveryAttempt, createStreamRecoveryState, resetStreamRecoveryState, } from "./stream-recovery.js";
6
- import { resolveToolDialect } from "../llm/capabilities.js";
6
+ import { modelSupportsVision, resolveToolDialect } from "../llm/capabilities.js";
7
7
  import { syntheticToolCallId, isTextOnlyModel, markTextOnlyModel, fromWireName, } from "../llm/tool-protocol.js";
8
8
  import { sanitizeAssistantText } from "../ui/ansi-box.js";
9
9
  import { randomUUID } from "node:crypto";
@@ -38,7 +38,7 @@ function safeEngagementActionsForToolCall(call) {
38
38
  }
39
39
  import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
40
40
  import { getToolDefinitions, getCompactToolDefinitions, RUNNER_META_TOOL_NAMES, } from "../tools/definitions.js";
41
- import { appendAssistantWithTools, ensureUniqueToolCallIds, appendToolResult, assertValidToolProtocol, fillMissingToolResults, repairToolProtocol, } from "./tool-history.js";
41
+ import { appendAssistantWithTools, ensureUniqueToolCallIds, toolCallIdsInHistory, appendToolResult, assertValidToolProtocol, fillMissingToolResults, repairToolProtocol, } from "./tool-history.js";
42
42
  import { formatViewportHint, registerViewport } from "../ui/output-pane.js";
43
43
  import { compactMessagesWithSummary, shouldApplyAutoCompact, COMPACTION_MEMORY_PREFIX, PLAN_IMPLEMENT_MEMORY_PREFIX, isCompactionMemoryMessage, } from "./context-manager.js";
44
44
  import { buildContextBreakdown, contextBreakdownAuditPayload, describeDominantContextBlock, toolSchemaHash, } from "./context-breakdown.js";
@@ -410,10 +410,20 @@ export async function runAgentTurn(prompt, options = {}) {
410
410
  const projectContext = await loadProjectContext();
411
411
  const hasAttachedImages = Boolean(options.images?.length);
412
412
  const imageOcrEnabled = shouldEnableImageOcr(prompt, hasAttachedImages, options.visionProven !== false);
413
- // A vision-capable request already carries the actual image bytes. Hiding
414
- // image.ocr from the model prevents it from replacing visual inspection
415
- // with a lossy Tesseract pass (which produced fabricated screenshot text).
416
- const toolNames = availableToolNames().filter((name) => name !== "image.ocr" || imageOcrEnabled);
413
+ const initialProvider = options.provider ?? config.defaultProvider;
414
+ const initialModel = options.model ?? config.defaultModel;
415
+ // image.view is different from optimistic user-attachment handling: once
416
+ // the tool succeeds, the model must actually receive and inspect its bytes.
417
+ // Offer it only with affirmative capability evidence for the active route.
418
+ const routeToolNames = (routeProvider, routeModel) => availableToolNames().filter((name) => {
419
+ if (name === "image.ocr")
420
+ return imageOcrEnabled;
421
+ if (name === "image.view") {
422
+ return modelSupportsVision(routeProvider, routeModel);
423
+ }
424
+ return true;
425
+ });
426
+ const toolNames = routeToolNames(initialProvider, initialModel);
417
427
  // Build / scaffold / continuation turns must NEVER be diverted into a
418
428
  // web.search for "current info". The /implement directive ("Execute it
419
429
  // now…") and prompts like "create a react app" contain words such as
@@ -442,9 +452,9 @@ export async function runAgentTurn(prompt, options = {}) {
442
452
  !idleOrSocialPrompt &&
443
453
  toolNames.includes("web.search") &&
444
454
  requiresFreshWebSearch(prompt);
445
- let provider = options.provider ?? config.defaultProvider;
455
+ let provider = initialProvider;
446
456
  await ensureProviderConfigured(provider);
447
- let model = options.model ?? config.defaultModel;
457
+ let model = initialModel;
448
458
  // Some Groq free-tier models have a per-request/per-minute input budget
449
459
  // below the normal agent prompt alone. Select a purpose-built compact
450
460
  // instruction set before the request is made, rather than treating the
@@ -456,13 +466,16 @@ export async function runAgentTurn(prompt, options = {}) {
456
466
  return { dialect, native: dialect !== "none" };
457
467
  };
458
468
  let { dialect: toolDialect, native: nativeToolsActive } = resolveNativeTools(provider, model);
459
- const selectToolDefs = (native, compact) => {
469
+ const selectToolDefs = (native, compact, routeProvider = provider, routeModel = model) => {
460
470
  if (!native)
461
471
  return undefined;
462
472
  const base = compact
463
473
  ? getCompactToolDefinitions()
464
474
  : getToolDefinitions();
465
- const allow = new Set([...toolNames, ...RUNNER_META_TOOL_NAMES]);
475
+ const allow = new Set([
476
+ ...routeToolNames(routeProvider, routeModel),
477
+ ...RUNNER_META_TOOL_NAMES,
478
+ ]);
466
479
  return base.filter((d) => allow.has(d.name));
467
480
  };
468
481
  let lastAnswer = "";
@@ -529,11 +542,13 @@ export async function runAgentTurn(prompt, options = {}) {
529
542
  // constitution (and, on Anthropic, the native tool schemas before it).
530
543
  const buildStableSystemContent = (native) => {
531
544
  const reliability = getReliabilityPolicy();
545
+ const visionAvailable = modelSupportsVision(provider, model);
532
546
  return (useCompactSystemPrompt
533
547
  ? renderCompactAgentSystemPrompt
534
- : renderAgentSystemPrompt)(toolNames.join(", "), {
548
+ : renderAgentSystemPrompt)(routeToolNames(provider, model).join(", "), {
535
549
  nativeTools: native,
536
550
  stableEnvironment: true,
551
+ imageView: visionAvailable,
537
552
  // E6: slim native constitution when API tool schemas are attached.
538
553
  ...(native ? { slimNative: reliability.slimNativePrompt } : {}),
539
554
  });
@@ -892,6 +907,7 @@ export async function runAgentTurn(prompt, options = {}) {
892
907
  // reset on any successful stream so each failure episode gets a fresh
893
908
  // budget and we only give up in the worst case.
894
909
  let allowModelFallback = false;
910
+ let preferModelFallback = false;
895
911
  const recoveryState = createStreamRecoveryState();
896
912
  // Track tool calls truncated by the token limit so we can ask the model
897
913
  // to retry in smaller pieces instead of leaking broken JSON as an answer.
@@ -2068,6 +2084,10 @@ export async function runAgentTurn(prompt, options = {}) {
2068
2084
  },
2069
2085
  confirmed: true,
2070
2086
  userPrompt: prompt,
2087
+ // image.view needs the active route to check vision support and size
2088
+ // images to the provider's per-image budget.
2089
+ llmProvider: provider,
2090
+ llmModel: model,
2071
2091
  sessionId: session.sessionId,
2072
2092
  ...(delegation?.taskId ? { taskId: delegation.taskId } : {}),
2073
2093
  ...(delegation ? { delegationId: delegation.id } : {}),
@@ -3158,6 +3178,7 @@ export async function runAgentTurn(prompt, options = {}) {
3158
3178
  provider,
3159
3179
  model,
3160
3180
  allowModelFallback,
3181
+ preferModelFallback,
3161
3182
  messages,
3162
3183
  // Sampling is provider/model policy (llm/sampling.ts).
3163
3184
  // Sending a fixed 0.2 here overrode it for every model.
@@ -3314,6 +3335,7 @@ export async function runAgentTurn(prompt, options = {}) {
3314
3335
  // starts fresh (and we never give up while making progress).
3315
3336
  resetStreamRecoveryState(recoveryState);
3316
3337
  allowModelFallback = false;
3338
+ preferModelFallback = false;
3317
3339
  }
3318
3340
  catch (streamError) {
3319
3341
  // User cancelled (double-Esc) — never try to recover, just stop.
@@ -3389,6 +3411,8 @@ export async function runAgentTurn(prompt, options = {}) {
3389
3411
  retryWithoutThinking = true;
3390
3412
  if (plan.allowModelFallback)
3391
3413
  allowModelFallback = true;
3414
+ if (plan.preferModelFallback)
3415
+ preferModelFallback = true;
3392
3416
  if (plan.forceCompact) {
3393
3417
  await maybeAutoCompact(`stream-recovery:${failureKind}`, true);
3394
3418
  }
@@ -3608,11 +3632,14 @@ export async function runAgentTurn(prompt, options = {}) {
3608
3632
  writeNotice("info", `native tool call was truncated — salvaged ${lineCount} lines and wrote to ${salvaged.path}`, chalk.cyan(` ℹ native tool call was truncated — salvaged ${lineCount} lines to ${salvaged.path}\n`));
3609
3633
  // Pair assistant tool_calls with synthetic results so the
3610
3634
  // next turn is not orphaned, then nudge for append.
3611
- appendAssistantWithTools(messages, assistantText.visible, nativeToolCalls, completion.reasoningBlock);
3612
- for (const tc of nativeToolCalls) {
3613
- appendToolResult(messages, tc.id, tc.id === writeTc.id
3635
+ const salvageHistoryCalls = ensureUniqueToolCallIds(nativeToolCalls, toolCallIdsInHistory(messages));
3636
+ const salvagedCallIndex = nativeToolCalls.indexOf(writeTc);
3637
+ const salvagedCallId = salvageHistoryCalls[salvagedCallIndex]?.id ?? writeTc.id;
3638
+ appendAssistantWithTools(messages, assistantText.visible, salvageHistoryCalls, completion.reasoningBlock);
3639
+ for (const tc of salvageHistoryCalls) {
3640
+ appendToolResult(messages, tc.id, tc.id === salvagedCallId
3614
3641
  ? `Tool ${tc.name} result (exit=0, ok=true):\nSalvaged partial write: ${lineCount} lines to ${salvaged.path}`
3615
- : `Tool ${tc.name} result (exit=1, ok=false):\nCancelled — sibling write was truncated and salvaged.`, tc.name, tc.id === writeTc.id);
3642
+ : `Tool ${tc.name} result (exit=1, ok=false):\nCancelled — sibling write was truncated and salvaged.`, tc.name, tc.id === salvagedCallId);
3616
3643
  }
3617
3644
  const priorBytes = writeResult.bytesOnDisk;
3618
3645
  const salvagedToolName = salvaged.operation === "append"
@@ -4227,7 +4254,7 @@ export async function runAgentTurn(prompt, options = {}) {
4227
4254
  // assistant toolCalls and role:tool results always share the same id.
4228
4255
  // Mismatched ids make results look orphaned → repair injects placeholders
4229
4256
  // → model thrash-retries tools that already succeeded in the UI.
4230
- const historyNativeCalls = ensureUniqueToolCallIds(bound.map((b) => b.native));
4257
+ const historyNativeCalls = ensureUniqueToolCallIds(bound.map((b) => b.native), toolCallIdsInHistory(messages));
4231
4258
  for (let i = 0; i < bound.length; i++) {
4232
4259
  const fixed = historyNativeCalls[i];
4233
4260
  bound[i] = {
@@ -4433,6 +4460,23 @@ export async function runAgentTurn(prompt, options = {}) {
4433
4460
  content: toolContent,
4434
4461
  });
4435
4462
  }
4463
+ // image.view hands back real image bytes. Tool results are text-only
4464
+ // on every provider wire, and images are only serialized on user
4465
+ // turns, so the bytes ride a deferred internal user message that
4466
+ // lands after the assistant→tool group is closed — inserting it here
4467
+ // would orphan the remaining tool results.
4468
+ if (res.result.images?.length) {
4469
+ deferredPostToolMessages.push({
4470
+ role: "user",
4471
+ internal: true,
4472
+ content: `[${res.call.name}] The ${res.result.images.length === 1 ? "image" : `${res.result.images.length} images`} you asked to look at ` +
4473
+ `${res.result.images.length === 1 ? "is" : "are"} attached to this message` +
4474
+ `${res.result.images.length === 1 ? "" : ", in the order you requested them"}: ` +
4475
+ `${res.result.images.map((image) => image.path ?? "(unnamed)").join(", ")}. ` +
4476
+ "Judge them from the pixels and continue the task.",
4477
+ images: res.result.images,
4478
+ });
4479
+ }
4436
4480
  // Reset retry counters — they track consecutive failures, not cumulative.
4437
4481
  truncatedToolRetries = 0;
4438
4482
  malformedFenceRetries = 0;