Skip to content

Commit d3100b9

Browse files
authoredJun 27, 2024··
feat (ai/ui): support custom fetch function in useChat, useCompletion, useAssistant, useObject (#2120)
1 parent a805e61 commit d3100b9

26 files changed

+216
-117
lines changed
 

‎.changeset/slimy-kangaroos-protect.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@ai-sdk/ui-utils': patch
3+
'@ai-sdk/svelte': patch
4+
'@ai-sdk/react': patch
5+
'@ai-sdk/solid': patch
6+
'ai': patch
7+
'@ai-sdk/vue': patch
8+
---
9+
10+
feat (ai/ui): support custom fetch function in useChat, useCompletion, useAssistant, useObject

‎content/docs/07-reference/ai-sdk-ui/01-use-chat.mdx

+7
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ Allows you to easily create a conversational user interface for your chatbot app
127127
description:
128128
'An optional literal that sets the mode of the stream to be used. Defaults to `stream-data`. If set to `text`, the stream will be treated as a text stream.',
129129
},
130+
{
131+
name: 'fetch',
132+
type: 'FetchFunction',
133+
optional: true,
134+
description:
135+
'Optional. A custom fetch function to be used for the API call. Defaults to the global fetch function.',
136+
},
130137
{
131138
name: 'experimental_prepareRequestBody',
132139
type: '(options: { messages: Message[]; requestData?: Record<string, string>; requestBody?: object }) => JSONValue',

‎content/docs/07-reference/ai-sdk-ui/02-use-completion.mdx

+7
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ Allows you to create text completion based capabilities for your application. It
116116
description:
117117
'An optional literal that sets the mode of the stream to be used. Defaults to `stream-data`. If set to `text`, the stream will be treated as a text stream.',
118118
},
119+
{
120+
name: 'fetch',
121+
type: 'FetchFunction',
122+
optional: true,
123+
description:
124+
'Optional. A custom fetch function to be used for the API call. Defaults to the global fetch function.',
125+
},
119126
]}
120127
/>
121128

‎content/docs/07-reference/ai-sdk-ui/03-use-object.mdx

+7
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ export default function Page() {
6767
type: 'DeepPartial<RESULT> | undefined',
6868
description: 'An optional value for the initial object.',
6969
},
70+
{
71+
name: 'fetch',
72+
type: 'FetchFunction',
73+
optional: true,
74+
description:
75+
'Optional. A custom fetch function to be used for the API call. Defaults to the global fetch function.',
76+
},
7077
]}
7178
/>
7279

‎content/docs/07-reference/ai-sdk-ui/20-use-assistant.mdx

+7
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ This works in conjunction with [`AssistantResponse`](./assistant-response) in th
7878
description:
7979
'Callback that will be called when the assistant encounters an error',
8080
},
81+
{
82+
name: 'fetch',
83+
type: 'FetchFunction',
84+
optional: true,
85+
description:
86+
'Optional. A custom fetch function to be used for the API call. Defaults to the global fetch function.',
87+
},
8188
]}
8289
/>
8390

‎examples/nuxt-openai/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@nuxt/devtools": "0.8.0",
1414
"@nuxt/ui-templates": "^1.3.1",
1515
"@nuxtjs/tailwindcss": "^6.8.0",
16-
"@types/node": "^20.5.0",
16+
"@types/node": "^18",
1717
"@vue/reactivity": "^3.3.4",
1818
"@vue/runtime-core": "^3.3.4",
1919
"@vue/runtime-dom": "^3.3.4",

‎examples/sveltekit-openai/src/routes/+page.svelte

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<script>
1+
<script lang="ts">
22
import { useChat } from '@ai-sdk/svelte'
33
44
const { input, handleSubmit, messages } = useChat()

