@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.
- package/dist/app/agentkit/react/components.d.ts.map +1 -1
- package/dist/app/agentkit/react/components.js +3 -3
- package/dist/app/agentkit/react/components.js.map +1 -1
- package/dist/app/agentkit/react/context.d.ts +1 -0
- package/dist/app/agentkit/react/context.d.ts.map +1 -1
- package/dist/app/agentkit/react/context.js +1 -0
- package/dist/app/agentkit/react/context.js.map +1 -1
- package/dist/app/agentkit/react/styles.css +43 -0
- package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
- package/dist/app/chat/AgentKitAssistantChat.js +219 -75
- package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
- package/dist/app/chat/agentkit-chat/guided-questions.d.ts +18 -0
- package/dist/app/chat/agentkit-chat/guided-questions.d.ts.map +1 -1
- package/dist/app/chat/agentkit-chat/guided-questions.js +12 -4
- package/dist/app/chat/agentkit-chat/guided-questions.js.map +1 -1
- package/dist/app/chat/chat/run-recovery.d.ts +6 -1
- package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
- package/dist/app/chat/chat/run-recovery.js +6 -3
- package/dist/app/chat/chat/run-recovery.js.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ar-SA.js +8 -2
- package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/de-DE.js +8 -2
- package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
- package/dist/app/i18n/catalogs/en-US.d.ts +8 -2
- package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/en-US.js +8 -2
- package/dist/app/i18n/catalogs/en-US.js.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/es-ES.js +8 -2
- package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/fr-FR.js +8 -2
- package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/hi-IN.js +8 -2
- package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ja-JP.js +8 -2
- package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/ko-KR.js +8 -2
- package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/pt-BR.js +8 -2
- package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-CN.js +8 -2
- package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
- package/dist/app/i18n/catalogs/zh-TW.js +8 -2
- package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
- package/dist/app/sharing/ShareButton.d.ts +9 -0
- package/dist/app/sharing/ShareButton.d.ts.map +1 -1
- package/dist/app/sharing/ShareButton.js +23 -11
- package/dist/app/sharing/ShareButton.js.map +1 -1
- package/package.json +4 -4
- package/src/app/agentkit/react/components.tsx +6 -3
- package/src/app/agentkit/react/context.tsx +2 -0
- package/src/app/agentkit/react/styles.css +43 -0
- package/src/app/chat/AgentKitAssistantChat.spec.tsx +425 -15
- package/src/app/chat/AgentKitAssistantChat.tsx +306 -95
- package/src/app/chat/agentkit-chat/guided-questions.flow.spec.tsx +89 -5
- package/src/app/chat/agentkit-chat/guided-questions.tsx +17 -6
- package/src/app/chat/chat/run-recovery.spec.tsx +48 -0
- package/src/app/chat/chat/run-recovery.tsx +23 -1
- package/src/app/i18n/catalogs/ar-SA.ts +14 -2
- package/src/app/i18n/catalogs/de-DE.ts +12 -2
- package/src/app/i18n/catalogs/en-US.ts +13 -2
- package/src/app/i18n/catalogs/es-ES.ts +12 -2
- package/src/app/i18n/catalogs/fr-FR.ts +12 -2
- package/src/app/i18n/catalogs/hi-IN.ts +12 -2
- package/src/app/i18n/catalogs/ja-JP.ts +12 -2
- package/src/app/i18n/catalogs/ko-KR.ts +12 -2
- package/src/app/i18n/catalogs/pt-BR.ts +12 -2
- package/src/app/i18n/catalogs/zh-CN.ts +8 -2
- package/src/app/i18n/catalogs/zh-TW.ts +8 -2
- package/src/app/settings/model/ProviderDialog.spec.tsx +1 -0
- package/src/app/sharing/ShareButton.spec.tsx +148 -0
- package/src/app/sharing/ShareButton.tsx +44 -15
- package/src/app/sharing/useShareButtonController.spec.tsx +25 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// @vitest-environment happy-dom
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
AgentKitClient,
|
|
5
|
+
AgentKitUploadError,
|
|
6
|
+
} from "@agent-native/agentkit/client";
|
|
4
7
|
import type {
|
|
5
8
|
FilePart,
|
|
6
9
|
AgentMessage,
|
|
@@ -1129,7 +1132,7 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
1129
1132
|
).toBeNull();
|
|
1130
1133
|
});
|
|
1131
1134
|
|
|
1132
|
-
it("
|
|
1135
|
+
it("copies the active run ID from its action menu", async () => {
|
|
1133
1136
|
const props = baseProps();
|
|
1134
1137
|
await mount(props);
|
|
1135
1138
|
|
|
@@ -1140,22 +1143,72 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
1140
1143
|
chatMocks.thread = { ...chatMocks.thread, activeRunIds: ["run-active"] };
|
|
1141
1144
|
await act(async () => root.render(<AgentKitAssistantChat {...props} />));
|
|
1142
1145
|
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1146
|
+
const actions = container.querySelector(
|
|
1147
|
+
"[data-agentkit-active-run-id-copy]",
|
|
1148
|
+
);
|
|
1149
|
+
const actionsTrigger = actions?.querySelector(
|
|
1150
|
+
'button[aria-label="agentChat.message.actions"]',
|
|
1151
|
+
);
|
|
1152
|
+
expect(actionsTrigger).toBeTruthy();
|
|
1153
|
+
expect(actionsTrigger?.getAttribute("aria-haspopup")).toBe("menu");
|
|
1154
|
+
expect(
|
|
1155
|
+
actions?.querySelector(
|
|
1156
|
+
'button[aria-label="agentChat.message.copyRequestId"]',
|
|
1157
|
+
),
|
|
1158
|
+
).toBeNull();
|
|
1159
|
+
|
|
1160
|
+
await act(async () => {
|
|
1161
|
+
actionsTrigger?.focus();
|
|
1162
|
+
actionsTrigger?.dispatchEvent(
|
|
1163
|
+
new KeyboardEvent("keydown", { bubbles: true, key: "ArrowDown" }),
|
|
1164
|
+
);
|
|
1165
|
+
await Promise.resolve();
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
const menuItems = Array.from(
|
|
1169
|
+
document.body.querySelectorAll('[role="menuitem"]'),
|
|
1145
1170
|
);
|
|
1146
|
-
expect(
|
|
1171
|
+
expect(menuItems.map((item) => item.textContent?.trim())).toEqual([
|
|
1172
|
+
"agentChat.message.copyRequestId",
|
|
1173
|
+
]);
|
|
1147
1174
|
|
|
1148
1175
|
await act(async () => {
|
|
1149
|
-
|
|
1176
|
+
menuItems[0]?.click();
|
|
1150
1177
|
await Promise.resolve();
|
|
1151
1178
|
});
|
|
1152
1179
|
|
|
1153
1180
|
expect(chatMocks.writeClipboardText).toHaveBeenCalledWith("run-active");
|
|
1181
|
+
const copiedFeedback = container.querySelector(
|
|
1182
|
+
'[data-agentkit-active-run-id-copy] [role="status"]',
|
|
1183
|
+
);
|
|
1184
|
+
expect(copiedFeedback?.textContent).toContain("agentChat.common.copied");
|
|
1185
|
+
expect(copiedFeedback?.classList.contains("sr-only")).toBe(false);
|
|
1186
|
+
|
|
1187
|
+
chatMocks.writeClipboardText.mockResolvedValueOnce(false);
|
|
1188
|
+
await act(async () => {
|
|
1189
|
+
actionsTrigger?.focus();
|
|
1190
|
+
actionsTrigger?.dispatchEvent(
|
|
1191
|
+
new KeyboardEvent("keydown", { bubbles: true, key: "ArrowDown" }),
|
|
1192
|
+
);
|
|
1193
|
+
await Promise.resolve();
|
|
1194
|
+
});
|
|
1195
|
+
const retryMenuItem = document.body.querySelector('[role="menuitem"]');
|
|
1196
|
+
await act(async () => {
|
|
1197
|
+
retryMenuItem?.click();
|
|
1198
|
+
await Promise.resolve();
|
|
1199
|
+
});
|
|
1200
|
+
const failedFeedback = container.querySelector(
|
|
1201
|
+
'[data-agentkit-active-run-id-copy] [role="alert"]',
|
|
1202
|
+
);
|
|
1203
|
+
expect(failedFeedback?.textContent).toContain(
|
|
1204
|
+
"agentChat.recovery.copyFailed",
|
|
1205
|
+
);
|
|
1206
|
+
expect(failedFeedback?.classList.contains("sr-only")).toBe(false);
|
|
1154
1207
|
expect(
|
|
1155
|
-
|
|
1156
|
-
|
|
1208
|
+
failedFeedback?.classList.contains(
|
|
1209
|
+
"agentkit-active-run-actions-feedback-error",
|
|
1157
1210
|
),
|
|
1158
|
-
).
|
|
1211
|
+
).toBe(true);
|
|
1159
1212
|
});
|
|
1160
1213
|
|
|
1161
1214
|
it("does not duplicate Thinking after the run becomes active", async () => {
|
|
@@ -2905,7 +2958,10 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
2905
2958
|
expect(context.prepareSubmission).toHaveBeenCalledOnce(),
|
|
2906
2959
|
);
|
|
2907
2960
|
});
|
|
2908
|
-
rejection = expect(submission).rejects.
|
|
2961
|
+
rejection = expect(submission).rejects.toMatchObject({
|
|
2962
|
+
message: "agentChat.composer.submissionScopeChanged",
|
|
2963
|
+
code: "submission_scope_changed",
|
|
2964
|
+
});
|
|
2909
2965
|
|
|
2910
2966
|
await act(async () => {
|
|
2911
2967
|
root.render(
|
|
@@ -3392,13 +3448,31 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
3392
3448
|
"agentChat.composer.sessionExpired",
|
|
3393
3449
|
"upload_session_expired",
|
|
3394
3450
|
],
|
|
3451
|
+
[
|
|
3452
|
+
403,
|
|
3453
|
+
{ error: "Forbidden" },
|
|
3454
|
+
"agentChat.composer.sessionExpired",
|
|
3455
|
+
"upload_session_expired",
|
|
3456
|
+
],
|
|
3457
|
+
[
|
|
3458
|
+
429,
|
|
3459
|
+
{ error: "Too many uploads" },
|
|
3460
|
+
"agentChat.composer.uploadUnavailable",
|
|
3461
|
+
"upload_unavailable",
|
|
3462
|
+
],
|
|
3395
3463
|
[
|
|
3396
3464
|
503,
|
|
3397
3465
|
{
|
|
3398
3466
|
error: "Storage provider is unavailable. Check File uploads settings.",
|
|
3399
3467
|
},
|
|
3468
|
+
"agentChat.composer.uploadUnavailable",
|
|
3469
|
+
"upload_unavailable",
|
|
3470
|
+
],
|
|
3471
|
+
[
|
|
3472
|
+
400,
|
|
3473
|
+
{ error: "No file uploaded" },
|
|
3400
3474
|
"agentChat.composer.uploadFailed",
|
|
3401
|
-
"
|
|
3475
|
+
"upload_http_400",
|
|
3402
3476
|
],
|
|
3403
3477
|
] as const)(
|
|
3404
3478
|
"preserves actionable upload failures for HTTP %s",
|
|
@@ -3424,17 +3498,284 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
3424
3498
|
message,
|
|
3425
3499
|
code,
|
|
3426
3500
|
status,
|
|
3427
|
-
retryable: status >= 500,
|
|
3501
|
+
retryable: status === 429 || status >= 500,
|
|
3428
3502
|
});
|
|
3429
|
-
|
|
3430
|
-
expect(error.message).not.toContain(payload.error);
|
|
3431
|
-
}
|
|
3503
|
+
expect(error.message).not.toContain(payload.error);
|
|
3432
3504
|
} finally {
|
|
3433
3505
|
fetch.mockRestore();
|
|
3434
3506
|
}
|
|
3435
3507
|
},
|
|
3436
3508
|
);
|
|
3437
3509
|
|
|
3510
|
+
it("localizes an upload that never reached the server", async () => {
|
|
3511
|
+
const fetch = vi
|
|
3512
|
+
.spyOn(globalThis, "fetch")
|
|
3513
|
+
.mockRejectedValue(new TypeError("Failed to fetch"));
|
|
3514
|
+
await mount(baseProps());
|
|
3515
|
+
|
|
3516
|
+
try {
|
|
3517
|
+
await expect(
|
|
3518
|
+
chatMocks.rootProps.clientOptions.upload(
|
|
3519
|
+
{ uploadId: "upload-1", method: "POST", url: "/uploads" },
|
|
3520
|
+
{
|
|
3521
|
+
name: "reference.png",
|
|
3522
|
+
mediaType: "image/png",
|
|
3523
|
+
size: 4,
|
|
3524
|
+
body: new Blob(["data"], { type: "image/png" }),
|
|
3525
|
+
},
|
|
3526
|
+
),
|
|
3527
|
+
).rejects.toMatchObject({
|
|
3528
|
+
message: "agentChat.composer.uploadOffline",
|
|
3529
|
+
code: "upload_network_error",
|
|
3530
|
+
retryable: true,
|
|
3531
|
+
});
|
|
3532
|
+
} finally {
|
|
3533
|
+
fetch.mockRestore();
|
|
3534
|
+
}
|
|
3535
|
+
});
|
|
3536
|
+
|
|
3537
|
+
it("names a file over the upload limit before uploading anything", async () => {
|
|
3538
|
+
await mount(baseProps());
|
|
3539
|
+
const notes = new File(["notes"], "notes.txt", { type: "text/plain" });
|
|
3540
|
+
const huge = new File(["video"], "huge.mov", { type: "video/quicktime" });
|
|
3541
|
+
Object.defineProperty(huge, "size", { value: 26 * 1024 * 1024 });
|
|
3542
|
+
|
|
3543
|
+
await act(async () => {
|
|
3544
|
+
await expect(
|
|
3545
|
+
chatMocks.composerProps.onSubmit("Review these", [notes, huge], [], {
|
|
3546
|
+
intent: "immediate",
|
|
3547
|
+
}),
|
|
3548
|
+
).rejects.toMatchObject({
|
|
3549
|
+
message: "huge.mov: agentChat.composer.fileTooLarge",
|
|
3550
|
+
code: "upload_failed",
|
|
3551
|
+
});
|
|
3552
|
+
});
|
|
3553
|
+
|
|
3554
|
+
expect(chatMocks.control.uploadFiles).not.toHaveBeenCalled();
|
|
3555
|
+
expect(chatMocks.control.sendMessage).not.toHaveBeenCalled();
|
|
3556
|
+
});
|
|
3557
|
+
|
|
3558
|
+
it("reuses the files that uploaded when a sibling failed", async () => {
|
|
3559
|
+
await mount(baseProps());
|
|
3560
|
+
const notes = new File(["notes"], "notes.txt", { type: "text/plain" });
|
|
3561
|
+
const sheet = new File(["sheet"], "sheet.csv", { type: "text/csv" });
|
|
3562
|
+
const notesPart = {
|
|
3563
|
+
type: "file" as const,
|
|
3564
|
+
name: "notes.txt",
|
|
3565
|
+
mediaType: "text/plain",
|
|
3566
|
+
url: "https://files.example.test/notes.txt",
|
|
3567
|
+
};
|
|
3568
|
+
const sheetPart = {
|
|
3569
|
+
type: "file" as const,
|
|
3570
|
+
name: "sheet.csv",
|
|
3571
|
+
mediaType: "text/csv",
|
|
3572
|
+
url: "https://files.example.test/sheet.csv",
|
|
3573
|
+
};
|
|
3574
|
+
chatMocks.control.uploadFiles
|
|
3575
|
+
.mockRejectedValueOnce(
|
|
3576
|
+
new AgentKitUploadError(
|
|
3577
|
+
[
|
|
3578
|
+
{
|
|
3579
|
+
index: 1,
|
|
3580
|
+
name: "sheet.csv",
|
|
3581
|
+
error: new Error("agentChat.composer.uploadUnavailable"),
|
|
3582
|
+
},
|
|
3583
|
+
],
|
|
3584
|
+
[{ index: 0, part: notesPart }],
|
|
3585
|
+
),
|
|
3586
|
+
)
|
|
3587
|
+
.mockResolvedValueOnce([sheetPart]);
|
|
3588
|
+
|
|
3589
|
+
await act(async () => {
|
|
3590
|
+
await expect(
|
|
3591
|
+
chatMocks.composerProps.onSubmit("Compare", [notes, sheet], [], {
|
|
3592
|
+
intent: "immediate",
|
|
3593
|
+
}),
|
|
3594
|
+
).rejects.toThrow("sheet.csv: agentChat.composer.uploadUnavailable");
|
|
3595
|
+
});
|
|
3596
|
+
await act(async () => {
|
|
3597
|
+
await chatMocks.composerProps.onSubmit("Compare", [notes, sheet], [], {
|
|
3598
|
+
intent: "immediate",
|
|
3599
|
+
});
|
|
3600
|
+
});
|
|
3601
|
+
|
|
3602
|
+
expect(chatMocks.control.uploadFiles).toHaveBeenCalledTimes(2);
|
|
3603
|
+
expect(chatMocks.control.uploadFiles.mock.calls[1]?.[0]).toMatchObject([
|
|
3604
|
+
{ name: "sheet.csv" },
|
|
3605
|
+
]);
|
|
3606
|
+
expect(
|
|
3607
|
+
chatMocks.control.sendMessage.mock.calls[0]?.[0].attachments,
|
|
3608
|
+
).toEqual([notesPart, sheetPart]);
|
|
3609
|
+
});
|
|
3610
|
+
|
|
3611
|
+
it("keeps an upload when provider readiness flips while it runs", async () => {
|
|
3612
|
+
const props = baseProps();
|
|
3613
|
+
await mount(props);
|
|
3614
|
+
const file = new File(["image"], "keep.png", { type: "image/png" });
|
|
3615
|
+
const uploading = Promise.withResolvers<FilePart[]>();
|
|
3616
|
+
chatMocks.control.uploadFiles.mockReturnValueOnce(uploading.promise);
|
|
3617
|
+
let submission!: Promise<void>;
|
|
3618
|
+
await act(async () => {
|
|
3619
|
+
submission = chatMocks.composerProps.onSubmit("Use this", [file], [], {
|
|
3620
|
+
intent: "immediate",
|
|
3621
|
+
});
|
|
3622
|
+
await vi.waitFor(() =>
|
|
3623
|
+
expect(chatMocks.control.uploadFiles).toHaveBeenCalledOnce(),
|
|
3624
|
+
);
|
|
3625
|
+
});
|
|
3626
|
+
|
|
3627
|
+
await act(async () =>
|
|
3628
|
+
root.render(
|
|
3629
|
+
<AgentKitAssistantChat {...props} composerSubmissionDisabled />,
|
|
3630
|
+
),
|
|
3631
|
+
);
|
|
3632
|
+
await act(async () => {
|
|
3633
|
+
uploading.resolve([
|
|
3634
|
+
{
|
|
3635
|
+
type: "file",
|
|
3636
|
+
name: "keep.png",
|
|
3637
|
+
mediaType: "image/png",
|
|
3638
|
+
url: "https://files.example.test/keep.png",
|
|
3639
|
+
},
|
|
3640
|
+
]);
|
|
3641
|
+
await submission;
|
|
3642
|
+
});
|
|
3643
|
+
|
|
3644
|
+
expect(chatMocks.control.sendMessage).toHaveBeenCalledOnce();
|
|
3645
|
+
expect(
|
|
3646
|
+
chatMocks.control.sendMessage.mock.calls[0]?.[0].attachments,
|
|
3647
|
+
).toEqual([
|
|
3648
|
+
{
|
|
3649
|
+
type: "file",
|
|
3650
|
+
name: "keep.png",
|
|
3651
|
+
mediaType: "image/png",
|
|
3652
|
+
url: "https://files.example.test/keep.png",
|
|
3653
|
+
},
|
|
3654
|
+
]);
|
|
3655
|
+
});
|
|
3656
|
+
|
|
3657
|
+
it("stores no inline image bytes in thread or queue snapshots", async () => {
|
|
3658
|
+
const dataUrl = "data:image/png;base64,aGVsbG8=";
|
|
3659
|
+
chatMocks.thread.messages = [
|
|
3660
|
+
{
|
|
3661
|
+
id: "user-image",
|
|
3662
|
+
role: "user",
|
|
3663
|
+
createdAt: "2026-10-09T00:00:00.000Z",
|
|
3664
|
+
parts: [
|
|
3665
|
+
{ type: "text", text: "Use this image" },
|
|
3666
|
+
{
|
|
3667
|
+
type: "file",
|
|
3668
|
+
name: "inline.png",
|
|
3669
|
+
mediaType: "image/png",
|
|
3670
|
+
url: dataUrl,
|
|
3671
|
+
},
|
|
3672
|
+
{
|
|
3673
|
+
type: "file",
|
|
3674
|
+
name: "durable.png",
|
|
3675
|
+
mediaType: "image/png",
|
|
3676
|
+
url: "https://files.example.test/durable.png",
|
|
3677
|
+
},
|
|
3678
|
+
],
|
|
3679
|
+
},
|
|
3680
|
+
];
|
|
3681
|
+
chatMocks.thread.events = [
|
|
3682
|
+
{
|
|
3683
|
+
type: "message.created",
|
|
3684
|
+
message: chatMocks.thread.messages[0],
|
|
3685
|
+
},
|
|
3686
|
+
];
|
|
3687
|
+
chatMocks.thread.queuedMessages = [
|
|
3688
|
+
{
|
|
3689
|
+
id: "queued-image",
|
|
3690
|
+
threadId: "thread-1",
|
|
3691
|
+
text: "And this one",
|
|
3692
|
+
createdAt: "2026-10-09T00:00:01.000Z",
|
|
3693
|
+
attachments: [
|
|
3694
|
+
{
|
|
3695
|
+
type: "file",
|
|
3696
|
+
name: "queued.png",
|
|
3697
|
+
mediaType: "image/png",
|
|
3698
|
+
url: dataUrl,
|
|
3699
|
+
},
|
|
3700
|
+
],
|
|
3701
|
+
requestAttachments: [
|
|
3702
|
+
{ type: "image", name: "pending.png", data: dataUrl },
|
|
3703
|
+
{
|
|
3704
|
+
type: "image",
|
|
3705
|
+
name: "resized.png",
|
|
3706
|
+
data: dataUrl,
|
|
3707
|
+
url: "https://files.example.test/resized.png",
|
|
3708
|
+
},
|
|
3709
|
+
],
|
|
3710
|
+
},
|
|
3711
|
+
];
|
|
3712
|
+
const ref = createRef<AssistantChatHandle>();
|
|
3713
|
+
await mount(
|
|
3714
|
+
baseProps({ createTransport: () => chatMocks.transport as never }),
|
|
3715
|
+
ref,
|
|
3716
|
+
);
|
|
3717
|
+
|
|
3718
|
+
const snapshot = ref.current?.exportThreadSnapshot();
|
|
3719
|
+
const threadData = snapshot?.threadData ?? "";
|
|
3720
|
+
const saved = JSON.parse(threadData);
|
|
3721
|
+
|
|
3722
|
+
expect(threadData).not.toContain("base64,");
|
|
3723
|
+
expect(threadData).not.toContain("data:image");
|
|
3724
|
+
expect(saved.agentKit.messages[0].parts).toEqual([
|
|
3725
|
+
{ type: "text", text: "Use this image" },
|
|
3726
|
+
{
|
|
3727
|
+
type: "file",
|
|
3728
|
+
name: "inline.png",
|
|
3729
|
+
mediaType: "image/png",
|
|
3730
|
+
omitted: "inline-bytes",
|
|
3731
|
+
},
|
|
3732
|
+
{
|
|
3733
|
+
type: "file",
|
|
3734
|
+
name: "durable.png",
|
|
3735
|
+
mediaType: "image/png",
|
|
3736
|
+
url: "https://files.example.test/durable.png",
|
|
3737
|
+
},
|
|
3738
|
+
]);
|
|
3739
|
+
expect(saved.queuedMessages[0].requestAttachments).toEqual([
|
|
3740
|
+
{
|
|
3741
|
+
type: "image",
|
|
3742
|
+
name: "resized.png",
|
|
3743
|
+
url: "https://files.example.test/resized.png",
|
|
3744
|
+
},
|
|
3745
|
+
]);
|
|
3746
|
+
expect(
|
|
3747
|
+
chatMocks.thread.messages[0].parts.some(
|
|
3748
|
+
(part: { url?: string }) => part.url === dataUrl,
|
|
3749
|
+
),
|
|
3750
|
+
).toBe(true);
|
|
3751
|
+
});
|
|
3752
|
+
|
|
3753
|
+
it("uploads inline images before storing a deferred submission", async () => {
|
|
3754
|
+
chatMocks.history = { isRestoring: true };
|
|
3755
|
+
chatMocks.control.uploadFiles.mockResolvedValueOnce([
|
|
3756
|
+
{
|
|
3757
|
+
type: "file",
|
|
3758
|
+
name: "image",
|
|
3759
|
+
mediaType: "image/png",
|
|
3760
|
+
url: "https://files.example.test/image.png",
|
|
3761
|
+
},
|
|
3762
|
+
]);
|
|
3763
|
+
const ref = createRef<AssistantChatHandle>();
|
|
3764
|
+
await mount(baseProps(), ref);
|
|
3765
|
+
|
|
3766
|
+
await act(async () => {
|
|
3767
|
+
await ref.current?.sendMessage("Use this image", [
|
|
3768
|
+
"data:image/png;base64,aGVsbG8=",
|
|
3769
|
+
]);
|
|
3770
|
+
});
|
|
3771
|
+
|
|
3772
|
+
const stored = JSON.stringify([...chatMocks.appState.values()]);
|
|
3773
|
+
expect(chatMocks.control.uploadFiles).toHaveBeenCalledOnce();
|
|
3774
|
+
expect(stored).toContain("https://files.example.test/image.png");
|
|
3775
|
+
expect(stored).not.toContain("base64,");
|
|
3776
|
+
expect(stored).not.toContain("data:image");
|
|
3777
|
+
});
|
|
3778
|
+
|
|
3438
3779
|
it("does not clear persisted selection when hydration fails", async () => {
|
|
3439
3780
|
chatMocks.appState.set("pending-selection-context", {
|
|
3440
3781
|
text: "selection to preserve",
|
|
@@ -4839,6 +5180,75 @@ describe("AgentKitAssistantChat host behavior", () => {
|
|
|
4839
5180
|
});
|
|
4840
5181
|
});
|
|
4841
5182
|
|
|
5183
|
+
it("retries a rejected attachment's request with its text and context only", async () => {
|
|
5184
|
+
const requestText = appendAgentChatContextToMessage(
|
|
5185
|
+
"Make a LinkedIn ad 1200x627",
|
|
5186
|
+
"Selected brand kit",
|
|
5187
|
+
);
|
|
5188
|
+
chatMocks.failureError = {
|
|
5189
|
+
code: "invalid_attachment",
|
|
5190
|
+
message: "The provider rejected this attachment.",
|
|
5191
|
+
retryable: false,
|
|
5192
|
+
};
|
|
5193
|
+
chatMocks.thread.messages = [
|
|
5194
|
+
{
|
|
5195
|
+
id: "user-attachment",
|
|
5196
|
+
role: "user",
|
|
5197
|
+
createdAt: new Date().toISOString(),
|
|
5198
|
+
parts: [
|
|
5199
|
+
{ type: "text", text: requestText },
|
|
5200
|
+
{
|
|
5201
|
+
type: "file",
|
|
5202
|
+
name: "huge.tiff",
|
|
5203
|
+
mediaType: "image/tiff",
|
|
5204
|
+
url: "https://files.example.test/huge.tiff",
|
|
5205
|
+
},
|
|
5206
|
+
],
|
|
5207
|
+
metadata: { model: "model-original", requestMode: "act" },
|
|
5208
|
+
},
|
|
5209
|
+
];
|
|
5210
|
+
await mount(baseProps());
|
|
5211
|
+
|
|
5212
|
+
expect(chatMocks.failureProps.info).toMatchObject({
|
|
5213
|
+
errorCode: "invalid_attachment",
|
|
5214
|
+
});
|
|
5215
|
+
await act(async () => {
|
|
5216
|
+
chatMocks.failureProps.onRetryWithoutAttachments();
|
|
5217
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
5218
|
+
});
|
|
5219
|
+
|
|
5220
|
+
expect(chatMocks.control.sendMessage).toHaveBeenCalledOnce();
|
|
5221
|
+
const request = chatMocks.control.sendMessage.mock.calls[0]?.[0];
|
|
5222
|
+
expect(request.text).toBe(requestText);
|
|
5223
|
+
expect(request.attachments).toEqual([]);
|
|
5224
|
+
expect(request.requestAttachments).toBeUndefined();
|
|
5225
|
+
expect(request.metadata).toMatchObject({
|
|
5226
|
+
model: "model-original",
|
|
5227
|
+
custom: {
|
|
5228
|
+
agentNativeRecoveryAction: "retry",
|
|
5229
|
+
agentNativeRecoveryOfRunId: "run-1",
|
|
5230
|
+
},
|
|
5231
|
+
});
|
|
5232
|
+
});
|
|
5233
|
+
|
|
5234
|
+
it("offers no attachment-free retry when the failed request had no attachments", async () => {
|
|
5235
|
+
chatMocks.failureError = {
|
|
5236
|
+
code: "invalid_attachment",
|
|
5237
|
+
message: "The provider rejected this attachment.",
|
|
5238
|
+
retryable: false,
|
|
5239
|
+
};
|
|
5240
|
+
chatMocks.thread.messages = [
|
|
5241
|
+
{
|
|
5242
|
+
id: "user-text",
|
|
5243
|
+
role: "user",
|
|
5244
|
+
parts: [{ type: "text", text: "Just text" }],
|
|
5245
|
+
},
|
|
5246
|
+
];
|
|
5247
|
+
await mount(baseProps());
|
|
5248
|
+
|
|
5249
|
+
expect(chatMocks.failureProps.onRetryWithoutAttachments).toBeUndefined();
|
|
5250
|
+
});
|
|
5251
|
+
|
|
4842
5252
|
it("reuses file IDs when retrying a saved request", async () => {
|
|
4843
5253
|
chatMocks.thread.messages = [
|
|
4844
5254
|
{
|