@convex-dev/agent 0.6.0-alpha.1 → 0.6.0
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/package.json +1 -1
- package/src/UIMessages.ts +135 -0
- package/src/client/index.ts +56 -14
- package/src/client/search.test.ts +4 -5
- package/src/client/search.ts +45 -4
- package/src/client/streaming.integration.test.ts +1206 -0
- package/src/client/streaming.ts +9 -2
- package/src/mapping.test.ts +136 -71
- package/src/mapping.ts +81 -28
- package/dist/UIMessages.d.ts +0 -46
- package/dist/UIMessages.d.ts.map +0 -1
- package/dist/UIMessages.js +0 -546
- package/dist/UIMessages.js.map +0 -1
- package/dist/client/createTool.d.ts +0 -167
- package/dist/client/createTool.d.ts.map +0 -1
- package/dist/client/createTool.js +0 -116
- package/dist/client/createTool.js.map +0 -1
- package/dist/client/defaultComponent.d.ts +0 -11
- package/dist/client/defaultComponent.d.ts.map +0 -1
- package/dist/client/defaultComponent.js +0 -7
- package/dist/client/defaultComponent.js.map +0 -1
- package/dist/client/definePlaygroundAPI.d.ts +0 -1725
- package/dist/client/definePlaygroundAPI.d.ts.map +0 -1
- package/dist/client/definePlaygroundAPI.js +0 -271
- package/dist/client/definePlaygroundAPI.js.map +0 -1
- package/dist/client/files.d.ts +0 -69
- package/dist/client/files.d.ts.map +0 -1
- package/dist/client/files.js +0 -181
- package/dist/client/files.js.map +0 -1
- package/dist/client/index.d.ts +0 -2091
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js +0 -895
- package/dist/client/index.js.map +0 -1
- package/dist/client/messages.d.ts +0 -461
- package/dist/client/messages.d.ts.map +0 -1
- package/dist/client/messages.js +0 -106
- package/dist/client/messages.js.map +0 -1
- package/dist/client/mockModel.d.ts +0 -42
- package/dist/client/mockModel.d.ts.map +0 -1
- package/dist/client/mockModel.js +0 -182
- package/dist/client/mockModel.js.map +0 -1
- package/dist/client/saveInputMessages.d.ts +0 -20
- package/dist/client/saveInputMessages.d.ts.map +0 -1
- package/dist/client/saveInputMessages.js +0 -58
- package/dist/client/saveInputMessages.js.map +0 -1
- package/dist/client/search.d.ts +0 -493
- package/dist/client/search.d.ts.map +0 -1
- package/dist/client/search.js +0 -425
- package/dist/client/search.js.map +0 -1
- package/dist/client/start.d.ts +0 -84
- package/dist/client/start.d.ts.map +0 -1
- package/dist/client/start.js +0 -185
- package/dist/client/start.js.map +0 -1
- package/dist/client/streamText.d.ts +0 -46
- package/dist/client/streamText.d.ts.map +0 -1
- package/dist/client/streamText.js +0 -117
- package/dist/client/streamText.js.map +0 -1
- package/dist/client/streaming.d.ts +0 -3778
- package/dist/client/streaming.d.ts.map +0 -1
- package/dist/client/streaming.js +0 -314
- package/dist/client/streaming.js.map +0 -1
- package/dist/client/threads.d.ts +0 -46
- package/dist/client/threads.d.ts.map +0 -1
- package/dist/client/threads.js +0 -49
- package/dist/client/threads.js.map +0 -1
- package/dist/client/types.d.ts +0 -461
- package/dist/client/types.d.ts.map +0 -1
- package/dist/client/types.js +0 -2
- package/dist/client/types.js.map +0 -1
- package/dist/client/utils.d.ts +0 -4
- package/dist/client/utils.d.ts.map +0 -1
- package/dist/client/utils.js +0 -21
- package/dist/client/utils.js.map +0 -1
- package/dist/component/_generated/api.d.ts +0 -48
- package/dist/component/_generated/api.d.ts.map +0 -1
- package/dist/component/_generated/api.js +0 -31
- package/dist/component/_generated/api.js.map +0 -1
- package/dist/component/_generated/component.d.ts +0 -3120
- package/dist/component/_generated/component.d.ts.map +0 -1
- package/dist/component/_generated/component.js +0 -11
- package/dist/component/_generated/component.js.map +0 -1
- package/dist/component/_generated/dataModel.d.ts +0 -46
- package/dist/component/_generated/dataModel.d.ts.map +0 -1
- package/dist/component/_generated/dataModel.js +0 -11
- package/dist/component/_generated/dataModel.js.map +0 -1
- package/dist/component/_generated/server.d.ts +0 -121
- package/dist/component/_generated/server.d.ts.map +0 -1
- package/dist/component/_generated/server.js +0 -78
- package/dist/component/_generated/server.js.map +0 -1
- package/dist/component/apiKeys.d.ts +0 -11
- package/dist/component/apiKeys.d.ts.map +0 -1
- package/dist/component/apiKeys.js +0 -69
- package/dist/component/apiKeys.js.map +0 -1
- package/dist/component/convex.config.d.ts +0 -3
- package/dist/component/convex.config.d.ts.map +0 -1
- package/dist/component/convex.config.js +0 -3
- package/dist/component/convex.config.js.map +0 -1
- package/dist/component/files.d.ts +0 -97
- package/dist/component/files.d.ts.map +0 -1
- package/dist/component/files.js +0 -190
- package/dist/component/files.js.map +0 -1
- package/dist/component/messages.d.ts +0 -3851
- package/dist/component/messages.d.ts.map +0 -1
- package/dist/component/messages.js +0 -757
- package/dist/component/messages.js.map +0 -1
- package/dist/component/schema.d.ts +0 -8026
- package/dist/component/schema.d.ts.map +0 -1
- package/dist/component/schema.js +0 -147
- package/dist/component/schema.js.map +0 -1
- package/dist/component/streams.d.ts +0 -128
- package/dist/component/streams.d.ts.map +0 -1
- package/dist/component/streams.js +0 -413
- package/dist/component/streams.js.map +0 -1
- package/dist/component/threads.d.ts +0 -115
- package/dist/component/threads.d.ts.map +0 -1
- package/dist/component/threads.js +0 -208
- package/dist/component/threads.js.map +0 -1
- package/dist/component/users.d.ts +0 -52
- package/dist/component/users.d.ts.map +0 -1
- package/dist/component/users.js +0 -229
- package/dist/component/users.js.map +0 -1
- package/dist/component/vector/index.d.ts +0 -61
- package/dist/component/vector/index.d.ts.map +0 -1
- package/dist/component/vector/index.js +0 -146
- package/dist/component/vector/index.js.map +0 -1
- package/dist/component/vector/tables.d.ts +0 -58
- package/dist/component/vector/tables.d.ts.map +0 -1
- package/dist/component/vector/tables.js +0 -56
- package/dist/component/vector/tables.js.map +0 -1
- package/dist/deltas.d.ts +0 -43
- package/dist/deltas.d.ts.map +0 -1
- package/dist/deltas.js +0 -446
- package/dist/deltas.js.map +0 -1
- package/dist/mapping.d.ts +0 -72
- package/dist/mapping.d.ts.map +0 -1
- package/dist/mapping.js +0 -677
- package/dist/mapping.js.map +0 -1
- package/dist/react/SmoothText.d.ts +0 -5
- package/dist/react/SmoothText.d.ts.map +0 -1
- package/dist/react/SmoothText.js +0 -6
- package/dist/react/SmoothText.js.map +0 -1
- package/dist/react/index.d.ts +0 -25
- package/dist/react/index.d.ts.map +0 -1
- package/dist/react/index.js +0 -70
- package/dist/react/index.js.map +0 -1
- package/dist/react/optimisticallySendMessage.d.ts +0 -42
- package/dist/react/optimisticallySendMessage.d.ts.map +0 -1
- package/dist/react/optimisticallySendMessage.js +0 -74
- package/dist/react/optimisticallySendMessage.js.map +0 -1
- package/dist/react/types.d.ts +0 -12
- package/dist/react/types.d.ts.map +0 -1
- package/dist/react/types.js +0 -2
- package/dist/react/types.js.map +0 -1
- package/dist/react/useDeltaStreams.d.ts +0 -10
- package/dist/react/useDeltaStreams.d.ts.map +0 -1
- package/dist/react/useDeltaStreams.js +0 -106
- package/dist/react/useDeltaStreams.js.map +0 -1
- package/dist/react/useSmoothText.d.ts +0 -27
- package/dist/react/useSmoothText.d.ts.map +0 -1
- package/dist/react/useSmoothText.js +0 -68
- package/dist/react/useSmoothText.js.map +0 -1
- package/dist/react/useStreamingUIMessages.d.ts +0 -22
- package/dist/react/useStreamingUIMessages.d.ts.map +0 -1
- package/dist/react/useStreamingUIMessages.js +0 -92
- package/dist/react/useStreamingUIMessages.js.map +0 -1
- package/dist/react/useThreadMessages.d.ts +0 -104
- package/dist/react/useThreadMessages.d.ts.map +0 -1
- package/dist/react/useThreadMessages.js +0 -148
- package/dist/react/useThreadMessages.js.map +0 -1
- package/dist/react/useUIMessages.d.ts +0 -96
- package/dist/react/useUIMessages.d.ts.map +0 -1
- package/dist/react/useUIMessages.js +0 -108
- package/dist/react/useUIMessages.js.map +0 -1
- package/dist/shared.d.ts +0 -26
- package/dist/shared.d.ts.map +0 -1
- package/dist/shared.js +0 -67
- package/dist/shared.js.map +0 -1
- package/dist/validators.d.ts +0 -24516
- package/dist/validators.d.ts.map +0 -1
- package/dist/validators.js +0 -475
- package/dist/validators.js.map +0 -1
package/package.json
CHANGED
package/src/UIMessages.ts
CHANGED
|
@@ -396,6 +396,54 @@ function createAssistantUIMessage<
|
|
|
396
396
|
? ("streaming" as const)
|
|
397
397
|
: lastMessage.status;
|
|
398
398
|
|
|
399
|
+
<<<<<<< Updated upstream
|
|
400
|
+
=======
|
|
401
|
+
// Extract approval parts from raw message content for UI rendering
|
|
402
|
+
type ApprovalPart =
|
|
403
|
+
| { type: "tool-approval-request"; approvalId: string; toolCallId: string }
|
|
404
|
+
| {
|
|
405
|
+
type: "tool-approval-response";
|
|
406
|
+
approvalId: string;
|
|
407
|
+
approved: boolean;
|
|
408
|
+
reason?: string;
|
|
409
|
+
};
|
|
410
|
+
const approvalParts: ApprovalPart[] = [];
|
|
411
|
+
|
|
412
|
+
// Extract execution-denied tool results from raw content for UI rendering
|
|
413
|
+
// (these are converted to text format for provider compatibility in start.ts)
|
|
414
|
+
type ExecutionDeniedInfo = {
|
|
415
|
+
toolCallId: string;
|
|
416
|
+
reason?: string;
|
|
417
|
+
};
|
|
418
|
+
const executionDeniedResults: ExecutionDeniedInfo[] = [];
|
|
419
|
+
|
|
420
|
+
for (const message of group) {
|
|
421
|
+
const rawContent = message.message?.content;
|
|
422
|
+
if (Array.isArray(rawContent)) {
|
|
423
|
+
for (const part of rawContent) {
|
|
424
|
+
if (
|
|
425
|
+
part.type === "tool-approval-request" ||
|
|
426
|
+
part.type === "tool-approval-response"
|
|
427
|
+
) {
|
|
428
|
+
approvalParts.push(part as ApprovalPart);
|
|
429
|
+
}
|
|
430
|
+
// Check for execution-denied in tool-result outputs
|
|
431
|
+
if (
|
|
432
|
+
part.type === "tool-result" &&
|
|
433
|
+
typeof part.output === "object" &&
|
|
434
|
+
part.output !== null &&
|
|
435
|
+
(part.output as { type?: string }).type === "execution-denied"
|
|
436
|
+
) {
|
|
437
|
+
executionDeniedResults.push({
|
|
438
|
+
toolCallId: part.toolCallId as string,
|
|
439
|
+
reason: (part.output as { reason?: string }).reason,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
>>>>>>> Stashed changes
|
|
399
447
|
// Collect all parts from all messages
|
|
400
448
|
const allParts: UIMessage<METADATA, DATA_PARTS, TOOLS>["parts"] = [];
|
|
401
449
|
|
|
@@ -477,6 +525,12 @@ function createAssistantUIMessage<
|
|
|
477
525
|
break;
|
|
478
526
|
}
|
|
479
527
|
case "tool-result": {
|
|
528
|
+
<<<<<<< Updated upstream
|
|
529
|
+
=======
|
|
530
|
+
// Note: execution-denied outputs are handled separately via pre-extraction
|
|
531
|
+
// from raw content (converted to text format for providers in start.ts).
|
|
532
|
+
// See executionDeniedResults processing at the end of this function.
|
|
533
|
+
>>>>>>> Stashed changes
|
|
480
534
|
const typedPart = contentPart as unknown as ToolResultPart & {
|
|
481
535
|
output: { type: string; value?: unknown; reason?: string };
|
|
482
536
|
};
|
|
@@ -642,6 +696,87 @@ function createAssistantUIMessage<
|
|
|
642
696
|
}
|
|
643
697
|
}
|
|
644
698
|
|
|
699
|
+
<<<<<<< Updated upstream
|
|
700
|
+
=======
|
|
701
|
+
// Final output states that should not be overwritten by approval processing
|
|
702
|
+
const finalStates = new Set([
|
|
703
|
+
"output-available",
|
|
704
|
+
"output-error",
|
|
705
|
+
"output-denied",
|
|
706
|
+
]);
|
|
707
|
+
|
|
708
|
+
// Process approval parts to update tool call states
|
|
709
|
+
for (const approvalPart of approvalParts) {
|
|
710
|
+
if (approvalPart.type === "tool-approval-request") {
|
|
711
|
+
const toolCallPart = allParts.find(
|
|
712
|
+
(part) =>
|
|
713
|
+
"toolCallId" in part && part.toolCallId === approvalPart.toolCallId,
|
|
714
|
+
) as ToolUIPart | undefined;
|
|
715
|
+
|
|
716
|
+
if (toolCallPart) {
|
|
717
|
+
// Always set approval info (needed for response matching), but only
|
|
718
|
+
// update state if not in a final state
|
|
719
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
720
|
+
id: approvalPart.approvalId,
|
|
721
|
+
};
|
|
722
|
+
if (!finalStates.has(toolCallPart.state)) {
|
|
723
|
+
toolCallPart.state = "approval-requested";
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} else if (approvalPart.type === "tool-approval-response") {
|
|
727
|
+
const toolCallPart = allParts.find(
|
|
728
|
+
(part) =>
|
|
729
|
+
"approval" in part &&
|
|
730
|
+
(part as ToolUIPart & { approval?: { id: string } }).approval?.id ===
|
|
731
|
+
approvalPart.approvalId,
|
|
732
|
+
) as ToolUIPart | undefined;
|
|
733
|
+
|
|
734
|
+
if (toolCallPart) {
|
|
735
|
+
// Always update approval info, but only update state if not in a final state
|
|
736
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
737
|
+
id: approvalPart.approvalId,
|
|
738
|
+
approved: approvalPart.approved,
|
|
739
|
+
reason: approvalPart.reason,
|
|
740
|
+
};
|
|
741
|
+
if (!finalStates.has(toolCallPart.state)) {
|
|
742
|
+
if (approvalPart.approved) {
|
|
743
|
+
toolCallPart.state = "approval-responded";
|
|
744
|
+
} else {
|
|
745
|
+
toolCallPart.state = "output-denied";
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// Process execution-denied results to update tool call states
|
|
753
|
+
for (const denied of executionDeniedResults) {
|
|
754
|
+
const toolCallPart = allParts.find(
|
|
755
|
+
(part) =>
|
|
756
|
+
"toolCallId" in part && part.toolCallId === denied.toolCallId,
|
|
757
|
+
) as ToolUIPart | undefined;
|
|
758
|
+
|
|
759
|
+
if (toolCallPart) {
|
|
760
|
+
toolCallPart.state = "output-denied";
|
|
761
|
+
if (!("approval" in toolCallPart) || !toolCallPart.approval) {
|
|
762
|
+
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
|
|
763
|
+
id: "",
|
|
764
|
+
approved: false,
|
|
765
|
+
reason: denied.reason,
|
|
766
|
+
};
|
|
767
|
+
} else {
|
|
768
|
+
const approval = (
|
|
769
|
+
toolCallPart as ToolUIPart & {
|
|
770
|
+
approval: { approved?: boolean; reason?: string };
|
|
771
|
+
}
|
|
772
|
+
).approval;
|
|
773
|
+
approval.approved = false;
|
|
774
|
+
approval.reason = denied.reason;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
>>>>>>> Stashed changes
|
|
645
780
|
return {
|
|
646
781
|
...common,
|
|
647
782
|
role: "assistant",
|
package/src/client/index.ts
CHANGED
|
@@ -1070,10 +1070,35 @@ export class Agent<
|
|
|
1070
1070
|
reason?: string;
|
|
1071
1071
|
},
|
|
1072
1072
|
): Promise<{ messageId: string }> {
|
|
1073
|
-
const promptMessageId
|
|
1074
|
-
|
|
1073
|
+
const { promptMessageId, existingResponseMessage } =
|
|
1074
|
+
await this.findApprovalContext(ctx, {
|
|
1075
|
+
threadId: args.threadId,
|
|
1076
|
+
approvalId: args.approvalId,
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
const newPart = {
|
|
1080
|
+
type: "tool-approval-response" as const,
|
|
1075
1081
|
approvalId: args.approvalId,
|
|
1076
|
-
|
|
1082
|
+
approved: args.approved,
|
|
1083
|
+
reason: args.reason,
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// Merge into an existing approval-response message for this step
|
|
1087
|
+
// so the AI SDK sees a single tool message per step.
|
|
1088
|
+
if (existingResponseMessage) {
|
|
1089
|
+
const existingContent = existingResponseMessage.message?.content;
|
|
1090
|
+
const mergedContent = Array.isArray(existingContent)
|
|
1091
|
+
? [...(existingContent as any[]), newPart]
|
|
1092
|
+
: [newPart];
|
|
1093
|
+
await this.updateMessage(ctx, {
|
|
1094
|
+
messageId: existingResponseMessage._id,
|
|
1095
|
+
patch: {
|
|
1096
|
+
message: { role: "tool", content: mergedContent },
|
|
1097
|
+
status: "success",
|
|
1098
|
+
},
|
|
1099
|
+
});
|
|
1100
|
+
return { messageId: existingResponseMessage._id };
|
|
1101
|
+
}
|
|
1077
1102
|
|
|
1078
1103
|
const { messageId } = await this.saveMessage(ctx, {
|
|
1079
1104
|
threadId: args.threadId,
|
|
@@ -1081,28 +1106,25 @@ export class Agent<
|
|
|
1081
1106
|
skipEmbeddings: true,
|
|
1082
1107
|
message: {
|
|
1083
1108
|
role: "tool",
|
|
1084
|
-
content: [
|
|
1085
|
-
{
|
|
1086
|
-
type: "tool-approval-response",
|
|
1087
|
-
approvalId: args.approvalId,
|
|
1088
|
-
approved: args.approved,
|
|
1089
|
-
reason: args.reason,
|
|
1090
|
-
},
|
|
1091
|
-
],
|
|
1109
|
+
content: [newPart],
|
|
1092
1110
|
},
|
|
1093
1111
|
});
|
|
1094
1112
|
return { messageId };
|
|
1095
1113
|
}
|
|
1096
1114
|
|
|
1097
|
-
private async
|
|
1115
|
+
private async findApprovalContext(
|
|
1098
1116
|
ctx: MutationCtx,
|
|
1099
1117
|
args: { threadId: string; approvalId: string },
|
|
1100
|
-
): Promise<
|
|
1118
|
+
): Promise<{
|
|
1119
|
+
promptMessageId: string;
|
|
1120
|
+
existingResponseMessage: MessageDoc | undefined;
|
|
1121
|
+
}> {
|
|
1101
1122
|
// NOTE: This pagination returns messages in descending order (newest first).
|
|
1102
1123
|
// The "already handled" check (tool-approval-response) relies on seeing
|
|
1103
1124
|
// responses before their corresponding requests. If the pagination order
|
|
1104
1125
|
// changes, this logic will need to be updated.
|
|
1105
1126
|
let cursor: string | null = null;
|
|
1127
|
+
let existingResponseMessage: MessageDoc | undefined;
|
|
1106
1128
|
do {
|
|
1107
1129
|
const page = await this.listMessages(ctx, {
|
|
1108
1130
|
threadId: args.threadId,
|
|
@@ -1111,6 +1133,19 @@ export class Agent<
|
|
|
1111
1133
|
for (const message of page.page) {
|
|
1112
1134
|
const content = message.message?.content;
|
|
1113
1135
|
if (!Array.isArray(content)) continue;
|
|
1136
|
+
// Check if this assistant message starts a different approval step.
|
|
1137
|
+
// If so, any response message we've seen so far belongs to a newer
|
|
1138
|
+
// step — reset it so we don't merge across step boundaries.
|
|
1139
|
+
if (
|
|
1140
|
+
message.message?.role === "assistant" &&
|
|
1141
|
+
content.some(
|
|
1142
|
+
(p: any) =>
|
|
1143
|
+
p.type === "tool-approval-request" &&
|
|
1144
|
+
p.approvalId !== args.approvalId,
|
|
1145
|
+
)
|
|
1146
|
+
) {
|
|
1147
|
+
existingResponseMessage = undefined;
|
|
1148
|
+
}
|
|
1114
1149
|
for (const part of content) {
|
|
1115
1150
|
const typedPart = part as { type?: unknown; approvalId?: unknown };
|
|
1116
1151
|
if (
|
|
@@ -1119,11 +1154,18 @@ export class Agent<
|
|
|
1119
1154
|
) {
|
|
1120
1155
|
throw new Error(`Approval ${args.approvalId} was already handled`);
|
|
1121
1156
|
}
|
|
1157
|
+
// Track the most recent tool-approval-response message for merging
|
|
1158
|
+
if (
|
|
1159
|
+
typedPart.type === "tool-approval-response" &&
|
|
1160
|
+
!existingResponseMessage
|
|
1161
|
+
) {
|
|
1162
|
+
existingResponseMessage = message;
|
|
1163
|
+
}
|
|
1122
1164
|
if (
|
|
1123
1165
|
typedPart.type === "tool-approval-request" &&
|
|
1124
1166
|
typedPart.approvalId === args.approvalId
|
|
1125
1167
|
) {
|
|
1126
|
-
return message._id;
|
|
1168
|
+
return { promptMessageId: message._id, existingResponseMessage };
|
|
1127
1169
|
}
|
|
1128
1170
|
}
|
|
1129
1171
|
}
|
|
@@ -292,7 +292,7 @@ describe("search.ts", () => {
|
|
|
292
292
|
expect(result[1]._id).toBe("2");
|
|
293
293
|
});
|
|
294
294
|
|
|
295
|
-
it("should
|
|
295
|
+
it("should keep tool calls with approval request but NO approval response (auto-deny handles them)", () => {
|
|
296
296
|
const messages: MessageDoc[] = [
|
|
297
297
|
{
|
|
298
298
|
_id: "1",
|
|
@@ -321,19 +321,18 @@ describe("search.ts", () => {
|
|
|
321
321
|
|
|
322
322
|
const result = filterOutOrphanedToolMessages(messages);
|
|
323
323
|
expect(result).toHaveLength(1);
|
|
324
|
-
// The assistant message should
|
|
324
|
+
// The assistant message should keep the tool-call (auto-deny resolves it downstream)
|
|
325
325
|
const assistantContent = result[0].message?.content;
|
|
326
326
|
expect(Array.isArray(assistantContent)).toBe(true);
|
|
327
327
|
if (Array.isArray(assistantContent)) {
|
|
328
|
-
|
|
329
|
-
expect(assistantContent).toHaveLength(2);
|
|
328
|
+
expect(assistantContent).toHaveLength(3);
|
|
330
329
|
expect(assistantContent.find((p) => p.type === "text")).toBeDefined();
|
|
331
330
|
expect(
|
|
332
331
|
assistantContent.find((p) => p.type === "tool-approval-request"),
|
|
333
332
|
).toBeDefined();
|
|
334
333
|
expect(
|
|
335
334
|
assistantContent.find((p) => p.type === "tool-call"),
|
|
336
|
-
).
|
|
335
|
+
).toBeDefined();
|
|
337
336
|
}
|
|
338
337
|
});
|
|
339
338
|
|
package/src/client/search.ts
CHANGED
|
@@ -30,11 +30,33 @@ import type {
|
|
|
30
30
|
} from "./types.js";
|
|
31
31
|
import { inlineMessagesFiles } from "./files.js";
|
|
32
32
|
import {
|
|
33
|
+
autoDenyUnresolvedApprovals,
|
|
33
34
|
docsToModelMessages,
|
|
34
|
-
mergeApprovalResponseMessages,
|
|
35
35
|
toModelMessage,
|
|
36
36
|
} from "../mapping.js";
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Filter out approval parts from messages before sending to AI providers.
|
|
40
|
+
* Providers like Anthropic don't understand tool-approval-request and
|
|
41
|
+
* tool-approval-response parts and will error if they are included.
|
|
42
|
+
*/
|
|
43
|
+
function filterApprovalPartsForProvider(messages: ModelMessage[]): ModelMessage[] {
|
|
44
|
+
return messages.map((message) => {
|
|
45
|
+
if (!Array.isArray(message.content)) {
|
|
46
|
+
return message;
|
|
47
|
+
}
|
|
48
|
+
const filteredContent = message.content.filter(
|
|
49
|
+
(part) =>
|
|
50
|
+
(part as { type: string }).type !== "tool-approval-request" &&
|
|
51
|
+
(part as { type: string }).type !== "tool-approval-response",
|
|
52
|
+
);
|
|
53
|
+
return {
|
|
54
|
+
...message,
|
|
55
|
+
content: filteredContent,
|
|
56
|
+
} as typeof message;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
const DEFAULT_VECTOR_SCORE_THRESHOLD = 0.0;
|
|
39
61
|
// 10k characters should be more than enough for most cases, and stays under
|
|
40
62
|
// the 8k token limit for some models.
|
|
@@ -234,7 +256,11 @@ export async function fetchRecentAndSearchMessages(
|
|
|
234
256
|
searchMessages = filterOutOrphanedToolMessages(
|
|
235
257
|
sorted(searchResults.filter((m) => !included?.has(m._id))),
|
|
236
258
|
);
|
|
259
|
+
searchMessages = filterApprovalPartsForProvider(searchMessages);
|
|
237
260
|
}
|
|
261
|
+
|
|
262
|
+
// filter out approval parts before returning
|
|
263
|
+
searchMessages = filterApprovalPartsForProvider(searchMessages);
|
|
238
264
|
// Ensure we don't include tool messages without a corresponding tool call
|
|
239
265
|
return { recentMessages, searchMessages };
|
|
240
266
|
}
|
|
@@ -289,6 +315,12 @@ export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
|
|
|
289
315
|
return approvalId !== undefined && approvalResponseIds.has(approvalId);
|
|
290
316
|
};
|
|
291
317
|
|
|
318
|
+
// Helper: check if tool call has a pending approval request
|
|
319
|
+
// (auto-deny handles these downstream, so they must survive the filter)
|
|
320
|
+
const hasApprovalRequest = (toolCallId: string) => {
|
|
321
|
+
return approvalRequestsByToolCallId.has(toolCallId);
|
|
322
|
+
};
|
|
323
|
+
|
|
292
324
|
for (const doc of docs) {
|
|
293
325
|
if (
|
|
294
326
|
doc.message?.role === "assistant" &&
|
|
@@ -298,7 +330,8 @@ export function filterOutOrphanedToolMessages(docs: MessageDoc[]) {
|
|
|
298
330
|
(p) =>
|
|
299
331
|
p.type !== "tool-call" ||
|
|
300
332
|
toolResultIds.has(p.toolCallId) ||
|
|
301
|
-
hasApprovalResponse(p.toolCallId)
|
|
333
|
+
hasApprovalResponse(p.toolCallId) ||
|
|
334
|
+
hasApprovalRequest(p.toolCallId),
|
|
302
335
|
);
|
|
303
336
|
if (content.length) {
|
|
304
337
|
result.push({
|
|
@@ -641,13 +674,13 @@ export async function fetchContextWithPrompt(
|
|
|
641
674
|
const inputPrompt = promptArray.map(toModelMessage);
|
|
642
675
|
const existingResponses = docsToModelMessages(existingResponseDocs);
|
|
643
676
|
|
|
644
|
-
const allMessages =
|
|
677
|
+
const allMessages = [
|
|
645
678
|
...search,
|
|
646
679
|
...recent,
|
|
647
680
|
...inputMessages,
|
|
648
681
|
...inputPrompt,
|
|
649
682
|
...existingResponses,
|
|
650
|
-
]
|
|
683
|
+
];
|
|
651
684
|
let processedMessages = args.contextHandler
|
|
652
685
|
? await args.contextHandler(ctx, {
|
|
653
686
|
allMessages,
|
|
@@ -661,11 +694,19 @@ export async function fetchContextWithPrompt(
|
|
|
661
694
|
})
|
|
662
695
|
: allMessages;
|
|
663
696
|
|
|
697
|
+
// Post-process: auto-deny unresolved approvals so the AI SDK sees a
|
|
698
|
+
// complete history. Applied after contextHandler so custom handlers
|
|
699
|
+
// don't need to handle this.
|
|
700
|
+
processedMessages = autoDenyUnresolvedApprovals(processedMessages);
|
|
701
|
+
|
|
664
702
|
// Process messages to inline localhost files (if not, file urls pointing to localhost will be sent to LLM providers)
|
|
665
703
|
if (process.env.CONVEX_CLOUD_URL?.startsWith("http://127.0.0.1")) {
|
|
666
704
|
processedMessages = await inlineMessagesFiles(processedMessages);
|
|
667
705
|
}
|
|
668
706
|
|
|
707
|
+
// Filter out approval parts before sending to AI providers
|
|
708
|
+
processedMessages = filterApprovalPartsForProvider(processedMessages);
|
|
709
|
+
|
|
669
710
|
return {
|
|
670
711
|
messages: processedMessages,
|
|
671
712
|
order: promptMessage?.order,
|