‎packages/core/svelte/use-assistant.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type {
88
import { generateId, readDataStream } from '@ai-sdk/ui-utils';
99
import { Readable, Writable, get, writable } from 'svelte/store';
1010

11+
// use function to allow for mocking in tests:
12+
const getOriginalFetch = () => fetch;
13+
1114
let uniqueId = 0;
1215

1316
const store: Record<string, any> = {};
@@ -78,6 +81,7 @@ export function useAssistant({
7881
headers,
7982
body,
8083
onError,
84+
fetch,
8185
}: UseAssistantOptions): UseAssistantHelpers {
8286
// Generate a unique thread ID
8387
const threadIdStore = writable<string | undefined>(threadIdParam);
@@ -115,7 +119,8 @@ export function useAssistant({
115119
input.set('');
116120

117121
try {
118-
const response = await fetch(api, {
122+
const actualFetch = fetch ?? getOriginalFetch();
123+
const response = await actualFetch(api, {
119124
method: 'POST',
120125
credentials,
121126
signal: abortController.signal,

‎packages/core/svelte/use-chat.ts

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ChatRequest,
33
ChatRequestOptions,
44
CreateMessage,
5+
FetchFunction,
56
IdGenerator,
67
JSONValue,
78
Message,
@@ -51,13 +52,11 @@ export type UseChatHelpers = {
5152
setMessages: (messages: Message[]) => void;
5253
/** The current value of the input */
5354
input: Writable<string>;
54-
5555
/** Form submission handler to automatically reset input and append a user message */
5656
handleSubmit: (
5757
event?: { preventDefault?: () => void },
5858
chatRequestOptions?: ChatRequestOptions,
5959
) => void;
60-
6160
metadata?: Object;
6261
/** Whether the API request is in progress */
6362
isLoading: Readable<boolean | undefined>;
@@ -79,10 +78,11 @@ const getStreamedResponse = async (
7978
previousMessages: Message[],
8079
abortControllerRef: AbortController | null,
8180
generateId: IdGenerator,
82-
streamMode?: 'stream-data' | 'text',
83-
onFinish?: (message: Message) => void,
84-
onResponse?: (response: Response) => void | Promise<void>,
85-
sendExtraMessageFields?: boolean,
81+
streamMode: 'stream-data' | 'text' | undefined,
82+
onFinish: ((message: Message) => void) | undefined,
83+
onResponse: ((response: Response) => void | Promise<void>) | undefined,
84+
sendExtraMessageFields: boolean | undefined,
85+
fetch: FetchFunction | undefined,
8686
) => {
8787
// Do an optimistic update to the chat state to show the updated messages
8888
// immediately.
@@ -115,7 +115,6 @@ const getStreamedResponse = async (
115115

116116
return await callChatApi({
117117
api,
118-
messages: constructedMessagesPayload,
119118
body: {
120119
messages: constructedMessagesPayload,
121120
data: chatRequest.data,
@@ -151,6 +150,8 @@ const getStreamedResponse = async (
151150
},
152151
onFinish,
153152
generateId,
153+
onToolCall: undefined, // not implemented yet
154+
fetch,
154155
});
155156
};
156157

@@ -177,6 +178,7 @@ export function useChat({
177178
headers,
178179
body,
179180
generateId = generateIdFunc,
181+
fetch,
180182
}: UseChatOptions = {}): UseChatHelpers {
181183
// Generate a unique id for the chat if not provided.
182184
const chatId = id || `chat-${uniqueId++}`;
@@ -243,6 +245,7 @@ export function useChat({
243245
onFinish,
244246
onResponse,
245247
sendExtraMessageFields,
248+
fetch,
246249
),
247250
experimental_onFunctionCall,
248251
experimental_onToolCall,
@@ -353,7 +356,6 @@ export function useChat({
353356
options: ChatRequestOptions = {},
354357
) => {
355358
event?.preventDefault?.();
356-
357359
const inputValue = get(input);
358360
if (!inputValue) return;
359361

‎packages/core/svelte/use-completion.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export type UseCompletionHelpers = {
3131
setCompletion: (completion: string) => void;
3232
/** The current value of the input */
3333
input: Writable<string>;
34-
3534
/**
3635
* Form submission handler to automatically reset input and append a user message
3736
* @example
@@ -42,7 +41,6 @@ export type UseCompletionHelpers = {
4241
* ```
4342
*/
4443
handleSubmit: (event?: { preventDefault?: () => void }) => void;
45-
4644
/** Whether the API request is in progress */
4745
isLoading: Readable<boolean | undefined>;
4846

@@ -69,6 +67,7 @@ export function useCompletion({
6967
onResponse,
7068
onFinish,
7169
onError,
70+
fetch,
7271
}: UseCompletionOptions = {}): UseCompletionHelpers {
7372
// Generate an unique id for the completion if not provided.
7473
const completionId = id || `completion-${uniqueId++}`;
@@ -132,6 +131,7 @@ export function useCompletion({
132131
onData(data) {
133132
streamData.set([...(existingData || []), ...(data || [])]);
134133
},
134+
fetch,
135135
});
136136
};
137137

‎packages/react/src/use-assistant.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable react-hooks/rules-of-hooks */
2-
31
import { isAbortError } from '@ai-sdk/provider-utils';
42
import {
53
AssistantStatus,
@@ -11,6 +9,9 @@ import {
119
} from '@ai-sdk/ui-utils';
1210
import { useCallback, useRef, useState } from 'react';
1311

12+
// use function to allow for mocking in tests:
13+
const getOriginalFetch = () => fetch;
14+
1415
export type UseAssistantHelpers = {
1516
/**
1617
* The current array of chat messages.
@@ -92,6 +93,7 @@ export function useAssistant({
9293
headers,
9394
body,
9495
onError,
96+
fetch,
9597
}: UseAssistantOptions): UseAssistantHelpers {
9698
const [messages, setMessages] = useState<Message[]>([]);
9799
const [input, setInput] = useState('');
@@ -140,7 +142,8 @@ export function useAssistant({
140142
try {
141143
abortControllerRef.current = abortController;
142144

143-
const response = await fetch(api, {
145+
const actualFetch = fetch ?? getOriginalFetch();
146+
const response = await actualFetch(api, {
144147
method: 'POST',
145148
credentials,
146149
signal: abortController.signal,

‎packages/react/src/use-chat.ts

+16-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ChatRequest,
33
ChatRequestOptions,
44
CreateMessage,
5+
FetchFunction,
56
IdGenerator,
67
JSONValue,
78
Message,
@@ -82,16 +83,19 @@ const getStreamedResponse = async (
8283
messagesRef: React.MutableRefObject<Message[]>,
8384
abortControllerRef: React.MutableRefObject<AbortController | null>,
8485
generateId: IdGenerator,
85-
streamMode?: 'stream-data' | 'text',
86-
onFinish?: (message: Message) => void,
87-
onResponse?: (response: Response) => void | Promise<void>,
88-
onToolCall?: UseChatOptions['onToolCall'],
89-
sendExtraMessageFields?: boolean,
90-
experimental_prepareRequestBody?: (options: {
91-
messages: Message[];
92-
requestData?: Record<string, string>;
93-
requestBody?: object;
94-
}) => JSONValue,
86+
streamMode: 'stream-data' | 'text' | undefined,
87+
onFinish: ((message: Message) => void) | undefined,
88+
onResponse: ((response: Response) => void | Promise<void>) | undefined,
89+
onToolCall: UseChatOptions['onToolCall'] | undefined,
90+
sendExtraMessageFields: boolean | undefined,
91+
experimental_prepareRequestBody:
92+
| ((options: {
93+
messages: Message[];
94+
requestData?: Record<string, string>;
95+
requestBody?: object;
96+
}) => JSONValue)
97+
| undefined,
98+
fetch: FetchFunction | undefined,
9599
) => {
96100
// Do an optimistic update to the chat state to show the updated messages
97101
// immediately.
@@ -127,7 +131,6 @@ const getStreamedResponse = async (
127131

128132
return await callChatApi({
129133
api,
130-
messages: constructedMessagesPayload,
131134
body: experimental_prepareRequestBody?.({
132135
messages: chatRequest.messages,
133136
requestData: chatRequest.data,
@@ -168,6 +171,7 @@ const getStreamedResponse = async (
168171
onToolCall,
169172
onFinish,
170173
generateId,
174+
fetch,
171175
});
172176
};
173177

@@ -335,6 +339,7 @@ By default, it's set to 0, which will disable the feature.
335339
onToolCall,
336340
sendExtraMessageFields,
337341
experimental_prepareRequestBody,
342+
fetch,
338343
),
339344
experimental_onFunctionCall,
340345
experimental_onToolCall,

‎packages/react/src/use-completion.ts

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function useCompletion({
7272
headers,
7373
body,
7474
streamMode,
75+
fetch,
7576
onResponse,
7677
onFinish,
7778
onError,
@@ -126,6 +127,7 @@ export function useCompletion({
126127
...options?.body,
127128
},
128129
streamMode,
130+
fetch,
129131
setCompletion: completion => mutate(completion, false),
130132
setLoading: mutateLoading,
131133
setError,
@@ -149,6 +151,7 @@ export function useCompletion({
149151
setError,
150152
streamData,
151153
streamMode,
154+
fetch,
152155
mutateStreamData,
153156
],
154157
);

‎packages/react/src/use-object.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import {
22
DeepPartial,
3+
FetchFunction,
34
isDeepEqualData,
45
parsePartialJson,
56
} from '@ai-sdk/ui-utils';
67
import { useCallback, useId, useRef, useState } from 'react';
78
import useSWR from 'swr';
89
import z from 'zod';
910

11+
// use function to allow for mocking in tests:
12+
const getOriginalFetch = () => fetch;
13+
1014
export type Experimental_UseObjectOptions<RESULT> = {
1115
/**
1216
* The API endpoint. It should stream JSON that matches the schema as chunked text.
@@ -29,6 +33,12 @@ export type Experimental_UseObjectOptions<RESULT> = {
2933
* An optional value for the initial object.
3034
*/
3135
initialValue?: DeepPartial<RESULT>;
36+
37+
/**
38+
Custom fetch implementation. You can use it as a middleware to intercept requests,
39+
or to provide a custom fetch implementation for e.g. testing.
40+
*/
41+
fetch?: FetchFunction;
3242
};
3343

3444
export type Experimental_UseObjectHelpers<RESULT, INPUT> = {
@@ -68,6 +78,7 @@ function useObject<RESULT, INPUT = any>({
6878
id,
6979
schema, // required, in the future we will use it for validation
7080
initialValue,
81+
fetch,
7182
}: Experimental_UseObjectOptions<RESULT>): Experimental_UseObjectHelpers<
7283
RESULT,
7384
INPUT
@@ -101,7 +112,8 @@ function useObject<RESULT, INPUT = any>({
101112
const abortController = new AbortController();
102113
abortControllerRef.current = abortController;
103114

104-
const response = await fetch(api, {
115+
const actualFetch = fetch ?? getOriginalFetch();
116+
const response = await actualFetch(api, {
105117
method: 'POST',
106118
headers: { 'Content-Type': 'application/json' },
107119
signal: abortController.signal,

‎packages/solid/src/use-chat.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ChatRequest,
33
ChatRequestOptions,
44
CreateMessage,
5+
FetchFunction,
56
IdGenerator,
67
JSONValue,
78
Message,
@@ -76,6 +77,12 @@ export type UseChatHelpers = {
7677
isLoading: Accessor<boolean>;
7778
/** Additional data added on the server via StreamData */
7879
data: Accessor<JSONValue[] | undefined>;
80+
81+
/**
82+
Custom fetch implementation. You can use it as a middleware to intercept requests,
83+
or to provide a custom fetch implementation for e.g. testing.
84+
*/
85+
fetch?: FetchFunction;
7986
};
8087

8188
const getStreamedResponse = async (
@@ -88,15 +95,17 @@ const getStreamedResponse = async (
8895
messagesRef: Message[],
8996
abortController: AbortController | null,
9097
generateId: IdGenerator,
91-
streamMode?: 'stream-data' | 'text',
92-
onFinish?: UseChatOptions['onFinish'],
93-
onResponse?: UseChatOptions['onResponse'],
94-
onToolCall?: UseChatOptions['onToolCall'],
95-
sendExtraMessageFields?: boolean,
98+
streamMode: 'stream-data' | 'text' | undefined,
99+
onFinish: UseChatOptions['onFinish'] | undefined,
100+
onResponse: UseChatOptions['onResponse'] | undefined,
101+
onToolCall: UseChatOptions['onToolCall'] | undefined,
102+
sendExtraMessageFields: boolean | undefined,
103+
fetch: FetchFunction | undefined,
96104
) => {
97105
// Do an optimistic update to the chat state to show the updated messages
98106
// immediately.
99107
const previousMessages = messagesRef;
108+
100109
mutate(chatRequest.messages);
101110

102111
const existingStreamData = streamData() ?? [];
@@ -116,7 +125,6 @@ const getStreamedResponse = async (
116125

117126
return await callChatApi({
118127
api,
119-
messages: constructedMessagesPayload,
120128
body: {
121129
messages: constructedMessagesPayload,
122130
data: chatRequest.data,
@@ -141,6 +149,7 @@ const getStreamedResponse = async (
141149
onToolCall,
142150
onFinish,
143151
generateId,
152+
fetch,
144153
});
145154
};
146155

@@ -248,6 +257,7 @@ export function useChat(
248257
useChatOptions().onResponse?.(),
249258
useChatOptions().onToolCall?.(),
250259
useChatOptions().sendExtraMessageFields?.(),
260+
useChatOptions().fetch?.(),
251261
),
252262
experimental_onFunctionCall:
253263
useChatOptions().experimental_onFunctionCall?.(),

‎packages/solid/src/use-completion.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
FetchFunction,
23
JSONValue,
34
RequestOptions,
45
UseCompletionOptions,
@@ -61,6 +62,12 @@ export type UseCompletionHelpers = {
6162
isLoading: Accessor<boolean>;
6263
/** Additional data added on the server via StreamData */
6364
data: Accessor<JSONValue[] | undefined>;
65+
66+
/**
67+
Custom fetch implementation. You can use it as a middleware to intercept requests,
68+
or to provide a custom fetch implementation for e.g. testing.
69+
*/
70+
fetch?: FetchFunction;
6471
};
6572

6673
const [store, setStore] = createStore<Record<string, string>>({});
@@ -139,6 +146,7 @@ export function useCompletion(
139146
onData: data => {
140147
setStreamData([...existingData, ...(data ?? [])]);
141148
},
149+
fetch: useCompletionOptions().fetch?.(),
142150
});
143151
};
144152

‎packages/svelte/src/use-assistant.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type {
88
import { generateId, readDataStream } from '@ai-sdk/ui-utils';
99
import { Readable, Writable, get, writable } from 'svelte/store';
1010

11+
// use function to allow for mocking in tests:
12+
const getOriginalFetch = () => fetch;
13+
1114
let uniqueId = 0;
1215

1316
const store: Record<string, any> = {};
@@ -75,6 +78,7 @@ export function useAssistant({
7578
headers,
7679
body,
7780
onError,
81+
fetch,
7882
}: UseAssistantOptions): UseAssistantHelpers {
7983
// Generate a unique thread ID
8084
const threadIdStore = writable<string | undefined>(threadIdParam);
@@ -112,7 +116,8 @@ export function useAssistant({
112116
input.set('');
113117

114118
try {
115-
const response = await fetch(api, {
119+
const actualFetch = fetch ?? getOriginalFetch();
120+
const response = await actualFetch(api, {
116121
method: 'POST',
117122
credentials,
118123
signal: abortController.signal,

‎packages/svelte/src/use-chat.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
ChatRequest,
33
ChatRequestOptions,
44
CreateMessage,
5+
FetchFunction,
56
IdGenerator,
67
JSONValue,
78
Message,
@@ -77,10 +78,11 @@ const getStreamedResponse = async (
7778
previousMessages: Message[],
7879
abortControllerRef: AbortController | null,
7980
generateId: IdGenerator,
80-
streamMode?: 'stream-data' | 'text',
81-
onFinish?: (message: Message) => void,
82-
onResponse?: (response: Response) => void | Promise<void>,
83-
sendExtraMessageFields?: boolean,
81+
streamMode: 'stream-data' | 'text' | undefined,
82+
onFinish: ((message: Message) => void) | undefined,
83+
onResponse: ((response: Response) => void | Promise<void>) | undefined,
84+
sendExtraMessageFields: boolean | undefined,
85+
fetch: FetchFunction | undefined,
8486
) => {
8587
// Do an optimistic update to the chat state to show the updated messages
8688
// immediately.
@@ -113,7 +115,6 @@ const getStreamedResponse = async (
113115

114116
return await callChatApi({
115117
api,
116-
messages: constructedMessagesPayload,
117118
body: {
118119
messages: constructedMessagesPayload,
119120
data: chatRequest.data,
@@ -149,6 +150,8 @@ const getStreamedResponse = async (
149150
},
150151
onFinish,
151152
generateId,
153+
onToolCall: undefined, // not implemented yet
154+
fetch,
152155
});
153156
};
154157

@@ -172,6 +175,7 @@ export function useChat({
172175
headers,
173176
body,
174177
generateId = generateIdFunc,
178+
fetch,
175179
}: UseChatOptions = {}): UseChatHelpers {
176180
// Generate a unique id for the chat if not provided.
177181
const chatId = id || `chat-${uniqueId++}`;
@@ -238,6 +242,7 @@ export function useChat({
238242
onFinish,
239243
onResponse,
240244
sendExtraMessageFields,
245+
fetch,
241246
),
242247
experimental_onFunctionCall,
243248
experimental_onToolCall,

‎packages/svelte/src/use-completion.ts

+2
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export function useCompletion({
6464
onResponse,
6565
onFinish,
6666
onError,
67+
fetch,
6768
}: UseCompletionOptions = {}): UseCompletionHelpers {
6869
// Generate an unique id for the completion if not provided.
6970
const completionId = id || `completion-${uniqueId++}`;
@@ -127,6 +128,7 @@ export function useCompletion({
127128
onData(data) {
128129
streamData.set([...(existingData || []), ...(data || [])]);
129130
},
131+
fetch,
130132
});
131133
};
132134

‎packages/ui-utils/src/call-chat-api.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { parseComplexResponse } from './parse-complex-response';
22
import { IdGenerator, JSONValue, Message, UseChatOptions } from './types';
33
import { createChunkDecoder } from './index';
44

5+
// use function to allow for mocking in tests:
6+
const getOriginalFetch = () => fetch;
7+
58
export async function callChatApi({
69
api,
7-
messages,
810
body,
911
streamMode = 'stream-data',
1012
credentials,
@@ -16,20 +18,21 @@ export async function callChatApi({
1618
onFinish,
1719
onToolCall,
1820
generateId,
21+
fetch = getOriginalFetch(),
1922
}: {
2023
api: string;
21-
messages: Omit<Message, 'id'>[];
2224
body: Record<string, any>;
23-
streamMode?: 'stream-data' | 'text';
24-
credentials?: RequestCredentials;
25-
headers?: HeadersInit;
26-
abortController?: () => AbortController | null;
25+
streamMode: 'stream-data' | 'text' | undefined;
26+
credentials: RequestCredentials | undefined;
27+
headers: HeadersInit | undefined;
28+
abortController: (() => AbortController | null) | undefined;
2729
restoreMessagesOnFailure: () => void;
28-
onResponse?: (response: Response) => void | Promise<void>;
30+
onResponse: ((response: Response) => void | Promise<void>) | undefined;
2931
onUpdate: (merged: Message[], data: JSONValue[] | undefined) => void;
30-
onFinish?: (message: Message) => void;
31-
onToolCall?: UseChatOptions['onToolCall'];
32+
onFinish: ((message: Message) => void) | undefined;
33+
onToolCall: UseChatOptions['onToolCall'] | undefined;
3234
generateId: IdGenerator;
35+
fetch: ReturnType<typeof getOriginalFetch> | undefined;
3336
}) {
3437
const response = await fetch(api, {
3538
method: 'POST',

‎packages/ui-utils/src/call-completion-api.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { readDataStream } from './read-data-stream';
22
import { JSONValue } from './types';
33
import { createChunkDecoder } from './index';
44

5+
// use function to allow for mocking in tests:
6+
const getOriginalFetch = () => fetch;
7+
58
export async function callCompletionApi({
69
api,
710
prompt,
@@ -17,21 +20,23 @@ export async function callCompletionApi({
1720
onFinish,
1821
onError,
1922
onData,
23+
fetch = getOriginalFetch(),
2024
}: {
2125
api: string;
2226
prompt: string;
23-
credentials?: RequestCredentials;
24-
headers?: HeadersInit;
27+
credentials: RequestCredentials | undefined;
28+
headers: HeadersInit | undefined;
2529
body: Record<string, any>;
26-
streamMode?: 'stream-data' | 'text';
30+
streamMode: 'stream-data' | 'text' | undefined;
2731
setCompletion: (completion: string) => void;
2832
setLoading: (loading: boolean) => void;
2933
setError: (error: Error | undefined) => void;
3034
setAbortController: (abortController: AbortController | null) => void;
31-
onResponse?: (response: Response) => void | Promise<void>;
32-
onFinish?: (prompt: string, completion: string) => void;
33-
onError?: (error: Error) => void;
34-
onData?: (data: JSONValue[]) => void;
35+
onResponse: ((response: Response) => void | Promise<void>) | undefined;
36+
onFinish: ((prompt: string, completion: string) => void) | undefined;
37+
onError: ((error: Error) => void) | undefined;
38+
onData: ((data: JSONValue[]) => void) | undefined;
39+
fetch: ReturnType<typeof getOriginalFetch> | undefined;
3540
}) {
3641
try {
3742
setLoading(true);

‎packages/ui-utils/src/types.ts

+17
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ either synchronously or asynchronously.
386386

387387
/** Stream mode (default to "stream-data") */
388388
streamMode?: 'stream-data' | 'text';
389+
390+
/**
391+
Custom fetch implementation. You can use it as a middleware to intercept requests,
392+
or to provide a custom fetch implementation for e.g. testing.
393+
*/
394+
fetch?: FetchFunction;
389395
};
390396

391397
export type UseCompletionOptions = {
@@ -454,6 +460,12 @@ export type UseCompletionOptions = {
454460

455461
/** Stream mode (default to "stream-data") */
456462
streamMode?: 'stream-data' | 'text';
463+
464+
/**
465+
Custom fetch implementation. You can use it as a middleware to intercept requests,
466+
or to provide a custom fetch implementation for e.g. testing.
467+
*/
468+
fetch?: FetchFunction;
457469
};
458470

459471
export type JSONValue =
@@ -487,3 +499,8 @@ export type DataMessage = {
487499
role: 'data';
488500
data: JSONValue; // application-specific data
489501
};
502+
503+
/**
504+
* Fetch function type (standardizes the version of fetch used).
505+
*/
506+
export type FetchFunction = typeof fetch;

‎packages/ui-utils/src/use-assistant-types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { FetchFunction } from './types';
2+
13
// Define a type for the assistant status
24
export type AssistantStatus = 'in_progress' | 'awaiting_message';
35

@@ -35,4 +37,10 @@ export type UseAssistantOptions = {
3537
* An optional callback that will be called when the assistant encounters an error.
3638
*/
3739
onError?: (error: Error) => void;
40+
41+
/**
42+
Custom fetch implementation. You can use it as a middleware to intercept requests,
43+
or to provide a custom fetch implementation for e.g. testing.
44+
*/
45+
fetch?: FetchFunction;
3846
};

‎packages/vue/src/use-chat.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export function useChat({
8383
headers,
8484
body,
8585
generateId = generateIdFunc,
86+
fetch,
8687
}: UseChatOptions = {}): UseChatHelpers {
8788
// Generate a unique ID for the chat if not provided.
8889
const chatId = id || `chat-${uniqueId++}`;
@@ -164,7 +165,6 @@ export function useChat({
164165

165166
return await callChatApi({
166167
api,
167-
messages: constructedMessagesPayload,
168168
body: {
169169
messages: constructedMessagesPayload,
170170
data: chatRequest.data,
@@ -194,6 +194,8 @@ export function useChat({
194194
mutate(previousMessages);
195195
},
196196
generateId,
197+
onToolCall: undefined, // not implemented yet
198+
fetch,
197199
});
198200
},
199201
experimental_onFunctionCall,

‎packages/vue/src/use-completion.ts

+2
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export function useCompletion({
6767
onResponse,
6868
onFinish,
6969
onError,
70+
fetch,
7071
}: UseCompletionOptions = {}): UseCompletionHelpers {
7172
// Generate an unique id for the completion if not provided.
7273
const completionId = id || `completion-${uniqueId++}`;
@@ -132,6 +133,7 @@ export function useCompletion({
132133
onData: data => {
133134
mutateStreamData(() => [...existingData, ...(data ?? [])]);
134135
},
136+
fetch,
135137
});
136138
}
137139

‎pnpm-lock.yaml

+24-60
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.