@ai-sdk/solid
Version:
> **Warning** `@ai-sdk/solid` has been deprecated and will be removed in AI SDK 5
849 lines (841 loc) • 26.8 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
experimental_useObject: () => experimental_useObject,
useAssistant: () => useAssistant,
useChat: () => useChat,
useCompletion: () => useCompletion
});
module.exports = __toCommonJS(src_exports);
// src/use-chat.ts
var import_ui_utils = require("@ai-sdk/ui-utils");
var import_solid_js3 = require("solid-js");
var import_store = require("solid-js/store");
// src/utils/convert-to-accessor-options.ts
var import_solid_js = require("solid-js");
function convertToAccessorOptions(options) {
const resolvedOptions = typeof options === "function" ? options() : options;
return Object.entries(resolvedOptions).reduce(
(reactiveOptions, [key, value]) => {
reactiveOptions[key] = (0, import_solid_js.createMemo)(() => value);
return reactiveOptions;
},
{}
);
}
// src/utils/reactive-lru.ts
var import_solid_js2 = require("solid-js");
var import_trigger = require("@solid-primitives/trigger");
var $KEYS = Symbol("track-keys");
var ReactiveLRU = class extends Map {
#keyTriggers = new import_trigger.TriggerCache();
#valueTriggers = new import_trigger.TriggerCache();
#maxSize;
#accessList = [];
constructor(maxSize = 10, initial) {
super();
this.#maxSize = maxSize;
if (initial) {
for (const [key, value] of initial) {
this.set(key, value);
}
}
}
#recordAccess(key) {
const index = this.#accessList.indexOf(key);
if (index > -1) {
this.#accessList.splice(index, 1);
}
this.#accessList.push(key);
if (this.#accessList.length > this.#maxSize) {
const lru = this.#accessList.shift();
this.delete(lru);
}
}
// reads
has(key) {
this.#keyTriggers.track(key);
const exists = super.has(key);
if (exists) {
this.#recordAccess(key);
}
return exists;
}
get(key) {
this.#valueTriggers.track(key);
const value = super.get(key);
if (value !== void 0) {
this.#recordAccess(key);
}
return value;
}
get size() {
this.#keyTriggers.track($KEYS);
return super.size;
}
*keys() {
for (const key of super.keys()) {
this.#keyTriggers.track(key);
yield key;
}
this.#keyTriggers.track($KEYS);
}
*values() {
for (const [key, v] of super.entries()) {
this.#valueTriggers.track(key);
yield v;
}
this.#keyTriggers.track($KEYS);
}
*entries() {
for (const entry of super.entries()) {
this.#valueTriggers.track(entry[0]);
yield entry;
}
this.#keyTriggers.track($KEYS);
}
// writes
set(key, value) {
(0, import_solid_js2.batch)(() => {
if (super.has(key)) {
if (super.get(key) === value) {
this.#recordAccess(key);
return;
}
} else {
this.#keyTriggers.dirty(key);
this.#keyTriggers.dirty($KEYS);
}
this.#valueTriggers.dirty(key);
super.set(key, value);
this.#recordAccess(key);
});
return this;
}
delete(key) {
const r = super.delete(key);
if (r) {
(0, import_solid_js2.batch)(() => {
this.#keyTriggers.dirty(key);
this.#keyTriggers.dirty($KEYS);
this.#valueTriggers.dirty(key);
const index = this.#accessList.indexOf(key);
if (index > -1) {
this.#accessList.splice(index, 1);
}
});
}
return r;
}
clear() {
if (super.size) {
(0, import_solid_js2.batch)(() => {
for (const v of super.keys()) {
this.#keyTriggers.dirty(v);
this.#valueTriggers.dirty(v);
}
super.clear();
this.#accessList = [];
this.#keyTriggers.dirty($KEYS);
});
}
}
// callback
forEach(callbackfn) {
this.#keyTriggers.track($KEYS);
for (const [key, v] of super.entries()) {
this.#valueTriggers.track(key);
this.#recordAccess(key);
callbackfn(v, key, this);
}
}
[Symbol.iterator]() {
return this.entries();
}
};
// src/use-chat.ts
var chatCache = new ReactiveLRU();
function useChat(rawUseChatOptions = {}) {
const resolvedOptions = (0, import_solid_js3.createMemo)(
() => convertToAccessorOptions(rawUseChatOptions)
);
const prepareFn = (0, import_solid_js3.createMemo)(() => {
const opts = resolvedOptions();
return opts.experimental_prepareRequestBody?.();
});
const useChatOptions = (0, import_solid_js3.createMemo)(() => ({
...resolvedOptions(),
experimental_prepareRequestBody: prepareFn
}));
const api = (0, import_solid_js3.createMemo)(() => useChatOptions().api?.() ?? "/api/chat");
const generateId2 = (0, import_solid_js3.createMemo)(
() => useChatOptions().generateId?.() ?? import_ui_utils.generateId
);
const chatId = (0, import_solid_js3.createMemo)(() => useChatOptions().id?.() ?? generateId2()());
const chatKey = (0, import_solid_js3.createMemo)(() => `${api()}|${chatId()}|messages`);
const _messages = (0, import_solid_js3.createMemo)(
() => chatCache.get(chatKey()) ?? useChatOptions().initialMessages?.() ?? []
);
const [messagesStore, setMessagesStore] = (0, import_store.createStore)(
(0, import_ui_utils.fillMessageParts)(_messages())
);
(0, import_solid_js3.createEffect)(() => {
setMessagesStore((0, import_store.reconcile)((0, import_ui_utils.fillMessageParts)(_messages()), { merge: true }));
});
const mutate = (messages) => {
chatCache.set(chatKey(), messages);
};
const [error, setError] = (0, import_solid_js3.createSignal)(void 0);
const [streamData, setStreamData] = (0, import_solid_js3.createSignal)(
void 0
);
const [status, setStatus] = (0, import_solid_js3.createSignal)("ready");
let messagesRef = (0, import_ui_utils.fillMessageParts)(_messages()) || [];
(0, import_solid_js3.createEffect)(() => {
messagesRef = (0, import_ui_utils.fillMessageParts)(_messages()) || [];
});
let abortController = null;
let extraMetadata = {
credentials: useChatOptions().credentials?.(),
headers: useChatOptions().headers?.(),
body: useChatOptions().body?.()
};
(0, import_solid_js3.createEffect)(() => {
extraMetadata = {
credentials: useChatOptions().credentials?.(),
headers: useChatOptions().headers?.(),
body: useChatOptions().body?.()
};
});
const triggerRequest = async (chatRequest) => {
setError(void 0);
setStatus("submitted");
const messageCount = messagesRef.length;
const maxStep = (0, import_ui_utils.extractMaxToolInvocationStep)(
chatRequest.messages[chatRequest.messages.length - 1]?.toolInvocations
);
try {
abortController = new AbortController();
const streamProtocol = useChatOptions().streamProtocol?.() ?? "data";
const onFinish = useChatOptions().onFinish?.();
const onResponse = useChatOptions().onResponse?.();
const onToolCall = useChatOptions().onToolCall?.();
const sendExtraMessageFields = useChatOptions().sendExtraMessageFields?.();
const keepLastMessageOnError = useChatOptions().keepLastMessageOnError?.() ?? true;
const experimental_prepareRequestBody = useChatOptions().experimental_prepareRequestBody?.();
const previousMessages = messagesRef;
const chatMessages = (0, import_ui_utils.fillMessageParts)(chatRequest.messages);
mutate(chatMessages);
const existingStreamData = streamData() ?? [];
const constructedMessagesPayload = sendExtraMessageFields ? chatMessages : chatMessages.map(
({
role,
content,
experimental_attachments,
data,
annotations,
toolInvocations,
parts
}) => ({
role,
content,
...experimental_attachments !== void 0 && {
experimental_attachments
},
...data !== void 0 && { data },
...annotations !== void 0 && { annotations },
...toolInvocations !== void 0 && { toolInvocations },
...parts !== void 0 && { parts }
})
);
await (0, import_ui_utils.callChatApi)({
api: api(),
body: experimental_prepareRequestBody?.({
id: chatId(),
messages: chatMessages,
requestData: chatRequest.data,
requestBody: chatRequest.body
}) ?? {
id: chatId(),
messages: constructedMessagesPayload,
data: chatRequest.data,
...extraMetadata.body,
...chatRequest.body
},
streamProtocol,
credentials: extraMetadata.credentials,
headers: {
...extraMetadata.headers,
...chatRequest.headers
},
abortController: () => abortController,
restoreMessagesOnFailure() {
if (!keepLastMessageOnError) {
mutate(previousMessages);
}
},
onResponse,
onUpdate({ message, data, replaceLastMessage }) {
setStatus("streaming");
mutate([
...replaceLastMessage ? chatMessages.slice(0, chatMessages.length - 1) : chatMessages,
message
]);
if (data?.length) {
setStreamData([...existingStreamData, ...data]);
}
},
onToolCall,
onFinish,
generateId: generateId2(),
fetch: useChatOptions().fetch?.(),
lastMessage: chatMessages[chatMessages.length - 1]
});
abortController = null;
setStatus("ready");
} catch (err) {
if (err.name === "AbortError") {
abortController = null;
setStatus("ready");
return null;
}
const onError = useChatOptions().onError?.();
if (onError && err instanceof Error) {
onError(err);
}
setError(err);
setStatus("error");
}
const maxSteps = useChatOptions().maxSteps?.() ?? 1;
const messages = messagesRef;
if ((0, import_ui_utils.shouldResubmitMessages)({
originalMaxToolInvocationStep: maxStep,
originalMessageCount: messageCount,
maxSteps,
messages
})) {
await triggerRequest({ messages });
}
};
const append = async (message, {
data,
headers,
body,
experimental_attachments = message.experimental_attachments
} = {}) => {
const attachmentsForRequest = await (0, import_ui_utils.prepareAttachmentsForRequest)(
experimental_attachments
);
const messages = messagesRef.concat({
...message,
id: message.id ?? generateId2()(),
createdAt: message.createdAt ?? /* @__PURE__ */ new Date(),
experimental_attachments: attachmentsForRequest.length > 0 ? attachmentsForRequest : void 0,
parts: (0, import_ui_utils.getMessageParts)(message)
});
return triggerRequest({
messages,
headers,
body,
data
});
};
const reload = async ({
data,
headers,
body
} = {}) => {
if (messagesRef.length === 0) {
return null;
}
const lastMessage = messagesRef[messagesRef.length - 1];
return triggerRequest({
messages: lastMessage.role === "assistant" ? messagesRef.slice(0, -1) : messagesRef,
headers,
body,
data
});
};
const stop = () => {
if (abortController) {
abortController.abort();
abortController = null;
}
};
const setMessages = (messagesArg) => {
if (typeof messagesArg === "function") {
messagesArg = messagesArg(messagesRef);
}
const messagesWithParts = (0, import_ui_utils.fillMessageParts)(messagesArg);
mutate(messagesWithParts);
messagesRef = messagesWithParts;
};
const setData = (dataArg) => {
if (typeof dataArg === "function") {
dataArg = dataArg(streamData());
}
setStreamData(dataArg);
};
const [input, setInput] = (0, import_solid_js3.createSignal)(
useChatOptions().initialInput?.() || ""
);
const handleSubmit = async (event, options = {}, metadata) => {
event?.preventDefault?.();
const inputValue = input();
if (!inputValue && !options.allowEmptySubmit)
return;
const attachmentsForRequest = await (0, import_ui_utils.prepareAttachmentsForRequest)(
options.experimental_attachments
);
if (metadata) {
extraMetadata = {
...extraMetadata,
...metadata
};
}
triggerRequest({
messages: messagesRef.concat({
id: generateId2()(),
role: "user",
content: inputValue,
createdAt: /* @__PURE__ */ new Date(),
experimental_attachments: attachmentsForRequest.length > 0 ? attachmentsForRequest : void 0,
parts: [{ type: "text", text: inputValue }]
}),
headers: options.headers,
body: options.body,
data: options.data
});
setInput("");
};
const handleInputChange = (e) => {
setInput(e.target.value);
};
const addToolResult = ({
toolCallId,
result
}) => {
const currentMessages = messagesRef ?? [];
(0, import_ui_utils.updateToolCallResult)({
messages: currentMessages,
toolCallId,
toolResult: result
});
mutate(currentMessages);
if (status() === "submitted" || status() === "streaming") {
return;
}
const lastMessage = currentMessages[currentMessages.length - 1];
if ((0, import_ui_utils.isAssistantMessageWithCompletedToolCalls)(lastMessage)) {
triggerRequest({ messages: currentMessages });
}
};
const isLoading = (0, import_solid_js3.createMemo)(
() => status() === "submitted" || status() === "streaming"
);
return {
// TODO next major release: replace with direct message store access (breaking change)
messages: () => messagesStore,
id: chatId(),
append,
error,
reload,
stop,
setMessages,
input,
setInput,
handleInputChange,
handleSubmit,
isLoading,
status,
data: streamData,
setData,
addToolResult
};
}
// src/use-completion.ts
var import_ui_utils2 = require("@ai-sdk/ui-utils");
var import_solid_js4 = require("solid-js");
var completionCache = new ReactiveLRU();
function useCompletion(rawUseCompletionOptions = {}) {
const useCompletionOptions = (0, import_solid_js4.createMemo)(
() => convertToAccessorOptions(rawUseCompletionOptions)
);
const api = (0, import_solid_js4.createMemo)(
() => useCompletionOptions().api?.() ?? "/api/completion"
);
const idKey = (0, import_solid_js4.createMemo)(
() => useCompletionOptions().id?.() ?? `completion-${(0, import_solid_js4.createUniqueId)()}`
);
const completionKey = (0, import_solid_js4.createMemo)(() => `${api()}|${idKey()}|completion`);
const completion = (0, import_solid_js4.createMemo)(
() => completionCache.get(completionKey()) ?? useCompletionOptions().initialCompletion?.() ?? ""
);
const mutate = (data) => {
completionCache.set(completionKey(), data);
};
const [error, setError] = (0, import_solid_js4.createSignal)(void 0);
const [streamData, setStreamData] = (0, import_solid_js4.createSignal)(
void 0
);
const [isLoading, setIsLoading] = (0, import_solid_js4.createSignal)(false);
const [abortController, setAbortController] = (0, import_solid_js4.createSignal)(null);
let extraMetadata = {
credentials: useCompletionOptions().credentials?.(),
headers: useCompletionOptions().headers?.(),
body: useCompletionOptions().body?.()
};
(0, import_solid_js4.createEffect)(() => {
extraMetadata = {
credentials: useCompletionOptions().credentials?.(),
headers: useCompletionOptions().headers?.(),
body: useCompletionOptions().body?.()
};
});
const complete = async (prompt, options) => {
const existingData = streamData() ?? [];
return (0, import_ui_utils2.callCompletionApi)({
api: api(),
prompt,
credentials: useCompletionOptions().credentials?.(),
headers: { ...extraMetadata.headers, ...options?.headers },
body: {
...extraMetadata.body,
...options?.body
},
streamProtocol: useCompletionOptions().streamProtocol?.(),
setCompletion: mutate,
setLoading: setIsLoading,
setError,
setAbortController,
onResponse: useCompletionOptions().onResponse?.(),
onFinish: useCompletionOptions().onFinish?.(),
onError: useCompletionOptions().onError?.(),
onData: (data) => {
setStreamData([...existingData, ...data ?? []]);
},
fetch: useCompletionOptions().fetch?.()
});
};
const stop = () => {
if (abortController()) {
abortController().abort();
}
};
const setCompletion = (completion2) => {
mutate(completion2);
};
const [input, setInput] = (0, import_solid_js4.createSignal)(
useCompletionOptions().initialInput?.() ?? ""
);
const handleInputChange = (event) => {
setInput(event.target.value);
};
const handleSubmit = (event) => {
event?.preventDefault?.();
const inputValue = input();
return inputValue ? complete(inputValue) : void 0;
};
return {
completion,
complete,
error,
stop,
setCompletion,
input,
setInput,
handleInputChange,
handleSubmit,
isLoading,
data: streamData
};
}
// src/use-object.ts
var import_provider_utils = require("@ai-sdk/provider-utils");
var import_ui_utils3 = require("@ai-sdk/ui-utils");
var import_solid_js5 = require("solid-js");
var getOriginalFetch = () => fetch;
var objectCache = new ReactiveLRU();
function useObject(rawUseObjectOptions) {
const useObjectOptions = (0, import_solid_js5.createMemo)(
() => convertToAccessorOptions(rawUseObjectOptions)
);
const idKey = (0, import_solid_js5.createMemo)(
() => useObjectOptions().id?.() ?? `object-${(0, import_solid_js5.createUniqueId)()}`
);
const data = (0, import_solid_js5.createMemo)(
() => objectCache.get(idKey()) ?? useObjectOptions().initialValue?.()
);
const mutate = (value) => {
objectCache.set(idKey(), value);
};
const [error, setError] = (0, import_solid_js5.createSignal)();
const [isLoading, setIsLoading] = (0, import_solid_js5.createSignal)(false);
let abortControllerRef = null;
const stop = () => {
try {
abortControllerRef?.abort();
} catch (ignored) {
} finally {
setIsLoading(false);
abortControllerRef = null;
}
};
const submit = async (input) => {
try {
mutate(void 0);
setIsLoading(true);
setError(void 0);
const abortController = new AbortController();
abortControllerRef = abortController;
const actualFetch = fetch ?? getOriginalFetch();
const response = await actualFetch(useObjectOptions().api(), {
method: "POST",
headers: {
"Content-Type": "application/json",
...useObjectOptions().headers?.()
},
credentials: useObjectOptions().credentials?.(),
signal: abortController.signal,
body: JSON.stringify(input)
});
if (!response.ok) {
throw new Error(
await response.text() ?? "Failed to fetch the response."
);
}
if (response.body == null) {
throw new Error("The response body is empty.");
}
let accumulatedText = "";
let latestObject = void 0;
await response.body.pipeThrough(new TextDecoderStream()).pipeTo(
new WritableStream({
write(chunk) {
accumulatedText += chunk;
const { value } = (0, import_ui_utils3.parsePartialJson)(accumulatedText);
const currentObject = value;
if (!(0, import_ui_utils3.isDeepEqualData)(latestObject, currentObject)) {
latestObject = currentObject;
mutate(currentObject);
}
},
close() {
setIsLoading(false);
abortControllerRef = null;
const onFinish = useObjectOptions().onFinish?.();
if (onFinish != null) {
const validationResult = (0, import_provider_utils.safeValidateTypes)({
value: latestObject,
schema: (0, import_ui_utils3.asSchema)(useObjectOptions().schema())
});
onFinish(
validationResult.success ? { object: validationResult.value, error: void 0 } : { object: void 0, error: validationResult.error }
);
}
}
})
);
} catch (error2) {
if ((0, import_provider_utils.isAbortError)(error2)) {
return;
}
const onError = useObjectOptions().onError?.();
if (onError && error2 instanceof Error) {
onError(error2);
}
setIsLoading(false);
setError(error2 instanceof Error ? error2 : new Error(String(error2)));
}
};
return {
submit,
object: data,
error,
isLoading,
stop
};
}
var experimental_useObject = useObject;
// src/use-assistant.ts
var import_provider_utils2 = require("@ai-sdk/provider-utils");
var import_ui_utils4 = require("@ai-sdk/ui-utils");
var import_solid_js6 = require("solid-js");
var import_store2 = require("solid-js/store");
var getOriginalFetch2 = () => fetch;
function useAssistant(rawUseAssistantOptions) {
const useAssistantOptions = (0, import_solid_js6.createMemo)(
() => convertToAccessorOptions(rawUseAssistantOptions)
);
const [messages, setMessages] = (0, import_store2.createStore)([]);
const [input, setInput] = (0, import_solid_js6.createSignal)("");
const [currentThreadId, setCurrentThreadId] = (0, import_solid_js6.createSignal)();
const [status, setStatus] = (0, import_solid_js6.createSignal)("awaiting_message");
const [error, setError] = (0, import_solid_js6.createSignal)();
const handleInputChange = (event) => {
setInput(event.target.value);
};
let abortControllerRef = null;
const stop = () => {
if (abortControllerRef) {
abortControllerRef?.abort();
abortControllerRef = null;
}
};
const append = async (message, requestOptions) => {
setStatus("in_progress");
setMessages((messages2) => [
...messages2,
{
...message,
id: message.id ?? (0, import_ui_utils4.generateId)()
}
]);
setInput("");
const abortController = new AbortController();
try {
abortControllerRef = abortController;
const actualFetch = fetch ?? getOriginalFetch2();
const response = await actualFetch(useAssistantOptions().api(), {
method: "POST",
credentials: useAssistantOptions().credentials?.(),
signal: abortController.signal,
headers: {
"Content-Type": "application/json",
...useAssistantOptions().headers?.()
},
body: JSON.stringify({
...useAssistantOptions().body?.(),
// always use user-provided threadId when available:
threadId: useAssistantOptions().threadId?.() ?? currentThreadId(),
message: message.content,
// optional request data:
data: requestOptions?.data
})
});
if (!response.ok) {
throw new Error(
await response.text() ?? "Failed to fetch the assistant response."
);
}
if (response.body == null) {
throw new Error("The response body is empty.");
}
await (0, import_ui_utils4.processAssistantStream)({
stream: response.body,
onAssistantMessagePart(value) {
setMessages((messages2) => [
...messages2,
{
id: value.id,
role: value.role,
content: value.content[0].text.value,
parts: []
}
]);
},
onTextPart(value) {
setMessages((messages2) => {
const lastMessage = messages2[messages2.length - 1];
return [
...messages2.slice(0, messages2.length - 1),
{
id: lastMessage.id,
role: lastMessage.role,
content: lastMessage.content + value,
parts: lastMessage.parts
}
];
});
},
onAssistantControlDataPart(value) {
setCurrentThreadId(value.threadId);
setMessages((messages2) => {
const lastMessage = messages2[messages2.length - 1];
lastMessage.id = value.messageId;
return [...messages2.slice(0, messages2.length - 1), lastMessage];
});
},
onDataMessagePart(value) {
setMessages((messages2) => [
...messages2,
{
id: value.id ?? (0, import_ui_utils4.generateId)(),
role: "data",
content: "",
data: value.data,
parts: []
}
]);
},
onErrorPart(value) {
setError(new Error(value));
}
});
} catch (error2) {
if ((0, import_provider_utils2.isAbortError)(error2) && abortController.signal.aborted) {
abortControllerRef = null;
return;
}
const onError = useAssistantOptions().onError?.();
if (onError && error2 instanceof Error) {
onError(error2);
}
setError(error2);
} finally {
abortControllerRef = null;
setStatus("awaiting_message");
}
};
const submitMessage = async (event, requestOptions) => {
event?.preventDefault?.();
if (input() === "") {
return;
}
append({ role: "user", content: input(), parts: [] }, requestOptions);
};
const setThreadId = (threadId) => {
setCurrentThreadId(threadId);
setMessages([]);
};
return {
append,
messages,
setMessages,
threadId: currentThreadId,
setThreadId,
input,
setInput,
handleInputChange,
submitMessage,
status,
error,
stop
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
experimental_useObject,
useAssistant,
useChat,
useCompletion
});
//# sourceMappingURL=index.js.map