@agent-native/toolkit 0.206.0 → 0.206.1-nightly-20261009213000

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 (82) hide show
  1. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  2. package/dist/app/agentkit/react/components.js +3 -3
  3. package/dist/app/agentkit/react/components.js.map +1 -1
  4. package/dist/app/agentkit/react/context.d.ts +1 -0
  5. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  6. package/dist/app/agentkit/react/context.js +1 -0
  7. package/dist/app/agentkit/react/context.js.map +1 -1
  8. package/dist/app/agentkit/react/styles.css +43 -0
  9. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  10. package/dist/app/chat/AgentKitAssistantChat.js +219 -75
  11. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  12. package/dist/app/chat/agentkit-chat/guided-questions.d.ts +18 -0
  13. package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
  14. package/dist/app/chat/agentkit-chat/guided-questions.js +12 -4
  15. package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
  16. package/dist/app/chat/chat/run-recovery.d.ts +6 -1
  17. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  18. package/dist/app/chat/chat/run-recovery.js +6 -3
  19. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  20. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  21. package/dist/app/i18n/catalogs/ar-SA.js +8 -2
  22. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  23. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  24. package/dist/app/i18n/catalogs/de-DE.js +8 -2
  25. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  26. package/dist/app/i18n/catalogs/en-US.d.ts +8 -2
  27. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/en-US.js +8 -2
  29. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  30. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/es-ES.js +8 -2
  32. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  33. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/fr-FR.js +8 -2
  35. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  36. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  37. package/dist/app/i18n/catalogs/hi-IN.js +8 -2
  38. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  39. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/ja-JP.js +8 -2
  41. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  42. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/ko-KR.js +8 -2
  44. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  45. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  46. package/dist/app/i18n/catalogs/pt-BR.js +8 -2
  47. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  48. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  49. package/dist/app/i18n/catalogs/zh-CN.js +8 -2
  50. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  51. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  52. package/dist/app/i18n/catalogs/zh-TW.js +8 -2
  53. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  54. package/dist/app/sharing/ShareButton.d.ts +9 -0
  55. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  56. package/dist/app/sharing/ShareButton.js +23 -11
  57. package/dist/app/sharing/ShareButton.js.map +1 -1
  58. package/package.json +4 -4
  59. package/src/app/agentkit/react/components.tsx +6 -3
  60. package/src/app/agentkit/react/context.tsx +2 -0
  61. package/src/app/agentkit/react/styles.css +43 -0
  62. package/src/app/chat/AgentKitAssistantChat.spec.tsx +425 -15
  63. package/src/app/chat/AgentKitAssistantChat.tsx +306 -95
  64. package/src/app/chat/agentkit-chat/guided-questions.flow.spec.tsx +89 -5
  65. package/src/app/chat/agentkit-chat/guided-questions.tsx +17 -6
  66. package/src/app/chat/chat/run-recovery.spec.tsx +48 -0
  67. package/src/app/chat/chat/run-recovery.tsx +23 -1
  68. package/src/app/i18n/catalogs/ar-SA.ts +14 -2
  69. package/src/app/i18n/catalogs/de-DE.ts +12 -2
  70. package/src/app/i18n/catalogs/en-US.ts +13 -2
  71. package/src/app/i18n/catalogs/es-ES.ts +12 -2
  72. package/src/app/i18n/catalogs/fr-FR.ts +12 -2
  73. package/src/app/i18n/catalogs/hi-IN.ts +12 -2
  74. package/src/app/i18n/catalogs/ja-JP.ts +12 -2
  75. package/src/app/i18n/catalogs/ko-KR.ts +12 -2
  76. package/src/app/i18n/catalogs/pt-BR.ts +12 -2
  77. package/src/app/i18n/catalogs/zh-CN.ts +8 -2
  78. package/src/app/i18n/catalogs/zh-TW.ts +8 -2
  79. package/src/app/settings/model/ProviderDialog.spec.tsx +1 -0
  80. package/src/app/sharing/ShareButton.spec.tsx +148 -0
  81. package/src/app/sharing/ShareButton.tsx +44 -15
  82. package/src/app/sharing/useShareButtonController.spec.tsx +25 -1
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { hasActiveAgentRuns, selectAgentSuggestions, isCurrentAgentSuggestion, } from "@agent-native/agentkit";
3
- import { MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS } from "@agent-native/agentkit/protocol";
2
+ import { AgentKitUploadError, hasActiveAgentRuns, selectAgentSuggestions, isCurrentAgentSuggestion, } from "@agent-native/agentkit";
3
+ import { MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS, isInlineDataUrl, persistableFilePart, } from "@agent-native/agentkit/protocol";
4
4
  import { CHATGPT_SUBSCRIPTION_ENGINE_NAME } from "@agent-native/core/agent/chatgpt-subscription-contract";
