@agent-native/toolkit 0.22.0 → 0.22.1-nightly-20260926182238
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/composer/ComposerPlusMenu.d.ts +2 -1
- package/dist/composer/ComposerPlusMenu.d.ts.map +1 -1
- package/dist/composer/ComposerPlusMenu.js +24 -18
- package/dist/composer/ComposerPlusMenu.js.map +1 -1
- package/dist/composer/PromptComposer.context.spec.js +40 -25
- package/dist/composer/PromptComposer.context.spec.js.map +1 -1
- package/dist/composer/PromptComposer.d.ts +15 -7
- package/dist/composer/PromptComposer.d.ts.map +1 -1
- package/dist/composer/PromptComposer.js +59 -30
- package/dist/composer/PromptComposer.js.map +1 -1
- package/dist/composer/PromptComposer.spec.js +29 -18
- package/dist/composer/PromptComposer.spec.js.map +1 -1
- package/dist/composer/TiptapComposer.d.ts +5 -2
- package/dist/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/composer/TiptapComposer.js +35 -5
- package/dist/composer/TiptapComposer.js.map +1 -1
- package/dist/composer/TiptapComposer.spec.js +96 -0
- package/dist/composer/TiptapComposer.spec.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/index.d.ts.map +1 -1
- package/dist/composer/index.js +1 -1
- package/dist/composer/index.js.map +1 -1
- package/dist/composer/runtime-adapters.d.ts +3 -2
- package/dist/composer/runtime-adapters.d.ts.map +1 -1
- package/dist/composer/runtime-adapters.js +4 -1
- package/dist/composer/runtime-adapters.js.map +1 -1
- package/dist/icons/ResourceIconPicker.d.ts.map +1 -1
- package/dist/icons/ResourceIconPicker.js +6 -1
- package/dist/icons/ResourceIconPicker.js.map +1 -1
- package/package.json +1 -1
- package/src/composer/ComposerPlusMenu.tsx +25 -14
- package/src/composer/PromptComposer.context.spec.tsx +60 -34
- package/src/composer/PromptComposer.spec.ts +39 -20
- package/src/composer/PromptComposer.tsx +106 -46
- package/src/composer/TiptapComposer.spec.ts +130 -0
- package/src/composer/TiptapComposer.tsx +61 -26
- package/src/composer/index.ts +1 -0
- package/src/composer/runtime-adapters.tsx +13 -4
- package/src/icons/ResourceIconPicker.tsx +13 -2
|
@@ -1172,6 +1172,31 @@ describe("createTiptapComposerExtensions", () => {
|
|
|
1172
1172
|
expect(added[0]?.name).toMatch(/^\d+-[a-z0-9]+-image\.png$/);
|
|
1173
1173
|
});
|
|
1174
1174
|
|
|
1175
|
+
it("consumes but does not attach file drops while uploads are disabled", () => {
|
|
1176
|
+
const file = new File(["fake"], "image.png", { type: "image/png" });
|
|
1177
|
+
const added = vi.fn();
|
|
1178
|
+
let prevented = false;
|
|
1179
|
+
let stopped = false;
|
|
1180
|
+
const handled = handleComposerFileDrop({
|
|
1181
|
+
event: {
|
|
1182
|
+
dataTransfer: { files: [file] },
|
|
1183
|
+
preventDefault: () => {
|
|
1184
|
+
prevented = true;
|
|
1185
|
+
},
|
|
1186
|
+
stopPropagation: () => {
|
|
1187
|
+
stopped = true;
|
|
1188
|
+
},
|
|
1189
|
+
} as unknown as DragEvent,
|
|
1190
|
+
addAttachment: added,
|
|
1191
|
+
attachmentsEnabled: false,
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
expect(handled).toBe(true);
|
|
1195
|
+
expect(prevented).toBe(true);
|
|
1196
|
+
expect(stopped).toBe(true);
|
|
1197
|
+
expect(added).not.toHaveBeenCalled();
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1175
1200
|
it("caps the model picker height without forcing empty vertical space", () => {
|
|
1176
1201
|
expect(MODEL_SELECTOR_POPOVER_STYLE).toMatchObject({
|
|
1177
1202
|
fontSize: 13,
|
|
@@ -1481,6 +1506,111 @@ describe("createTiptapComposerExtensions", () => {
|
|
|
1481
1506
|
});
|
|
1482
1507
|
});
|
|
1483
1508
|
|
|
1509
|
+
describe("TiptapComposer paste handling", () => {
|
|
1510
|
+
it("keeps text from a mixed text and image paste when uploads are disabled", async () => {
|
|
1511
|
+
const pastedText = "Keep this text";
|
|
1512
|
+
|
|
1513
|
+
function Harness() {
|
|
1514
|
+
const runtime = useLocalRuntime(emptyChatModelAdapter);
|
|
1515
|
+
return React.createElement(
|
|
1516
|
+
AssistantRuntimeProvider,
|
|
1517
|
+
{ runtime },
|
|
1518
|
+
React.createElement(
|
|
1519
|
+
TooltipProvider,
|
|
1520
|
+
null,
|
|
1521
|
+
React.createElement(TiptapComposer, {
|
|
1522
|
+
attachmentsEnabled: false,
|
|
1523
|
+
includeDefaultSlashSkills: false,
|
|
1524
|
+
plusMenuMode: "hidden",
|
|
1525
|
+
providerConnectStatusEnabled: false,
|
|
1526
|
+
voiceEnabled: false,
|
|
1527
|
+
}),
|
|
1528
|
+
),
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
await act(async () => {
|
|
1533
|
+
root.render(React.createElement(Harness));
|
|
1534
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
const editor = container.querySelector<HTMLElement>(
|
|
1538
|
+
".agent-composer-prosemirror",
|
|
1539
|
+
);
|
|
1540
|
+
expect(editor).not.toBeNull();
|
|
1541
|
+
const clipboardData = {
|
|
1542
|
+
files: [new File(["image"], "image.png", { type: "image/png" })],
|
|
1543
|
+
getData: (type: string) => (type === "text/plain" ? pastedText : ""),
|
|
1544
|
+
};
|
|
1545
|
+
const pasteEvent = new Event("paste", {
|
|
1546
|
+
bubbles: true,
|
|
1547
|
+
cancelable: true,
|
|
1548
|
+
});
|
|
1549
|
+
Object.defineProperty(pasteEvent, "clipboardData", {
|
|
1550
|
+
value: clipboardData,
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
await act(async () => {
|
|
1554
|
+
editor?.dispatchEvent(pasteEvent);
|
|
1555
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
expect(editor?.textContent).toBe(pastedText);
|
|
1559
|
+
expect(pasteEvent.defaultPrevented).toBe(true);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
it("keeps large plain-text pastes in the composer when uploads are disabled", async () => {
|
|
1563
|
+
const pastedText = "A".repeat(3200);
|
|
1564
|
+
|
|
1565
|
+
function Harness() {
|
|
1566
|
+
const runtime = useLocalRuntime(emptyChatModelAdapter);
|
|
1567
|
+
return React.createElement(
|
|
1568
|
+
AssistantRuntimeProvider,
|
|
1569
|
+
{ runtime },
|
|
1570
|
+
React.createElement(
|
|
1571
|
+
TooltipProvider,
|
|
1572
|
+
null,
|
|
1573
|
+
React.createElement(TiptapComposer, {
|
|
1574
|
+
attachmentsEnabled: false,
|
|
1575
|
+
includeDefaultSlashSkills: false,
|
|
1576
|
+
plusMenuMode: "hidden",
|
|
1577
|
+
providerConnectStatusEnabled: false,
|
|
1578
|
+
voiceEnabled: false,
|
|
1579
|
+
}),
|
|
1580
|
+
),
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
await act(async () => {
|
|
1585
|
+
root.render(React.createElement(Harness));
|
|
1586
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
const editor = container.querySelector<HTMLElement>(
|
|
1590
|
+
".agent-composer-prosemirror",
|
|
1591
|
+
);
|
|
1592
|
+
expect(editor).not.toBeNull();
|
|
1593
|
+
const clipboardData = {
|
|
1594
|
+
files: [],
|
|
1595
|
+
getData: (type: string) => (type === "text/plain" ? pastedText : ""),
|
|
1596
|
+
};
|
|
1597
|
+
const pasteEvent = new Event("paste", {
|
|
1598
|
+
bubbles: true,
|
|
1599
|
+
cancelable: true,
|
|
1600
|
+
});
|
|
1601
|
+
Object.defineProperty(pasteEvent, "clipboardData", {
|
|
1602
|
+
value: clipboardData,
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
await act(async () => {
|
|
1606
|
+
editor?.dispatchEvent(pasteEvent);
|
|
1607
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
expect(editor?.textContent).toBe(pastedText);
|
|
1611
|
+
});
|
|
1612
|
+
});
|
|
1613
|
+
|
|
1484
1614
|
describe("TiptapComposer slash commands", () => {
|
|
1485
1615
|
it("locks submission without blurring the editable surface", () => {
|
|
1486
1616
|
const focusRef = React.createRef<TiptapComposerHandle>();
|
|
@@ -513,6 +513,7 @@ function clearComposerDraft(
|
|
|
513
513
|
export function handleComposerFileDrop(options: {
|
|
514
514
|
event: Pick<DragEvent, "dataTransfer" | "preventDefault" | "stopPropagation">;
|
|
515
515
|
addAttachment: (file: File) => Promise<unknown>;
|
|
516
|
+
attachmentsEnabled?: boolean;
|
|
516
517
|
onError?: (error: unknown) => void;
|
|
517
518
|
}): boolean {
|
|
518
519
|
const droppedFiles = Array.from(options.event.dataTransfer?.files ?? []);
|
|
@@ -520,6 +521,7 @@ export function handleComposerFileDrop(options: {
|
|
|
520
521
|
|
|
521
522
|
options.event.preventDefault();
|
|
522
523
|
options.event.stopPropagation();
|
|
524
|
+
if (options.attachmentsEnabled === false) return true;
|
|
523
525
|
const attachments = droppedFiles.map(uniquifyComposerImageFile);
|
|
524
526
|
void Promise.all(
|
|
525
527
|
attachments.map((file) => options.addAttachment(file)),
|
|
@@ -783,6 +785,8 @@ export interface TiptapComposerProps {
|
|
|
783
785
|
submitting?: boolean;
|
|
784
786
|
/** Override the generic document attachment cap for a multipart host. */
|
|
785
787
|
maxDocumentAttachmentBytes?: number;
|
|
788
|
+
/** Disable file attachments while keeping text chat available. */
|
|
789
|
+
attachmentsEnabled?: boolean;
|
|
786
790
|
/** Label used in the visible document attachment limit error. */
|
|
787
791
|
documentAttachmentLimitLabel?: string;
|
|
788
792
|
focusRef?: React.Ref<TiptapComposerHandle>;
|
|
@@ -913,7 +917,6 @@ export interface TiptapComposerProps {
|
|
|
913
917
|
onInspectContextItem?: (key: string) => void;
|
|
914
918
|
onRetryContextItem?: (key: string) => void;
|
|
915
919
|
contextMenuItems?: readonly ComposerContextMenuItem[];
|
|
916
|
-
attachmentsEnabled?: boolean;
|
|
917
920
|
/**
|
|
918
921
|
* Controls the "+" menu next to the composer. `"full"` (default) shows the
|
|
919
922
|
* normal Upload / Skill / Job / Automation / MCP picker, plus Extension when
|
|
@@ -1148,11 +1151,15 @@ const LOCAL_RUNTIME_ENGINES = new Set([
|
|
|
1148
1151
|
"opencode-cli",
|
|
1149
1152
|
]);
|
|
1150
1153
|
|
|
1154
|
+
export function isLocalRuntimeEngine(engine?: string): boolean {
|
|
1155
|
+
return engine !== undefined && LOCAL_RUNTIME_ENGINES.has(engine);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1151
1158
|
export function hasConfiguredCloudProvider(
|
|
1152
1159
|
groups: ReadonlyArray<{ engine: string; configured: boolean }>,
|
|
1153
1160
|
): boolean {
|
|
1154
1161
|
return groups.some(
|
|
1155
|
-
(group) => group.configured && !
|
|
1162
|
+
(group) => group.configured && !isLocalRuntimeEngine(group.engine),
|
|
1156
1163
|
);
|
|
1157
1164
|
}
|
|
1158
1165
|
|
|
@@ -2460,6 +2467,7 @@ export function TiptapComposer({
|
|
|
2460
2467
|
submitting = false,
|
|
2461
2468
|
maxDocumentAttachmentBytes = MAX_DOCUMENT_ATTACHMENT_BYTES,
|
|
2462
2469
|
documentAttachmentLimitLabel = "PDFs",
|
|
2470
|
+
attachmentsEnabled = true,
|
|
2463
2471
|
focusRef,
|
|
2464
2472
|
initialText,
|
|
2465
2473
|
initialTextKey,
|
|
@@ -2510,7 +2518,6 @@ export function TiptapComposer({
|
|
|
2510
2518
|
onInspectContextItem,
|
|
2511
2519
|
onRetryContextItem,
|
|
2512
2520
|
contextMenuItems,
|
|
2513
|
-
attachmentsEnabled = true,
|
|
2514
2521
|
plusMenuMode = "full",
|
|
2515
2522
|
terminalModeControl,
|
|
2516
2523
|
extensionTools = false,
|
|
@@ -2776,7 +2783,25 @@ export function TiptapComposer({
|
|
|
2776
2783
|
class:
|
|
2777
2784
|
"agent-composer-prosemirror flex-1 resize-none bg-transparent text-sm text-foreground outline-none leading-[1.625rem] min-h-[3.25rem] max-h-[10rem] overflow-y-auto",
|
|
2778
2785
|
},
|
|
2779
|
-
handlePaste: (
|
|
2786
|
+
handlePaste: (view, event) => {
|
|
2787
|
+
if (disabled) {
|
|
2788
|
+
if (event.clipboardData?.files.length) {
|
|
2789
|
+
event.preventDefault();
|
|
2790
|
+
return true;
|
|
2791
|
+
}
|
|
2792
|
+
return false;
|
|
2793
|
+
}
|
|
2794
|
+
if (!attachmentsEnabled) {
|
|
2795
|
+
if (event.clipboardData?.files.length) {
|
|
2796
|
+
event.preventDefault();
|
|
2797
|
+
const pastedText = readClipboardPaste(event.clipboardData).text;
|
|
2798
|
+
if (pastedText) {
|
|
2799
|
+
view.pasteText(pastedText, new Event("paste") as ClipboardEvent);
|
|
2800
|
+
}
|
|
2801
|
+
return true;
|
|
2802
|
+
}
|
|
2803
|
+
return false;
|
|
2804
|
+
}
|
|
2780
2805
|
const paste = readClipboardPaste(event.clipboardData);
|
|
2781
2806
|
const pastedText = paste.text;
|
|
2782
2807
|
const files = Array.from(event.clipboardData?.files ?? []).filter(
|
|
@@ -2842,12 +2867,20 @@ export function TiptapComposer({
|
|
|
2842
2867
|
return false;
|
|
2843
2868
|
},
|
|
2844
2869
|
handleDrop: (_view, event) => {
|
|
2870
|
+
if (disabled || !attachmentsEnabled) {
|
|
2871
|
+
if (event.dataTransfer?.files.length) {
|
|
2872
|
+
event.preventDefault();
|
|
2873
|
+
return true;
|
|
2874
|
+
}
|
|
2875
|
+
return false;
|
|
2876
|
+
}
|
|
2845
2877
|
// Drag-and-drop files (decks, images, PDFs, etc.) into the composer.
|
|
2846
2878
|
// Mark handled drops as consumed so the chat-wide drop target does not
|
|
2847
2879
|
// add the same file a second time.
|
|
2848
2880
|
return handleComposerFileDrop({
|
|
2849
2881
|
event: event as DragEvent,
|
|
2850
2882
|
addAttachment: addAttachmentForCurrentScope,
|
|
2883
|
+
attachmentsEnabled,
|
|
2851
2884
|
onError: (error) => {
|
|
2852
2885
|
const msg = formatAttachmentError(
|
|
2853
2886
|
error,
|
|
@@ -4259,28 +4292,30 @@ export function TiptapComposer({
|
|
|
4259
4292
|
data-agent-composer-slot="toolbar"
|
|
4260
4293
|
className="agent-composer-toolbar flex items-center gap-1 px-2 py-1.5"
|
|
4261
4294
|
>
|
|
4262
|
-
{attachButton
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4295
|
+
{!disabled && attachmentsEnabled && attachButton ? (
|
|
4296
|
+
attachButton
|
|
4297
|
+
) : contextMenuItems !== undefined || plusMenuMode === "upload-only" ? (
|
|
4298
|
+
<ComposerContextMenu
|
|
4299
|
+
items={contextMenuItems ?? []}
|
|
4300
|
+
addAttachment={
|
|
4301
|
+
attachmentsEnabled ? addAttachmentForCurrentScope : undefined
|
|
4302
|
+
}
|
|
4303
|
+
attachmentAccept={composerRuntime.getState().attachmentAccept}
|
|
4304
|
+
onAttachmentError={onAttachmentError}
|
|
4305
|
+
disabled={disabled}
|
|
4306
|
+
/>
|
|
4307
|
+
) : disabled || plusMenuMode === "hidden" ? null : (
|
|
4308
|
+
<ComposerPlusMenu
|
|
4309
|
+
addAttachment={addAttachmentForCurrentScope}
|
|
4310
|
+
attachmentsEnabled={attachmentsEnabled}
|
|
4311
|
+
attachmentAccept={composerRuntime.getState().attachmentAccept}
|
|
4312
|
+
onSelectMode={handleSelectMode}
|
|
4313
|
+
mode={plusMenuMode}
|
|
4314
|
+
terminalModeControl={terminalModeControl}
|
|
4315
|
+
extensionTools={extensionTools}
|
|
4316
|
+
onAttachmentError={onAttachmentError}
|
|
4317
|
+
/>
|
|
4318
|
+
)}
|
|
4284
4319
|
{toolbarSlot ?? modeControl}
|
|
4285
4320
|
<div data-agent-composer-slot="toolbar-spacer" className="flex-1" />
|
|
4286
4321
|
{shouldRenderModelSelector(availableModels, onModelChange) && (
|
package/src/composer/index.ts
CHANGED
|
@@ -25,6 +25,12 @@ export type ReasoningEffort =
|
|
|
25
25
|
| "xhigh"
|
|
26
26
|
| "max";
|
|
27
27
|
|
|
28
|
+
export type ComposerAgentEngineState =
|
|
29
|
+
| "unknown"
|
|
30
|
+
| "unavailable"
|
|
31
|
+
| "missing"
|
|
32
|
+
| "configured";
|
|
33
|
+
|
|
28
34
|
export interface EngineModelGroup {
|
|
29
35
|
engine: string;
|
|
30
36
|
label: string;
|
|
@@ -144,12 +150,12 @@ export interface ComposerRuntimeAdapters {
|
|
|
144
150
|
useChatModels?: (options: { enabled: boolean }) => ComposerModelState;
|
|
145
151
|
useAgentEngineConfigured?: (enabled: boolean) => {
|
|
146
152
|
missing: boolean;
|
|
147
|
-
state:
|
|
153
|
+
state: ComposerAgentEngineState;
|
|
148
154
|
};
|
|
149
155
|
fetchAgentEngineConfiguredState?: (
|
|
150
156
|
enabled: boolean,
|
|
151
157
|
options: { timeoutMs: number },
|
|
152
|
-
) => Promise<
|
|
158
|
+
) => Promise<ComposerAgentEngineState>;
|
|
153
159
|
BuilderSetupCard?: ComponentType<any>;
|
|
154
160
|
BuilderSetupContent?: ComponentType<any>;
|
|
155
161
|
reasoning?: {
|
|
@@ -236,8 +242,11 @@ const fallbackModels = {
|
|
|
236
242
|
onModelChange: () => {},
|
|
237
243
|
onEffortChange: () => {},
|
|
238
244
|
}),
|
|
239
|
-
useAgentEngineConfigured: () => ({
|
|
240
|
-
|
|
245
|
+
useAgentEngineConfigured: () => ({
|
|
246
|
+
missing: false,
|
|
247
|
+
state: "configured" as const,
|
|
248
|
+
}),
|
|
249
|
+
fetchAgentEngineConfiguredState: async () => "configured" as const,
|
|
241
250
|
};
|
|
242
251
|
const FragmentBoundary: ComponentType<{ children?: ReactNode }> = ({
|
|
243
252
|
children,
|
|
@@ -534,6 +534,10 @@ export function ResourceIconPicker({
|
|
|
534
534
|
other.authority === entry.authority,
|
|
535
535
|
) === index,
|
|
536
536
|
);
|
|
537
|
+
const showUploadTab = !!onUpload || uploadChoices.length > 0;
|
|
538
|
+
React.useEffect(() => {
|
|
539
|
+
if (!showUploadTab && tab === "upload") setTab("icons");
|
|
540
|
+
}, [showUploadTab, tab]);
|
|
537
541
|
return (
|
|
538
542
|
<Popover
|
|
539
543
|
open={open}
|
|
@@ -590,10 +594,17 @@ export function ResourceIconPicker({
|
|
|
590
594
|
}}
|
|
591
595
|
>
|
|
592
596
|
<div className="p-2">
|
|
593
|
-
<TabsList
|
|
597
|
+
<TabsList
|
|
598
|
+
className={cn(
|
|
599
|
+
"grid w-full",
|
|
600
|
+
showUploadTab ? "grid-cols-3" : "grid-cols-2",
|
|
601
|
+
)}
|
|
602
|
+
>
|
|
594
603
|
<TabsTrigger value="icons">{labels.iconsTab}</TabsTrigger>
|
|
595
604
|
<TabsTrigger value="emoji">{labels.emojiTab}</TabsTrigger>
|
|
596
|
-
|
|
605
|
+
{showUploadTab && (
|
|
606
|
+
<TabsTrigger value="upload">{labels.uploadTab}</TabsTrigger>
|
|
607
|
+
)}
|
|
597
608
|
</TabsList>
|
|
598
609
|
</div>
|
|
599
610
|
{tab !== "upload" && (
|