@absolutejs/ai 0.0.1
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/README.md +6 -0
- package/dist/ai/client/index.js +682 -0
- package/dist/ai/client/index.js.map +15 -0
- package/dist/ai/index.js +3317 -0
- package/dist/ai/index.js.map +28 -0
- package/dist/ai/providers/anthropic.js +371 -0
- package/dist/ai/providers/anthropic.js.map +10 -0
- package/dist/ai/providers/gemini.js +260 -0
- package/dist/ai/providers/gemini.js.map +10 -0
- package/dist/ai/providers/ollama.js +221 -0
- package/dist/ai/providers/ollama.js.map +10 -0
- package/dist/ai/providers/openai.js +322 -0
- package/dist/ai/providers/openai.js.map +10 -0
- package/dist/ai/providers/openaiCompatible.js +360 -0
- package/dist/ai/providers/openaiCompatible.js.map +11 -0
- package/dist/ai/providers/openaiResponses.js +381 -0
- package/dist/ai/providers/openaiResponses.js.map +10 -0
- package/dist/src/ai/client/actions.d.ts +186 -0
- package/dist/src/ai/client/connection.d.ts +9 -0
- package/dist/src/ai/client/createAIStream.d.ts +12 -0
- package/dist/src/ai/client/index.d.ts +4 -0
- package/dist/src/ai/client/messageStore.d.ts +12 -0
- package/dist/src/ai/conversationManager.d.ts +19 -0
- package/dist/src/ai/htmxRenderers.d.ts +3 -0
- package/dist/src/ai/index.d.ts +19 -0
- package/dist/src/ai/memoryStore.d.ts +2 -0
- package/dist/src/ai/protocol.d.ts +4 -0
- package/dist/src/ai/providers/anthropic.d.ts +3 -0
- package/dist/src/ai/providers/gemini.d.ts +8 -0
- package/dist/src/ai/providers/ollama.d.ts +6 -0
- package/dist/src/ai/providers/openai.d.ts +7 -0
- package/dist/src/ai/providers/openaiCompatible.d.ts +30 -0
- package/dist/src/ai/providers/openaiResponses.d.ts +33 -0
- package/dist/src/ai/streamAI.d.ts +2 -0
- package/dist/src/ai/streamAIToSSE.d.ts +6 -0
- package/dist/src/constants.d.ts +60 -0
- package/dist/src/plugins/aiChat.d.ts +239 -0
- package/dist/types/ai.d.ts +4773 -0
- package/dist/types/anthropic.d.ts +20 -0
- package/dist/types/session.d.ts +16 -0
- package/dist/types/typeGuards.d.ts +3 -0
- package/package.json +59 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/ai/providers/gemini.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type {\n\tAIProviderConfig,\n\tAIProviderContentBlock,\n\tAIProviderMessage,\n\tAIProviderStreamParams,\n\tAIProviderToolDefinition,\n\tAIUsage\n} from '../../../types/ai';\n\ntype GeminiConfig = {\n\tapiKey: string;\n\tbaseUrl?: string;\n\timageModels?: Set<string> | string[];\n};\n\ntype StreamState = {\n\tbuffer: string;\n\tusage: AIUsage | undefined;\n};\n\nconst DEFAULT_BASE_URL = 'https://generativelanguage.googleapis.com';\nconst SSE_DATA_PREFIX_LENGTH = 6;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n\ttypeof value === 'object' && value !== null;\n\nconst isRecordArray = (\n\tvalue: unknown\n): value is Array<Record<string, unknown>> =>\n\tArray.isArray(value) && value.length > 0 && isRecord(value[0]);\n\n/* ─── Message conversion ─── */\n\nconst mapRole = (role: string) => {\n\tif (role === 'assistant' || role === 'system') return 'model';\n\n\treturn 'user';\n};\n\nconst mapContentBlock = (block: AIProviderContentBlock) => {\n\tswitch (block.type) {\n\t\tcase 'text':\n\t\t\treturn { text: block.content };\n\t\tcase 'image':\n\t\t\treturn {\n\t\t\t\tinlineData: {\n\t\t\t\t\tdata: block.source.data,\n\t\t\t\t\tmimeType: block.source.media_type\n\t\t\t\t}\n\t\t\t};\n\t\tcase 'document':\n\t\t\treturn {\n\t\t\t\tinlineData: {\n\t\t\t\t\tdata: block.source.data,\n\t\t\t\t\tmimeType: block.source.media_type\n\t\t\t\t}\n\t\t\t};\n\t\tcase 'tool_use':\n\t\t\treturn {\n\t\t\t\tfunctionCall: {\n\t\t\t\t\targs:\n\t\t\t\t\t\ttypeof block.input === 'string'\n\t\t\t\t\t\t\t? JSON.parse(block.input)\n\t\t\t\t\t\t\t: block.input,\n\t\t\t\t\tname: block.name\n\t\t\t\t}\n\t\t\t};\n\t\tcase 'tool_result':\n\t\t\treturn {\n\t\t\t\tfunctionResponse: {\n\t\t\t\t\tname: block.tool_use_id,\n\t\t\t\t\tresponse: { result: block.content }\n\t\t\t\t}\n\t\t\t};\n\t\tdefault:\n\t\t\treturn null;\n\t}\n};\n\nconst convertMessageContent = (content: string | AIProviderContentBlock[]) => {\n\tif (typeof content === 'string') {\n\t\treturn [{ text: content }];\n\t}\n\n\treturn content.map(mapContentBlock).filter((mapped) => mapped !== null);\n};\n\nconst hasFunctionResponse = (content: AIProviderContentBlock[]) =>\n\tcontent.some((block) => block.type === 'tool_result');\n\nconst convertSingleMessage = (msg: AIProviderMessage) => {\n\tif (typeof msg.content !== 'string' && hasFunctionResponse(msg.content)) {\n\t\treturn { parts: convertMessageContent(msg.content), role: 'user' };\n\t}\n\n\treturn {\n\t\tparts: convertMessageContent(msg.content),\n\t\trole: mapRole(msg.role)\n\t};\n};\n\nconst convertMessages = (messages: AIProviderMessage[]) =>\n\tmessages.map(convertSingleMessage);\n\nconst mapToolDefinitions = (tools: AIProviderToolDefinition[]) =>\n\ttools.map((tool) => ({\n\t\tdescription: tool.description,\n\t\tname: tool.name,\n\t\tparameters: tool.input_schema\n\t}));\n\nconst buildRequestBody = (\n\tparams: AIProviderStreamParams,\n\tisImageModel: boolean\n) => {\n\tconst body: Record<string, unknown> = {\n\t\tcontents: convertMessages(params.messages)\n\t};\n\n\tif (isImageModel) {\n\t\tbody.generationConfig = {\n\t\t\tresponseModalities: ['TEXT', 'IMAGE']\n\t\t};\n\t}\n\n\tif (params.systemPrompt) {\n\t\tbody.systemInstruction = {\n\t\t\tparts: [{ text: params.systemPrompt }]\n\t\t};\n\t}\n\n\tif (params.tools && params.tools.length > 0) {\n\t\tbody.tools = [\n\t\t\t{ functionDeclarations: mapToolDefinitions(params.tools) }\n\t\t];\n\t}\n\n\treturn body;\n};\n\n/* ─── SSE parsing ─── */\n\nconst extractMimeFormat = (mimeType: unknown) => {\n\tif (typeof mimeType !== 'string') {\n\t\treturn 'png';\n\t}\n\n\tif (mimeType.includes('jpeg') || mimeType.includes('jpg')) return 'jpeg';\n\tif (mimeType.includes('webp')) return 'webp';\n\n\treturn 'png';\n};\n\nconst processTextPart = function* (part: Record<string, unknown>) {\n\tif (typeof part.text === 'string' && part.text) {\n\t\tyield { content: part.text, type: 'text' as const };\n\t}\n};\n\nconst processInlineDataPart = function* (inlineData: Record<string, unknown>) {\n\tconst data = typeof inlineData.data === 'string' ? inlineData.data : '';\n\tconst { mimeType } = inlineData;\n\n\tif (!data) {\n\t\treturn;\n\t}\n\n\tyield {\n\t\tdata,\n\t\tformat: extractMimeFormat(mimeType),\n\t\tisPartial: false,\n\t\ttype: 'image' as const\n\t};\n};\n\nconst processFunctionCallPart = function* (\n\tfunctionCall: Record<string, unknown>\n) {\n\tconst name = typeof functionCall.name === 'string' ? functionCall.name : '';\n\tconst args = functionCall.args ?? {};\n\n\tyield {\n\t\tid: `gemini-${name}-${Date.now()}`,\n\t\tinput: args,\n\t\tname,\n\t\ttype: 'tool_use' as const\n\t};\n};\n\nconst processPart = function* (part: Record<string, unknown>) {\n\tif ('text' in part) {\n\t\tyield* processTextPart(part);\n\t}\n\n\tif (isRecord(part.inlineData)) {\n\t\tyield* processInlineDataPart(part.inlineData);\n\t}\n\n\tif (isRecord(part.functionCall)) {\n\t\tyield* processFunctionCallPart(part.functionCall);\n\t}\n};\n\nconst extractUsage = (parsed: Record<string, unknown>): AIUsage | undefined => {\n\tif (!isRecord(parsed.usageMetadata)) {\n\t\treturn undefined;\n\t}\n\n\tconst { usageMetadata } = parsed;\n\n\treturn {\n\t\tinputTokens:\n\t\t\ttypeof usageMetadata.promptTokenCount === 'number'\n\t\t\t\t? usageMetadata.promptTokenCount\n\t\t\t\t: 0,\n\t\toutputTokens:\n\t\t\ttypeof usageMetadata.candidatesTokenCount === 'number'\n\t\t\t\t? usageMetadata.candidatesTokenCount\n\t\t\t\t: 0\n\t};\n};\n\nconst processChunk = function* (\n\tparsed: Record<string, unknown>,\n\tstate: StreamState\n) {\n\tconst usage = extractUsage(parsed);\n\n\tif (usage) {\n\t\tstate.usage = usage;\n\t}\n\n\tif (!isRecordArray(parsed.candidates)) {\n\t\treturn;\n\t}\n\n\tconst [candidate] = parsed.candidates;\n\n\tif (!candidate || !isRecord(candidate.content)) {\n\t\treturn;\n\t}\n\n\tconst { content } = candidate;\n\n\tif (!isRecordArray(content.parts)) {\n\t\treturn;\n\t}\n\n\tfor (const part of content.parts) {\n\t\tyield* processPart(part);\n\t}\n};\n\nconst processSSELine = function* (line: string, state: StreamState) {\n\tconst trimmed = line.trim();\n\n\tif (!trimmed || !trimmed.startsWith('data: ')) {\n\t\treturn;\n\t}\n\n\tconst data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);\n\n\tlet parsed: Record<string, unknown>;\n\n\ttry {\n\t\tparsed = JSON.parse(data);\n\t} catch {\n\t\treturn;\n\t}\n\n\tyield* processChunk(parsed, state);\n};\n\nconst processSSEBuffer = function* (lines: string[], state: StreamState) {\n\tfor (const line of lines) {\n\t\tyield* processSSELine(line, state);\n\t}\n};\n\nconst drainReader = async function* (\n\treader: ReadableStreamDefaultReader<Uint8Array>,\n\tdecoder: TextDecoder,\n\tstate: StreamState,\n\tsignal?: AbortSignal\n) {\n\tlet textBuffer = '';\n\n\tfor (\n\t\tlet result = await reader.read();\n\t\t!result.done && !signal?.aborted;\n\t\t// eslint-disable-next-line no-await-in-loop\n\t\tresult = await reader.read()\n\t) {\n\t\ttextBuffer += decoder.decode(result.value, { stream: true });\n\t\tconst lines = textBuffer.split('\\n');\n\t\ttextBuffer = lines.pop() ?? '';\n\n\t\tyield* processSSEBuffer(lines, state);\n\t}\n\n\tif (textBuffer.trim()) {\n\t\tyield* processSSELine(textBuffer, state);\n\t}\n\n\tyield { type: 'done' as const, usage: state.usage };\n};\n\nconst parseSSEStream = async function* (\n\tbody: ReadableStream<Uint8Array>,\n\tsignal?: AbortSignal\n) {\n\tconst reader = body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst state: StreamState = {\n\t\tbuffer: '',\n\t\tusage: undefined\n\t};\n\n\ttry {\n\t\tyield* drainReader(reader, decoder, state, signal);\n\t} finally {\n\t\treader.releaseLock();\n\t}\n};\n\nconst fetchGeminiStream = async function* (\n\tbaseUrl: string,\n\tapiKey: string,\n\tmodel: string,\n\tbody: Record<string, unknown>,\n\tsignal?: AbortSignal\n) {\n\tconst url = `${baseUrl}/v1beta/models/${model}:streamGenerateContent?alt=sse&key=${apiKey}`;\n\n\tconst response = await fetch(url, {\n\t\tbody: JSON.stringify(body),\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json'\n\t\t},\n\t\tmethod: 'POST',\n\t\tsignal\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tthrow new Error(`Gemini API error ${response.status}: ${errorText}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error('Gemini API returned no response body');\n\t}\n\n\tyield* parseSSEStream(response.body, signal);\n};\n\nconst resolveImageModels = (raw: Set<string> | string[] | undefined) => {\n\tif (!raw) {\n\t\treturn new Set<string>();\n\t}\n\n\tif (raw instanceof Set) {\n\t\treturn raw;\n\t}\n\n\treturn new Set(raw);\n};\n\nexport const gemini = (config: GeminiConfig): AIProviderConfig => {\n\tconst baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n\tconst imageModels = resolveImageModels(config.imageModels);\n\n\treturn {\n\t\tstream: (params: AIProviderStreamParams) => {\n\t\t\tconst isImageModel = imageModels.has(params.model);\n\t\t\tconst body = buildRequestBody(params, isImageModel);\n\n\t\t\treturn fetchGeminiStream(\n\t\t\t\tbaseUrl,\n\t\t\t\tconfig.apiKey,\n\t\t\t\tparams.model,\n\t\t\t\tbody,\n\t\t\t\tparams.signal\n\t\t\t);\n\t\t}\n\t};\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "9DAEE9BA5DB0F89864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/ai/providers/ollama.ts
|
|
3
|
+
var DEFAULT_BASE_URL = "http://localhost:11434";
|
|
4
|
+
var ZERO_TOKENS = 0;
|
|
5
|
+
var DONE_CHUNK = { type: "done" };
|
|
6
|
+
var mapToolDefinitions = (tools) => tools.map((tool) => ({
|
|
7
|
+
function: {
|
|
8
|
+
description: tool.description,
|
|
9
|
+
name: tool.name,
|
|
10
|
+
parameters: tool.input_schema
|
|
11
|
+
},
|
|
12
|
+
type: "function"
|
|
13
|
+
}));
|
|
14
|
+
var convertMessage = (msg) => {
|
|
15
|
+
if (typeof msg.content === "string") {
|
|
16
|
+
return [{ content: msg.content, role: msg.role }];
|
|
17
|
+
}
|
|
18
|
+
const results = [];
|
|
19
|
+
const toolUseBlocks = msg.content.filter((block) => block.type === "tool_use");
|
|
20
|
+
const toolResultBlocks = msg.content.filter((block) => block.type === "tool_result");
|
|
21
|
+
if (toolUseBlocks.length > 0) {
|
|
22
|
+
results.push({
|
|
23
|
+
content: "",
|
|
24
|
+
role: "assistant",
|
|
25
|
+
tool_calls: toolUseBlocks.map((block) => ({
|
|
26
|
+
function: {
|
|
27
|
+
arguments: typeof block.input === "object" && block.input !== null ? { ...block.input } : {},
|
|
28
|
+
name: block.name
|
|
29
|
+
}
|
|
30
|
+
}))
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
for (const block of toolResultBlocks) {
|
|
34
|
+
results.push({
|
|
35
|
+
content: typeof block.content === "string" ? block.content : "",
|
|
36
|
+
role: "tool"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return results;
|
|
40
|
+
};
|
|
41
|
+
var buildRequestBody = (params) => {
|
|
42
|
+
const messages = params.messages.flatMap(convertMessage);
|
|
43
|
+
if (params.systemPrompt) {
|
|
44
|
+
messages.unshift({ content: params.systemPrompt, role: "system" });
|
|
45
|
+
}
|
|
46
|
+
const body = {
|
|
47
|
+
messages,
|
|
48
|
+
model: params.model,
|
|
49
|
+
stream: true
|
|
50
|
+
};
|
|
51
|
+
if (params.tools && params.tools.length > 0) {
|
|
52
|
+
body.tools = mapToolDefinitions(params.tools);
|
|
53
|
+
}
|
|
54
|
+
return body;
|
|
55
|
+
};
|
|
56
|
+
var isRecord = (val) => val !== null && typeof val === "object" && !Array.isArray(val);
|
|
57
|
+
var tryParseJSON = (text) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = JSON.parse(text);
|
|
60
|
+
if (isRecord(result)) {
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var buildDoneChunk = (parsed) => {
|
|
69
|
+
const promptEvalCount = typeof parsed.prompt_eval_count === "number" ? parsed.prompt_eval_count : undefined;
|
|
70
|
+
const evalCount = typeof parsed.eval_count === "number" ? parsed.eval_count : undefined;
|
|
71
|
+
const totalDuration = typeof parsed.total_duration === "number" ? parsed.total_duration : undefined;
|
|
72
|
+
const hasTokenCounts = promptEvalCount !== undefined || evalCount !== undefined;
|
|
73
|
+
if (hasTokenCounts) {
|
|
74
|
+
return {
|
|
75
|
+
type: "done",
|
|
76
|
+
usage: {
|
|
77
|
+
inputTokens: promptEvalCount ?? ZERO_TOKENS,
|
|
78
|
+
outputTokens: evalCount ?? ZERO_TOKENS
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (totalDuration !== undefined) {
|
|
83
|
+
return {
|
|
84
|
+
type: "done",
|
|
85
|
+
usage: { inputTokens: ZERO_TOKENS, outputTokens: ZERO_TOKENS }
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { type: "done" };
|
|
89
|
+
};
|
|
90
|
+
var buildTextChunk = (content) => ({
|
|
91
|
+
content,
|
|
92
|
+
type: "text"
|
|
93
|
+
});
|
|
94
|
+
var extractToolCalls = (message) => {
|
|
95
|
+
const toolCalls = message.tool_calls;
|
|
96
|
+
if (!Array.isArray(toolCalls) || toolCalls.length === 0) {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
return toolCalls.filter(isRecord).filter((call) => isRecord(call.function)).map((call) => {
|
|
100
|
+
const func = call.function;
|
|
101
|
+
if (!isRecord(func)) {
|
|
102
|
+
return {
|
|
103
|
+
id: crypto.randomUUID(),
|
|
104
|
+
input: {},
|
|
105
|
+
name: "",
|
|
106
|
+
type: "tool_use"
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
id: crypto.randomUUID(),
|
|
111
|
+
input: func.arguments ?? {},
|
|
112
|
+
name: typeof func.name === "string" ? func.name : "",
|
|
113
|
+
type: "tool_use"
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
var extractChunks = (parsed) => {
|
|
118
|
+
const { message } = parsed;
|
|
119
|
+
if (!isRecord(message)) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const toolChunks = extractToolCalls(message);
|
|
123
|
+
if (toolChunks.length > 0) {
|
|
124
|
+
return toolChunks;
|
|
125
|
+
}
|
|
126
|
+
const { content } = message;
|
|
127
|
+
if (typeof content !== "string" || !content) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
return [buildTextChunk(content)];
|
|
131
|
+
};
|
|
132
|
+
var processLine = (line) => {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (!trimmed) {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const parsed = tryParseJSON(trimmed);
|
|
138
|
+
if (!parsed) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
if (parsed.done === true) {
|
|
142
|
+
return [buildDoneChunk(parsed)];
|
|
143
|
+
}
|
|
144
|
+
return extractChunks(parsed);
|
|
145
|
+
};
|
|
146
|
+
var processBufferedLines = (lines) => lines.flatMap(processLine);
|
|
147
|
+
var readStreamChunks = async (reader, decoder, buffer, signal) => {
|
|
148
|
+
const emptyChunks = [];
|
|
149
|
+
if (signal?.aborted) {
|
|
150
|
+
return {
|
|
151
|
+
allChunks: emptyChunks,
|
|
152
|
+
currentBuffer: buffer,
|
|
153
|
+
finished: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const result = await reader.read();
|
|
157
|
+
const { done, value } = result;
|
|
158
|
+
if (done) {
|
|
159
|
+
return {
|
|
160
|
+
allChunks: emptyChunks,
|
|
161
|
+
currentBuffer: buffer,
|
|
162
|
+
finished: true
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const currentBuffer = buffer + decoder.decode(value, { stream: true });
|
|
166
|
+
const lines = currentBuffer.split(`
|
|
167
|
+
`);
|
|
168
|
+
const remainder = lines.pop() ?? "";
|
|
169
|
+
const allChunks = processBufferedLines(lines);
|
|
170
|
+
const finished = allChunks.some((c) => c.type === "done");
|
|
171
|
+
return { allChunks, currentBuffer: remainder, finished };
|
|
172
|
+
};
|
|
173
|
+
var parseNDJSONStream = async function* (body, signal) {
|
|
174
|
+
const reader = body.getReader();
|
|
175
|
+
try {
|
|
176
|
+
yield* parseNDJSONStreamInner(reader, signal);
|
|
177
|
+
} finally {
|
|
178
|
+
reader.releaseLock();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
var parseNDJSONStreamInner = async function* (reader, signal) {
|
|
182
|
+
const decoder = new TextDecoder;
|
|
183
|
+
let buffer = "";
|
|
184
|
+
let done = false;
|
|
185
|
+
while (!done) {
|
|
186
|
+
const result = await readStreamChunks(reader, decoder, buffer, signal);
|
|
187
|
+
buffer = result.currentBuffer;
|
|
188
|
+
done = result.finished;
|
|
189
|
+
yield* result.allChunks;
|
|
190
|
+
}
|
|
191
|
+
yield DONE_CHUNK;
|
|
192
|
+
};
|
|
193
|
+
var fetchAndStream = async function* (baseUrl, params) {
|
|
194
|
+
const requestBody = buildRequestBody(params);
|
|
195
|
+
const response = await fetch(`${baseUrl}/api/chat`, {
|
|
196
|
+
body: JSON.stringify(requestBody),
|
|
197
|
+
headers: { "Content-Type": "application/json" },
|
|
198
|
+
method: "POST",
|
|
199
|
+
signal: params.signal
|
|
200
|
+
});
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
const errorText = await response.text();
|
|
203
|
+
throw new Error(`Ollama API error ${response.status}: ${errorText}`);
|
|
204
|
+
}
|
|
205
|
+
if (!response.body) {
|
|
206
|
+
throw new Error("Ollama API returned no response body");
|
|
207
|
+
}
|
|
208
|
+
yield* parseNDJSONStream(response.body, params.signal);
|
|
209
|
+
};
|
|
210
|
+
var ollama = (config = {}) => {
|
|
211
|
+
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
212
|
+
return {
|
|
213
|
+
stream: (params) => fetchAndStream(baseUrl, params)
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
export {
|
|
217
|
+
ollama
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
//# debugId=2C147FDA7B95BFB664756E2164756E21
|
|
221
|
+
//# sourceMappingURL=ollama.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/ai/providers/ollama.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type {\n\tAIChunk,\n\tAIDoneChunk,\n\tAIProviderConfig,\n\tAIProviderStreamParams,\n\tAIProviderToolDefinition\n} from '../../../types/ai';\n\ntype OllamaConfig = {\n\tbaseUrl?: string;\n};\n\ntype OllamaMessage = {\n\tcontent: string;\n\trole: 'assistant' | 'system' | 'tool' | 'user';\n\ttool_calls?: Array<{\n\t\tfunction: { arguments: Record<string, unknown>; name: string };\n\t}>;\n};\n\nconst DEFAULT_BASE_URL = 'http://localhost:11434';\nconst ZERO_TOKENS = 0;\n\nconst DONE_CHUNK: AIChunk = { type: 'done' };\n\nconst mapToolDefinitions = (tools: AIProviderToolDefinition[]) =>\n\ttools.map((tool) => ({\n\t\tfunction: {\n\t\t\tdescription: tool.description,\n\t\t\tname: tool.name,\n\t\t\tparameters: tool.input_schema\n\t\t},\n\t\ttype: 'function'\n\t}));\n\nconst convertMessage = (msg: AIProviderStreamParams['messages'][number]) => {\n\tif (typeof msg.content === 'string') {\n\t\treturn [{ content: msg.content, role: msg.role }];\n\t}\n\n\tconst results: OllamaMessage[] = [];\n\tconst toolUseBlocks = msg.content.filter(\n\t\t(block) => block.type === 'tool_use'\n\t);\n\tconst toolResultBlocks = msg.content.filter(\n\t\t(block) => block.type === 'tool_result'\n\t);\n\n\tif (toolUseBlocks.length > 0) {\n\t\tresults.push({\n\t\t\tcontent: '',\n\t\t\trole: 'assistant',\n\t\t\ttool_calls: toolUseBlocks.map((block) => ({\n\t\t\t\tfunction: {\n\t\t\t\t\targuments:\n\t\t\t\t\t\ttypeof block.input === 'object' && block.input !== null\n\t\t\t\t\t\t\t? { ...block.input }\n\t\t\t\t\t\t\t: {},\n\t\t\t\t\tname: block.name\n\t\t\t\t}\n\t\t\t}))\n\t\t});\n\t}\n\n\tfor (const block of toolResultBlocks) {\n\t\tresults.push({\n\t\t\tcontent: typeof block.content === 'string' ? block.content : '',\n\t\t\trole: 'tool'\n\t\t});\n\t}\n\n\treturn results;\n};\n\nconst buildRequestBody = (params: AIProviderStreamParams) => {\n\tconst messages: OllamaMessage[] = params.messages.flatMap(convertMessage);\n\n\tif (params.systemPrompt) {\n\t\tmessages.unshift({ content: params.systemPrompt, role: 'system' });\n\t}\n\n\tconst body: Record<string, unknown> = {\n\t\tmessages,\n\t\tmodel: params.model,\n\t\tstream: true\n\t};\n\n\tif (params.tools && params.tools.length > 0) {\n\t\tbody.tools = mapToolDefinitions(params.tools);\n\t}\n\n\treturn body;\n};\n\nconst isRecord = (val: unknown): val is Record<string, unknown> =>\n\tval !== null && typeof val === 'object' && !Array.isArray(val);\n\nconst tryParseJSON = (text: string) => {\n\ttry {\n\t\tconst result: unknown = JSON.parse(text);\n\t\tif (isRecord(result)) {\n\t\t\treturn result;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst buildDoneChunk = (parsed: Record<string, unknown>): AIDoneChunk => {\n\tconst promptEvalCount =\n\t\ttypeof parsed.prompt_eval_count === 'number'\n\t\t\t? parsed.prompt_eval_count\n\t\t\t: undefined;\n\tconst evalCount =\n\t\ttypeof parsed.eval_count === 'number' ? parsed.eval_count : undefined;\n\tconst totalDuration =\n\t\ttypeof parsed.total_duration === 'number'\n\t\t\t? parsed.total_duration\n\t\t\t: undefined;\n\n\tconst hasTokenCounts =\n\t\tpromptEvalCount !== undefined || evalCount !== undefined;\n\tif (hasTokenCounts) {\n\t\treturn {\n\t\t\ttype: 'done',\n\t\t\tusage: {\n\t\t\t\tinputTokens: promptEvalCount ?? ZERO_TOKENS,\n\t\t\t\toutputTokens: evalCount ?? ZERO_TOKENS\n\t\t\t}\n\t\t};\n\t}\n\n\tif (totalDuration !== undefined) {\n\t\treturn {\n\t\t\ttype: 'done',\n\t\t\tusage: { inputTokens: ZERO_TOKENS, outputTokens: ZERO_TOKENS }\n\t\t};\n\t}\n\n\treturn { type: 'done' };\n};\n\nconst buildTextChunk = (content: string): AIChunk => ({\n\tcontent,\n\ttype: 'text'\n});\n\nconst extractToolCalls = (message: Record<string, unknown>) => {\n\tconst toolCalls = message.tool_calls;\n\n\tif (!Array.isArray(toolCalls) || toolCalls.length === 0) {\n\t\treturn [];\n\t}\n\n\treturn toolCalls\n\t\t.filter(isRecord)\n\t\t.filter((call) => isRecord(call.function))\n\t\t.map((call) => {\n\t\t\tconst func = call.function;\n\n\t\t\tif (!isRecord(func)) {\n\t\t\t\treturn {\n\t\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\t\tinput: {},\n\t\t\t\t\tname: '',\n\t\t\t\t\ttype: 'tool_use' as const\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: crypto.randomUUID(),\n\t\t\t\tinput: func.arguments ?? {},\n\t\t\t\tname: typeof func.name === 'string' ? func.name : '',\n\t\t\t\ttype: 'tool_use' as const\n\t\t\t};\n\t\t});\n};\n\nconst extractChunks = (parsed: Record<string, unknown>) => {\n\tconst { message } = parsed;\n\n\tif (!isRecord(message)) {\n\t\treturn [];\n\t}\n\n\tconst toolChunks = extractToolCalls(message);\n\n\tif (toolChunks.length > 0) {\n\t\treturn toolChunks;\n\t}\n\n\tconst { content } = message;\n\n\tif (typeof content !== 'string' || !content) {\n\t\treturn [];\n\t}\n\n\treturn [buildTextChunk(content)];\n};\n\nconst processLine = (line: string) => {\n\tconst trimmed = line.trim();\n\n\tif (!trimmed) {\n\t\treturn [];\n\t}\n\n\tconst parsed = tryParseJSON(trimmed);\n\n\tif (!parsed) {\n\t\treturn [];\n\t}\n\n\tif (parsed.done === true) {\n\t\treturn [buildDoneChunk(parsed)];\n\t}\n\n\treturn extractChunks(parsed);\n};\n\nconst processBufferedLines = (lines: string[]) => lines.flatMap(processLine);\n\nconst readStreamChunks = async (\n\treader: ReadableStreamDefaultReader<Uint8Array>,\n\tdecoder: TextDecoder,\n\tbuffer: string,\n\tsignal?: AbortSignal\n) => {\n\tconst emptyChunks: AIChunk[] = [];\n\n\tif (signal?.aborted) {\n\t\treturn {\n\t\t\tallChunks: emptyChunks,\n\t\t\tcurrentBuffer: buffer,\n\t\t\tfinished: true\n\t\t};\n\t}\n\n\tconst result = await reader.read();\n\tconst { done, value } = result;\n\n\tif (done) {\n\t\treturn {\n\t\t\tallChunks: emptyChunks,\n\t\t\tcurrentBuffer: buffer,\n\t\t\tfinished: true\n\t\t};\n\t}\n\n\tconst currentBuffer = buffer + decoder.decode(value, { stream: true });\n\tconst lines = currentBuffer.split('\\n');\n\tconst remainder = lines.pop() ?? '';\n\tconst allChunks = processBufferedLines(lines);\n\tconst finished = allChunks.some((c) => c.type === 'done');\n\n\treturn { allChunks, currentBuffer: remainder, finished };\n};\n\nconst parseNDJSONStream = async function* (\n\tbody: ReadableStream<Uint8Array>,\n\tsignal?: AbortSignal\n) {\n\tconst reader = body.getReader();\n\n\ttry {\n\t\tyield* parseNDJSONStreamInner(reader, signal);\n\t} finally {\n\t\treader.releaseLock();\n\t}\n};\n\nconst parseNDJSONStreamInner = async function* (\n\treader: ReadableStreamDefaultReader<Uint8Array>,\n\tsignal?: AbortSignal\n) {\n\tconst decoder = new TextDecoder();\n\tlet buffer = '';\n\tlet done = false;\n\n\twhile (!done) {\n\t\t// eslint-disable-next-line no-await-in-loop\n\t\tconst result = await readStreamChunks(reader, decoder, buffer, signal);\n\t\tbuffer = result.currentBuffer;\n\t\tdone = result.finished;\n\n\t\tyield* result.allChunks;\n\t}\n\n\tyield DONE_CHUNK;\n};\n\nconst fetchAndStream = async function* (\n\tbaseUrl: string,\n\tparams: AIProviderStreamParams\n) {\n\tconst requestBody = buildRequestBody(params);\n\n\tconst response = await fetch(`${baseUrl}/api/chat`, {\n\t\tbody: JSON.stringify(requestBody),\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tmethod: 'POST',\n\t\tsignal: params.signal\n\t});\n\n\tif (!response.ok) {\n\t\tconst errorText = await response.text();\n\t\tthrow new Error(`Ollama API error ${response.status}: ${errorText}`);\n\t}\n\n\tif (!response.body) {\n\t\tthrow new Error('Ollama API returned no response body');\n\t}\n\n\tyield* parseNDJSONStream(response.body, params.signal);\n};\n\nexport const ollama = (config: OllamaConfig = {}): AIProviderConfig => {\n\tconst baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;\n\n\treturn {\n\t\tstream: (params: AIProviderStreamParams) =>\n\t\t\tfetchAndStream(baseUrl, params)\n\t};\n};\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "2C147FDA7B95BFB664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/ai/providers/openai.ts
|
|
3
|
+
var DEFAULT_BASE_URL = "https://api.openai.com";
|
|
4
|
+
var SSE_DATA_PREFIX_LENGTH = 6;
|
|
5
|
+
var DONE_SENTINEL = "[DONE]";
|
|
6
|
+
var NOT_FOUND = -1;
|
|
7
|
+
var isRecord = (value) => typeof value === "object" && value !== null;
|
|
8
|
+
var isRecordArray = (value) => Array.isArray(value) && value.length > 0 && isRecord(value[0]);
|
|
9
|
+
var hasArrayContent = (msg) => typeof msg.content !== "string" && Array.isArray(msg.content);
|
|
10
|
+
var buildToolMessages = (blocks) => {
|
|
11
|
+
const toolUseBlocks = blocks.filter((block) => block.type === "tool_use");
|
|
12
|
+
const toolResultBlocks = blocks.filter((block) => block.type === "tool_result");
|
|
13
|
+
const messages = [];
|
|
14
|
+
if (toolUseBlocks.length > 0) {
|
|
15
|
+
messages.push({
|
|
16
|
+
content: null,
|
|
17
|
+
role: "assistant",
|
|
18
|
+
tool_calls: toolUseBlocks.map((block) => ({
|
|
19
|
+
function: {
|
|
20
|
+
arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input),
|
|
21
|
+
name: block.name
|
|
22
|
+
},
|
|
23
|
+
id: block.id,
|
|
24
|
+
type: "function"
|
|
25
|
+
}))
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
for (const result of toolResultBlocks) {
|
|
29
|
+
messages.push({
|
|
30
|
+
content: typeof result.content === "string" ? result.content : "",
|
|
31
|
+
role: "tool",
|
|
32
|
+
tool_call_id: result.tool_use_id
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return messages;
|
|
36
|
+
};
|
|
37
|
+
var processMessageAtIndex = (result, msg, idx) => {
|
|
38
|
+
if (!hasArrayContent(msg)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const hasToolBlocks = msg.content.some((block) => block.type === "tool_use" || block.type === "tool_result");
|
|
42
|
+
if (!hasToolBlocks) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const toolMessages = buildToolMessages(msg.content);
|
|
46
|
+
result.splice(idx, 1, ...toolMessages);
|
|
47
|
+
};
|
|
48
|
+
var convertSingleMessage = (result, msg, idx) => {
|
|
49
|
+
if (!msg) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
processMessageAtIndex(result, msg, idx);
|
|
53
|
+
};
|
|
54
|
+
var convertToolResultMessages = (messages, params) => {
|
|
55
|
+
const result = [...messages];
|
|
56
|
+
for (let idx = 0;idx < params.messages.length; idx++) {
|
|
57
|
+
convertSingleMessage(result, params.messages[idx], idx);
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
61
|
+
var mapToolDefinitions = (tools) => tools.map((tool) => ({
|
|
62
|
+
function: {
|
|
63
|
+
description: tool.description,
|
|
64
|
+
name: tool.name,
|
|
65
|
+
parameters: tool.input_schema
|
|
66
|
+
},
|
|
67
|
+
type: "function"
|
|
68
|
+
}));
|
|
69
|
+
var mapContentBlockToOpenAI = (block) => {
|
|
70
|
+
if (block.type === "image") {
|
|
71
|
+
return {
|
|
72
|
+
image_url: {
|
|
73
|
+
url: `data:${block.source.media_type};base64,${block.source.data}`
|
|
74
|
+
},
|
|
75
|
+
type: "image_url"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (block.type === "document") {
|
|
79
|
+
return {
|
|
80
|
+
file: {
|
|
81
|
+
file_data: `data:${block.source.media_type};base64,${block.source.data}`,
|
|
82
|
+
filename: block.name ?? "document.pdf"
|
|
83
|
+
},
|
|
84
|
+
type: "file"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (block.type === "text") {
|
|
88
|
+
return { text: block.content, type: "text" };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
};
|
|
92
|
+
var mapOpenAIContent = (msg) => {
|
|
93
|
+
if (typeof msg.content === "string") {
|
|
94
|
+
return msg.content;
|
|
95
|
+
}
|
|
96
|
+
const hasMedia = msg.content.some((block) => block.type === "image" || block.type === "document");
|
|
97
|
+
if (!hasMedia) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return msg.content.map(mapContentBlockToOpenAI).filter((mapped) => mapped !== null);
|
|
101
|
+
};
|
|
102
|
+
var buildRequestBody = (params) => {
|
|
103
|
+
const messages = convertToolResultMessages(params.messages.map((msg) => ({
|
|
104
|
+
content: mapOpenAIContent(msg),
|
|
105
|
+
role: msg.role
|
|
106
|
+
})), params);
|
|
107
|
+
const body = {
|
|
108
|
+
messages,
|
|
109
|
+
model: params.model,
|
|
110
|
+
stream: true,
|
|
111
|
+
stream_options: { include_usage: true }
|
|
112
|
+
};
|
|
113
|
+
if (params.tools && params.tools.length > 0) {
|
|
114
|
+
body.tools = mapToolDefinitions(params.tools);
|
|
115
|
+
}
|
|
116
|
+
return body;
|
|
117
|
+
};
|
|
118
|
+
var parseToolInput = (rawArguments) => {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(rawArguments);
|
|
121
|
+
} catch {
|
|
122
|
+
return rawArguments;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var flushPendingToolCalls = function* (pendingToolCalls) {
|
|
126
|
+
for (const [, tool] of pendingToolCalls) {
|
|
127
|
+
const input = parseToolInput(tool.arguments);
|
|
128
|
+
yield {
|
|
129
|
+
id: tool.id,
|
|
130
|
+
input,
|
|
131
|
+
name: tool.name,
|
|
132
|
+
type: "tool_use"
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
pendingToolCalls.clear();
|
|
136
|
+
};
|
|
137
|
+
var extractUsage = (parsedUsage) => ({
|
|
138
|
+
inputTokens: parsedUsage.prompt_tokens ?? 0,
|
|
139
|
+
outputTokens: parsedUsage.completion_tokens ?? 0
|
|
140
|
+
});
|
|
141
|
+
var resolveToolCallIndex = (toolCall) => {
|
|
142
|
+
const raw = typeof toolCall.index === "number" ? toolCall.index : NOT_FOUND;
|
|
143
|
+
return raw < 0 ? undefined : raw;
|
|
144
|
+
};
|
|
145
|
+
var initPendingToolCall = (toolCall, func, index, pendingToolCalls) => {
|
|
146
|
+
if (pendingToolCalls.has(index)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const toolId = typeof toolCall.id === "string" ? toolCall.id : "";
|
|
150
|
+
const toolName = func && typeof func.name === "string" ? func.name : "";
|
|
151
|
+
pendingToolCalls.set(index, {
|
|
152
|
+
arguments: "",
|
|
153
|
+
id: toolId,
|
|
154
|
+
name: toolName
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
var updatePendingToolCall = (toolCall, func, pending) => {
|
|
158
|
+
if (typeof toolCall.id === "string") {
|
|
159
|
+
pending.id = toolCall.id;
|
|
160
|
+
}
|
|
161
|
+
if (func && typeof func.name === "string") {
|
|
162
|
+
pending.name = func.name;
|
|
163
|
+
}
|
|
164
|
+
if (func && typeof func.arguments === "string") {
|
|
165
|
+
pending.arguments += func.arguments;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var processToolCallDelta = (toolCall, pendingToolCalls) => {
|
|
169
|
+
const index = resolveToolCallIndex(toolCall);
|
|
170
|
+
if (index === undefined) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const func = isRecord(toolCall.function) ? toolCall.function : null;
|
|
174
|
+
initPendingToolCall(toolCall, func, index, pendingToolCalls);
|
|
175
|
+
const pending = pendingToolCalls.get(index);
|
|
176
|
+
if (!pending) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
updatePendingToolCall(toolCall, func, pending);
|
|
180
|
+
};
|
|
181
|
+
var processToolCallDeltas = (toolCalls, pendingToolCalls) => {
|
|
182
|
+
for (const toolCall of toolCalls) {
|
|
183
|
+
processToolCallDelta(toolCall, pendingToolCalls);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var processDelta = function* (delta, pendingToolCalls) {
|
|
187
|
+
if (typeof delta.content === "string") {
|
|
188
|
+
yield { content: delta.content, type: "text" };
|
|
189
|
+
}
|
|
190
|
+
if (isRecordArray(delta.tool_calls)) {
|
|
191
|
+
processToolCallDeltas(delta.tool_calls, pendingToolCalls);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
var processChoice = function* (choice, pendingToolCalls) {
|
|
195
|
+
const delta = isRecord(choice.delta) ? choice.delta : null;
|
|
196
|
+
if (delta) {
|
|
197
|
+
yield* processDelta(delta, pendingToolCalls);
|
|
198
|
+
}
|
|
199
|
+
if (choice.finish_reason === "tool_calls") {
|
|
200
|
+
yield* flushPendingToolCalls(pendingToolCalls);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var narrowUsageRecord = (parsed) => {
|
|
204
|
+
if (!isRecord(parsed.usage)) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const { usage } = parsed;
|
|
208
|
+
const promptTokens = typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : 0;
|
|
209
|
+
const completionTokens = typeof usage.completion_tokens === "number" ? usage.completion_tokens : 0;
|
|
210
|
+
return extractUsage({
|
|
211
|
+
completion_tokens: completionTokens,
|
|
212
|
+
prompt_tokens: promptTokens
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
var processSSELine = function* (line, pendingToolCalls, currentUsage) {
|
|
216
|
+
const trimmed = line.trim();
|
|
217
|
+
if (!trimmed || !trimmed.startsWith("data: ")) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const data = trimmed.slice(SSE_DATA_PREFIX_LENGTH);
|
|
221
|
+
if (data === DONE_SENTINEL) {
|
|
222
|
+
yield* flushPendingToolCalls(pendingToolCalls);
|
|
223
|
+
yield { type: "done", usage: currentUsage };
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
let parsed;
|
|
227
|
+
try {
|
|
228
|
+
parsed = JSON.parse(data);
|
|
229
|
+
} catch {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const usageUpdate = narrowUsageRecord(parsed);
|
|
233
|
+
if (usageUpdate) {
|
|
234
|
+
yield { type: "usage_update", usage: usageUpdate };
|
|
235
|
+
}
|
|
236
|
+
const { choices } = parsed;
|
|
237
|
+
if (!isRecordArray(choices)) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const [firstChoice] = choices;
|
|
241
|
+
if (!firstChoice) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
yield* processChoice(firstChoice, pendingToolCalls);
|
|
245
|
+
};
|
|
246
|
+
var isUsageUpdate = (chunk) => chunk.type === "usage_update";
|
|
247
|
+
var collectYieldableChunks = (line, pendingToolCalls, usageRef) => {
|
|
248
|
+
const allChunks = Array.from(processSSELine(line, pendingToolCalls, usageRef.current));
|
|
249
|
+
const usageChunks = allChunks.filter(isUsageUpdate);
|
|
250
|
+
const lastUsage = usageChunks.at(NOT_FOUND);
|
|
251
|
+
if (lastUsage) {
|
|
252
|
+
usageRef.current = lastUsage.usage;
|
|
253
|
+
}
|
|
254
|
+
return allChunks.filter((chunk) => !isUsageUpdate(chunk));
|
|
255
|
+
};
|
|
256
|
+
var processSSELines = function* (lines, pendingToolCalls, usageRef) {
|
|
257
|
+
for (const line of lines) {
|
|
258
|
+
yield* collectYieldableChunks(line, pendingToolCalls, usageRef);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
var processStreamValue = (value, decoder, state) => {
|
|
262
|
+
state.buffer += decoder.decode(value, { stream: true });
|
|
263
|
+
const lines = state.buffer.split(`
|
|
264
|
+
`);
|
|
265
|
+
state.buffer = lines.pop() ?? "";
|
|
266
|
+
return lines;
|
|
267
|
+
};
|
|
268
|
+
var drainReader = async function* (reader, decoder, state, signal) {
|
|
269
|
+
for (let result = await reader.read();!result.done && !signal?.aborted; result = await reader.read()) {
|
|
270
|
+
const lines = processStreamValue(result.value, decoder, state);
|
|
271
|
+
yield* processSSELines(lines, state.pendingToolCalls, state.usageRef);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var parseSSEStream = async function* (body, signal) {
|
|
275
|
+
const reader = body.getReader();
|
|
276
|
+
const decoder = new TextDecoder;
|
|
277
|
+
const state = {
|
|
278
|
+
buffer: "",
|
|
279
|
+
pendingToolCalls: new Map,
|
|
280
|
+
usageRef: { current: undefined }
|
|
281
|
+
};
|
|
282
|
+
try {
|
|
283
|
+
yield* drainReader(reader, decoder, state, signal);
|
|
284
|
+
yield { type: "done", usage: state.usageRef.current };
|
|
285
|
+
} finally {
|
|
286
|
+
reader.releaseLock();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
var fetchOpenAIStream = async function* (baseUrl, apiKey, body, signal) {
|
|
290
|
+
const response = await fetch(`${baseUrl}/v1/chat/completions`, {
|
|
291
|
+
body: JSON.stringify(body),
|
|
292
|
+
headers: {
|
|
293
|
+
Authorization: `Bearer ${apiKey}`,
|
|
294
|
+
"Content-Type": "application/json"
|
|
295
|
+
},
|
|
296
|
+
method: "POST",
|
|
297
|
+
signal
|
|
298
|
+
});
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
const errorText = await response.text();
|
|
301
|
+
throw new Error(`OpenAI API error ${response.status}: ${errorText}`);
|
|
302
|
+
}
|
|
303
|
+
if (!response.body) {
|
|
304
|
+
throw new Error("OpenAI API returned no response body");
|
|
305
|
+
}
|
|
306
|
+
yield* parseSSEStream(response.body, signal);
|
|
307
|
+
};
|
|
308
|
+
var openai = (config) => {
|
|
309
|
+
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
310
|
+
return {
|
|
311
|
+
stream: (params) => {
|
|
312
|
+
const body = buildRequestBody(params);
|
|
313
|
+
return fetchOpenAIStream(baseUrl, config.apiKey, body, params.signal);
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
export {
|
|
318
|
+
openai
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
//# debugId=B928538A3EDE03A364756E2164756E21
|
|
322
|
+
//# sourceMappingURL=openai.js.map
|