forked from CopilotKit/CopilotKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse-chat-completion.ts
More file actions
112 lines (96 loc) · 2.79 KB
/
Copy pathparse-chat-completion.ts
File metadata and controls
112 lines (96 loc) · 2.79 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
import { Role } from "../types/openai-assistant";
export interface ToolCallFunctionCall {
arguments?: string;
name?: string;
// TODO:
// Temporarily add scope to the OpenAI protocol until we
// have our own protocol.
// When scope is "server", the client will not attempt to
// execute the function.
scope?: "client" | "server";
}
export interface ToolCallPayload {
index: number;
id?: string;
function: ToolCallFunctionCall;
}
export interface ChatCompletionChunk {
choices: {
delta: {
id?: string;
role: Role;
content?: string | null;
// TODO:
// Temporarily add name to the OpenAI protocol until we
// have our own protocol.
// When name is set, we return the result of a server-side
// function call.
name?: string;
function_call?: {
name?: string;
arguments?: string;
};
tool_calls?: ToolCallPayload[];
};
}[];
}
// TODO:
// it's possible that unicode characters could be split across chunks
// make sure to properly handle that
export function parseChatCompletion(
stream: ReadableStream<Uint8Array>,
): ReadableStream<ChatCompletionChunk> {
const reader = stream.getReader();
let buffer = new Uint8Array();
async function cleanup(controller?: ReadableStreamDefaultController<any>) {
if (controller) {
try {
controller.close();
} catch (_) {}
}
if (reader) {
try {
await reader.cancel();
} catch (_) {}
}
}
return new ReadableStream<ChatCompletionChunk>({
async pull(controller) {
while (true) {
try {
const { done, value } = await reader.read();
if (done) {
await cleanup(controller);
return;
}
const newBuffer = new Uint8Array(buffer.length + value.length);
newBuffer.set(buffer);
newBuffer.set(value, buffer.length);
buffer = newBuffer;
const valueString = new TextDecoder("utf-8").decode(buffer);
const lines = valueString.split("\n").filter((line) => line.trim() !== "");
// If the last line isn't complete, keep it in the buffer for next time
buffer = !valueString.endsWith("\n")
? new TextEncoder().encode(lines.pop() || "")
: new Uint8Array();
for (const line of lines) {
const cleanedLine = line.replace(/^data: /, "");
if (cleanedLine === "[DONE]") {
await cleanup(controller);
return;
}
const json = JSON.parse(cleanedLine);
controller.enqueue(json);
}
} catch (error) {
controller.error(error);
await cleanup(controller);
return;
}
}
},
cancel() {
reader.cancel();
},
});
}