@cloudflare/ai-chat 0.0.4 → 0.0.5
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/CHANGELOG.md +17 -0
- package/dist/ai-chat-v5-migration.d.ts +0 -1
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/index.d.ts +15 -8
- package/dist/index.js +386 -353
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +27 -48
- package/dist/react.js +18 -18
- package/dist/react.js.map +1 -1
- package/dist/types.d.ts +19 -47
- package/dist/types.js +11 -11
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
- package/src/index.ts +720 -628
- package/src/react-tests/setup.ts +3 -0
- package/src/react-tests/use-agent-chat.test.tsx +35 -23
- package/src/react-tests/vitest.config.ts +2 -1
- package/src/tests/chat-context.test.ts +2 -1
- package/src/tests/chat-persistence.test.ts +4 -3
- package/src/tests/client-tool-duplicate-message.test.ts +9 -8
- package/src/tests/resumable-streaming.test.ts +90 -39
- package/src/tests/worker.ts +24 -0
package/dist/index.js
CHANGED
|
@@ -80,6 +80,7 @@ var AIChatAgent = class extends Agent {
|
|
|
80
80
|
this._chunkBuffer = [];
|
|
81
81
|
this._isFlushingChunks = false;
|
|
82
82
|
this._lastCleanupTime = 0;
|
|
83
|
+
this._pendingResumeConnections = /* @__PURE__ */ new Set();
|
|
83
84
|
this.sql`create table if not exists cf_ai_chat_agent_messages (
|
|
84
85
|
id text primary key,
|
|
85
86
|
message text not null,
|
|
@@ -105,9 +106,14 @@ var AIChatAgent = class extends Agent {
|
|
|
105
106
|
this._chatMessageAbortControllers = /* @__PURE__ */ new Map();
|
|
106
107
|
this._restoreActiveStream();
|
|
107
108
|
const _onConnect = this.onConnect.bind(this);
|
|
108
|
-
this.onConnect = async (connection, ctx
|
|
109
|
+
this.onConnect = async (connection, ctx) => {
|
|
109
110
|
if (this._activeStreamId) this._notifyStreamResuming(connection);
|
|
110
|
-
return _onConnect(connection, ctx
|
|
111
|
+
return _onConnect(connection, ctx);
|
|
112
|
+
};
|
|
113
|
+
const _onClose = this.onClose.bind(this);
|
|
114
|
+
this.onClose = async (connection, code, reason, wasClean) => {
|
|
115
|
+
this._pendingResumeConnections.delete(connection.id);
|
|
116
|
+
return _onClose(connection, code, reason, wasClean);
|
|
111
117
|
};
|
|
112
118
|
const _onMessage = this.onMessage.bind(this);
|
|
113
119
|
this.onMessage = async (connection, message) => {
|
|
@@ -177,6 +183,7 @@ var AIChatAgent = class extends Agent {
|
|
|
177
183
|
this._activeStreamId = null;
|
|
178
184
|
this._activeRequestId = null;
|
|
179
185
|
this._streamChunkIndex = 0;
|
|
186
|
+
this._pendingResumeConnections.clear();
|
|
180
187
|
this.messages = [];
|
|
181
188
|
this._broadcastChatMessage({ type: MessageType.CF_AGENT_CHAT_CLEAR }, [connection.id]);
|
|
182
189
|
return;
|
|
@@ -191,6 +198,7 @@ var AIChatAgent = class extends Agent {
|
|
|
191
198
|
return;
|
|
192
199
|
}
|
|
193
200
|
if (data.type === MessageType.CF_AGENT_STREAM_RESUME_ACK) {
|
|
201
|
+
this._pendingResumeConnections.delete(connection.id);
|
|
194
202
|
if (this._activeStreamId && this._activeRequestId && this._activeRequestId === data.id) this._sendStreamChunks(connection, this._activeStreamId, this._activeRequestId);
|
|
195
203
|
return;
|
|
196
204
|
}
|
|
@@ -274,6 +282,7 @@ var AIChatAgent = class extends Agent {
|
|
|
274
282
|
*/
|
|
275
283
|
_notifyStreamResuming(connection) {
|
|
276
284
|
if (!this._activeStreamId || !this._activeRequestId) return;
|
|
285
|
+
this._pendingResumeConnections.add(connection.id);
|
|
277
286
|
connection.send(JSON.stringify({
|
|
278
287
|
type: MessageType.CF_AGENT_STREAM_RESUMING,
|
|
279
288
|
id: this._activeRequestId
|
|
@@ -376,6 +385,7 @@ var AIChatAgent = class extends Agent {
|
|
|
376
385
|
this._activeStreamId = null;
|
|
377
386
|
this._activeRequestId = null;
|
|
378
387
|
this._streamChunkIndex = 0;
|
|
388
|
+
this._pendingResumeConnections.clear();
|
|
379
389
|
this._maybeCleanupOldStreams();
|
|
380
390
|
}
|
|
381
391
|
/**
|
|
@@ -400,7 +410,8 @@ var AIChatAgent = class extends Agent {
|
|
|
400
410
|
`;
|
|
401
411
|
}
|
|
402
412
|
_broadcastChatMessage(message, exclude) {
|
|
403
|
-
this.
|
|
413
|
+
const allExclusions = [...exclude || [], ...this._pendingResumeConnections];
|
|
414
|
+
this.broadcast(JSON.stringify(message), allExclusions);
|
|
404
415
|
}
|
|
405
416
|
/**
|
|
406
417
|
* Broadcasts a text event for non-SSE responses.
|
|
@@ -689,6 +700,374 @@ var AIChatAgent = class extends Agent {
|
|
|
689
700
|
}
|
|
690
701
|
return true;
|
|
691
702
|
}
|
|
703
|
+
async _streamSSEReply(id, streamId, reader, message, streamCompleted, continuation = false) {
|
|
704
|
+
let activeTextParts = {};
|
|
705
|
+
let activeReasoningParts = {};
|
|
706
|
+
const partialToolCalls = {};
|
|
707
|
+
const { getToolName, isToolUIPart, parsePartialJson } = await import("ai");
|
|
708
|
+
streamCompleted.value = false;
|
|
709
|
+
while (true) {
|
|
710
|
+
const { done, value } = await reader.read();
|
|
711
|
+
if (done) {
|
|
712
|
+
this._completeStream(streamId);
|
|
713
|
+
streamCompleted.value = true;
|
|
714
|
+
this._broadcastChatMessage({
|
|
715
|
+
body: "",
|
|
716
|
+
done: true,
|
|
717
|
+
id,
|
|
718
|
+
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,
|
|
719
|
+
...continuation && { continuation: true }
|
|
720
|
+
});
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
const lines = decoder.decode(value).split("\n");
|
|
724
|
+
for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") try {
|
|
725
|
+
const data = JSON.parse(line.slice(6));
|
|
726
|
+
switch (data.type) {
|
|
727
|
+
case "text-start": {
|
|
728
|
+
const textPart = {
|
|
729
|
+
type: "text",
|
|
730
|
+
text: "",
|
|
731
|
+
providerMetadata: data.providerMetadata,
|
|
732
|
+
state: "streaming"
|
|
733
|
+
};
|
|
734
|
+
activeTextParts[data.id] = textPart;
|
|
735
|
+
message.parts.push(textPart);
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
case "text-delta": {
|
|
739
|
+
const textPart = activeTextParts[data.id];
|
|
740
|
+
textPart.text += data.delta;
|
|
741
|
+
textPart.providerMetadata = data.providerMetadata ?? textPart.providerMetadata;
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
case "text-end": {
|
|
745
|
+
const textPart = activeTextParts[data.id];
|
|
746
|
+
textPart.state = "done";
|
|
747
|
+
textPart.providerMetadata = data.providerMetadata ?? textPart.providerMetadata;
|
|
748
|
+
delete activeTextParts[data.id];
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
case "reasoning-start": {
|
|
752
|
+
const reasoningPart = {
|
|
753
|
+
type: "reasoning",
|
|
754
|
+
text: "",
|
|
755
|
+
providerMetadata: data.providerMetadata,
|
|
756
|
+
state: "streaming"
|
|
757
|
+
};
|
|
758
|
+
activeReasoningParts[data.id] = reasoningPart;
|
|
759
|
+
message.parts.push(reasoningPart);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
case "reasoning-delta": {
|
|
763
|
+
const reasoningPart = activeReasoningParts[data.id];
|
|
764
|
+
reasoningPart.text += data.delta;
|
|
765
|
+
reasoningPart.providerMetadata = data.providerMetadata ?? reasoningPart.providerMetadata;
|
|
766
|
+
break;
|
|
767
|
+
}
|
|
768
|
+
case "reasoning-end": {
|
|
769
|
+
const reasoningPart = activeReasoningParts[data.id];
|
|
770
|
+
reasoningPart.providerMetadata = data.providerMetadata ?? reasoningPart.providerMetadata;
|
|
771
|
+
reasoningPart.state = "done";
|
|
772
|
+
delete activeReasoningParts[data.id];
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
case "file":
|
|
776
|
+
message.parts.push({
|
|
777
|
+
type: "file",
|
|
778
|
+
mediaType: data.mediaType,
|
|
779
|
+
url: data.url
|
|
780
|
+
});
|
|
781
|
+
break;
|
|
782
|
+
case "source-url":
|
|
783
|
+
message.parts.push({
|
|
784
|
+
type: "source-url",
|
|
785
|
+
sourceId: data.sourceId,
|
|
786
|
+
url: data.url,
|
|
787
|
+
title: data.title,
|
|
788
|
+
providerMetadata: data.providerMetadata
|
|
789
|
+
});
|
|
790
|
+
break;
|
|
791
|
+
case "source-document":
|
|
792
|
+
message.parts.push({
|
|
793
|
+
type: "source-document",
|
|
794
|
+
sourceId: data.sourceId,
|
|
795
|
+
mediaType: data.mediaType,
|
|
796
|
+
title: data.title,
|
|
797
|
+
filename: data.filename,
|
|
798
|
+
providerMetadata: data.providerMetadata
|
|
799
|
+
});
|
|
800
|
+
break;
|
|
801
|
+
case "tool-input-start": {
|
|
802
|
+
const toolInvocations = message.parts.filter(isToolUIPart);
|
|
803
|
+
partialToolCalls[data.toolCallId] = {
|
|
804
|
+
text: "",
|
|
805
|
+
toolName: data.toolName,
|
|
806
|
+
index: toolInvocations.length,
|
|
807
|
+
dynamic: data.dynamic
|
|
808
|
+
};
|
|
809
|
+
if (data.dynamic) this.updateDynamicToolPart(message, {
|
|
810
|
+
toolCallId: data.toolCallId,
|
|
811
|
+
toolName: data.toolName,
|
|
812
|
+
state: "input-streaming",
|
|
813
|
+
input: void 0
|
|
814
|
+
});
|
|
815
|
+
else await this.updateToolPart(message, {
|
|
816
|
+
toolCallId: data.toolCallId,
|
|
817
|
+
toolName: data.toolName,
|
|
818
|
+
state: "input-streaming",
|
|
819
|
+
input: void 0
|
|
820
|
+
});
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
823
|
+
case "tool-input-delta": {
|
|
824
|
+
const partialToolCall = partialToolCalls[data.toolCallId];
|
|
825
|
+
partialToolCall.text += data.inputTextDelta;
|
|
826
|
+
const partialArgs = (await parsePartialJson(partialToolCall.text)).value;
|
|
827
|
+
if (partialToolCall.dynamic) this.updateDynamicToolPart(message, {
|
|
828
|
+
toolCallId: data.toolCallId,
|
|
829
|
+
toolName: partialToolCall.toolName,
|
|
830
|
+
state: "input-streaming",
|
|
831
|
+
input: partialArgs
|
|
832
|
+
});
|
|
833
|
+
else await this.updateToolPart(message, {
|
|
834
|
+
toolCallId: data.toolCallId,
|
|
835
|
+
toolName: partialToolCall.toolName,
|
|
836
|
+
state: "input-streaming",
|
|
837
|
+
input: partialArgs
|
|
838
|
+
});
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
case "tool-input-available":
|
|
842
|
+
if (data.dynamic) this.updateDynamicToolPart(message, {
|
|
843
|
+
toolCallId: data.toolCallId,
|
|
844
|
+
toolName: data.toolName,
|
|
845
|
+
state: "input-available",
|
|
846
|
+
input: data.input,
|
|
847
|
+
providerMetadata: data.providerMetadata
|
|
848
|
+
});
|
|
849
|
+
else await this.updateToolPart(message, {
|
|
850
|
+
toolCallId: data.toolCallId,
|
|
851
|
+
toolName: data.toolName,
|
|
852
|
+
state: "input-available",
|
|
853
|
+
input: data.input,
|
|
854
|
+
providerExecuted: data.providerExecuted,
|
|
855
|
+
providerMetadata: data.providerMetadata
|
|
856
|
+
});
|
|
857
|
+
break;
|
|
858
|
+
case "tool-input-error":
|
|
859
|
+
if (data.dynamic) this.updateDynamicToolPart(message, {
|
|
860
|
+
toolCallId: data.toolCallId,
|
|
861
|
+
toolName: data.toolName,
|
|
862
|
+
state: "output-error",
|
|
863
|
+
input: data.input,
|
|
864
|
+
errorText: data.errorText,
|
|
865
|
+
providerMetadata: data.providerMetadata
|
|
866
|
+
});
|
|
867
|
+
else await this.updateToolPart(message, {
|
|
868
|
+
toolCallId: data.toolCallId,
|
|
869
|
+
toolName: data.toolName,
|
|
870
|
+
state: "output-error",
|
|
871
|
+
input: void 0,
|
|
872
|
+
rawInput: data.input,
|
|
873
|
+
errorText: data.errorText,
|
|
874
|
+
providerExecuted: data.providerExecuted,
|
|
875
|
+
providerMetadata: data.providerMetadata
|
|
876
|
+
});
|
|
877
|
+
break;
|
|
878
|
+
case "tool-output-available":
|
|
879
|
+
if (data.dynamic) {
|
|
880
|
+
const toolInvocation = message.parts.filter((part) => part.type === "dynamic-tool").find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
881
|
+
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
882
|
+
this.updateDynamicToolPart(message, {
|
|
883
|
+
toolCallId: data.toolCallId,
|
|
884
|
+
toolName: toolInvocation.toolName,
|
|
885
|
+
state: "output-available",
|
|
886
|
+
input: toolInvocation.input,
|
|
887
|
+
output: data.output,
|
|
888
|
+
preliminary: data.preliminary
|
|
889
|
+
});
|
|
890
|
+
} else {
|
|
891
|
+
const toolInvocation = message.parts.filter(isToolUIPart).find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
892
|
+
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
893
|
+
await this.updateToolPart(message, {
|
|
894
|
+
toolCallId: data.toolCallId,
|
|
895
|
+
toolName: getToolName(toolInvocation),
|
|
896
|
+
state: "output-available",
|
|
897
|
+
input: toolInvocation.input,
|
|
898
|
+
output: data.output,
|
|
899
|
+
providerExecuted: data.providerExecuted,
|
|
900
|
+
preliminary: data.preliminary
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
break;
|
|
904
|
+
case "tool-output-error":
|
|
905
|
+
if (data.dynamic) {
|
|
906
|
+
const toolInvocation = message.parts.filter((part) => part.type === "dynamic-tool").find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
907
|
+
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
908
|
+
this.updateDynamicToolPart(message, {
|
|
909
|
+
toolCallId: data.toolCallId,
|
|
910
|
+
toolName: toolInvocation.toolName,
|
|
911
|
+
state: "output-error",
|
|
912
|
+
input: toolInvocation.input,
|
|
913
|
+
errorText: data.errorText
|
|
914
|
+
});
|
|
915
|
+
} else {
|
|
916
|
+
const toolInvocation = message.parts.filter(isToolUIPart).find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
917
|
+
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
918
|
+
await this.updateToolPart(message, {
|
|
919
|
+
toolCallId: data.toolCallId,
|
|
920
|
+
toolName: getToolName(toolInvocation),
|
|
921
|
+
state: "output-error",
|
|
922
|
+
input: toolInvocation.input,
|
|
923
|
+
rawInput: "rawInput" in toolInvocation ? toolInvocation.rawInput : void 0,
|
|
924
|
+
errorText: data.errorText
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
break;
|
|
928
|
+
case "start-step":
|
|
929
|
+
message.parts.push({ type: "step-start" });
|
|
930
|
+
break;
|
|
931
|
+
case "finish-step":
|
|
932
|
+
activeTextParts = {};
|
|
933
|
+
activeReasoningParts = {};
|
|
934
|
+
break;
|
|
935
|
+
case "start":
|
|
936
|
+
if (data.messageId != null) message.id = data.messageId;
|
|
937
|
+
await this.updateMessageMetadata(message, data.messageMetadata);
|
|
938
|
+
break;
|
|
939
|
+
case "finish":
|
|
940
|
+
await this.updateMessageMetadata(message, data.messageMetadata);
|
|
941
|
+
break;
|
|
942
|
+
case "message-metadata":
|
|
943
|
+
await this.updateMessageMetadata(message, data.messageMetadata);
|
|
944
|
+
break;
|
|
945
|
+
case "error":
|
|
946
|
+
this._broadcastChatMessage({
|
|
947
|
+
error: true,
|
|
948
|
+
body: data.errorText ?? JSON.stringify(data),
|
|
949
|
+
done: false,
|
|
950
|
+
id,
|
|
951
|
+
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE
|
|
952
|
+
});
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
let eventToSend = data;
|
|
956
|
+
if (data.type === "finish" && "finishReason" in data) {
|
|
957
|
+
const { finishReason, ...rest } = data;
|
|
958
|
+
eventToSend = {
|
|
959
|
+
...rest,
|
|
960
|
+
type: "finish",
|
|
961
|
+
messageMetadata: { finishReason }
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
const chunkBody = JSON.stringify(eventToSend);
|
|
965
|
+
this._storeStreamChunk(streamId, chunkBody);
|
|
966
|
+
this._broadcastChatMessage({
|
|
967
|
+
body: chunkBody,
|
|
968
|
+
done: false,
|
|
969
|
+
id,
|
|
970
|
+
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,
|
|
971
|
+
...continuation && { continuation: true }
|
|
972
|
+
});
|
|
973
|
+
} catch (_error) {}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
async _sendPlaintextReply(id, streamId, reader, message, streamCompleted, continuation = false) {
|
|
977
|
+
this._broadcastTextEvent(streamId, {
|
|
978
|
+
type: "text-start",
|
|
979
|
+
id
|
|
980
|
+
}, continuation);
|
|
981
|
+
while (true) {
|
|
982
|
+
const { done, value } = await reader.read();
|
|
983
|
+
if (done) {
|
|
984
|
+
this._broadcastTextEvent(streamId, {
|
|
985
|
+
type: "text-end",
|
|
986
|
+
id
|
|
987
|
+
}, continuation);
|
|
988
|
+
this._completeStream(streamId);
|
|
989
|
+
streamCompleted.value = true;
|
|
990
|
+
this._broadcastChatMessage({
|
|
991
|
+
body: "",
|
|
992
|
+
done: true,
|
|
993
|
+
id,
|
|
994
|
+
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,
|
|
995
|
+
...continuation && { continuation: true }
|
|
996
|
+
});
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
const chunk = decoder.decode(value);
|
|
1000
|
+
if (chunk.length > 0) {
|
|
1001
|
+
message.parts.push({
|
|
1002
|
+
type: "text",
|
|
1003
|
+
text: chunk
|
|
1004
|
+
});
|
|
1005
|
+
this._broadcastTextEvent(streamId, {
|
|
1006
|
+
type: "text-delta",
|
|
1007
|
+
id,
|
|
1008
|
+
delta: chunk
|
|
1009
|
+
}, continuation);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
updateDynamicToolPart(message, options) {
|
|
1014
|
+
const part = message.parts.find((part) => part.type === "dynamic-tool" && part.toolCallId === options.toolCallId);
|
|
1015
|
+
const anyOptions = options;
|
|
1016
|
+
const anyPart = part;
|
|
1017
|
+
if (part != null) {
|
|
1018
|
+
part.state = options.state;
|
|
1019
|
+
anyPart.toolName = options.toolName;
|
|
1020
|
+
anyPart.input = anyOptions.input;
|
|
1021
|
+
anyPart.output = anyOptions.output;
|
|
1022
|
+
anyPart.errorText = anyOptions.errorText;
|
|
1023
|
+
anyPart.rawInput = anyOptions.rawInput ?? anyPart.rawInput;
|
|
1024
|
+
anyPart.preliminary = anyOptions.preliminary;
|
|
1025
|
+
if (anyOptions.providerMetadata != null && part.state === "input-available") part.callProviderMetadata = anyOptions.providerMetadata;
|
|
1026
|
+
} else message.parts.push({
|
|
1027
|
+
type: "dynamic-tool",
|
|
1028
|
+
toolName: options.toolName,
|
|
1029
|
+
toolCallId: options.toolCallId,
|
|
1030
|
+
state: options.state,
|
|
1031
|
+
input: anyOptions.input,
|
|
1032
|
+
output: anyOptions.output,
|
|
1033
|
+
errorText: anyOptions.errorText,
|
|
1034
|
+
preliminary: anyOptions.preliminary,
|
|
1035
|
+
...anyOptions.providerMetadata != null ? { callProviderMetadata: anyOptions.providerMetadata } : {}
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
async updateToolPart(message, options) {
|
|
1039
|
+
const { isToolUIPart } = await import("ai");
|
|
1040
|
+
const part = message.parts.find((part) => isToolUIPart(part) && part.toolCallId === options.toolCallId);
|
|
1041
|
+
const anyOptions = options;
|
|
1042
|
+
const anyPart = part;
|
|
1043
|
+
if (part != null) {
|
|
1044
|
+
part.state = options.state;
|
|
1045
|
+
anyPart.input = anyOptions.input;
|
|
1046
|
+
anyPart.output = anyOptions.output;
|
|
1047
|
+
anyPart.errorText = anyOptions.errorText;
|
|
1048
|
+
anyPart.rawInput = anyOptions.rawInput;
|
|
1049
|
+
anyPart.preliminary = anyOptions.preliminary;
|
|
1050
|
+
anyPart.providerExecuted = anyOptions.providerExecuted ?? part.providerExecuted;
|
|
1051
|
+
if (anyOptions.providerMetadata != null && part.state === "input-available") part.callProviderMetadata = anyOptions.providerMetadata;
|
|
1052
|
+
} else message.parts.push({
|
|
1053
|
+
type: `tool-${options.toolName}`,
|
|
1054
|
+
toolCallId: options.toolCallId,
|
|
1055
|
+
state: options.state,
|
|
1056
|
+
input: anyOptions.input,
|
|
1057
|
+
output: anyOptions.output,
|
|
1058
|
+
rawInput: anyOptions.rawInput,
|
|
1059
|
+
errorText: anyOptions.errorText,
|
|
1060
|
+
providerExecuted: anyOptions.providerExecuted,
|
|
1061
|
+
preliminary: anyOptions.preliminary,
|
|
1062
|
+
...anyOptions.providerMetadata != null ? { callProviderMetadata: anyOptions.providerMetadata } : {}
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
async updateMessageMetadata(message, metadata) {
|
|
1066
|
+
if (metadata != null) message.metadata = message.metadata != null ? {
|
|
1067
|
+
...message.metadata,
|
|
1068
|
+
...metadata
|
|
1069
|
+
} : metadata;
|
|
1070
|
+
}
|
|
692
1071
|
async _reply(id, response, excludeBroadcastIds = [], options = {}) {
|
|
693
1072
|
const { continuation = false } = options;
|
|
694
1073
|
return this._tryCatchChat(async () => {
|
|
@@ -703,7 +1082,6 @@ var AIChatAgent = class extends Agent {
|
|
|
703
1082
|
return;
|
|
704
1083
|
}
|
|
705
1084
|
const streamId = this._startStream(id);
|
|
706
|
-
const { getToolName: getToolName$1, isToolUIPart: isToolUIPart$1, parsePartialJson } = await import("ai");
|
|
707
1085
|
const reader = response.body.getReader();
|
|
708
1086
|
const message = {
|
|
709
1087
|
id: `assistant_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`,
|
|
@@ -714,358 +1092,13 @@ var AIChatAgent = class extends Agent {
|
|
|
714
1092
|
this._streamCompletionPromise = new Promise((resolve) => {
|
|
715
1093
|
this._streamCompletionResolve = resolve;
|
|
716
1094
|
});
|
|
717
|
-
let activeTextParts = {};
|
|
718
|
-
let activeReasoningParts = {};
|
|
719
|
-
const partialToolCalls = {};
|
|
720
|
-
function updateDynamicToolPart(options$1) {
|
|
721
|
-
const part = message.parts.find((part$1) => part$1.type === "dynamic-tool" && part$1.toolCallId === options$1.toolCallId);
|
|
722
|
-
const anyOptions = options$1;
|
|
723
|
-
const anyPart = part;
|
|
724
|
-
if (part != null) {
|
|
725
|
-
part.state = options$1.state;
|
|
726
|
-
anyPart.toolName = options$1.toolName;
|
|
727
|
-
anyPart.input = anyOptions.input;
|
|
728
|
-
anyPart.output = anyOptions.output;
|
|
729
|
-
anyPart.errorText = anyOptions.errorText;
|
|
730
|
-
anyPart.rawInput = anyOptions.rawInput ?? anyPart.rawInput;
|
|
731
|
-
anyPart.preliminary = anyOptions.preliminary;
|
|
732
|
-
if (anyOptions.providerMetadata != null && part.state === "input-available") part.callProviderMetadata = anyOptions.providerMetadata;
|
|
733
|
-
} else message.parts.push({
|
|
734
|
-
type: "dynamic-tool",
|
|
735
|
-
toolName: options$1.toolName,
|
|
736
|
-
toolCallId: options$1.toolCallId,
|
|
737
|
-
state: options$1.state,
|
|
738
|
-
input: anyOptions.input,
|
|
739
|
-
output: anyOptions.output,
|
|
740
|
-
errorText: anyOptions.errorText,
|
|
741
|
-
preliminary: anyOptions.preliminary,
|
|
742
|
-
...anyOptions.providerMetadata != null ? { callProviderMetadata: anyOptions.providerMetadata } : {}
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
function updateToolPart(options$1) {
|
|
746
|
-
const part = message.parts.find((part$1) => isToolUIPart$1(part$1) && part$1.toolCallId === options$1.toolCallId);
|
|
747
|
-
const anyOptions = options$1;
|
|
748
|
-
const anyPart = part;
|
|
749
|
-
if (part != null) {
|
|
750
|
-
part.state = options$1.state;
|
|
751
|
-
anyPart.input = anyOptions.input;
|
|
752
|
-
anyPart.output = anyOptions.output;
|
|
753
|
-
anyPart.errorText = anyOptions.errorText;
|
|
754
|
-
anyPart.rawInput = anyOptions.rawInput;
|
|
755
|
-
anyPart.preliminary = anyOptions.preliminary;
|
|
756
|
-
anyPart.providerExecuted = anyOptions.providerExecuted ?? part.providerExecuted;
|
|
757
|
-
if (anyOptions.providerMetadata != null && part.state === "input-available") part.callProviderMetadata = anyOptions.providerMetadata;
|
|
758
|
-
} else message.parts.push({
|
|
759
|
-
type: `tool-${options$1.toolName}`,
|
|
760
|
-
toolCallId: options$1.toolCallId,
|
|
761
|
-
state: options$1.state,
|
|
762
|
-
input: anyOptions.input,
|
|
763
|
-
output: anyOptions.output,
|
|
764
|
-
rawInput: anyOptions.rawInput,
|
|
765
|
-
errorText: anyOptions.errorText,
|
|
766
|
-
providerExecuted: anyOptions.providerExecuted,
|
|
767
|
-
preliminary: anyOptions.preliminary,
|
|
768
|
-
...anyOptions.providerMetadata != null ? { callProviderMetadata: anyOptions.providerMetadata } : {}
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
async function updateMessageMetadata(metadata) {
|
|
772
|
-
if (metadata != null) message.metadata = message.metadata != null ? {
|
|
773
|
-
...message.metadata,
|
|
774
|
-
...metadata
|
|
775
|
-
} : metadata;
|
|
776
|
-
}
|
|
777
1095
|
const isSSE = (response.headers.get("content-type") || "").includes("text/event-stream");
|
|
778
|
-
|
|
779
|
-
type: "text-start",
|
|
780
|
-
id
|
|
781
|
-
}, continuation);
|
|
782
|
-
let streamCompleted = false;
|
|
1096
|
+
const streamCompleted = { value: false };
|
|
783
1097
|
try {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (done) {
|
|
787
|
-
if (!isSSE) this._broadcastTextEvent(streamId, {
|
|
788
|
-
type: "text-end",
|
|
789
|
-
id
|
|
790
|
-
}, continuation);
|
|
791
|
-
this._completeStream(streamId);
|
|
792
|
-
streamCompleted = true;
|
|
793
|
-
this._broadcastChatMessage({
|
|
794
|
-
body: "",
|
|
795
|
-
done: true,
|
|
796
|
-
id,
|
|
797
|
-
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,
|
|
798
|
-
...continuation && { continuation: true }
|
|
799
|
-
});
|
|
800
|
-
break;
|
|
801
|
-
}
|
|
802
|
-
const chunk = decoder.decode(value);
|
|
803
|
-
if (isSSE) {
|
|
804
|
-
const lines = chunk.split("\n");
|
|
805
|
-
for (const line of lines) if (line.startsWith("data: ") && line !== "data: [DONE]") try {
|
|
806
|
-
const data = JSON.parse(line.slice(6));
|
|
807
|
-
switch (data.type) {
|
|
808
|
-
case "text-start": {
|
|
809
|
-
const textPart = {
|
|
810
|
-
type: "text",
|
|
811
|
-
text: "",
|
|
812
|
-
providerMetadata: data.providerMetadata,
|
|
813
|
-
state: "streaming"
|
|
814
|
-
};
|
|
815
|
-
activeTextParts[data.id] = textPart;
|
|
816
|
-
message.parts.push(textPart);
|
|
817
|
-
break;
|
|
818
|
-
}
|
|
819
|
-
case "text-delta": {
|
|
820
|
-
const textPart = activeTextParts[data.id];
|
|
821
|
-
textPart.text += data.delta;
|
|
822
|
-
textPart.providerMetadata = data.providerMetadata ?? textPart.providerMetadata;
|
|
823
|
-
break;
|
|
824
|
-
}
|
|
825
|
-
case "text-end": {
|
|
826
|
-
const textPart = activeTextParts[data.id];
|
|
827
|
-
textPart.state = "done";
|
|
828
|
-
textPart.providerMetadata = data.providerMetadata ?? textPart.providerMetadata;
|
|
829
|
-
delete activeTextParts[data.id];
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
case "reasoning-start": {
|
|
833
|
-
const reasoningPart = {
|
|
834
|
-
type: "reasoning",
|
|
835
|
-
text: "",
|
|
836
|
-
providerMetadata: data.providerMetadata,
|
|
837
|
-
state: "streaming"
|
|
838
|
-
};
|
|
839
|
-
activeReasoningParts[data.id] = reasoningPart;
|
|
840
|
-
message.parts.push(reasoningPart);
|
|
841
|
-
break;
|
|
842
|
-
}
|
|
843
|
-
case "reasoning-delta": {
|
|
844
|
-
const reasoningPart = activeReasoningParts[data.id];
|
|
845
|
-
reasoningPart.text += data.delta;
|
|
846
|
-
reasoningPart.providerMetadata = data.providerMetadata ?? reasoningPart.providerMetadata;
|
|
847
|
-
break;
|
|
848
|
-
}
|
|
849
|
-
case "reasoning-end": {
|
|
850
|
-
const reasoningPart = activeReasoningParts[data.id];
|
|
851
|
-
reasoningPart.providerMetadata = data.providerMetadata ?? reasoningPart.providerMetadata;
|
|
852
|
-
reasoningPart.state = "done";
|
|
853
|
-
delete activeReasoningParts[data.id];
|
|
854
|
-
break;
|
|
855
|
-
}
|
|
856
|
-
case "file":
|
|
857
|
-
message.parts.push({
|
|
858
|
-
type: "file",
|
|
859
|
-
mediaType: data.mediaType,
|
|
860
|
-
url: data.url
|
|
861
|
-
});
|
|
862
|
-
break;
|
|
863
|
-
case "source-url":
|
|
864
|
-
message.parts.push({
|
|
865
|
-
type: "source-url",
|
|
866
|
-
sourceId: data.sourceId,
|
|
867
|
-
url: data.url,
|
|
868
|
-
title: data.title,
|
|
869
|
-
providerMetadata: data.providerMetadata
|
|
870
|
-
});
|
|
871
|
-
break;
|
|
872
|
-
case "source-document":
|
|
873
|
-
message.parts.push({
|
|
874
|
-
type: "source-document",
|
|
875
|
-
sourceId: data.sourceId,
|
|
876
|
-
mediaType: data.mediaType,
|
|
877
|
-
title: data.title,
|
|
878
|
-
filename: data.filename,
|
|
879
|
-
providerMetadata: data.providerMetadata
|
|
880
|
-
});
|
|
881
|
-
break;
|
|
882
|
-
case "tool-input-start": {
|
|
883
|
-
const toolInvocations = message.parts.filter(isToolUIPart$1);
|
|
884
|
-
partialToolCalls[data.toolCallId] = {
|
|
885
|
-
text: "",
|
|
886
|
-
toolName: data.toolName,
|
|
887
|
-
index: toolInvocations.length,
|
|
888
|
-
dynamic: data.dynamic
|
|
889
|
-
};
|
|
890
|
-
if (data.dynamic) updateDynamicToolPart({
|
|
891
|
-
toolCallId: data.toolCallId,
|
|
892
|
-
toolName: data.toolName,
|
|
893
|
-
state: "input-streaming",
|
|
894
|
-
input: void 0
|
|
895
|
-
});
|
|
896
|
-
else updateToolPart({
|
|
897
|
-
toolCallId: data.toolCallId,
|
|
898
|
-
toolName: data.toolName,
|
|
899
|
-
state: "input-streaming",
|
|
900
|
-
input: void 0
|
|
901
|
-
});
|
|
902
|
-
break;
|
|
903
|
-
}
|
|
904
|
-
case "tool-input-delta": {
|
|
905
|
-
const partialToolCall = partialToolCalls[data.toolCallId];
|
|
906
|
-
partialToolCall.text += data.inputTextDelta;
|
|
907
|
-
const partialArgs = (await parsePartialJson(partialToolCall.text)).value;
|
|
908
|
-
if (partialToolCall.dynamic) updateDynamicToolPart({
|
|
909
|
-
toolCallId: data.toolCallId,
|
|
910
|
-
toolName: partialToolCall.toolName,
|
|
911
|
-
state: "input-streaming",
|
|
912
|
-
input: partialArgs
|
|
913
|
-
});
|
|
914
|
-
else updateToolPart({
|
|
915
|
-
toolCallId: data.toolCallId,
|
|
916
|
-
toolName: partialToolCall.toolName,
|
|
917
|
-
state: "input-streaming",
|
|
918
|
-
input: partialArgs
|
|
919
|
-
});
|
|
920
|
-
break;
|
|
921
|
-
}
|
|
922
|
-
case "tool-input-available":
|
|
923
|
-
if (data.dynamic) updateDynamicToolPart({
|
|
924
|
-
toolCallId: data.toolCallId,
|
|
925
|
-
toolName: data.toolName,
|
|
926
|
-
state: "input-available",
|
|
927
|
-
input: data.input,
|
|
928
|
-
providerMetadata: data.providerMetadata
|
|
929
|
-
});
|
|
930
|
-
else updateToolPart({
|
|
931
|
-
toolCallId: data.toolCallId,
|
|
932
|
-
toolName: data.toolName,
|
|
933
|
-
state: "input-available",
|
|
934
|
-
input: data.input,
|
|
935
|
-
providerExecuted: data.providerExecuted,
|
|
936
|
-
providerMetadata: data.providerMetadata
|
|
937
|
-
});
|
|
938
|
-
break;
|
|
939
|
-
case "tool-input-error":
|
|
940
|
-
if (data.dynamic) updateDynamicToolPart({
|
|
941
|
-
toolCallId: data.toolCallId,
|
|
942
|
-
toolName: data.toolName,
|
|
943
|
-
state: "output-error",
|
|
944
|
-
input: data.input,
|
|
945
|
-
errorText: data.errorText,
|
|
946
|
-
providerMetadata: data.providerMetadata
|
|
947
|
-
});
|
|
948
|
-
else updateToolPart({
|
|
949
|
-
toolCallId: data.toolCallId,
|
|
950
|
-
toolName: data.toolName,
|
|
951
|
-
state: "output-error",
|
|
952
|
-
input: void 0,
|
|
953
|
-
rawInput: data.input,
|
|
954
|
-
errorText: data.errorText,
|
|
955
|
-
providerExecuted: data.providerExecuted,
|
|
956
|
-
providerMetadata: data.providerMetadata
|
|
957
|
-
});
|
|
958
|
-
break;
|
|
959
|
-
case "tool-output-available":
|
|
960
|
-
if (data.dynamic) {
|
|
961
|
-
const toolInvocation = message.parts.filter((part) => part.type === "dynamic-tool").find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
962
|
-
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
963
|
-
updateDynamicToolPart({
|
|
964
|
-
toolCallId: data.toolCallId,
|
|
965
|
-
toolName: toolInvocation.toolName,
|
|
966
|
-
state: "output-available",
|
|
967
|
-
input: toolInvocation.input,
|
|
968
|
-
output: data.output,
|
|
969
|
-
preliminary: data.preliminary
|
|
970
|
-
});
|
|
971
|
-
} else {
|
|
972
|
-
const toolInvocation = message.parts.filter(isToolUIPart$1).find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
973
|
-
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
974
|
-
updateToolPart({
|
|
975
|
-
toolCallId: data.toolCallId,
|
|
976
|
-
toolName: getToolName$1(toolInvocation),
|
|
977
|
-
state: "output-available",
|
|
978
|
-
input: toolInvocation.input,
|
|
979
|
-
output: data.output,
|
|
980
|
-
providerExecuted: data.providerExecuted,
|
|
981
|
-
preliminary: data.preliminary
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
break;
|
|
985
|
-
case "tool-output-error":
|
|
986
|
-
if (data.dynamic) {
|
|
987
|
-
const toolInvocation = message.parts.filter((part) => part.type === "dynamic-tool").find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
988
|
-
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
989
|
-
updateDynamicToolPart({
|
|
990
|
-
toolCallId: data.toolCallId,
|
|
991
|
-
toolName: toolInvocation.toolName,
|
|
992
|
-
state: "output-error",
|
|
993
|
-
input: toolInvocation.input,
|
|
994
|
-
errorText: data.errorText
|
|
995
|
-
});
|
|
996
|
-
} else {
|
|
997
|
-
const toolInvocation = message.parts.filter(isToolUIPart$1).find((invocation) => invocation.toolCallId === data.toolCallId);
|
|
998
|
-
if (!toolInvocation) throw new Error("Tool invocation not found");
|
|
999
|
-
updateToolPart({
|
|
1000
|
-
toolCallId: data.toolCallId,
|
|
1001
|
-
toolName: getToolName$1(toolInvocation),
|
|
1002
|
-
state: "output-error",
|
|
1003
|
-
input: toolInvocation.input,
|
|
1004
|
-
rawInput: "rawInput" in toolInvocation ? toolInvocation.rawInput : void 0,
|
|
1005
|
-
errorText: data.errorText
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
break;
|
|
1009
|
-
case "start-step":
|
|
1010
|
-
message.parts.push({ type: "step-start" });
|
|
1011
|
-
break;
|
|
1012
|
-
case "finish-step":
|
|
1013
|
-
activeTextParts = {};
|
|
1014
|
-
activeReasoningParts = {};
|
|
1015
|
-
break;
|
|
1016
|
-
case "start":
|
|
1017
|
-
if (data.messageId != null) message.id = data.messageId;
|
|
1018
|
-
await updateMessageMetadata(data.messageMetadata);
|
|
1019
|
-
break;
|
|
1020
|
-
case "finish":
|
|
1021
|
-
await updateMessageMetadata(data.messageMetadata);
|
|
1022
|
-
break;
|
|
1023
|
-
case "message-metadata":
|
|
1024
|
-
await updateMessageMetadata(data.messageMetadata);
|
|
1025
|
-
break;
|
|
1026
|
-
case "error":
|
|
1027
|
-
this._broadcastChatMessage({
|
|
1028
|
-
error: true,
|
|
1029
|
-
body: data.errorText ?? JSON.stringify(data),
|
|
1030
|
-
done: false,
|
|
1031
|
-
id,
|
|
1032
|
-
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE
|
|
1033
|
-
});
|
|
1034
|
-
break;
|
|
1035
|
-
}
|
|
1036
|
-
let eventToSend = data;
|
|
1037
|
-
if (data.type === "finish" && "finishReason" in data) {
|
|
1038
|
-
const { finishReason, ...rest } = data;
|
|
1039
|
-
eventToSend = {
|
|
1040
|
-
...rest,
|
|
1041
|
-
type: "finish",
|
|
1042
|
-
messageMetadata: { finishReason }
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
const chunkBody = JSON.stringify(eventToSend);
|
|
1046
|
-
this._storeStreamChunk(streamId, chunkBody);
|
|
1047
|
-
this._broadcastChatMessage({
|
|
1048
|
-
body: chunkBody,
|
|
1049
|
-
done: false,
|
|
1050
|
-
id,
|
|
1051
|
-
type: MessageType.CF_AGENT_USE_CHAT_RESPONSE,
|
|
1052
|
-
...continuation && { continuation: true }
|
|
1053
|
-
});
|
|
1054
|
-
} catch (_error) {}
|
|
1055
|
-
} else if (chunk.length > 0) {
|
|
1056
|
-
message.parts.push({
|
|
1057
|
-
type: "text",
|
|
1058
|
-
text: chunk
|
|
1059
|
-
});
|
|
1060
|
-
this._broadcastTextEvent(streamId, {
|
|
1061
|
-
type: "text-delta",
|
|
1062
|
-
id,
|
|
1063
|
-
delta: chunk
|
|
1064
|
-
}, continuation);
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1098
|
+
if (isSSE) await this._streamSSEReply(id, streamId, reader, message, streamCompleted, continuation);
|
|
1099
|
+
else await this._sendPlaintextReply(id, streamId, reader, message, streamCompleted, continuation);
|
|
1067
1100
|
} catch (error) {
|
|
1068
|
-
if (!streamCompleted) {
|
|
1101
|
+
if (!streamCompleted.value) {
|
|
1069
1102
|
this._markStreamError(streamId);
|
|
1070
1103
|
this._broadcastChatMessage({
|
|
1071
1104
|
body: error instanceof Error ? error.message : "Stream error",
|