forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecode-chat-completion.ts
More file actions
127 lines (113 loc) · 3.39 KB
/
Copy pathdecode-chat-completion.ts
File metadata and controls
127 lines (113 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { ChatCompletionChunk } from "./parse-chat-completion";
export interface ChatCompletionContentEvent {
type: "content";
content: string;
}
export interface ChatCompletionPartialEvent {
type: "partial";
name: string;
arguments: string;
}
export interface ChatCompletionFunctionEvent {
type: "function";
name: string;
arguments: any;
}
export type ChatCompletionEvent =
| ChatCompletionContentEvent
| ChatCompletionPartialEvent
| ChatCompletionFunctionEvent;
export function decodeChatCompletion(
stream: ReadableStream<ChatCompletionChunk>,
): ReadableStream<ChatCompletionEvent> {
const reader = stream.getReader();
let mode: "function" | "message" | null = null;
let functionCallName: string = "";
let functionCallArguments: string = "";
async function cleanup(controller?: ReadableStreamDefaultController<any>) {
if (controller) {
try {
controller.close();
} catch (_) {}
}
if (reader) {
try {
await reader.cancel();
} catch (_) {}
}
}
return new ReadableStream<ChatCompletionEvent>({
async pull(controller) {
const flushFunctionCall = (): boolean => {
let args: any = null;
try {
args = JSON.parse(functionCallArguments);
} catch (error) {
cleanup(controller);
controller.error(error);
return false;
}
controller.enqueue({
type: "function",
name: functionCallName,
arguments: args,
});
mode = null;
functionCallName = "";
functionCallArguments = "";
return true;
};
while (true) {
try {
const { done, value } = await reader.read();
if (done) {
if (mode === "function") {
flushFunctionCall();
}
await cleanup(controller);
return;
}
// In case we are in a function call but the next message is not a function call, flush it.
if (mode === "function" && !value.choices[0].delta.function_call) {
if (!flushFunctionCall()) {
return;
}
}
mode = value.choices[0].delta.function_call ? "function" : "message";
// if we get a message, emit the content and continue;
if (mode === "message") {
if (value.choices[0].delta.content) {
controller.enqueue({
type: "content",
content: value.choices[0].delta.content,
});
}
continue;
}
// if we get a function call, buffer the name and arguments, then emit a partial event.
else if (mode === "function") {
if (value.choices[0].delta.function_call!.name) {
functionCallName = value.choices[0].delta.function_call!.name!;
}
if (value.choices[0].delta.function_call!.arguments) {
functionCallArguments += value.choices[0].delta.function_call!.arguments!;
}
controller.enqueue({
type: "partial",
name: functionCallName,
arguments: functionCallArguments,
});
continue;
}
} catch (error) {
controller.error(error);
await cleanup(controller);
return;
}
}
},
cancel() {
reader.cancel();
},
});
}