163 lines
4.4 KiB
TypeScript
163 lines
4.4 KiB
TypeScript
import { readDataStream } from './read-data-stream';
|
|
import type { FunctionCall, JSONValue, Message, ToolCall } from './types';
|
|
import { generateId as generateIdFunction } from './generate-id';
|
|
|
|
type PrefixMap = {
|
|
text?: Message;
|
|
function_call?: Message & {
|
|
role: 'assistant';
|
|
function_call: FunctionCall;
|
|
};
|
|
tool_calls?: Message & {
|
|
role: 'assistant';
|
|
tool_calls: ToolCall[];
|
|
};
|
|
data: JSONValue[];
|
|
};
|
|
|
|
function assignAnnotationsToMessage<T extends Message | null | undefined>(
|
|
message: T,
|
|
annotations: JSONValue[] | undefined,
|
|
): T {
|
|
if (!message || !annotations || !annotations.length) return message;
|
|
return { ...message, annotations: [...annotations] } as T;
|
|
}
|
|
|
|
export async function parseComplexResponse({
|
|
reader,
|
|
abortControllerRef,
|
|
update,
|
|
onFinish,
|
|
generateId = generateIdFunction,
|
|
getCurrentDate = () => new Date(),
|
|
}: {
|
|
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
abortControllerRef?: {
|
|
current: AbortController | null;
|
|
};
|
|
update: (merged: Message[], data: JSONValue[] | undefined) => void;
|
|
onFinish?: (prefixMap: PrefixMap) => void;
|
|
generateId?: () => string;
|
|
getCurrentDate?: () => Date;
|
|
}) {
|
|
const createdAt = getCurrentDate();
|
|
const prefixMap: PrefixMap = {
|
|
data: [],
|
|
};
|
|
|
|
// keep list of current message annotations for message
|
|
let message_annotations: JSONValue[] | undefined = undefined;
|
|
|
|
// we create a map of each prefix, and for each prefixed message we push to the map
|
|
for await (const { type, value } of readDataStream(reader, {
|
|
isAborted: () => abortControllerRef?.current === null,
|
|
})) {
|
|
if (type === 'text') {
|
|
if (prefixMap['text']) {
|
|
prefixMap['text'] = {
|
|
...prefixMap['text'],
|
|
content: (prefixMap['text'].content || '') + value,
|
|
};
|
|
} else {
|
|
prefixMap['text'] = {
|
|
id: generateId(),
|
|
role: 'assistant',
|
|
content: value,
|
|
createdAt,
|
|
};
|
|
}
|
|
}
|
|
|
|
let functionCallMessage: Message | null | undefined = null;
|
|
|
|
if (type === 'function_call') {
|
|
prefixMap['function_call'] = {
|
|
id: generateId(),
|
|
role: 'assistant',
|
|
content: '',
|
|
function_call: value.function_call,
|
|
name: value.function_call.name,
|
|
createdAt,
|
|
};
|
|
|
|
functionCallMessage = prefixMap['function_call'];
|
|
}
|
|
|
|
let toolCallMessage: Message | null | undefined = null;
|
|
|
|
if (type === 'tool_calls') {
|
|
prefixMap['tool_calls'] = {
|
|
id: generateId(),
|
|
role: 'assistant',
|
|
content: '',
|
|
tool_calls: value.tool_calls,
|
|
createdAt,
|
|
};
|
|
|
|
toolCallMessage = prefixMap['tool_calls'];
|
|
}
|
|
|
|
if (type === 'data') {
|
|
prefixMap['data'].push(...value);
|
|
}
|
|
|
|
let responseMessage = prefixMap['text'];
|
|
|
|
if (type === 'message_annotations') {
|
|
if (!message_annotations) {
|
|
message_annotations = [...value];
|
|
} else {
|
|
message_annotations.push(...value);
|
|
}
|
|
|
|
// Update any existing message with the latest annotations
|
|
functionCallMessage = assignAnnotationsToMessage(
|
|
prefixMap['function_call'],
|
|
message_annotations,
|
|
);
|
|
toolCallMessage = assignAnnotationsToMessage(
|
|
prefixMap['tool_calls'],
|
|
message_annotations,
|
|
);
|
|
responseMessage = assignAnnotationsToMessage(
|
|
prefixMap['text'],
|
|
message_annotations,
|
|
);
|
|
}
|
|
|
|
// keeps the prefixMap up to date with the latest annotations, even if annotations preceded the message
|
|
if (message_annotations?.length) {
|
|
const messagePrefixKeys: (keyof PrefixMap)[] = [
|
|
'text',
|
|
'function_call',
|
|
'tool_calls',
|
|
];
|
|
messagePrefixKeys.forEach(key => {
|
|
if (prefixMap[key]) {
|
|
(prefixMap[key] as Message).annotations = [...message_annotations!];
|
|
}
|
|
});
|
|
}
|
|
|
|
// We add function & tool calls and response messages to the messages[], but data is its own thing
|
|
const merged = [functionCallMessage, toolCallMessage, responseMessage]
|
|
.filter(Boolean)
|
|
.map(message => ({
|
|
...assignAnnotationsToMessage(message, message_annotations),
|
|
})) as Message[];
|
|
|
|
update(merged, [...prefixMap['data']]); // make a copy of the data array
|
|
}
|
|
|
|
onFinish?.(prefixMap);
|
|
|
|
return {
|
|
messages: [
|
|
prefixMap.text,
|
|
prefixMap.function_call,
|
|
prefixMap.tool_calls,
|
|
].filter(Boolean) as Message[],
|
|
data: prefixMap.data,
|
|
};
|
|
}
|