ai 2.2.22 → 2.2.24

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.
@@ -64,6 +64,7 @@ type ChatRequest = {
64
64
  options?: RequestOptions;
65
65
  functions?: Array<Function>;
66
66
  function_call?: FunctionCall;
67
+ data?: Record<string, string>;
67
68
  };
68
69
  type FunctionCallHandler = (chatMessages: Message[], functionCall: FunctionCall) => Promise<ChatRequest | void>;
69
70
  type RequestOptions = {
@@ -194,6 +195,9 @@ type UseCompletionOptions = {
194
195
  */
195
196
  body?: object;
196
197
  };
198
+ type JSONValue = null | string | number | boolean | {
199
+ [x: string]: JSONValue;
200
+ } | Array<JSONValue>;
197
201
 
198
202
  type UseChatHelpers = {
199
203
  /** Current messages in the chat */
@@ -231,8 +235,10 @@ type UseChatHelpers = {
231
235
  handleSubmit: (e: any) => void;
232
236
  /** Whether the API request is in progress */
233
237
  isLoading: Accessor<boolean>;
238
+ /** Additional data added on the server via StreamData */
239
+ data: Accessor<JSONValue[] | undefined>;
234
240
  };
235
- declare function useChat({ api, id, initialMessages, initialInput, sendExtraMessageFields, onResponse, onFinish, onError, credentials, headers, body, }?: UseChatOptions): UseChatHelpers;
241
+ declare function useChat({ api, id, initialMessages, initialInput, sendExtraMessageFields, experimental_onFunctionCall, onResponse, onFinish, onError, credentials, headers, body, }?: UseChatOptions): UseChatHelpers;
236
242
 
237
243
  type UseCompletionHelpers = {
238
244
  /** The current completion result */
@@ -30,8 +30,134 @@ var import_solid_js = require("solid-js");
30
30
  var import_solid_swr_store = require("solid-swr-store");
31
31
  var import_swr_store = require("swr-store");
32
32
 
33
+ // shared/call-api.ts
34
+ var import_nanoid = require("nanoid");
35
+
33
36
  // shared/utils.ts
34
37
  var import_non_secure = require("nanoid/non-secure");
38
+
39
+ // shared/stream-parts.ts
40
+ var textStreamPart = {
41
+ code: "0",
42
+ name: "text",
43
+ parse: (value) => {
44
+ if (typeof value !== "string") {
45
+ throw new Error('"text" parts expect a string value.');
46
+ }
47
+ return { type: "text", value };
48
+ }
49
+ };
50
+ var functionCallStreamPart = {
51
+ code: "1",
52
+ name: "function_call",
53
+ parse: (value) => {
54
+ if (value == null || typeof value !== "object" || !("function_call" in value) || typeof value.function_call !== "object" || value.function_call == null || !("name" in value.function_call) || !("arguments" in value.function_call) || typeof value.function_call.name !== "string" || typeof value.function_call.arguments !== "string") {
55
+ throw new Error(
56
+ '"function_call" parts expect an object with a "function_call" property.'
57
+ );
58
+ }
59
+ return {
60
+ type: "function_call",
61
+ value
62
+ };
63
+ }
64
+ };
65
+ var dataStreamPart = {
66
+ code: "2",
67
+ name: "data",
68
+ parse: (value) => {
69
+ if (!Array.isArray(value)) {
70
+ throw new Error('"data" parts expect an array value.');
71
+ }
72
+ return { type: "data", value };
73
+ }
74
+ };
75
+ var errorStreamPart = {
76
+ code: "3",
77
+ name: "error",
78
+ parse: (value) => {
79
+ if (typeof value !== "string") {
80
+ throw new Error('"error" parts expect a string value.');
81
+ }
82
+ return { type: "error", value };
83
+ }
84
+ };
85
+ var assistantMessage = {
86
+ code: "4",
87
+ name: "assistant_message",
88
+ parse: (value) => {
89
+ if (value == null || typeof value !== "object" || !("id" in value) || !("role" in value) || !("content" in value) || typeof value.id !== "string" || typeof value.role !== "string" || value.role !== "assistant" || !Array.isArray(value.content) || !value.content.every(
90
+ (item) => item != null && typeof item === "object" && "type" in item && item.type === "text" && "text" in item && item.text != null && typeof item.text === "object" && "value" in item.text && typeof item.text.value === "string"
91
+ )) {
92
+ throw new Error(
93
+ '"assistant_message" parts expect an object with an "id", "role", and "content" property.'
94
+ );
95
+ }
96
+ return {
97
+ type: "assistant_message",
98
+ value
99
+ };
100
+ }
101
+ };
102
+ var assistantControlData = {
103
+ code: "5",
104
+ name: "assistant_control_data",
105
+ parse: (value) => {
106
+ if (value == null || typeof value !== "object" || !("threadId" in value) || !("messageId" in value) || typeof value.threadId !== "string" || typeof value.messageId !== "string") {
107
+ throw new Error(
108
+ '"assistant_control_data" parts expect an object with a "threadId" and "messageId" property.'
109
+ );
110
+ }
111
+ return {
112
+ type: "assistant_control_data",
113
+ value: {
114
+ threadId: value.threadId,
115
+ messageId: value.messageId
116
+ }
117
+ };
118
+ }
119
+ };
120
+ var streamParts = [
121
+ textStreamPart,
122
+ functionCallStreamPart,
123
+ dataStreamPart,
124
+ errorStreamPart,
125
+ assistantMessage,
126
+ assistantControlData
127
+ ];
128
+ var streamPartsByCode = {
129
+ [textStreamPart.code]: textStreamPart,
130
+ [functionCallStreamPart.code]: functionCallStreamPart,
131
+ [dataStreamPart.code]: dataStreamPart,
132
+ [errorStreamPart.code]: errorStreamPart,
133
+ [assistantMessage.code]: assistantMessage,
134
+ [assistantControlData.code]: assistantControlData
135
+ };
136
+ var StreamStringPrefixes = {
137
+ [textStreamPart.name]: textStreamPart.code,
138
+ [functionCallStreamPart.name]: functionCallStreamPart.code,
139
+ [dataStreamPart.name]: dataStreamPart.code,
140
+ [errorStreamPart.name]: errorStreamPart.code,
141
+ [assistantMessage.name]: assistantMessage.code,
142
+ [assistantControlData.name]: assistantControlData.code
143
+ };
144
+ var validCodes = streamParts.map((part) => part.code);
145
+ var parseStreamPart = (line) => {
146
+ const firstSeparatorIndex = line.indexOf(":");
147
+ if (firstSeparatorIndex === -1) {
148
+ throw new Error("Failed to parse stream string. No separator found.");
149
+ }
150
+ const prefix = line.slice(0, firstSeparatorIndex);
151
+ if (!validCodes.includes(prefix)) {
152
+ throw new Error(`Failed to parse stream string. Invalid code ${prefix}.`);
153
+ }
154
+ const code = prefix;
155
+ const textValue = line.slice(firstSeparatorIndex + 1);
156
+ const jsonValue = JSON.parse(textValue);
157
+ return streamPartsByCode[code].parse(jsonValue);
158
+ };
159
+
160
+ // shared/utils.ts
35
161
  var nanoid = (0, import_non_secure.customAlphabet)(
36
162
  "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
37
163
  7
@@ -47,33 +173,249 @@ function createChunkDecoder(complex) {
47
173
  }
48
174
  return function(chunk) {
49
175
  const decoded = decoder.decode(chunk, { stream: true }).split("\n").filter((line) => line !== "");
50
- return decoded.map(getStreamStringTypeAndValue).filter(Boolean);
176
+ return decoded.map(parseStreamPart).filter(Boolean);
51
177
  };
52
178
  }
53
- var StreamStringPrefixes = {
54
- text: 0,
55
- function_call: 1,
56
- data: 2
57
- // user_err: 3?
58
- };
59
- var getStreamStringTypeAndValue = (line) => {
60
- const firstSeperatorIndex = line.indexOf(":");
61
- const prefix = line.slice(0, firstSeperatorIndex);
62
- const type = Object.keys(StreamStringPrefixes).find(
63
- (key) => StreamStringPrefixes[key] === Number(prefix)
64
- );
65
- const val = line.slice(firstSeperatorIndex + 1);
66
- let parsedVal = val;
67
- if (!val) {
68
- return { type, value: "" };
179
+ var COMPLEX_HEADER = "X-Experimental-Stream-Data";
180
+
181
+ // shared/parse-complex-response.ts
182
+ async function parseComplexResponse({
183
+ reader,
184
+ abortControllerRef,
185
+ update,
186
+ onFinish,
187
+ generateId = nanoid,
188
+ getCurrentDate = () => /* @__PURE__ */ new Date()
189
+ }) {
190
+ const createdAt = getCurrentDate();
191
+ const decode = createChunkDecoder(true);
192
+ const prefixMap = {
193
+ data: []
194
+ };
195
+ const NEWLINE = "\n".charCodeAt(0);
196
+ const chunks = [];
197
+ let totalLength = 0;
198
+ while (true) {
199
+ const { value } = await reader.read();
200
+ if (value) {
201
+ chunks.push(value);
202
+ totalLength += value.length;
203
+ if (value[value.length - 1] !== NEWLINE) {
204
+ continue;
205
+ }
206
+ }
207
+ if (chunks.length === 0) {
208
+ break;
209
+ }
210
+ let concatenatedChunks = new Uint8Array(totalLength);
211
+ let offset = 0;
212
+ for (const chunk of chunks) {
213
+ concatenatedChunks.set(chunk, offset);
214
+ offset += chunk.length;
215
+ }
216
+ chunks.length = 0;
217
+ totalLength = 0;
218
+ const lines = decode(concatenatedChunks);
219
+ if (typeof lines === "string") {
220
+ throw new Error(
221
+ "Invalid response format. Complex mode was set but the response is a string. This should never happen."
222
+ );
223
+ }
224
+ for (const { type, value: value2 } of lines) {
225
+ if (type === "text") {
226
+ if (prefixMap["text"]) {
227
+ prefixMap["text"] = {
228
+ ...prefixMap["text"],
229
+ content: (prefixMap["text"].content || "") + value2
230
+ };
231
+ } else {
232
+ prefixMap["text"] = {
233
+ id: generateId(),
234
+ role: "assistant",
235
+ content: value2,
236
+ createdAt
237
+ };
238
+ }
239
+ }
240
+ let functionCallMessage = null;
241
+ if (type === "function_call") {
242
+ prefixMap["function_call"] = {
243
+ id: generateId(),
244
+ role: "assistant",
245
+ content: "",
246
+ function_call: value2.function_call,
247
+ name: value2.function_call.name,
248
+ createdAt
249
+ };
250
+ functionCallMessage = prefixMap["function_call"];
251
+ }
252
+ if (type === "data") {
253
+ prefixMap["data"].push(...value2);
254
+ }
255
+ const responseMessage = prefixMap["text"];
256
+ const merged = [functionCallMessage, responseMessage].filter(
257
+ Boolean
258
+ );
259
+ update(merged, [...prefixMap["data"]]);
260
+ if ((abortControllerRef == null ? void 0 : abortControllerRef.current) === null) {
261
+ reader.cancel();
262
+ break;
263
+ }
264
+ }
69
265
  }
70
- try {
71
- parsedVal = JSON.parse(val);
72
- } catch (e) {
73
- console.error("Failed to parse JSON value:", val);
266
+ onFinish == null ? void 0 : onFinish(prefixMap);
267
+ return {
268
+ messages: [prefixMap.text, prefixMap.function_call].filter(
269
+ Boolean
270
+ ),
271
+ data: prefixMap.data
272
+ };
273
+ }
274
+
275
+ // shared/call-api.ts
276
+ async function callApi({
277
+ api,
278
+ messages,
279
+ body,
280
+ credentials,
281
+ headers,
282
+ abortController,
283
+ appendMessage,
284
+ restoreMessagesOnFailure,
285
+ onResponse,
286
+ onUpdate,
287
+ onFinish
288
+ }) {
289
+ var _a;
290
+ const response = await fetch(api, {
291
+ method: "POST",
292
+ body: JSON.stringify({
293
+ messages,
294
+ ...body
295
+ }),
296
+ headers,
297
+ signal: (_a = abortController == null ? void 0 : abortController()) == null ? void 0 : _a.signal,
298
+ credentials
299
+ }).catch((err) => {
300
+ restoreMessagesOnFailure();
301
+ throw err;
302
+ });
303
+ if (onResponse) {
304
+ try {
305
+ await onResponse(response);
306
+ } catch (err) {
307
+ throw err;
308
+ }
74
309
  }
75
- return { type, value: parsedVal };
76
- };
310
+ if (!response.ok) {
311
+ restoreMessagesOnFailure();
312
+ throw new Error(
313
+ await response.text() || "Failed to fetch the chat response."
314
+ );
315
+ }
316
+ if (!response.body) {
317
+ throw new Error("The response body is empty.");
318
+ }
319
+ const reader = response.body.getReader();
320
+ const isComplexMode = response.headers.get(COMPLEX_HEADER) === "true";
321
+ if (isComplexMode) {
322
+ return await parseComplexResponse({
323
+ reader,
324
+ abortControllerRef: abortController != null ? { current: abortController() } : void 0,
325
+ update: onUpdate,
326
+ onFinish(prefixMap) {
327
+ if (onFinish && prefixMap.text != null) {
328
+ onFinish(prefixMap.text);
329
+ }
330
+ }
331
+ });
332
+ } else {
333
+ const createdAt = /* @__PURE__ */ new Date();
334
+ const decode = createChunkDecoder(false);
335
+ let streamedResponse = "";
336
+ const replyId = (0, import_nanoid.nanoid)();
337
+ let responseMessage = {
338
+ id: replyId,
339
+ createdAt,
340
+ content: "",
341
+ role: "assistant"
342
+ };
343
+ while (true) {
344
+ const { done, value } = await reader.read();
345
+ if (done) {
346
+ break;
347
+ }
348
+ streamedResponse += decode(value);
349
+ if (streamedResponse.startsWith('{"function_call":')) {
350
+ responseMessage["function_call"] = streamedResponse;
351
+ } else {
352
+ responseMessage["content"] = streamedResponse;
353
+ }
354
+ appendMessage({ ...responseMessage });
355
+ if ((abortController == null ? void 0 : abortController()) === null) {
356
+ reader.cancel();
357
+ break;
358
+ }
359
+ }
360
+ if (streamedResponse.startsWith('{"function_call":')) {
361
+ const parsedFunctionCall = JSON.parse(streamedResponse).function_call;
362
+ responseMessage["function_call"] = parsedFunctionCall;
363
+ appendMessage({ ...responseMessage });
364
+ }
365
+ if (onFinish) {
366
+ onFinish(responseMessage);
367
+ }
368
+ return responseMessage;
369
+ }
370
+ }
371
+
372
+ // shared/process-chat-stream.ts
373
+ async function processChatStream({
374
+ getStreamedResponse,
375
+ experimental_onFunctionCall,
376
+ updateChatRequest,
377
+ getCurrentMessages
378
+ }) {
379
+ while (true) {
380
+ const messagesAndDataOrJustMessage = await getStreamedResponse();
381
+ if ("messages" in messagesAndDataOrJustMessage) {
382
+ let hasFollowingResponse = false;
383
+ for (const message of messagesAndDataOrJustMessage.messages) {
384
+ if (message.function_call === void 0 || typeof message.function_call === "string") {
385
+ continue;
386
+ }
387
+ hasFollowingResponse = true;
388
+ if (experimental_onFunctionCall) {
389
+ const functionCall = message.function_call;
390
+ const functionCallResponse = await experimental_onFunctionCall(
391
+ getCurrentMessages(),
392
+ functionCall
393
+ );
394
+ if (functionCallResponse === void 0) {
395
+ hasFollowingResponse = false;
396
+ break;
397
+ }
398
+ updateChatRequest(functionCallResponse);
399
+ }
400
+ }
401
+ if (!hasFollowingResponse) {
402
+ break;
403
+ }
404
+ } else {
405
+ const streamedResponseMessage = messagesAndDataOrJustMessage;
406
+ if (streamedResponseMessage.function_call === void 0 || typeof streamedResponseMessage.function_call === "string") {
407
+ break;
408
+ }
409
+ if (experimental_onFunctionCall) {
410
+ const functionCall = streamedResponseMessage.function_call;
411
+ const functionCallResponse = await experimental_onFunctionCall(getCurrentMessages(), functionCall);
412
+ if (functionCallResponse === void 0)
413
+ break;
414
+ updateChatRequest(functionCallResponse);
415
+ }
416
+ }
417
+ }
418
+ }
77
419
 
78
420
  // solid/use-chat.ts
79
421
  var uniqueId = 0;
@@ -90,6 +432,7 @@ function useChat({
90
432
  initialMessages = [],
91
433
  initialInput = "",
92
434
  sendExtraMessageFields,
435
+ experimental_onFunctionCall,
93
436
  onResponse,
94
437
  onFinish,
95
438
  onError,
@@ -99,18 +442,20 @@ function useChat({
99
442
  } = {}) {
100
443
  const chatId = id || `chat-${uniqueId++}`;
101
444
  const key = `${api}|${chatId}`;
102
- const data = (0, import_solid_swr_store.useSWRStore)(chatApiStore, () => [key], {
445
+ const messages = (0, import_solid_swr_store.useSWRStore)(chatApiStore, () => [key], {
103
446
  initialData: initialMessages
104
447
  });
105
- const mutate = (data2) => {
106
- store[key] = data2;
448
+ const mutate = (data) => {
449
+ store[key] = data;
107
450
  return chatApiStore.mutate([key], {
108
451
  status: "success",
109
- data: data2
452
+ data
110
453
  });
111
454
  };
112
- const messages = data;
113
455
  const [error, setError] = (0, import_solid_js.createSignal)(void 0);
456
+ const [streamData, setStreamData] = (0, import_solid_js.createSignal)(
457
+ void 0
458
+ );
114
459
  const [isLoading, setIsLoading] = (0, import_solid_js.createSignal)(false);
115
460
  let abortController = null;
116
461
  async function triggerRequest(messagesSnapshot, options) {
@@ -118,91 +463,64 @@ function useChat({
118
463
  setError(void 0);
119
464
  setIsLoading(true);
120
465
  abortController = new AbortController();
121
- const previousMessages = chatApiStore.get([key], {
466
+ const getCurrentMessages = () => chatApiStore.get([key], {
122
467
  shouldRevalidate: false
123
468
  });
469
+ const previousMessages = getCurrentMessages();
124
470
  mutate(messagesSnapshot);
125
- const res = await fetch(api, {
126
- method: "POST",
127
- body: JSON.stringify({
128
- messages: sendExtraMessageFields ? messagesSnapshot : messagesSnapshot.map(
129
- ({ role, content, name, function_call }) => ({
130
- role,
131
- content,
132
- ...name !== void 0 && { name },
133
- ...function_call !== void 0 && {
134
- function_call
471
+ let chatRequest = {
472
+ messages: messagesSnapshot,
473
+ options
474
+ };
475
+ await processChatStream({
476
+ getStreamedResponse: async () => {
477
+ var _a;
478
+ const existingData = (_a = streamData()) != null ? _a : [];
479
+ return await callApi({
480
+ api,
481
+ messages: sendExtraMessageFields ? chatRequest.messages : chatRequest.messages.map(
482
+ ({ role, content, name, function_call }) => ({
483
+ role,
484
+ content,
485
+ ...name !== void 0 && { name },
486
+ ...function_call !== void 0 && {
487
+ function_call
488
+ }
489
+ })
490
+ ),
491
+ body: {
492
+ ...body,
493
+ ...options == null ? void 0 : options.body
494
+ },
495
+ headers: {
496
+ ...headers,
497
+ ...options == null ? void 0 : options.headers
498
+ },
499
+ abortController: () => abortController,
500
+ credentials,
501
+ onResponse,
502
+ onUpdate(merged, data) {
503
+ mutate([...chatRequest.messages, ...merged]);
504
+ setStreamData([...existingData, ...data != null ? data : []]);
505
+ },
506
+ onFinish,
507
+ appendMessage(message) {
508
+ mutate([...chatRequest.messages, message]);
509
+ },
510
+ restoreMessagesOnFailure() {
511
+ if (previousMessages.status === "success") {
512
+ mutate(previousMessages.data);
135
513
  }
136
- })
137
- ),
138
- ...body,
139
- ...options == null ? void 0 : options.body
140
- }),
141
- headers: {
142
- ...headers,
143
- ...options == null ? void 0 : options.headers
514
+ }
515
+ });
144
516
  },
145
- signal: abortController.signal,
146
- credentials
147
- }).catch((err) => {
148
- if (previousMessages.status === "success") {
149
- mutate(previousMessages.data);
150
- }
151
- throw err;
517
+ experimental_onFunctionCall,
518
+ updateChatRequest(newChatRequest) {
519
+ chatRequest = newChatRequest;
520
+ },
521
+ getCurrentMessages: () => getCurrentMessages().data
152
522
  });
153
- if (onResponse) {
154
- try {
155
- await onResponse(res);
156
- } catch (err) {
157
- throw err;
158
- }
159
- }
160
- if (!res.ok) {
161
- if (previousMessages.status === "success") {
162
- mutate(previousMessages.data);
163
- }
164
- throw new Error(
165
- await res.text() || "Failed to fetch the chat response."
166
- );
167
- }
168
- if (!res.body) {
169
- throw new Error("The response body is empty.");
170
- }
171
- let result = "";
172
- const createdAt = /* @__PURE__ */ new Date();
173
- const replyId = nanoid();
174
- const reader = res.body.getReader();
175
- const decoder = createChunkDecoder();
176
- while (true) {
177
- const { done, value } = await reader.read();
178
- if (done) {
179
- break;
180
- }
181
- result += decoder(value);
182
- mutate([
183
- ...messagesSnapshot,
184
- {
185
- id: replyId,
186
- createdAt,
187
- content: result,
188
- role: "assistant"
189
- }
190
- ]);
191
- if (abortController === null) {
192
- reader.cancel();
193
- break;
194
- }
195
- }
196
- if (onFinish) {
197
- onFinish({
198
- id: replyId,
199
- createdAt,
200
- content: result,
201
- role: "assistant"
202
- });
203
- }
204
523
  abortController = null;
205
- return result;
206
524
  } catch (err) {
207
525
  if (err.name === "AbortError") {
208
526
  abortController = null;
@@ -268,7 +586,8 @@ function useChat({
268
586
  input,
269
587
  setInput,
270
588
  handleSubmit,
271
- isLoading
589
+ isLoading,
590
+ data: streamData
272
591
  };
273
592
  }
274
593