5
5
  import { isCreditsLimitErrorCode } from "@agent-native/core/agent/engine/error-detail";
6
6
  import { appendAgentChatContextToMessage, filterAgentChatContextItems, formatAgentChatContextItemsForPrompt, getAgentChatContextState, normalizeAgentChatContextItem, publishAgentChatContextItems, reportAgentChatSubmitResult, refreshAgentChatContext, subscribeAgentChatContext, } from "@agent-native/core/client/agent-chat";
@@ -26,8 +26,9 @@ import { writeClipboardText } from "@agent-native/toolkit/clipboard";
26
26
  import { AgentSuggestionBar, agentSuggestionPrompt, snapshotComposerContextItems, AGENT_PROMPT_MAX_INLINE_IMAGE_BYTES, readAgentPromptAttachment, } from "@agent-native/toolkit/composer";
27
27
  import { appendRealtimeVoiceTranscriptToRepository, realtimeVoiceTranscriptRegistry, } from "@agent-native/toolkit/composer/realtime-voice-transcript";
28
28
  import { IconButton } from "@agent-native/toolkit/design-system";
29
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@agent-native/toolkit/ui/dropdown-menu";
29
30
  import { cn } from "@agent-native/toolkit/utils";
30
- import { IconAlertTriangle, IconCircleCheck, IconCopy, IconMessage, IconPlayerStopFilled, IconQuote, IconRefresh, IconX, } from "@tabler/icons-react";
31
+ import { IconAlertTriangle, IconCircleCheck, IconCopy, IconDotsVertical, IconMessage, IconPlayerStopFilled, IconQuote, IconRefresh, IconX, } from "@tabler/icons-react";
31
32
  import React, { createContext, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from "react";
32
33
  import { AgentKitChat, AgentKitComposer, AgentApprovalPrompt, AgentMessageView, useAgentKit, useAgentKitControl, useAgentThread, } from "../agentkit/react/index.js";
33
34
  import { AgentKitDevCheckpointProvider, AgentKitDevCheckpointRestore, useOptionalAgentKitHistory, } from "./agentkit-chat/history.js";
@@ -507,6 +508,7 @@ export const AgentKitAssistantChat = forwardRef(function AgentKitAssistantChat(p
507
508
  name: "{{name}}",
508
509
  }),
509
510
  pastedText: t("agentChat.pastedText.title"),
511
+ attachmentNotSaved: t("agentChat.composer.attachmentNotSaved"),
510
512
  imagePreview: t("agentChat.composer.imagePreview"),
511
513
  closePreview: t("agentChat.composer.closePreview"),
512
514
  dropFilesToAttach: t("agentChat.composer.dropToAttach"),
@@ -546,43 +548,28 @@ export const AgentKitAssistantChat = forwardRef(function AgentKitAssistantChat(p
546
548
  form.set(key, value);
547
549
  }
548
550
  form.set("file", file.body, file.name);
549
- const response = await fetch(target.url, {
550
- method: target.method,
551
- headers: target.headers,
552
- body: form,
553
- signal: context?.signal,
554
- });
555
- if (!response.ok) {
556
- const messageKey = response.status === 415
557
- ? "agentChat.composer.unsupportedFileType"
558
- : response.status === 413
559
- ? "agentChat.composer.fileTooLarge"
560
- : response.status === 401
561
- ? "agentChat.composer.sessionExpired"
562
- : undefined;
563
- const message = messageKey
564
- ? translatorRef.current(messageKey)
565
- : translatorRef.current("agentChat.composer.uploadFailed");
566
- const error = new Error(message);
567
- Object.assign(error, {
568
- code: response.status === 413
569
- ? "upload_too_large"
570
- : response.status === 401
571
- ? "upload_session_expired"
572
- : response.status === 415
573
- ? "upload_unsupported_type"
574
- : `upload_http_${response.status}`,
575
- status: response.status,
576
- retryable: response.status === 408 ||
577
- response.status === 429 ||
578
- response.status >= 500,
551
+ let response;
552
+ try {
553
+ response = await fetch(target.url, {
554
+ method: target.method,
555
+ headers: target.headers,
556
+ body: form,
557
+ signal: context?.signal,
579
558
  });
580
- throw error;
559
+ }
560
+ catch (error) {
561
+ if (context?.signal?.aborted)
562
+ throw error;
563
+ throw Object.assign(new Error(translatorRef.current("agentChat.composer.uploadOffline")), { code: "upload_network_error", retryable: true, cause: error });
564
+ }
565
+ // The server's error body is never shown: its text is untranslated.
566
+ if (!response.ok) {
567
+ throw uploadHttpError(response.status, translatorRef.current);
581
568
  }
582
569
  const result = await response.json();
583
570
  const uploaded = asRecord(result);
584
571
  if (typeof uploaded?.url !== "string" || !uploaded.url) {
585
- throw new TypeError("File upload response did not include a URL.");
572
+ throw Object.assign(new Error(translatorRef.current("agentChat.composer.uploadFailed")), { code: "upload_invalid_response", retryable: false });
586
573
  }
587
574
  uploadedFilesRef.current.set(target.uploadId, {
588
575
  type: "file",
@@ -1422,6 +1409,7 @@ const AgentKitAssistantChatBody = forwardRef(function AgentKitAssistantChatBody(
1422
1409
  : await uploadAgentChatAttachments(control, attachments, files, {
1423
1410
  storageConfigured: fileStorageConfigured,
1424
1411
  storageUnavailableMessage: t("onboarding.fileStorage.title"),
1412
+ fileTooLargeMessage: uploadTooLargeMessage(t),
1425
1413
  });
1426
1414
  const fileParts = uploadedAttachments.fileParts;
1427
1415
  let requestAttachments = options.deferredRequestAttachments ??
@@ -1458,7 +1446,9 @@ const AgentKitAssistantChatBody = forwardRef(function AgentKitAssistantChatBody(
1458
1446
  if (inlineImageDataChars > MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS) {
1459
1447
  throw new Error(t("agentChat.composer.requestTooLarge"));
1460
1448
  }
1461
- composerOptions.validateSubmission?.();
1449
+ // Readiness was gated before the upload; a provider status refresh during
1450
+ // it must not discard the upload, only a change of thread or scope.
1451
+ composerOptions.validateSubmissionScope?.();
1462
1452
  const selectionChangedDuringSubmission = selectionRevision !== selectionRevisionRef.current ||
1463
1453
  (options.pendingSelectionCapturedAt !== undefined &&
1464
1454
  options.pendingSelectionCapturedAt !==
@@ -1626,8 +1616,12 @@ const AgentKitAssistantChatBody = forwardRef(function AgentKitAssistantChatBody(
1626
1616
  : await uploadAgentChatAttachments(control, attachments, files, {
1627
1617
  storageConfigured: fileStorageConfigured,
1628
1618
  storageUnavailableMessage: t("onboarding.fileStorage.title"),
1619
+ fileTooLargeMessage: uploadTooLargeMessage(t),
1629
1620
  });
1630
- const fileParts = uploadedAttachments.fileParts;
1621
+ const fileParts = await durableFileParts(control, uploadedAttachments.fileParts, {
1622
+ storageConfigured: fileStorageConfigured,
1623
+ storageUnavailableMessage: t("onboarding.fileStorage.title"),
1624
+ });
1631
1625
  if (!fileStorageConfigured &&
1632
1626
  uploadedAttachments.requestAttachments.some((attachment) => attachment.data)) {
1633
1627
  throw new Error(t("onboarding.fileStorage.title"));
@@ -1668,6 +1662,7 @@ const AgentKitAssistantChatBody = forwardRef(function AgentKitAssistantChatBody(
1668
1662
  delete deferredComposerOptions.attachments;
1669
1663
  delete deferredComposerOptions.onLocalSubmit;
1670
1664
  delete deferredComposerOptions.validateSubmission;
1665
+ delete deferredComposerOptions.validateSubmissionScope;
1671
1666
  deferredComposerOptions.model ??= props.selectedModel;
1672
1667
  deferredComposerOptions.engine ??= props.selectedEngine;
1673
1668
  deferredComposerOptions.effort ??= props.selectedEffort;
@@ -2347,13 +2342,10 @@ function AgentKitTranscript({ children, threadId }) {
2347
2342
  }
2348
2343
  return (_jsxs(_Fragment, { children: [thread.messages.length > 0 || surface.hasRenderedMessages ? (_jsx(AgentKitHistoryBeginningRevert, {})) : null, surface.threadRestore.status === "error" ? (_jsxs("div", { className: "mx-4 mt-3 flex items-center justify-between gap-3 rounded-md border border-border px-3 py-2 text-xs text-muted-foreground", role: "alert", children: [_jsx("span", { children: surface.threadRestore.notFound
2349
2344
  ? t("agentChat.message.threadNotFound")
2350
- : t("agentChat.message.restoreRequestFailed") }), _jsx("button", { type: "button", onClick: surface.retryThreadRestore, className: "shrink-0 rounded px-2 py-1 hover:bg-accent hover:text-foreground", children: t("agentChat.common.retry") })] })) : null, renderThreadSlot(surface.props.threadContentSlot, threadId, surface.props.tabId), children, activeRunId ? (_jsxs("div", { className: "agentkit-activities-static", "data-agentkit-active-run-id-copy": "true", children: [_jsx(IconButton, { label: activeRunRequestIdCopyStatus === "copied"
2351
- ? t("agentChat.common.copied")
2352
- : activeRunRequestIdCopyStatus === "failed"
2353
- ? t("agentChat.recovery.copyFailed")
2354
- : t("agentChat.message.copyRequestId"), title: t("agentChat.message.copyRequestId"), icon: activeRunRequestIdCopyStatus === "copied" ? (_jsx(IconCircleCheck, { "aria-hidden": "true" })) : activeRunRequestIdCopyStatus === "failed" ? (_jsx(IconAlertTriangle, { "aria-hidden": "true" })) : (_jsx(IconCopy, { "aria-hidden": "true" })), size: "compact", onPress: () => void copyActiveRunRequestId() }), activeRunRequestIdCopyStatus ? (_jsx("span", { className: "sr-only", role: activeRunRequestIdCopyStatus === "failed" ? "alert" : "status", children: activeRunRequestIdCopyStatus === "copied"
2355
- ? t("agentChat.common.copied")
2356
- : t("agentChat.recovery.copyFailed") })) : null] })) : null, handoffMessages.map((message) => (_jsx(AgentMessageView, { value: message, threadId: threadId }, message.id))), pendingVoiceMessages.map((message) => (_jsx(AgentMessageView, { value: message, threadId: threadId }, message.id))), showThinking ? (_jsx("div", { className: "agentkit-activities agentkit-activities-summary-content", role: "status", "aria-live": "polite", children: _jsx("span", { className: "agentkit-activities-current", "data-running": "true", "data-agentkit-current-activity": "true", children: _jsx("span", { className: "agentkit-activities-current-label agent-running-shimmer", children: t("agentChat.status.thinking") }) }) })) : null, surface.props.showGuidedQuestions !== false &&
2345
+ : t("agentChat.message.restoreRequestFailed") }), _jsx("button", { type: "button", onClick: surface.retryThreadRestore, className: "shrink-0 rounded px-2 py-1 hover:bg-accent hover:text-foreground", children: t("agentChat.common.retry") })] })) : null, renderThreadSlot(surface.props.threadContentSlot, threadId, surface.props.tabId), children, activeRunId ? (_jsxs("div", { className: "agentkit-activities-static agentkit-active-run-actions", "data-agentkit-active-run-id-copy": "true", children: [activeRunRequestIdCopyStatus ? (_jsxs("span", { className: cn("agentkit-active-run-actions-feedback", activeRunRequestIdCopyStatus === "failed" &&
2346
+ "agentkit-active-run-actions-feedback-error"), role: activeRunRequestIdCopyStatus === "failed" ? "alert" : "status", children: [activeRunRequestIdCopyStatus === "copied" ? (_jsx(IconCircleCheck, { size: 14, "aria-hidden": "true" })) : (_jsx(IconAlertTriangle, { size: 14, "aria-hidden": "true" })), activeRunRequestIdCopyStatus === "copied"
2347
+ ? t("agentChat.common.copied")
2348
+ : t("agentChat.recovery.copyFailed")] })) : null, _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(IconButton, { label: t("agentChat.message.actions"), title: t("agentChat.message.actions"), icon: _jsx(IconDotsVertical, { "aria-hidden": "true" }), size: "compact", className: "agentkit-active-run-actions-trigger" }) }), _jsx(DropdownMenuContent, { align: "end", sideOffset: 6, children: _jsxs(DropdownMenuItem, { onSelect: () => void copyActiveRunRequestId(), children: [activeRunRequestIdCopyStatus === "copied" ? (_jsx(IconCircleCheck, { size: 14, "aria-hidden": "true" })) : activeRunRequestIdCopyStatus === "failed" ? (_jsx(IconAlertTriangle, { size: 14, "aria-hidden": "true" })) : (_jsx(IconCopy, { size: 14, "aria-hidden": "true" })), t("agentChat.message.copyRequestId")] }) })] })] })) : null, handoffMessages.map((message) => (_jsx(AgentMessageView, { value: message, threadId: threadId }, message.id))), pendingVoiceMessages.map((message) => (_jsx(AgentMessageView, { value: message, threadId: threadId }, message.id))), showThinking ? (_jsx("div", { className: "agentkit-activities agentkit-activities-summary-content", role: "status", "aria-live": "polite", children: _jsx("span", { className: "agentkit-activities-current", "data-running": "true", "data-agentkit-current-activity": "true", children: _jsx("span", { className: "agentkit-activities-current-label agent-running-shimmer", children: t("agentChat.status.thinking") }) }) })) : null, surface.props.showGuidedQuestions !== false &&
2357
2349
  guided.questions?.length ? (_jsx("div", { className: "px-3 pb-3", children: _jsx(GuidedQuestionFlow, { questions: guided.questions, onSubmit: guided.handleSubmit, onSkip: guided.handleSkip, isSubmitting: guided.isSubmitting, isSubmissionBlocked: !surface.canChat, providerStatus: surface.providerStatus, modelListUnavailable: surface.modelListUnavailable, showProviderStatusGate: !composerShowsSetupCard(surface), onRetryProviderStatus: surface.modelListUnavailable
2358
2350
  ? surface.retryModelList
2359
2351
  : surface.retryProviderStatus, ...(guided.title ? { title: guided.title } : {}), ...(guided.description ? { description: guided.description } : {}), ...(guided.skipLabel ? { skipLabel: guided.skipLabel } : {}), ...(guided.submitLabel ? { submitLabel: guided.submitLabel } : {}), className: "h-auto items-stretch justify-stretch bg-transparent" }) })) : null, suggestionBar, renderThreadSlot(surface.props.threadFooterSlot, threadId, surface.props.tabId)] }));
@@ -2452,20 +2444,28 @@ function AgentKitComposerSurface({ threadId, props, composerContext, canChat, se
2452
2444
  : {}),
2453
2445
  };
2454
2446
  setComposerError(null);
2447
+ const assertSubmissionScope = () => {
2448
+ const latest = controller.getThread(threadId);
2449
+ if (!mounted.current ||
2450
+ currentSubmissionScope.current !== submissionScope ||
2451
+ (options.suggestion &&
2452
+ (latest.messages.length || Object.keys(latest.runs).length) &&
2453
+ !isCurrentAgentSuggestion(latest, options.suggestion))) {
2454
+ throw Object.assign(new Error(t("agentChat.composer.submissionScopeChanged")), { code: "submission_scope_changed" });
2455
+ }
2456
+ };
2455
2457
  const assertCurrentSubmission = () => {
2456
- if (!submissionAllowed.current ||
2457
- !mounted.current ||
2458
- currentSubmissionScope.current !== submissionScope) {
2459
- throw new Error(t("agentChat.error.failed"));
2458
+ if (!submissionAllowed.current) {
2459
+ throw Object.assign(new Error(t("agentChat.composer.submissionNotReady")), { code: "submission_not_ready" });
2460
2460
  }
2461
- const latest = controller.getThread(threadId);
2462
- if (options.suggestion &&
2463
- (latest.messages.length || Object.keys(latest.runs).length) &&
2464
- !isCurrentAgentSuggestion(latest, options.suggestion))
2465
- throw new Error(t("agentChat.error.failed"));
2461
+ assertSubmissionScope();
2466
2462
  };
2467
2463
  assertCurrentSubmission();
2468
- options = { ...options, validateSubmission: assertCurrentSubmission };
2464
+ options = {
2465
+ ...options,
2466
+ validateSubmission: assertCurrentSubmission,
2467
+ validateSubmissionScope: assertSubmissionScope,
2468
+ };
2469
2469
  const captured = snapshotComposerContextItems(options.contextItems ?? visibleContextItems);
2470
2470
  let prepared;
2471
2471
  await onSubmit(message, files, references, options, async () => {
@@ -2826,6 +2826,15 @@ function AgentKitRunFailure({ error, runId, threadId, }) {
2826
2826
  const retryRequest = retryRequestFrom(failedPrompt ?? lastUserMessage);
2827
2827
  const alreadyRetried = wasRetried(thread.messages, runId, failedPrompt?.id, lastUserMessage?.id);
2828
2828
  const retryFailedTurn = () => sendRetryRequest(surface, retryRequest, runId);
2829
+ const retryWithoutAttachments = retryRequest.fileParts.length || retryRequest.requestAttachments.length
2830
+ ? () => void sendRetryRequest(surface, {
2831
+ ...retryRequest,
2832
+ text: retryRequest.textWithContext,
2833
+ fileParts: [],
2834
+ requestAttachments: [],
2835
+ hasUnavailableAttachment: false,
2836
+ }, runId)
2837
+ : undefined;
2829
2838
  const resumeAfterSetup = useResumeAfterAiSetup(`${threadId}:${(failedPrompt ?? lastUserMessage)?.id ?? runId}`, setupFailure &&
2830
2839
  !superseded &&
2831
2840
  !alreadyRetried &&
@@ -2874,7 +2883,7 @@ function AgentKitRunFailure({ error, runId, threadId, }) {
2874
2883
  runId,
2875
2884
  recoverable: error.retryable,
2876
2885
  };
2877
- return (_jsx(RunErrorRecoveryCard, { info: info, onContinue: continueStoppedRun, continueError: continueFailed ? t("agentChat.recovery.continueUnavailable") : null, onRetry: () => void retryFailedTurn(), retryHasUnavailableAttachment: retryRequest.hasUnavailableAttachment, onFork: async () => {
2886
+ return (_jsx(RunErrorRecoveryCard, { info: info, onContinue: continueStoppedRun, continueError: continueFailed ? t("agentChat.recovery.continueUnavailable") : null, onRetry: () => void retryFailedTurn(), onRetryWithoutAttachments: retryWithoutAttachments, retryHasUnavailableAttachment: retryRequest.hasUnavailableAttachment, onFork: async () => {
2878
2887
  if (!lastUserMessage)
2879
2888
  return surface.props.onForkChat?.();
2880
2889
  const fork = await control.fork(lastUserMessage.id);
@@ -2932,6 +2941,7 @@ function retryRequestFrom(message) {
2932
2941
  const requestMode = mode === "plan" || mode === "act" ? mode : undefined;
2933
2942
  return {
2934
2943
  text: message ? agentMessageText(message) : "",
2944
+ textWithContext: message ? rawAgentMessageText(message) : "",
2935
2945
  fileParts,
2936
2946
  requestAttachments: retryRequestAttachmentsFrom(custom?.agentNativeRetryRequestAttachments),
2937
2947
  hasUnavailableAttachment: custom?.agentNativeRetryAttachmentsUnavailable === true ||
@@ -3272,6 +3282,47 @@ function setupSubmissionResult(error) {
3272
3282
  : "submission-unavailable",
3273
3283
  };
3274
3284
  }
3285
+ /** The `/_agent-native/file-upload` ceiling (core's `DEFAULT_UPLOAD_MAX_FILE_BYTES`). */
3286
+ const AGENT_CHAT_UPLOAD_MAX_FILE_BYTES = 25 * 1024 * 1024;
3287
+ function uploadTooLargeMessage(t) {
3288
+ return t("agentChat.composer.fileTooLarge", {
3289
+ size: AGENT_CHAT_UPLOAD_MAX_FILE_BYTES / 1024 / 1024,
3290
+ });
3291
+ }
3292
+ function uploadHttpError(status, t) {
3293
+ const [code, message, retryable] = status === 413
3294
+ ? ["upload_too_large", uploadTooLargeMessage(t), false]
3295
+ : status === 401 || status === 403
3296
+ ? [
3297
+ "upload_session_expired",
3298
+ t("agentChat.composer.sessionExpired"),
3299
+ false,
3300
+ ]
3301
+ : status === 415
3302
+ ? [
3303
+ "upload_unsupported_type",
3304
+ t("agentChat.composer.unsupportedFileType"),
3305
+ false,
3306
+ ]
3307
+ : status === 408 || status === 429 || status >= 500
3308
+ ? [
3309
+ "upload_unavailable",
3310
+ t("agentChat.composer.uploadUnavailable"),
3311
+ true,
3312
+ ]
3313
+ : [
3314
+ `upload_http_${status}`,
3315
+ t("agentChat.composer.uploadFailed"),
3316
+ false,
3317
+ ];
3318
+ return Object.assign(new Error(message), { code, status, retryable });
3319
+ }
3320
+ /**
3321
+ * Durable uploads keyed by the attachment or file they came from, so a send
3322
+ * that failed on one file reuses its siblings' uploads instead of orphaning
3323
+ * them and uploading again.
3324
+ */
3325
+ const uploadedAgentChatSources = new WeakMap();
3275
3326
  const OPTIMIZABLE_RASTER_IMAGE_TYPES = new Set([
3276
3327
  "image/gif",
3277
3328
  "image/jpeg",
@@ -3307,6 +3358,7 @@ function requiresDurableAttachmentUpload(attachments, files) {
3307
3358
  }
3308
3359
  async function uploadAgentChatAttachments(control, attachments, files, options) {
3309
3360
  const entries = [];
3361
+ const sourceByFile = new Map();
3310
3362
  for (const attachment of attachments) {
3311
3363
  if (attachment.displayOnly)
3312
3364
  continue;
@@ -3321,12 +3373,15 @@ async function uploadAgentChatAttachments(control, attachments, files, options)
3321
3373
  }
3322
3374
  else {
3323
3375
  const file = await attachmentToFile(attachment);
3324
- if (file)
3376
+ if (file) {
3325
3377
  entries.push(file);
3378
+ sourceByFile.set(file, attachment);
3379
+ }
3326
3380
  }
3327
3381
  }
3328
3382
  entries.push(...files);
3329
3383
  const pending = entries.filter((entry) => entry instanceof File);
3384
+ const sourceOf = (file) => sourceByFile.get(file) ?? file;
3330
3385
  const optimizedImages = new Map();
3331
3386
  for (const file of pending) {
3332
3387
  if (!file.type.startsWith("image/") ||
@@ -3349,24 +3404,59 @@ async function uploadAgentChatAttachments(control, attachments, files, options)
3349
3404
  if (!options.storageConfigured && filesToUploadNormally.length > 0) {
3350
3405
  throw new Error(options.storageUnavailableMessage);
3351
3406
  }
3352
- if (filesToUploadNormally.length) {
3353
- const uploaded = await control.uploadFiles(filesToUploadNormally.map((file) => ({
3354
- name: file.name,
3355
- mediaType: file.type || "application/octet-stream",
3356
- size: file.size,
3357
- body: file,
3358
- })));
3359
- filesToUploadNormally.forEach((file, index) => {
3407
+ const oversized = filesToUploadNormally.flatMap((file, index) => file.size > AGENT_CHAT_UPLOAD_MAX_FILE_BYTES
3408
+ ? [
3409
+ {
3410
+ index,
3411
+ name: file.name,
3412
+ error: Object.assign(new Error(options.fileTooLargeMessage), {
3413
+ code: "upload_too_large",
3414
+ retryable: false,
3415
+ }),
3416
+ },
3417
+ ]
3418
+ : []);
3419
+ if (oversized.length)
3420
+ throw new AgentKitUploadError(oversized, []);
3421
+ const filesToUpload = filesToUploadNormally.filter((file) => {
3422
+ const reused = uploadedAgentChatSources.get(sourceOf(file));
3423
+ if (reused)
3424
+ uploadedByFile.set(file, reused);
3425
+ return !reused;
3426
+ });
3427
+ if (filesToUpload.length) {
3428
+ let uploaded;
3429
+ try {
3430
+ uploaded = await control.uploadFiles(filesToUpload.map((file) => ({
3431
+ name: file.name,
3432
+ mediaType: file.type || "application/octet-stream",
3433
+ size: file.size,
3434
+ body: file,
3435
+ })));
3436
+ }
3437
+ catch (error) {
3438
+ if (error instanceof AgentKitUploadError) {
3439
+ for (const { index, part } of error.uploaded) {
3440
+ uploadedAgentChatSources.set(sourceOf(filesToUpload[index]), part);
3441
+ }
3442
+ }
3443
+ throw error;
3444
+ }
3445
+ filesToUpload.forEach((file, index) => {
3360
3446
  const part = uploaded[index];
3361
3447
  if (!part) {
3362
3448
  throw new TypeError("File upload did not return every uploaded file.");
3363
3449
  }
3364
3450
  uploadedByFile.set(file, part);
3451
+ uploadedAgentChatSources.set(sourceOf(file), part);
3365
3452
  });
3366
3453
  }
3367
3454
  for (const file of pending) {
3368
- if (!optimizedImages.has(file) || !options.storageConfigured)
3455
+ if (!optimizedImages.has(file) ||
3456
+ !options.storageConfigured ||
3457
+ file.size > AGENT_CHAT_UPLOAD_MAX_FILE_BYTES) {
3369
3458
  continue;
3459
+ }
3370
3460
  try {
3371
3461
  const [part] = await control.uploadFiles([
3372
3462
  {
@@ -3439,6 +3529,34 @@ async function uploadRequestAttachments(control, attachments) {
3439
3529
  return { ...reference, url: part.url };
3440
3530
  });
3441
3531
  }
3532
+ /** Stored submissions carry durable references, so inline file bytes upload first. */
3533
+ async function durableFileParts(control, parts, options) {
3534
+ const inline = parts.filter((part) => isInlineDataUrl(part.url));
3535
+ if (inline.length === 0)
3536
+ return [...parts];
3537
+ if (!options.storageConfigured) {
3538
+ throw new Error(options.storageUnavailableMessage);
3539
+ }
3540
+ const uploaded = await control.uploadFiles(await Promise.all(inline.map(async (part) => {
3541
+ const body = await (await fetch(part.url)).blob();
3542
+ return {
3543
+ name: part.name,
3544
+ mediaType: part.mediaType ?? (body.type || "application/octet-stream"),
3545
+ size: body.size,
3546
+ body,
3547
+ };
3548
+ })));
3549
+ let uploadIndex = 0;
3550
+ return parts.map((part) => {
3551
+ if (!isInlineDataUrl(part.url))
3552
+ return part;
3553
+ const durable = uploaded[uploadIndex++];
3554
+ if (!durable?.url && !durable?.fileId) {
3555
+ throw new TypeError(`${part.name} did not receive a durable reference.`);
3556
+ }
3557
+ return durable;
3558
+ });
3559
+ }
3442
3560
  function rawAgentMessageTextFromParts(parts) {
3443
3561
  return parts
3444
3562
  .filter((part) => part.type === "text")
@@ -3454,21 +3572,47 @@ function rawAgentMessageText(message) {
3454
3572
  function agentMessageText(message) {
3455
3573
  return agentMessageTextFromParts(message.parts);
3456
3574
  }
3575
+ /**
3576
+ * The live message keeps inline image bytes for display; what is stored keeps
3577
+ * only a durable reference, or a named `omitted` marker when there is none.
3578
+ */
3457
3579
  function persistedAgentMessage(message) {
3458
3580
  return {
3459
3581
  ...message,
3460
3582
  parts: message.parts.map((part) => {
3461
3583
  const record = asRecord(part);
3462
- if ((record?.type === "file" || record?.type === "image") &&
3463
- typeof record.data === "string" &&
3464
- record.data.startsWith("data:")) {
3465
- const { data: _data, ...withoutInlineBody } = record;
3466
- return withoutInlineBody;
3584
+ if (!record ||
3585
+ (!isInlineDataUrl(record.data) && !isInlineDataUrl(record.url))) {
3586
+ return part;
3467
3587
  }
3468
- return part;
3588
+ const { data: _data, url, ...rest } = record;
3589
+ const durableUrl = isInlineDataUrl(url) ? undefined : url;
3590
+ return {
3591
+ ...rest,
3592
+ ...(durableUrl ? { url: durableUrl } : {}),
3593
+ ...(durableUrl || rest.fileId ? {} : { omitted: "inline-bytes" }),
3594
+ };
3469
3595
  }),
3470
3596
  };
3471
3597
  }
3598
+ function persistedAgentEvent(event) {
3599
+ return "message" in event && event.message
3600
+ ? { ...event, message: persistedAgentMessage(event.message) }
3601
+ : event;
3602
+ }
3603
+ function persistedQueuedMessage(message) {
3604
+ return {
3605
+ ...message,
3606
+ ...(message.attachments
3607
+ ? { attachments: message.attachments.map(persistableFilePart) }
3608
+ : {}),
3609
+ ...(message.requestAttachments
3610
+ ? {
3611
+ requestAttachments: message.requestAttachments.flatMap(({ data: _data, ...reference }) => reference.url ? [reference] : []),
3612
+ }
3613
+ : {}),
3614
+ };
3615
+ }
3472
3616
  function createAgentKitThreadSnapshot(thread) {
3473
3617
  const messages = thread.messages.map(persistedAgentMessage);
3474
3618
  const repositoryMessages = messages.map((message, index) => ({
@@ -3501,7 +3645,7 @@ function createAgentKitThreadSnapshot(thread) {
3501
3645
  }));
3502
3646
  const agentKit = {
3503
3647
  messages,
3504
- events: thread.events,
3648
+ events: thread.events.map(persistedAgentEvent),
3505
3649
  runs,
3506
3650
  activeRunIds: thread.activeRunIds,
3507
3651
  toolCalls: Object.values(thread.tools),
@@ -3511,7 +3655,7 @@ function createAgentKitThreadSnapshot(thread) {
3511
3655
  threadData: JSON.stringify({
3512
3656
  headId: messages.at(-1)?.id ?? null,
3513
3657
  messages: repositoryMessages,
3514
- queuedMessages: thread.queuedMessages,
3658
+ queuedMessages: thread.queuedMessages.map(persistedQueuedMessage),
3515
3659
  agentKit,
3516
3660
  }),
3517
3661
  title,
@@ -3569,8 +3713,8 @@ function storeAgentKitThreadHandoffSnapshot(key, thread, snapshot) {
3569
3713
  createdAt: thread.thread?.createdAt ?? messages[0]?.createdAt ?? now,
3570
3714
  updatedAt: thread.thread?.updatedAt ?? messages.at(-1)?.createdAt ?? now,
3571
3715
  messages,
3572
- queuedMessages: thread.queuedMessages,
3573
- events: thread.events,
3716
+ queuedMessages: thread.queuedMessages.map(persistedQueuedMessage),
3717
+ events: thread.events.map(persistedAgentEvent),
3574
3718
  runs: Object.entries(thread.runs).map(([id, run]) => ({
3575
3719
  ...run,
3576
3720
  id,