@agent-native/core 0.84.18 → 0.84.21
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +21 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/caller-auth.ts +37 -8
- package/corpus/core/src/a2a/client.ts +178 -67
- package/corpus/core/src/agent/production-agent.ts +19 -3
- package/corpus/core/src/agent/run-store.ts +180 -13
- package/corpus/core/src/agent/types.ts +1 -1
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
- package/corpus/core/src/cli/recap.ts +43 -34
- package/corpus/core/src/client/AssistantChat.tsx +49 -0
- package/corpus/core/src/client/PoweredByBadge.tsx +70 -17
- package/corpus/core/src/client/active-run-state.ts +30 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +5 -1
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +41 -3
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +133 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +2 -2
- package/corpus/core/src/client/i18n.tsx +10 -5
- package/corpus/core/src/client/sse-event-processor.ts +147 -5
- package/corpus/core/src/extensions/html-shell.ts +17 -3
- package/corpus/core/src/extensions/theme.ts +39 -0
- package/corpus/core/src/file-upload/builder.ts +35 -14
- package/corpus/core/src/file-upload/types.ts +3 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +49 -24
- package/corpus/core/src/mcp/builtin-tools.ts +3 -0
- package/corpus/core/src/mcp/org-directory.ts +66 -20
- package/corpus/core/src/scripts/call-agent.ts +7 -2
- package/corpus/templates/analytics/actions/provider-api-request.ts +8 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +91 -33
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +161 -12
- package/corpus/templates/analytics/changelog/2026-07-01-session-replays-load-large-production-recordings-from-scoped.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +14 -8
- package/corpus/templates/calendar/changelog/2026-07-01-meeting-invite-pages-have-cleaner-branding-a-black-dark-mo.md +6 -0
- package/corpus/templates/clips/actions/create-recording.ts +7 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +11 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +4 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
- package/corpus/templates/clips/actions/lib/loom-video.ts +16 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/countdown-overlay.tsx +4 -3
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +39 -12
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +65 -1
- package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +1 -22
- package/corpus/templates/clips/app/routes/record.tsx +48 -8
- package/corpus/templates/clips/changelog/2026-07-01-chrome-recording-uploads-now-serialize-chunks-to-avoid-resum.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-loom-imports-finish-sooner-by-skipping-the-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-clip-uploads-and-playback-recover-more-reliably-from-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-desktop-recording-avoids-unsupported-browser-tab-capture.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-embedded-player-progress-is-easier-to-see-on-dark-videos.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-loom-imports-show-clear-guidance-when-loom-does-not-provide-a-downloadable-video.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-preserve-in-flight-streaming-uploads-when-the-streaming-kill.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recording-controls-are-cleaner-and-the-countdown-uses-shadow.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-recordings-now-recover-when-final-upload-status-gets-lost.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +7 -224
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +94 -29
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +87 -5
- package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +7 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +14 -1
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +4 -1
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +96 -1
- package/corpus/templates/clips/shared/finalize-recovery.ts +242 -0
- package/corpus/templates/design/changelog/2026-07-01-design-recovers-sooner-when-an-agent-gets-stuck-preparing-a-.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -1
- package/dist/a2a/caller-auth.d.ts +1 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -1
- package/dist/a2a/caller-auth.js +34 -8
- package/dist/a2a/caller-auth.js.map +1 -1
- package/dist/a2a/client.d.ts +4 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +148 -58
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +19 -4
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +19 -6
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +163 -13
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +1 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +1 -1
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/cli/recap.d.ts +3 -3
- package/dist/cli/recap.d.ts.map +1 -1
- package/dist/cli/recap.js +41 -34
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +36 -2
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/PoweredByBadge.d.ts +9 -3
- package/dist/client/PoweredByBadge.d.ts.map +1 -1
- package/dist/client/PoweredByBadge.js +47 -15
- package/dist/client/PoweredByBadge.js.map +1 -1
- package/dist/client/active-run-state.d.ts +3 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +24 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +5 -1
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +32 -3
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +59 -2
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +2 -2
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/i18n.d.ts +1 -1
- package/dist/client/i18n.d.ts.map +1 -1
- package/dist/client/i18n.js +6 -2
- package/dist/client/i18n.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +2 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +121 -5
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +17 -3
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/theme.d.ts +9 -0
- package/dist/extensions/theme.d.ts.map +1 -1
- package/dist/extensions/theme.js +38 -0
- package/dist/extensions/theme.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/file-upload/builder.d.ts.map +1 -1
- package/dist/file-upload/builder.js +22 -8
- package/dist/file-upload/builder.js.map +1 -1
- package/dist/file-upload/types.d.ts +5 -1
- package/dist/file-upload/types.d.ts.map +1 -1
- package/dist/file-upload/types.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +43 -18
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +3 -0
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/org-directory.d.ts.map +1 -1
- package/dist/mcp/org-directory.js +58 -20
- package/dist/mcp/org-directory.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +6 -6
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +2 -2
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
IconArrowLeft,
|
|
3
3
|
IconArrowBackUp,
|
|
4
4
|
IconChevronRight,
|
|
5
|
+
IconCode,
|
|
5
6
|
IconDotsVertical,
|
|
6
7
|
IconHistory,
|
|
7
8
|
IconLoader2,
|
|
@@ -20,6 +21,7 @@ import { getThemeVars } from "../../extensions/theme.js";
|
|
|
20
21
|
import { sendToAgentChat } from "../agent-chat.js";
|
|
21
22
|
import { AgentToggleButton } from "../AgentPanel.js";
|
|
22
23
|
import { agentNativePath, appPath } from "../api-path.js";
|
|
24
|
+
import { Dialog, DialogContent, DialogTitle } from "../components/ui/dialog.js";
|
|
23
25
|
import {
|
|
24
26
|
Popover,
|
|
25
27
|
PopoverContent,
|
|
@@ -324,6 +326,131 @@ function applyCanonicalLink(path: string): () => void {
|
|
|
324
326
|
};
|
|
325
327
|
}
|
|
326
328
|
|
|
329
|
+
function SourceCodeDialog({
|
|
330
|
+
extension,
|
|
331
|
+
onSaved,
|
|
332
|
+
}: {
|
|
333
|
+
extension: Extension;
|
|
334
|
+
onSaved?: () => void;
|
|
335
|
+
}) {
|
|
336
|
+
const queryClient = useQueryClient();
|
|
337
|
+
const [open, setOpen] = useState(false);
|
|
338
|
+
const [code, setCode] = useState(extension.content ?? "");
|
|
339
|
+
const [saving, setSaving] = useState(false);
|
|
340
|
+
const [error, setError] = useState<string | null>(null);
|
|
341
|
+
|
|
342
|
+
// Sync in when the dialog opens, or when the viewed extension changes
|
|
343
|
+
// while the dialog stays mounted (e.g. re-parented to a different id).
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (open) setCode(extension.content ?? "");
|
|
346
|
+
}, [open, extension.id]);
|
|
347
|
+
|
|
348
|
+
const isDirty = code !== (extension.content ?? "");
|
|
349
|
+
|
|
350
|
+
// Block Escape / outside-click from closing while there are unsaved edits.
|
|
351
|
+
const handleOpenChange = (next: boolean) => {
|
|
352
|
+
if (!next && isDirty) return;
|
|
353
|
+
setOpen(next);
|
|
354
|
+
if (!next) setError(null);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const handleCancel = () => {
|
|
358
|
+
setCode(extension.content ?? "");
|
|
359
|
+
setOpen(false);
|
|
360
|
+
setError(null);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const handleSave = async () => {
|
|
364
|
+
setSaving(true);
|
|
365
|
+
setError(null);
|
|
366
|
+
try {
|
|
367
|
+
const res = await fetch(
|
|
368
|
+
agentNativePath(`/_agent-native/extensions/${extension.id}`),
|
|
369
|
+
{
|
|
370
|
+
method: "PUT",
|
|
371
|
+
headers: { "Content-Type": "application/json" },
|
|
372
|
+
body: JSON.stringify({ content: code }),
|
|
373
|
+
},
|
|
374
|
+
);
|
|
375
|
+
if (!res.ok) {
|
|
376
|
+
const body = await res.json().catch(() => ({}));
|
|
377
|
+
throw new Error(body?.error ?? `Save failed (${res.status})`);
|
|
378
|
+
}
|
|
379
|
+
setOpen(false);
|
|
380
|
+
queryClient.setQueryData<Extension>(["extension", extension.id], (old) =>
|
|
381
|
+
old ? { ...old, content: code } : old,
|
|
382
|
+
);
|
|
383
|
+
queryClient.invalidateQueries({
|
|
384
|
+
queryKey: ["extension", extension.id],
|
|
385
|
+
});
|
|
386
|
+
queryClient.invalidateQueries({ queryKey: ["extensions"] });
|
|
387
|
+
onSaved?.();
|
|
388
|
+
} catch (err: any) {
|
|
389
|
+
setError(err?.message ?? "Save failed");
|
|
390
|
+
} finally {
|
|
391
|
+
setSaving(false);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<>
|
|
397
|
+
<Tooltip>
|
|
398
|
+
<TooltipTrigger asChild>
|
|
399
|
+
<button
|
|
400
|
+
type="button"
|
|
401
|
+
onClick={() => setOpen(true)}
|
|
402
|
+
className="inline-flex items-center justify-center rounded-md h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer"
|
|
403
|
+
>
|
|
404
|
+
<IconCode className="h-4 w-4" />
|
|
405
|
+
</button>
|
|
406
|
+
</TooltipTrigger>
|
|
407
|
+
<TooltipContent>View / edit source</TooltipContent>
|
|
408
|
+
</Tooltip>
|
|
409
|
+
<Dialog open={open} onOpenChange={handleOpenChange}>
|
|
410
|
+
<DialogContent className="flex h-[85vh] w-[90vw] max-w-[900px] flex-col gap-0 overflow-hidden p-0">
|
|
411
|
+
<div className="flex shrink-0 items-center border-b border-border px-5 py-3 pr-12">
|
|
412
|
+
<DialogTitle className="truncate text-sm font-medium">
|
|
413
|
+
{extension.name} — source
|
|
414
|
+
</DialogTitle>
|
|
415
|
+
</div>
|
|
416
|
+
<textarea
|
|
417
|
+
className="flex-1 resize-none bg-muted/40 px-5 py-4 font-mono text-xs leading-relaxed text-foreground focus:outline-none"
|
|
418
|
+
value={code}
|
|
419
|
+
onChange={(e) => setCode(e.target.value)}
|
|
420
|
+
spellCheck={false}
|
|
421
|
+
/>
|
|
422
|
+
<div className="flex shrink-0 items-center justify-between border-t border-border px-5 py-3">
|
|
423
|
+
{error ? (
|
|
424
|
+
<p className="text-xs text-destructive">{error}</p>
|
|
425
|
+
) : (
|
|
426
|
+
<span className="text-xs text-muted-foreground">
|
|
427
|
+
Alpine.js / HTML · {code.length.toLocaleString()} chars
|
|
428
|
+
</span>
|
|
429
|
+
)}
|
|
430
|
+
<div className="flex items-center gap-2">
|
|
431
|
+
<button
|
|
432
|
+
type="button"
|
|
433
|
+
onClick={handleCancel}
|
|
434
|
+
className="inline-flex h-8 cursor-pointer items-center rounded-md border border-input px-3 text-xs hover:bg-accent"
|
|
435
|
+
>
|
|
436
|
+
Cancel
|
|
437
|
+
</button>
|
|
438
|
+
<button
|
|
439
|
+
type="button"
|
|
440
|
+
onClick={handleSave}
|
|
441
|
+
disabled={saving}
|
|
442
|
+
className="inline-flex h-8 cursor-pointer items-center rounded-md bg-primary px-4 text-xs font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50"
|
|
443
|
+
>
|
|
444
|
+
{saving ? "Saving…" : "Save"}
|
|
445
|
+
</button>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
</DialogContent>
|
|
449
|
+
</Dialog>
|
|
450
|
+
</>
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
327
454
|
function EditToolPopover({
|
|
328
455
|
extension,
|
|
329
456
|
onOpenChange,
|
|
@@ -1157,6 +1284,12 @@ export function ExtensionViewer({ extensionId }: ExtensionViewerProps) {
|
|
|
1157
1284
|
onRestored={() => setRefreshKey((k) => k + 1)}
|
|
1158
1285
|
onOpenChange={onPopoverOpenChange}
|
|
1159
1286
|
/>
|
|
1287
|
+
{extension.canEdit && (
|
|
1288
|
+
<SourceCodeDialog
|
|
1289
|
+
extension={extension}
|
|
1290
|
+
onSaved={() => setRefreshKey((k) => k + 1)}
|
|
1291
|
+
/>
|
|
1292
|
+
)}
|
|
1160
1293
|
<EditToolPopover
|
|
1161
1294
|
extension={extension}
|
|
1162
1295
|
onOpenChange={onPopoverOpenChange}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
IconChevronDown,
|
|
3
3
|
IconPlus,
|
|
4
|
-
|
|
4
|
+
IconFilter,
|
|
5
5
|
IconStar,
|
|
6
6
|
IconStarFilled,
|
|
7
7
|
IconTrash,
|
|
@@ -570,7 +570,7 @@ function ExtensionSortMenu({
|
|
|
570
570
|
className="inline-flex h-6 w-6 shrink-0 items-center justify-center rounded-md text-muted-foreground/45 opacity-0 transition-all hover:bg-accent hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring group-hover/extensions-section:opacity-100"
|
|
571
571
|
aria-label={copy.sortOptions}
|
|
572
572
|
>
|
|
573
|
-
<
|
|
573
|
+
<IconFilter className="h-3.5 w-3.5" />
|
|
574
574
|
</button>
|
|
575
575
|
</DropdownMenuTrigger>
|
|
576
576
|
</TooltipTrigger>
|
|
@@ -674,7 +674,7 @@ export function LanguagePicker({
|
|
|
674
674
|
className?: string;
|
|
675
675
|
includeSystem?: boolean;
|
|
676
676
|
label?: string;
|
|
677
|
-
variant?: "select" | "icon";
|
|
677
|
+
variant?: "select" | "icon" | "ghost-icon";
|
|
678
678
|
}) {
|
|
679
679
|
const { locale, preference, setPreference } = useLocale();
|
|
680
680
|
const [open, setOpen] = useState(false);
|
|
@@ -716,10 +716,15 @@ export function LanguagePicker({
|
|
|
716
716
|
title={triggerLabel}
|
|
717
717
|
data-language-picker-trigger
|
|
718
718
|
className={cn(
|
|
719
|
-
"shrink-0 rounded-md
|
|
719
|
+
"shrink-0 rounded-md outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
720
|
+
variant === "ghost-icon"
|
|
721
|
+
? "flex h-9 w-9 items-center justify-center text-muted-foreground hover:bg-accent hover:text-foreground data-[state=open]:bg-accent data-[state=open]:text-foreground"
|
|
722
|
+
: "border border-border bg-background text-foreground hover:border-foreground/30 hover:bg-accent/40 hover:text-foreground data-[state=open]:border-foreground/30 data-[state=open]:bg-accent/40",
|
|
720
723
|
variant === "icon"
|
|
721
724
|
? "flex h-8 w-8 items-center justify-center"
|
|
722
|
-
:
|
|
725
|
+
: variant === "select"
|
|
726
|
+
? "flex h-9 w-full items-center justify-between gap-2 px-3 text-start text-sm"
|
|
727
|
+
: null,
|
|
723
728
|
)}
|
|
724
729
|
>
|
|
725
730
|
<span className="flex min-w-0 items-center gap-2">
|
|
@@ -740,12 +745,12 @@ export function LanguagePicker({
|
|
|
740
745
|
</PopoverPrimitive.Trigger>
|
|
741
746
|
<PopoverPrimitive.Portal>
|
|
742
747
|
<PopoverPrimitive.Content
|
|
743
|
-
align={variant === "
|
|
748
|
+
align={variant === "select" ? "start" : "end"}
|
|
744
749
|
sideOffset={6}
|
|
745
750
|
role="menu"
|
|
746
751
|
className={cn(
|
|
747
752
|
"z-[9999] max-h-[min(20rem,var(--radix-popover-content-available-height))] overflow-y-auto rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-lg outline-none will-change-[transform,opacity] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:duration-100 data-[state=open]:duration-150 data-[state=closed]:ease-in data-[state=open]:ease-out data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
|
748
|
-
variant === "icon"
|
|
753
|
+
variant === "icon" || variant === "ghost-icon"
|
|
749
754
|
? "min-w-56"
|
|
750
755
|
: "w-[min(20rem,calc(100vw-2rem))] min-w-[var(--radix-popover-trigger-width)]",
|
|
751
756
|
)}
|
|
@@ -49,6 +49,7 @@ export interface SSEEvent {
|
|
|
49
49
|
/** Server-assigned call identifier emitted on tool_start / tool_done events. */
|
|
50
50
|
id?: string;
|
|
51
51
|
label?: string;
|
|
52
|
+
progressBytes?: number;
|
|
52
53
|
input?: Record<string, string>;
|
|
53
54
|
result?: string;
|
|
54
55
|
isError?: boolean;
|
|
@@ -144,9 +145,58 @@ export class AgentAutoContinueSignal extends Error {
|
|
|
144
145
|
}
|
|
145
146
|
|
|
146
147
|
export const SSE_NO_PROGRESS_TIMEOUT_MS = 75_000;
|
|
148
|
+
export const SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS = 90_000;
|
|
147
149
|
|
|
148
150
|
type ActivityTrailEntry = AgentActivityTrailEntry;
|
|
149
151
|
|
|
152
|
+
type PreparingActionState = {
|
|
153
|
+
tool?: string;
|
|
154
|
+
startedAt?: number;
|
|
155
|
+
/**
|
|
156
|
+
* Timestamp of the last real streaming progress for the in-preparation tool
|
|
157
|
+
* input. The server emits a throttled `activity` heartbeat per
|
|
158
|
+
* `tool-input-delta`, so while the model is actively streaming a (possibly
|
|
159
|
+
* very large) tool argument this keeps advancing. The stall guard measures
|
|
160
|
+
* silence from HERE — not from `startedAt` — so a legitimately large, still-
|
|
161
|
+
* streaming input is never aborted; only genuine silence (keepalive-only, no
|
|
162
|
+
* further deltas) can trip it.
|
|
163
|
+
*/
|
|
164
|
+
lastProgressAt?: number;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function formatProgressBytes(bytes: number): string {
|
|
168
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
169
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
170
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function activityProgressBytes(ev: SSEEvent): number | undefined {
|
|
174
|
+
return typeof ev.progressBytes === "number" &&
|
|
175
|
+
Number.isFinite(ev.progressBytes) &&
|
|
176
|
+
ev.progressBytes >= 0
|
|
177
|
+
? Math.floor(ev.progressBytes)
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isPreparingActionActivity(ev: SSEEvent): boolean {
|
|
182
|
+
if (ev.type !== "activity") return false;
|
|
183
|
+
const label = (ev.label ?? "").trim().toLowerCase();
|
|
184
|
+
return label.startsWith("preparing ") && label.includes(" action");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function baseActivityLabel(ev: SSEEvent, tool?: string): string {
|
|
188
|
+
return humanizeToolLabelText(ev.label ?? "Working", tool);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function visibleActivityLabel(ev: SSEEvent, tool?: string): string {
|
|
192
|
+
const label = baseActivityLabel(ev, tool);
|
|
193
|
+
const progressBytes = activityProgressBytes(ev);
|
|
194
|
+
if (progressBytes === undefined || !isPreparingActionActivity(ev)) {
|
|
195
|
+
return label;
|
|
196
|
+
}
|
|
197
|
+
return `${label} (${formatProgressBytes(progressBytes)} streamed)`;
|
|
198
|
+
}
|
|
199
|
+
|
|
150
200
|
function findPendingToolCallIndex(
|
|
151
201
|
content: ContentPart[],
|
|
152
202
|
toolName: string,
|
|
@@ -199,6 +249,53 @@ function appendActivityTrail(
|
|
|
199
249
|
}
|
|
200
250
|
}
|
|
201
251
|
|
|
252
|
+
function updatePreparingActionState(
|
|
253
|
+
state: PreparingActionState,
|
|
254
|
+
ev: SSEEvent,
|
|
255
|
+
now: number,
|
|
256
|
+
) {
|
|
257
|
+
if (ev.type === "activity" && isPreparingActionActivity(ev)) {
|
|
258
|
+
const tool = ev.tool?.trim() || undefined;
|
|
259
|
+
if (!tool) return;
|
|
260
|
+
if (state.tool !== tool || state.startedAt === undefined) {
|
|
261
|
+
state.tool = tool;
|
|
262
|
+
state.startedAt = now;
|
|
263
|
+
}
|
|
264
|
+
// Every tool-input activity is a throttled proof the model is still
|
|
265
|
+
// streaming this action's argument. Treat it as progress so large inputs
|
|
266
|
+
// that take many seconds/minutes to stream are NOT flagged as stalled.
|
|
267
|
+
state.lastProgressAt = now;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (
|
|
272
|
+
ev.type === "clear" ||
|
|
273
|
+
ev.type === "text" ||
|
|
274
|
+
ev.type === "tool_start" ||
|
|
275
|
+
ev.type === "tool_done" ||
|
|
276
|
+
ev.type === "done" ||
|
|
277
|
+
ev.type === "error" ||
|
|
278
|
+
ev.type === "missing_api_key"
|
|
279
|
+
) {
|
|
280
|
+
state.tool = undefined;
|
|
281
|
+
state.startedAt = undefined;
|
|
282
|
+
state.lastProgressAt = undefined;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function hasStalledPreparingAction(state: PreparingActionState, now: number) {
|
|
287
|
+
// Fire only when a tool input has gone SILENT — no further streaming deltas
|
|
288
|
+
// for the whole window — never merely because a large input has been
|
|
289
|
+
// streaming for a long time. `lastProgressAt` advances on every delta
|
|
290
|
+
// heartbeat, so an actively-streaming large output keeps resetting this and
|
|
291
|
+
// survives; a genuinely stuck prep (keepalive-only, no deltas) trips it.
|
|
292
|
+
return (
|
|
293
|
+
state.tool !== undefined &&
|
|
294
|
+
state.lastProgressAt !== undefined &&
|
|
295
|
+
now - state.lastProgressAt >= SSE_ACTION_PREPARATION_STALL_TIMEOUT_MS
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
202
299
|
async function readChunkWithProgressTimeout(
|
|
203
300
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
|
204
301
|
lastMeaningfulEventAt: number,
|
|
@@ -491,7 +588,7 @@ export function processEvent(
|
|
|
491
588
|
|
|
492
589
|
if (ev.type === "activity") {
|
|
493
590
|
const tool = ev.tool?.trim() || undefined;
|
|
494
|
-
const label =
|
|
591
|
+
const label = visibleActivityLabel(ev, tool);
|
|
495
592
|
if (typeof window !== "undefined") {
|
|
496
593
|
window.dispatchEvent(
|
|
497
594
|
new CustomEvent("agent-chat:activity", {
|
|
@@ -941,6 +1038,7 @@ export async function* readSSEStream(
|
|
|
941
1038
|
let buf = "";
|
|
942
1039
|
let lastMeaningfulEventAt = Date.now();
|
|
943
1040
|
const activityTrail: ActivityTrailEntry[] = [];
|
|
1041
|
+
const preparingActionState: PreparingActionState = {};
|
|
944
1042
|
const processEventState: ProcessEventState = {
|
|
945
1043
|
completedToolsAfterLastAssistantText: new Set(),
|
|
946
1044
|
};
|
|
@@ -1000,7 +1098,9 @@ export async function* readSSEStream(
|
|
|
1000
1098
|
continue;
|
|
1001
1099
|
}
|
|
1002
1100
|
sawDataEvent = true;
|
|
1003
|
-
|
|
1101
|
+
const now = Date.now();
|
|
1102
|
+
lastMeaningfulEventAt = now;
|
|
1103
|
+
updatePreparingActionState(preparingActionState, ev, now);
|
|
1004
1104
|
|
|
1005
1105
|
// Track sequence number for reconnection
|
|
1006
1106
|
if (ev.seq !== undefined && onSeq) {
|
|
@@ -1012,7 +1112,7 @@ export async function* readSSEStream(
|
|
|
1012
1112
|
} else if (ev.type === "activity") {
|
|
1013
1113
|
const tool = ev.tool?.trim() || undefined;
|
|
1014
1114
|
appendActivityTrail(activityTrail, {
|
|
1015
|
-
label:
|
|
1115
|
+
label: baseActivityLabel(ev, tool),
|
|
1016
1116
|
...(tool ? { tool } : {}),
|
|
1017
1117
|
});
|
|
1018
1118
|
} else if (ev.type === "tool_start") {
|
|
@@ -1039,6 +1139,12 @@ export async function* readSSEStream(
|
|
|
1039
1139
|
);
|
|
1040
1140
|
|
|
1041
1141
|
if (result) yield withStreamMetadata(result);
|
|
1142
|
+
if (hasStalledPreparingAction(preparingActionState, Date.now())) {
|
|
1143
|
+
throw new AgentAutoContinueSignal({
|
|
1144
|
+
reason: "no_progress",
|
|
1145
|
+
activityTrail: [...activityTrail],
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1042
1148
|
if (action === "auto_continue") {
|
|
1043
1149
|
throw new AgentAutoContinueSignal(
|
|
1044
1150
|
autoContinue
|
|
@@ -1099,6 +1205,8 @@ export async function readSSEStreamRaw(
|
|
|
1099
1205
|
const decoder = new TextDecoder();
|
|
1100
1206
|
let buf = "";
|
|
1101
1207
|
let lastMeaningfulEventAt = Date.now();
|
|
1208
|
+
const activityTrail: ActivityTrailEntry[] = [];
|
|
1209
|
+
const preparingActionState: PreparingActionState = {};
|
|
1102
1210
|
const processEventState: ProcessEventState = {
|
|
1103
1211
|
completedToolsAfterLastAssistantText: new Set(),
|
|
1104
1212
|
};
|
|
@@ -1134,12 +1242,37 @@ export async function readSSEStreamRaw(
|
|
|
1134
1242
|
continue;
|
|
1135
1243
|
}
|
|
1136
1244
|
sawDataEvent = true;
|
|
1137
|
-
|
|
1245
|
+
const now = Date.now();
|
|
1246
|
+
lastMeaningfulEventAt = now;
|
|
1247
|
+
updatePreparingActionState(preparingActionState, ev, now);
|
|
1138
1248
|
|
|
1139
1249
|
if (ev.seq !== undefined && onSeq) {
|
|
1140
1250
|
onSeq(ev.seq);
|
|
1141
1251
|
}
|
|
1142
1252
|
|
|
1253
|
+
if (ev.type === "clear") {
|
|
1254
|
+
activityTrail.length = 0;
|
|
1255
|
+
} else if (ev.type === "activity") {
|
|
1256
|
+
const tool = ev.tool?.trim() || undefined;
|
|
1257
|
+
appendActivityTrail(activityTrail, {
|
|
1258
|
+
label: baseActivityLabel(ev, tool),
|
|
1259
|
+
...(tool ? { tool } : {}),
|
|
1260
|
+
});
|
|
1261
|
+
} else if (ev.type === "tool_start") {
|
|
1262
|
+
const tool = ev.tool ?? "unknown";
|
|
1263
|
+
appendActivityTrail(activityTrail, {
|
|
1264
|
+
label: runningToolLabel(tool),
|
|
1265
|
+
tool,
|
|
1266
|
+
});
|
|
1267
|
+
} else if (ev.type === "tool_done") {
|
|
1268
|
+
const tool = ev.tool ?? "unknown";
|
|
1269
|
+
for (let i = activityTrail.length - 1; i >= 0; i--) {
|
|
1270
|
+
if (activityTrail[i]?.tool === tool) {
|
|
1271
|
+
activityTrail.splice(i, 1);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1143
1276
|
const { action, autoContinue } = processEvent(
|
|
1144
1277
|
ev,
|
|
1145
1278
|
content,
|
|
@@ -1159,9 +1292,18 @@ export async function readSSEStreamRaw(
|
|
|
1159
1292
|
if (action === "auto_continue") {
|
|
1160
1293
|
onUpdate([...content]);
|
|
1161
1294
|
throw new AgentAutoContinueSignal(
|
|
1162
|
-
autoContinue
|
|
1295
|
+
autoContinue
|
|
1296
|
+
? { ...autoContinue, activityTrail: [...activityTrail] }
|
|
1297
|
+
: { reason: "stream_ended", activityTrail: [...activityTrail] },
|
|
1163
1298
|
);
|
|
1164
1299
|
}
|
|
1300
|
+
if (hasStalledPreparingAction(preparingActionState, Date.now())) {
|
|
1301
|
+
onUpdate([...content]);
|
|
1302
|
+
throw new AgentAutoContinueSignal({
|
|
1303
|
+
reason: "no_progress",
|
|
1304
|
+
activityTrail: [...activityTrail],
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1165
1307
|
if (
|
|
1166
1308
|
action === "done" ||
|
|
1167
1309
|
action === "error" ||
|
|
@@ -226,11 +226,19 @@ export function buildExtensionHtml(
|
|
|
226
226
|
</style>
|
|
227
227
|
<style>
|
|
228
228
|
*, *::before, *::after { border-color: hsl(var(--border)); }
|
|
229
|
+
html, body {
|
|
230
|
+
/* Transparent so the iframe inherits the host surface (dashboard panel,
|
|
231
|
+
sidebar, chat) instead of painting the browser's default white canvas.
|
|
232
|
+
The dark class still flips the theme vars; content paints its own
|
|
233
|
+
bg-background / bg-card surfaces. */
|
|
234
|
+
background: transparent;
|
|
235
|
+
}
|
|
229
236
|
body {
|
|
230
237
|
--agent-native-extension-padding: clamp(16px, 2vw, 24px);
|
|
231
238
|
/* Legacy alias for pre-rename extension content (do not remove). */
|
|
232
239
|
--agent-native-tool-padding: var(--agent-native-extension-padding);
|
|
233
240
|
box-sizing: border-box;
|
|
241
|
+
color: hsl(var(--foreground));
|
|
234
242
|
font-family: 'Inter', sans-serif;
|
|
235
243
|
margin: 0;
|
|
236
244
|
min-height: 100vh;
|
|
@@ -403,8 +411,14 @@ export function buildExtensionHtml(
|
|
|
403
411
|
}
|
|
404
412
|
|
|
405
413
|
if (!res.ok) {
|
|
406
|
-
var err = res.body || {
|
|
407
|
-
|
|
414
|
+
var err = res.body || {};
|
|
415
|
+
var rawError = typeof err === 'string' ? err : err.error;
|
|
416
|
+
var message = (typeof rawError === 'string' && rawError.trim())
|
|
417
|
+
? rawError
|
|
418
|
+
: (res.status === 404
|
|
419
|
+
? "Action '" + name + "' is not available over HTTP (404). It may be agent-only (http: false); expose it with an HTTP-mounted action to call it from an extension."
|
|
420
|
+
: "Action '" + name + "' failed (" + (res.status || 'network error') + ")");
|
|
421
|
+
throw new Error(message);
|
|
408
422
|
}
|
|
409
423
|
return res.body;
|
|
410
424
|
}
|
|
@@ -730,7 +744,7 @@ export function buildExtensionHtml(
|
|
|
730
744
|
});
|
|
731
745
|
</script>
|
|
732
746
|
</head>
|
|
733
|
-
<body${extensionId ? ` data-extension-id="${extensionIdAttr}" data-tool-id="${extensionIdAttr}"` : ""} class="
|
|
747
|
+
<body${extensionId ? ` data-extension-id="${extensionIdAttr}" data-tool-id="${extensionIdAttr}"` : ""} class="text-foreground">
|
|
734
748
|
${content}
|
|
735
749
|
<div id="__extension-error-toast">
|
|
736
750
|
<div style="display:flex;align-items:flex-start;gap:8px;">
|
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical set of semantic theme token names shared by the app and extension
|
|
3
|
+
* iframes. This is the single source of truth: `getThemeVars` bakes default
|
|
4
|
+
* values for these into every iframe, and the host reads the *actual* computed
|
|
5
|
+
* values for the same names to sync its live palette into embedded extensions
|
|
6
|
+
* (see EmbeddedExtension). Custom properties can't be enumerated portably from
|
|
7
|
+
* a computed style, so the names must be listed explicitly.
|
|
8
|
+
*/
|
|
9
|
+
export const THEME_VAR_NAMES = [
|
|
10
|
+
"--background",
|
|
11
|
+
"--foreground",
|
|
12
|
+
"--card",
|
|
13
|
+
"--card-foreground",
|
|
14
|
+
"--popover",
|
|
15
|
+
"--popover-foreground",
|
|
16
|
+
"--primary",
|
|
17
|
+
"--primary-foreground",
|
|
18
|
+
"--secondary",
|
|
19
|
+
"--secondary-foreground",
|
|
20
|
+
"--muted",
|
|
21
|
+
"--muted-foreground",
|
|
22
|
+
"--accent",
|
|
23
|
+
"--accent-foreground",
|
|
24
|
+
"--destructive",
|
|
25
|
+
"--destructive-foreground",
|
|
26
|
+
"--border",
|
|
27
|
+
"--input",
|
|
28
|
+
"--ring",
|
|
29
|
+
"--radius",
|
|
30
|
+
"--sidebar-background",
|
|
31
|
+
"--sidebar-foreground",
|
|
32
|
+
"--sidebar-primary",
|
|
33
|
+
"--sidebar-primary-foreground",
|
|
34
|
+
"--sidebar-accent",
|
|
35
|
+
"--sidebar-accent-foreground",
|
|
36
|
+
"--sidebar-border",
|
|
37
|
+
"--sidebar-ring",
|
|
38
|
+
] as const;
|
|
39
|
+
|
|
1
40
|
/**
|
|
2
41
|
* CSS variables baked into every extension iframe. Both light and dark are
|
|
3
42
|
* always emitted — the `.dark` class on the iframe's `<html>` toggles between
|
|
@@ -27,6 +27,15 @@ function makeBody(bytes: Uint8Array, mimeType: string): BodyInit {
|
|
|
27
27
|
: (bytes as unknown as BodyInit);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function shouldUseSignedUrlUpload(
|
|
31
|
+
bytes: Uint8Array,
|
|
32
|
+
mimeType: string,
|
|
33
|
+
): boolean {
|
|
34
|
+
return (
|
|
35
|
+
bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES || /^video\//i.test(mimeType)
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
function fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {
|
|
31
40
|
const controller = new AbortController();
|
|
32
41
|
const timer = setTimeout(() => controller.abort(), UPLOAD_TIMEOUT_MS);
|
|
@@ -86,6 +95,7 @@ async function uploadLargeFileViaSignedUrl(
|
|
|
86
95
|
privateKey,
|
|
87
96
|
assetId,
|
|
88
97
|
input.filename,
|
|
98
|
+
{ skipCompressionWait: input.skipCompressionWait },
|
|
89
99
|
);
|
|
90
100
|
console.log(`[builder-upload] done [${assetId}]: ${url}`);
|
|
91
101
|
return { url, id, provider: "builder" };
|
|
@@ -139,19 +149,21 @@ async function completeBuilderUpload(
|
|
|
139
149
|
privateKey: string,
|
|
140
150
|
assetId: string,
|
|
141
151
|
filename: string | undefined,
|
|
152
|
+
options?: { skipCompressionWait?: boolean },
|
|
142
153
|
): Promise<{ url: string; id?: string }> {
|
|
143
154
|
const host = builderUploadHost();
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
|
|
155
|
+
const url = new URL("/api/v1/upload/complete", host);
|
|
156
|
+
if (options?.skipCompressionWait) {
|
|
157
|
+
url.searchParams.set("skipCompressionWait", "true");
|
|
158
|
+
}
|
|
159
|
+
const res = await fetchWithTimeout(url.toString(), {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: {
|
|
162
|
+
Authorization: `Bearer ${privateKey}`,
|
|
163
|
+
"Content-Type": "application/json",
|
|
153
164
|
},
|
|
154
|
-
|
|
165
|
+
body: JSON.stringify({ assetId, name: filename }),
|
|
166
|
+
});
|
|
155
167
|
await assertOk(res, "Builder.io upload complete failed");
|
|
156
168
|
const json = (await res.json()) as { url?: string; id?: string };
|
|
157
169
|
if (!json.url) throw new Error("Builder.io upload/complete returned no URL");
|
|
@@ -204,7 +216,8 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
204
216
|
id: "builder",
|
|
205
217
|
name: "Builder.io",
|
|
206
218
|
isConfigured: () => !!process.env.BUILDER_PRIVATE_KEY,
|
|
207
|
-
upload: async (
|
|
219
|
+
upload: async (input: FileUploadInput) => {
|
|
220
|
+
const { data, filename, mimeType } = input;
|
|
208
221
|
const { resolveBuilderPrivateKey } =
|
|
209
222
|
await import("../server/credential-provider.js");
|
|
210
223
|
const privateKey = await resolveBuilderPrivateKey();
|
|
@@ -226,9 +239,9 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
226
239
|
data instanceof Uint8Array ? data : new Uint8Array(data as any);
|
|
227
240
|
const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);
|
|
228
241
|
|
|
229
|
-
if (bytes
|
|
242
|
+
if (shouldUseSignedUrlUpload(bytes, bareMimeType)) {
|
|
230
243
|
return uploadLargeFileViaSignedUrl(
|
|
231
|
-
|
|
244
|
+
input,
|
|
232
245
|
privateKey,
|
|
233
246
|
bareMimeType,
|
|
234
247
|
bytes,
|
|
@@ -241,6 +254,9 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
241
254
|
|
|
242
255
|
const url = new URL("/api/v1/upload", builderUploadHost());
|
|
243
256
|
if (filename) url.searchParams.set("name", filename);
|
|
257
|
+
if (input.skipCompressionWait) {
|
|
258
|
+
url.searchParams.set("skipCompressionWait", "true");
|
|
259
|
+
}
|
|
244
260
|
|
|
245
261
|
const response = await uploadSmallFile(url, {
|
|
246
262
|
method: "POST",
|
|
@@ -366,7 +382,7 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
366
382
|
: new Error("GCS PUT failed after retries");
|
|
367
383
|
},
|
|
368
384
|
|
|
369
|
-
async completeSession(session, filename) {
|
|
385
|
+
async completeSession(session, filename, options) {
|
|
370
386
|
const { resolveBuilderPrivateKey } =
|
|
371
387
|
await import("../server/credential-provider.js");
|
|
372
388
|
const privateKey = await resolveBuilderPrivateKey();
|
|
@@ -378,6 +394,11 @@ export const builderFileUploadProvider: FileUploadProvider = {
|
|
|
378
394
|
privateKey,
|
|
379
395
|
assetId,
|
|
380
396
|
filename,
|
|
397
|
+
{
|
|
398
|
+
skipCompressionWait:
|
|
399
|
+
options?.skipCompressionWait ||
|
|
400
|
+
session.meta.skipCompressionWait === true,
|
|
401
|
+
},
|
|
381
402
|
);
|
|
382
403
|
console.log(`[builder-resumable] upload complete: ${url}`);
|
|
383
404
|
return url;
|
|
@@ -16,6 +16,8 @@ export interface FileUploadInput {
|
|
|
16
16
|
mimeType?: string;
|
|
17
17
|
/** Optional owner email for per-user scoping in fallback storage. */
|
|
18
18
|
ownerEmail?: string;
|
|
19
|
+
/** Builder.io upload hint: return after asset registration instead of waiting for server-side compression. */
|
|
20
|
+
skipCompressionWait?: boolean;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export interface FileUploadResult {
|
|
@@ -77,6 +79,7 @@ export interface FileUploadProvider {
|
|
|
77
79
|
completeSession(
|
|
78
80
|
session: ResumableUploadSession,
|
|
79
81
|
filename: string,
|
|
82
|
+
options?: { skipCompressionWait?: boolean },
|
|
80
83
|
): Promise<string>;
|
|
81
84
|
};
|
|
82
85
|
}
|