@reverbia/sdk 1.0.0-next.20251202090922 → 1.0.0-next.20251202095402

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.
@@ -818,6 +818,218 @@ var createClientConfig = (config) => ({
818
818
  // src/client/client.gen.ts
819
819
  var client = createClient(createClientConfig(createConfig()));
820
820
 
821
+ // src/lib/chat/constants.ts
822
+ var DEFAULT_LOCAL_CHAT_MODEL = "onnx-community/Qwen2.5-0.5B-Instruct";
823
+
824
+ // src/lib/chat/pipeline.ts
825
+ var sharedPipeline = null;
826
+ var currentModel = null;
827
+ var currentDevice = null;
828
+ async function getTextGenerationPipeline(options) {
829
+ const { model, device = "wasm", dtype = "q4" } = options;
830
+ if (sharedPipeline && currentModel === model && currentDevice === device) {
831
+ return sharedPipeline;
832
+ }
833
+ const { pipeline, env } = await import("./transformers.node-BSHUG7OY.mjs");
834
+ env.allowLocalModels = false;
835
+ if (env.backends?.onnx) {
836
+ env.backends.onnx.logLevel = "fatal";
837
+ }
838
+ console.log(`[Pipeline] Loading model: ${model} on ${device}...`);
839
+ sharedPipeline = await pipeline("text-generation", model, {
840
+ dtype,
841
+ device
842
+ });
843
+ currentModel = model;
844
+ currentDevice = device;
845
+ console.log(`[Pipeline] Model loaded: ${model}`);
846
+ return sharedPipeline;
847
+ }
848
+
849
+ // src/lib/chat/generation.ts
850
+ async function generateLocalChatCompletion(messages, options = {}) {
851
+ const {
852
+ model = DEFAULT_LOCAL_CHAT_MODEL,
853
+ temperature = 0.7,
854
+ max_tokens = 1024,
855
+ top_p = 0.9,
856
+ onToken,
857
+ signal
858
+ } = options;
859
+ const { TextStreamer } = await import("./transformers.node-BSHUG7OY.mjs");
860
+ const chatPipeline = await getTextGenerationPipeline({
861
+ model,
862
+ device: "wasm",
863
+ dtype: "q4"
864
+ });
865
+ class CallbackStreamer extends TextStreamer {
866
+ constructor(tokenizer, cb) {
867
+ super(tokenizer, {
868
+ skip_prompt: true,
869
+ skip_special_tokens: true
870
+ });
871
+ this.cb = cb;
872
+ }
873
+ on_finalized_text(text) {
874
+ if (signal?.aborted) {
875
+ throw new Error("AbortError");
876
+ }
877
+ this.cb(text);
878
+ }
879
+ }
880
+ const streamer = onToken ? new CallbackStreamer(chatPipeline.tokenizer, onToken) : void 0;
881
+ const output = await chatPipeline(messages, {
882
+ max_new_tokens: max_tokens,
883
+ temperature,
884
+ top_p,
885
+ streamer,
886
+ return_full_text: false
887
+ });
888
+ return output;
889
+ }
890
+
891
+ // src/lib/tools/selector.ts
892
+ var DEFAULT_TOOL_SELECTOR_MODEL = "Xenova/LaMini-GPT-124M";
893
+ function buildToolSelectionPrompt(userMessage, tools) {
894
+ const toolList = tools.map((t) => `${t.name} (${t.description})`).join("\n");
895
+ return `Pick the best tool for the task. Reply with ONLY the tool name.
896
+
897
+ Available tools:
898
+ ${toolList}
899
+ none (no tool needed)
900
+
901
+ Task: "${userMessage}"
902
+
903
+ Best tool:`;
904
+ }
905
+ function extractParams(userMessage, tool) {
906
+ const params = {};
907
+ if (!tool.parameters) return params;
908
+ for (const param of tool.parameters) {
909
+ if (param.name === "expression" || param.name === "query") {
910
+ params[param.name] = userMessage;
911
+ } else if (param.name === "location" || param.name === "city") {
912
+ const words = userMessage.split(/\s+/);
913
+ const capitalizedWords = words.filter(
914
+ (w) => w.length > 1 && w[0] === w[0].toUpperCase()
915
+ );
916
+ params[param.name] = capitalizedWords.length > 0 ? capitalizedWords.join(" ") : userMessage;
917
+ } else if (param.name === "text" || param.name === "input") {
918
+ params[param.name] = userMessage;
919
+ } else {
920
+ params[param.name] = userMessage;
921
+ }
922
+ }
923
+ return params;
924
+ }
925
+ function parseToolSelectionResponse(response, tools, userMessage) {
926
+ console.log("[Tool Selector] Raw response:", response);
927
+ const cleaned = response.toLowerCase().trim().split(/[\s\n,.]+/)[0].replace(/[^a-z0-9_-]/g, "");
928
+ console.log("[Tool Selector] Parsed tool name:", cleaned);
929
+ if (cleaned === "none" || cleaned === "null" || cleaned === "") {
930
+ console.log("[Tool Selector] No tool selected");
931
+ return { toolSelected: false };
932
+ }
933
+ const selectedTool = tools.find((t) => t.name.toLowerCase() === cleaned);
934
+ if (!selectedTool) {
935
+ const fuzzyTool = tools.find(
936
+ (t) => t.name.toLowerCase().includes(cleaned) || cleaned.includes(t.name.toLowerCase())
937
+ );
938
+ if (fuzzyTool) {
939
+ console.log(`[Tool Selector] Fuzzy matched tool: ${fuzzyTool.name}`);
940
+ const params2 = extractParams(userMessage, fuzzyTool);
941
+ return {
942
+ toolSelected: true,
943
+ toolName: fuzzyTool.name,
944
+ parameters: params2,
945
+ confidence: 0.6
946
+ };
947
+ }
948
+ console.warn(`[Tool Selector] Unknown tool: ${cleaned}`);
949
+ return { toolSelected: false };
950
+ }
951
+ const params = extractParams(userMessage, selectedTool);
952
+ console.log(`[Tool Selector] Selected tool: ${selectedTool.name}`, params);
953
+ return {
954
+ toolSelected: true,
955
+ toolName: selectedTool.name,
956
+ parameters: params,
957
+ confidence: 0.9
958
+ };
959
+ }
960
+ async function selectTool(userMessage, tools, options = {}) {
961
+ const {
962
+ model = DEFAULT_TOOL_SELECTOR_MODEL,
963
+ signal,
964
+ device = "wasm"
965
+ } = options;
966
+ if (!tools.length) {
967
+ return { toolSelected: false };
968
+ }
969
+ console.log(
970
+ `[Tool Selector] analyzing message: "${userMessage}" with model ${model}`
971
+ );
972
+ try {
973
+ const selectorPipeline = await getTextGenerationPipeline({
974
+ model,
975
+ device,
976
+ dtype: "q4"
977
+ // Aggressive quantization for speed
978
+ });
979
+ const prompt = buildToolSelectionPrompt(userMessage, tools);
980
+ const output = await selectorPipeline(prompt, {
981
+ max_new_tokens: 4,
982
+ // Just need the tool name
983
+ temperature: 0,
984
+ // Deterministic
985
+ do_sample: false,
986
+ return_full_text: false
987
+ });
988
+ if (signal?.aborted) {
989
+ return { toolSelected: false };
990
+ }
991
+ const generatedText = output?.[0]?.generated_text || output?.generated_text || "";
992
+ return parseToolSelectionResponse(generatedText, tools, userMessage);
993
+ } catch (error) {
994
+ console.error("[Tool Selector] Error:", error);
995
+ return { toolSelected: false };
996
+ }
997
+ }
998
+ var preloadPromise = null;
999
+ async function preloadToolSelectorModel(options = {}) {
1000
+ if (preloadPromise) {
1001
+ return preloadPromise;
1002
+ }
1003
+ const { model = DEFAULT_TOOL_SELECTOR_MODEL, device = "wasm" } = options;
1004
+ console.log(`[Tool Selector] Preloading model: ${model}`);
1005
+ preloadPromise = getTextGenerationPipeline({
1006
+ model,
1007
+ device,
1008
+ dtype: "q4"
1009
+ }).then(() => {
1010
+ console.log(`[Tool Selector] Model preloaded: ${model}`);
1011
+ }).catch((error) => {
1012
+ console.warn("[Tool Selector] Failed to preload model:", error);
1013
+ preloadPromise = null;
1014
+ });
1015
+ return preloadPromise;
1016
+ }
1017
+ async function executeTool(tool, params) {
1018
+ try {
1019
+ console.log(
1020
+ `[Tool Selector] Executing tool ${tool.name} with params:`,
1021
+ params
1022
+ );
1023
+ const result = await tool.execute(params);
1024
+ console.log(`[Tool Selector] Tool ${tool.name} execution result:`, result);
1025
+ return { success: true, result };
1026
+ } catch (error) {
1027
+ const errorMessage = error instanceof Error ? error.message : "Tool execution failed";
1028
+ console.error(`[Tool Selector] Tool ${tool.name} failed:`, errorMessage);
1029
+ return { success: false, error: errorMessage };
1030
+ }
1031
+ }
1032
+
821
1033
  // src/react/useChat.ts
822
1034
  function useChat(options) {
823
1035
  const {
@@ -825,9 +1037,15 @@ function useChat(options) {
825
1037
  baseUrl = BASE_URL,
826
1038
  onData: globalOnData,
827
1039
  onFinish,
828
- onError
1040
+ onError,
1041
+ chatProvider = "api",
1042
+ localModel = DEFAULT_LOCAL_CHAT_MODEL,
1043
+ tools,
1044
+ toolSelectorModel = DEFAULT_TOOL_SELECTOR_MODEL,
1045
+ onToolExecution
829
1046
  } = options || {};
830
1047
  const [isLoading, setIsLoading] = useState(false);
1048
+ const [isSelectingTool, setIsSelectingTool] = useState(false);
831
1049
  const abortControllerRef = useRef(null);
832
1050
  const stop = useCallback(() => {
833
1051
  if (abortControllerRef.current) {
@@ -843,120 +1061,257 @@ function useChat(options) {
843
1061
  }
844
1062
  };
845
1063
  }, []);
1064
+ useEffect(() => {
1065
+ if (tools && tools.length > 0) {
1066
+ preloadToolSelectorModel({ model: toolSelectorModel });
1067
+ }
1068
+ }, [tools, toolSelectorModel]);
846
1069
  const sendMessage = useCallback(
847
1070
  async ({
848
1071
  messages,
849
1072
  model,
850
- onData
1073
+ onData,
1074
+ runTools = true
851
1075
  }) => {
852
1076
  if (!messages?.length) {
853
1077
  const errorMsg = "messages are required to call sendMessage.";
854
1078
  if (onError) onError(new Error(errorMsg));
855
1079
  return { data: null, error: errorMsg };
856
1080
  }
857
- if (!model) {
858
- const errorMsg = "model is required to call sendMessage.";
859
- if (onError) onError(new Error(errorMsg));
860
- return { data: null, error: errorMsg };
861
- }
862
- if (!getToken) {
863
- const errorMsg = "Token getter function is required.";
864
- if (onError) onError(new Error(errorMsg));
865
- return { data: null, error: errorMsg };
866
- }
867
1081
  if (abortControllerRef.current) {
868
1082
  abortControllerRef.current.abort();
869
1083
  }
870
1084
  const abortController = new AbortController();
871
1085
  abortControllerRef.current = abortController;
872
1086
  setIsLoading(true);
873
- try {
874
- const token = await getToken();
875
- if (!token) {
876
- const errorMsg = "No access token available.";
877
- setIsLoading(false);
878
- if (onError) onError(new Error(errorMsg));
879
- return { data: null, error: errorMsg };
880
- }
881
- const sseResult = await client.sse.post({
882
- baseUrl,
883
- url: "/api/v1/chat/completions",
884
- body: {
885
- messages,
886
- model,
887
- stream: true
888
- },
889
- headers: {
890
- "Content-Type": "application/json",
891
- Authorization: `Bearer ${token}`
892
- },
893
- signal: abortController.signal
894
- });
895
- let accumulatedContent = "";
896
- let completionId = "";
897
- let completionModel = "";
898
- let accumulatedUsage = {};
899
- let finishReason;
900
- for await (const chunk of sseResult.stream) {
901
- if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
902
- continue;
1087
+ let toolExecutionResult;
1088
+ let messagesWithToolContext = messages;
1089
+ if (runTools && tools && tools.length > 0) {
1090
+ const lastUserMessage = [...messages].reverse().find((m) => m.role === "user");
1091
+ if (lastUserMessage?.content) {
1092
+ setIsSelectingTool(true);
1093
+ try {
1094
+ const selectionResult = await selectTool(
1095
+ lastUserMessage.content,
1096
+ tools,
1097
+ {
1098
+ model: toolSelectorModel,
1099
+ signal: abortController.signal
1100
+ }
1101
+ );
1102
+ if (selectionResult.toolSelected && selectionResult.toolName) {
1103
+ const selectedTool = tools.find(
1104
+ (t) => t.name === selectionResult.toolName
1105
+ );
1106
+ if (selectedTool) {
1107
+ const execResult = await executeTool(
1108
+ selectedTool,
1109
+ selectionResult.parameters || {}
1110
+ );
1111
+ toolExecutionResult = {
1112
+ toolName: selectionResult.toolName,
1113
+ success: execResult.success,
1114
+ result: execResult.result,
1115
+ error: execResult.error
1116
+ };
1117
+ if (onToolExecution) {
1118
+ onToolExecution(toolExecutionResult);
1119
+ }
1120
+ if (toolExecutionResult.success && toolExecutionResult.result !== void 0) {
1121
+ const toolResultContext = {
1122
+ role: "system",
1123
+ content: `Tool "${toolExecutionResult.toolName}" was executed with the following result:
1124
+ ${JSON.stringify(
1125
+ toolExecutionResult.result,
1126
+ null,
1127
+ 2
1128
+ )}
1129
+
1130
+ Use this information to respond to the user's request.`
1131
+ };
1132
+ messagesWithToolContext = [...messages, toolResultContext];
1133
+ } else if (toolExecutionResult.error) {
1134
+ const toolErrorContext = {
1135
+ role: "system",
1136
+ content: `Tool "${toolExecutionResult.toolName}" was executed but encountered an error: ${toolExecutionResult.error}
1137
+
1138
+ Please inform the user about this issue and try to help them alternatively.`
1139
+ };
1140
+ messagesWithToolContext = [...messages, toolErrorContext];
1141
+ }
1142
+ }
1143
+ }
1144
+ } catch (err) {
1145
+ console.warn("Tool selection error:", err);
1146
+ } finally {
1147
+ setIsSelectingTool(false);
903
1148
  }
904
- if (chunk && typeof chunk === "object") {
905
- const chunkData = chunk;
906
- if (chunkData.id && !completionId) {
907
- completionId = chunkData.id;
1149
+ }
1150
+ }
1151
+ try {
1152
+ if (chatProvider === "local") {
1153
+ let accumulatedContent = "";
1154
+ const usedModel = localModel;
1155
+ const formattedMessages = messagesWithToolContext.map((m) => ({
1156
+ role: m.role || "user",
1157
+ content: m.content || ""
1158
+ }));
1159
+ await generateLocalChatCompletion(formattedMessages, {
1160
+ model: usedModel,
1161
+ signal: abortController.signal,
1162
+ onToken: (token) => {
1163
+ accumulatedContent += token;
1164
+ if (onData) onData(token);
1165
+ if (globalOnData) globalOnData(token);
908
1166
  }
909
- if (chunkData.model && !completionModel) {
910
- completionModel = chunkData.model;
1167
+ });
1168
+ const completion = {
1169
+ id: `local-${Date.now()}`,
1170
+ model: usedModel,
1171
+ choices: [
1172
+ {
1173
+ index: 0,
1174
+ message: {
1175
+ role: "assistant",
1176
+ content: accumulatedContent
1177
+ },
1178
+ finish_reason: "stop"
1179
+ }
1180
+ ],
1181
+ usage: {
1182
+ prompt_tokens: 0,
1183
+ // Not easily available from simple pipeline usage
1184
+ completion_tokens: 0,
1185
+ total_tokens: 0
911
1186
  }
912
- if (chunkData.usage) {
913
- accumulatedUsage = {
914
- ...accumulatedUsage,
915
- ...chunkData.usage
916
- };
1187
+ };
1188
+ setIsLoading(false);
1189
+ if (onFinish) {
1190
+ onFinish(completion);
1191
+ }
1192
+ return {
1193
+ data: completion,
1194
+ error: null,
1195
+ toolExecution: toolExecutionResult
1196
+ };
1197
+ } else {
1198
+ if (!model) {
1199
+ const errorMsg = "model is required to call sendMessage.";
1200
+ if (onError) onError(new Error(errorMsg));
1201
+ return {
1202
+ data: null,
1203
+ error: errorMsg,
1204
+ toolExecution: toolExecutionResult
1205
+ };
1206
+ }
1207
+ if (!getToken) {
1208
+ const errorMsg = "Token getter function is required.";
1209
+ if (onError) onError(new Error(errorMsg));
1210
+ return {
1211
+ data: null,
1212
+ error: errorMsg,
1213
+ toolExecution: toolExecutionResult
1214
+ };
1215
+ }
1216
+ const token = await getToken();
1217
+ if (!token) {
1218
+ const errorMsg = "No access token available.";
1219
+ setIsLoading(false);
1220
+ if (onError) onError(new Error(errorMsg));
1221
+ return {
1222
+ data: null,
1223
+ error: errorMsg,
1224
+ toolExecution: toolExecutionResult
1225
+ };
1226
+ }
1227
+ const sseResult = await client.sse.post({
1228
+ baseUrl,
1229
+ url: "/api/v1/chat/completions",
1230
+ body: {
1231
+ messages: messagesWithToolContext,
1232
+ model,
1233
+ stream: true
1234
+ },
1235
+ headers: {
1236
+ "Content-Type": "application/json",
1237
+ Authorization: `Bearer ${token}`
1238
+ },
1239
+ signal: abortController.signal
1240
+ });
1241
+ let accumulatedContent = "";
1242
+ let completionId = "";
1243
+ let completionModel = "";
1244
+ let accumulatedUsage = {};
1245
+ let finishReason;
1246
+ for await (const chunk of sseResult.stream) {
1247
+ if (typeof chunk === "string" && (chunk.trim() === "[DONE]" || chunk.includes("[DONE]"))) {
1248
+ continue;
917
1249
  }
918
- if (chunkData.choices && Array.isArray(chunkData.choices) && chunkData.choices.length > 0) {
919
- const choice = chunkData.choices[0];
920
- if (choice.delta?.content) {
921
- const content = choice.delta.content;
922
- accumulatedContent += content;
923
- if (onData) {
924
- onData(content);
1250
+ if (chunk && typeof chunk === "object") {
1251
+ const chunkData = chunk;
1252
+ if (chunkData.id && !completionId) {
1253
+ completionId = chunkData.id;
1254
+ }
1255
+ if (chunkData.model && !completionModel) {
1256
+ completionModel = chunkData.model;
1257
+ }
1258
+ if (chunkData.usage) {
1259
+ accumulatedUsage = {
1260
+ ...accumulatedUsage,
1261
+ ...chunkData.usage
1262
+ };
1263
+ }
1264
+ if (chunkData.choices && Array.isArray(chunkData.choices) && chunkData.choices.length > 0) {
1265
+ const choice = chunkData.choices[0];
1266
+ if (choice.delta?.content) {
1267
+ const content = choice.delta.content;
1268
+ accumulatedContent += content;
1269
+ if (onData) {
1270
+ onData(content);
1271
+ }
1272
+ if (globalOnData) {
1273
+ globalOnData(content);
1274
+ }
925
1275
  }
926
- if (globalOnData) {
927
- globalOnData(content);
1276
+ if (choice.finish_reason) {
1277
+ finishReason = choice.finish_reason;
928
1278
  }
929
1279
  }
930
- if (choice.finish_reason) {
931
- finishReason = choice.finish_reason;
932
- }
933
1280
  }
934
1281
  }
1282
+ const completion = {
1283
+ id: completionId,
1284
+ model: completionModel,
1285
+ choices: [
1286
+ {
1287
+ index: 0,
1288
+ message: {
1289
+ role: "assistant",
1290
+ content: accumulatedContent
1291
+ },
1292
+ finish_reason: finishReason
1293
+ }
1294
+ ],
1295
+ usage: Object.keys(accumulatedUsage).length > 0 ? accumulatedUsage : void 0
1296
+ };
1297
+ setIsLoading(false);
1298
+ if (onFinish) {
1299
+ onFinish(completion);
1300
+ }
1301
+ return {
1302
+ data: completion,
1303
+ error: null,
1304
+ toolExecution: toolExecutionResult
1305
+ };
935
1306
  }
936
- const completion = {
937
- id: completionId,
938
- model: completionModel,
939
- choices: [
940
- {
941
- index: 0,
942
- message: {
943
- role: "assistant",
944
- content: accumulatedContent
945
- },
946
- finish_reason: finishReason
947
- }
948
- ],
949
- usage: Object.keys(accumulatedUsage).length > 0 ? accumulatedUsage : void 0
950
- };
951
- setIsLoading(false);
952
- if (onFinish) {
953
- onFinish(completion);
954
- }
955
- return { data: completion, error: null };
956
1307
  } catch (err) {
957
1308
  if (err instanceof Error && err.name === "AbortError") {
958
1309
  setIsLoading(false);
959
- return { data: null, error: "Request aborted" };
1310
+ return {
1311
+ data: null,
1312
+ error: "Request aborted",
1313
+ toolExecution: toolExecutionResult
1314
+ };
960
1315
  }
961
1316
  const errorMsg = err instanceof Error ? err.message : "Failed to send message.";
962
1317
  const errorObj = err instanceof Error ? err : new Error(errorMsg);
@@ -964,17 +1319,33 @@ function useChat(options) {
964
1319
  if (onError) {
965
1320
  onError(errorObj);
966
1321
  }
967
- return { data: null, error: errorMsg };
1322
+ return {
1323
+ data: null,
1324
+ error: errorMsg,
1325
+ toolExecution: toolExecutionResult
1326
+ };
968
1327
  } finally {
969
1328
  if (abortControllerRef.current === abortController) {
970
1329
  abortControllerRef.current = null;
971
1330
  }
972
1331
  }
973
1332
  },
974
- [getToken, baseUrl, globalOnData, onFinish, onError]
1333
+ [
1334
+ getToken,
1335
+ baseUrl,
1336
+ globalOnData,
1337
+ onFinish,
1338
+ onError,
1339
+ chatProvider,
1340
+ localModel,
1341
+ tools,
1342
+ toolSelectorModel,
1343
+ onToolExecution
1344
+ ]
975
1345
  );
976
1346
  return {
977
1347
  isLoading,
1348
+ isSelectingTool,
978
1349
  sendMessage,
979
1350
  stop
980
1351
  };
@@ -1896,12 +2267,15 @@ var extractConversationContext = (messages, maxMessages = 3) => {
1896
2267
  return userMessages.trim();
1897
2268
  };
1898
2269
  export {
2270
+ DEFAULT_TOOL_SELECTOR_MODEL,
1899
2271
  createMemoryContextSystemMessage,
1900
2272
  decryptData,
1901
2273
  decryptDataBytes,
1902
2274
  encryptData,
2275
+ executeTool,
1903
2276
  extractConversationContext,
1904
2277
  formatMemoriesForChat,
2278
+ selectTool,
1905
2279
  useChat,
1906
2280
  useEncryption,
1907
2281
  useMemory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reverbia/sdk",
3
- "version": "1.0.0-next.20251202090922",
3
+ "version": "1.0.0-next.20251202095402",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",