@threaded/ai 1.0.1 → 1.0.3

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/dist/index.cjs CHANGED
@@ -25,7 +25,9 @@ __export(index_exports, {
25
25
  compose: () => compose,
26
26
  convertMCPSchemaToToolSchema: () => convertMCPSchemaToToolSchema,
27
27
  convertStandardSchemaToJsonSchema: () => convertStandardSchemaToJsonSchema,
28
+ convertStandardSchemaToSchemaProperties: () => convertStandardSchemaToSchemaProperties,
28
29
  createMCPTools: () => createMCPTools,
30
+ embed: () => embed,
29
31
  everyNMessages: () => everyNMessages,
30
32
  everyNTokens: () => everyNTokens,
31
33
  generateApprovalToken: () => generateApprovalToken,
@@ -39,6 +41,7 @@ __export(index_exports, {
39
41
  onApprovalRequested: () => onApprovalRequested,
40
42
  onApprovalResolved: () => onApprovalResolved,
41
43
  parseModelName: () => parseModelName,
44
+ rateLimited: () => rateLimited,
42
45
  removeApprovalListener: () => removeApprovalListener,
43
46
  requestApproval: () => requestApproval,
44
47
  resolveApproval: () => resolveApproval,
@@ -54,24 +57,16 @@ __export(index_exports, {
54
57
  module.exports = __toCommonJS(index_exports);
55
58
 
56
59
  // src/schema.ts
60
+ var import_zod = require("zod");
57
61
  var isStandardSchema = (schema) => {
58
62
  return schema && typeof schema === "object" && "~standard" in schema;
59
63
  };
60
64
  var convertStandardSchemaToJsonSchema = (standardSchema, name = "Schema") => {
61
- try {
62
- const zodModule = require("zod");
63
- if (zodModule && typeof zodModule.toJSONSchema === "function") {
64
- const jsonSchema = zodModule.toJSONSchema(standardSchema);
65
- return {
66
- name,
67
- schema: jsonSchema
68
- };
69
- }
70
- } catch (error) {
71
- }
72
- throw new Error(
73
- "Standard Schema conversion requires zod v4+ with toJSONSchema support. Please install zod@^4.0.0 or provide a JsonSchema object instead."
74
- );
65
+ const jsonSchema = import_zod.z.toJSONSchema(standardSchema);
66
+ return {
67
+ name,
68
+ schema: jsonSchema
69
+ };
75
70
  };
76
71
  var convertMCPSchemaToToolSchema = (mcpSchema) => {
77
72
  if (!mcpSchema?.properties) return {};
@@ -92,6 +87,10 @@ function normalizeSchema(schema, name) {
92
87
  }
93
88
  return schema;
94
89
  }
90
+ var convertStandardSchemaToSchemaProperties = (standardSchema) => {
91
+ const jsonSchema = import_zod.z.toJSONSchema(standardSchema);
92
+ return convertMCPSchemaToToolSchema(jsonSchema);
93
+ };
95
94
 
96
95
  // src/mcp.ts
97
96
  var createMCPTools = async (client) => {
@@ -130,9 +129,10 @@ var Inherit = /* @__PURE__ */ ((Inherit2) => {
130
129
 
131
130
  // src/utils.ts
132
131
  var toolConfigToToolDefinition = (tool) => {
132
+ const schema = isStandardSchema(tool.schema) ? convertStandardSchemaToSchemaProperties(tool.schema) : tool.schema;
133
133
  const properties = {};
134
134
  const required = [];
135
- for (const [key, prop] of Object.entries(tool.schema)) {
135
+ for (const [key, prop] of Object.entries(schema)) {
136
136
  properties[key] = convertSchemaProperty(prop);
137
137
  if (!prop.optional) {
138
138
  required.push(key);
@@ -198,7 +198,66 @@ var maxCalls = (toolConfig, maxCalls2) => ({
198
198
  _maxCalls: maxCalls2
199
199
  });
200
200
 
201
+ // src/embed.ts
202
+ var import_transformers = require("@huggingface/transformers");
203
+ var modelCache = /* @__PURE__ */ new Map();
204
+ var embed = async (model2, text, config) => {
205
+ if (model2.startsWith("openai/")) {
206
+ const modelName = model2.replace("openai/", "");
207
+ const apiKey = getKey("openai") || process.env.OPENAI_API_KEY;
208
+ if (!apiKey) {
209
+ throw new Error("OpenAI API key not found");
210
+ }
211
+ const body = {
212
+ model: modelName,
213
+ input: text
214
+ };
215
+ if (config?.dimensions) {
216
+ body.dimensions = config.dimensions;
217
+ }
218
+ const response = await fetch("https://api.openai.com/v1/embeddings", {
219
+ method: "POST",
220
+ headers: {
221
+ "Content-Type": "application/json",
222
+ Authorization: `Bearer ${apiKey}`
223
+ },
224
+ body: JSON.stringify(body)
225
+ });
226
+ if (!response.ok) {
227
+ const error = await response.text();
228
+ throw new Error(`OpenAI API error: ${error}`);
229
+ }
230
+ const data = await response.json();
231
+ return data.data[0].embedding;
232
+ }
233
+ if (!modelCache.has(model2)) {
234
+ const extractor2 = await (0, import_transformers.pipeline)("feature-extraction", model2, {
235
+ dtype: "fp32"
236
+ });
237
+ modelCache.set(model2, extractor2);
238
+ }
239
+ const extractor = modelCache.get(model2);
240
+ const result = await extractor(text, { pooling: "mean", normalize: true });
241
+ return Array.from(result.data);
242
+ };
243
+
201
244
  // src/providers/openai.ts
245
+ var appendToolCalls = (toolCalls, tcchunklist) => {
246
+ for (const tcchunk of tcchunklist) {
247
+ while (toolCalls.length <= tcchunk.index) {
248
+ toolCalls.push({
249
+ id: "",
250
+ type: "function",
251
+ function: { name: "", arguments: "" }
252
+ });
253
+ }
254
+ const tc = toolCalls[tcchunk.index];
255
+ tc.id += tcchunk.id || "";
256
+ tc.function.name += tcchunk.function?.name || "";
257
+ tc.function.arguments += tcchunk.function?.arguments || "";
258
+ }
259
+ return toolCalls;
260
+ };
202
261
  var callOpenAI = async (config, ctx) => {
203
262
  const { model: model2, instructions, schema } = config;
204
263
  const apiKey = getKey("openai") || process.env.OPENAI_API_KEY;
@@ -265,17 +324,19 @@ var handleOpenAIStream = async (response, ctx) => {
265
324
  const decoder = new TextDecoder();
266
325
  let fullContent = "";
267
326
  let toolCalls = [];
268
- const toolCallsBuffer = {};
327
+ let buffer = "";
269
328
  try {
270
329
  while (true) {
271
330
  const { done, value } = await reader.read();
272
331
  if (done) break;
273
- const chunk = decoder.decode(value);
274
- const lines = chunk.split("\n");
332
+ buffer += decoder.decode(value, { stream: true });
333
+ const lines = buffer.split("\n");
334
+ buffer = lines.pop() || "";
275
335
  for (const line of lines) {
276
336
  if (line.startsWith("data: ")) {
277
- const data = line.slice(6);
337
+ const data = line.slice(6).trim();
278
338
  if (data === "[DONE]") continue;
339
+ if (!data) continue;
279
340
  try {
280
341
  const parsed = JSON.parse(data);
281
342
  const delta = parsed.choices?.[0]?.delta;
@@ -286,25 +347,7 @@ var handleOpenAIStream = async (response, ctx) => {
286
347
  }
287
348
  }
288
349
  if (delta?.tool_calls) {
289
- for (const toolCall of delta.tool_calls) {
290
- const { index } = toolCall;
291
- if (!toolCallsBuffer[index]) {
292
- toolCallsBuffer[index] = {
293
- id: toolCall.id || "",
294
- type: "function",
295
- function: { name: "", arguments: "" }
296
- };
297
- }
298
- if (toolCall.id) {
299
- toolCallsBuffer[index].id = toolCall.id;
300
- }
301
- if (toolCall.function?.name) {
302
- toolCallsBuffer[index].function.name += toolCall.function.name;
303
- }
304
- if (toolCall.function?.arguments) {
305
- toolCallsBuffer[index].function.arguments += toolCall.function.arguments;
306
- }
307
- }
350
+ toolCalls = appendToolCalls(toolCalls, delta.tool_calls);
308
351
  }
309
352
  } catch (e) {
310
353
  }
@@ -314,7 +357,6 @@ var handleOpenAIStream = async (response, ctx) => {
314
357
  } finally {
315
358
  reader.releaseLock();
316
359
  }
317
- toolCalls = Object.values(toolCallsBuffer);
318
360
  const msg = {
319
361
  role: "assistant",
320
362
  content: fullContent
@@ -330,18 +372,66 @@ var handleOpenAIStream = async (response, ctx) => {
330
372
  };
331
373
 
332
374
  // src/providers/anthropic.ts
375
+ var convertToAnthropicFormat = (messages) => {
376
+ const result = [];
377
+ let i = 0;
378
+ while (i < messages.length) {
379
+ const msg = messages[i];
380
+ if (msg.role === "system") {
381
+ i++;
382
+ continue;
383
+ }
384
+ if (msg.role === "assistant") {
385
+ if (msg.tool_calls) {
386
+ result.push({
387
+ role: "assistant",
388
+ content: msg.tool_calls.map((tc) => ({
389
+ type: "tool_use",
390
+ id: tc.id,
391
+ name: tc.function.name,
392
+ input: JSON.parse(tc.function.arguments)
393
+ }))
394
+ });
395
+ } else {
396
+ result.push({
397
+ role: "assistant",
398
+ content: msg.content
399
+ });
400
+ }
401
+ i++;
402
+ } else if (msg.role === "tool") {
403
+ const toolResults = [];
404
+ while (i < messages.length && messages[i].role === "tool") {
405
+ const toolMsg = messages[i];
406
+ toolResults.push({
407
+ type: "tool_result",
408
+ tool_use_id: toolMsg.tool_call_id,
409
+ content: toolMsg.content
410
+ });
411
+ i++;
412
+ }
413
+ result.push({
414
+ role: "user",
415
+ content: toolResults
416
+ });
417
+ } else {
418
+ result.push(msg);
419
+ i++;
420
+ }
421
+ }
422
+ return result;
423
+ };
333
424
  var callAnthropic = async (config, ctx) => {
334
425
  const { model: model2, instructions, schema } = config;
335
426
  const apiKey = getKey("anthropic") || process.env.ANTHROPIC_API_KEY;
336
427
  if (!apiKey) {
337
428
  throw new Error("Anthropic API key not found");
338
429
  }
339
- const messages = [...ctx.history];
340
430
  let system = instructions;
341
- if (messages[0]?.role === "system") {
342
- system = messages[0].content;
343
- messages.shift();
431
+ if (ctx.history[0]?.role === "system") {
432
+ system = ctx.history[0].content;
344
433
  }
434
+ const messages = convertToAnthropicFormat(ctx.history);
345
435
  if (schema) {
346
436
  const schemaPrompt = `
347
437
 
@@ -416,15 +506,18 @@ var handleAnthropicStream = async (response, ctx) => {
416
506
  const decoder = new TextDecoder();
417
507
  let fullContent = "";
418
508
  const toolCalls = [];
509
+ let buffer = "";
419
510
  try {
420
511
  while (true) {
421
512
  const { done, value } = await reader.read();
422
513
  if (done) break;
423
- const chunk = decoder.decode(value);
424
- const lines = chunk.split("\n");
514
+ buffer += decoder.decode(value, { stream: true });
515
+ const lines = buffer.split("\n");
516
+ buffer = lines.pop() || "";
425
517
  for (const line of lines) {
426
518
  if (line.startsWith("data: ")) {
427
- const data = line.slice(6);
519
+ const data = line.slice(6).trim();
520
+ if (!data) continue;
428
521
  try {
429
522
  const parsed = JSON.parse(data);
430
523
  if (parsed.type === "content_block_delta" && parsed.delta?.text) {
@@ -440,10 +533,17 @@ var handleAnthropicStream = async (response, ctx) => {
440
533
  type: "function",
441
534
  function: {
442
535
  name: toolUse.name,
443
- arguments: JSON.stringify(toolUse.input)
444
- }
536
+ arguments: ""
537
+ },
538
+ index: parsed.index
445
539
  });
446
540
  }
541
+ if (parsed.type === "content_block_delta" && parsed.delta?.type === "input_json_delta") {
542
+ const toolCall = toolCalls.find((tc) => tc.index === parsed.index);
543
+ if (toolCall) {
544
+ toolCall.function.arguments += parsed.delta.partial_json;
545
+ }
546
+ }
447
547
  } catch (e) {
448
548
  }
449
549
  }
@@ -457,7 +557,7 @@ var handleAnthropicStream = async (response, ctx) => {
457
557
  content: fullContent
458
558
  };
459
559
  if (toolCalls.length > 0) {
460
- msg.tool_calls = toolCalls;
560
+ msg.tool_calls = toolCalls.map(({ index, ...tc }) => tc);
461
561
  }
462
562
  return {
463
563
  ...ctx,
@@ -553,15 +653,18 @@ var handleGoogleStream = async (response, ctx) => {
553
653
  const decoder = new TextDecoder();
554
654
  let fullContent = "";
555
655
  const toolCalls = [];
656
+ let buffer = "";
556
657
  try {
557
658
  while (true) {
558
659
  const { done, value } = await reader.read();
559
660
  if (done) break;
560
- const chunk = decoder.decode(value);
561
- const lines = chunk.split("\n");
661
+ buffer += decoder.decode(value, { stream: true });
662
+ const lines = buffer.split("\n");
663
+ buffer = lines.pop() || "";
562
664
  for (const line of lines) {
563
665
  if (line.startsWith("data: ")) {
564
- const data = line.slice(6);
666
+ const data = line.slice(6).trim();
667
+ if (!data) continue;
565
668
  try {
566
669
  const parsed = JSON.parse(data);
567
670
  const candidate = parsed.candidates?.[0];
@@ -669,10 +772,16 @@ var model = ({
669
772
  schema
670
773
  } = {}) => {
671
774
  return async (ctxOrMessage) => {
672
- const ctx = typeof ctxOrMessage === "string" ? {
673
- history: [{ role: "user", content: ctxOrMessage }],
674
- tools: []
675
- } : ctxOrMessage;
775
+ const ctx = typeof ctxOrMessage === "string" ? (
776
+ // model()("hello!");
777
+ {
778
+ history: [{ role: "user", content: ctxOrMessage }],
779
+ tools: []
780
+ }
781
+ ) : (
782
+ // model()(/* few shot or history */);
783
+ ctxOrMessage
784
+ );
676
785
  const normalizedSchema = schema ? normalizeSchema(schema) : void 0;
677
786
  const systemMessage = ctx.history.find((m) => m.role === "system");
678
787
  const instructions = systemMessage?.content;
@@ -701,29 +810,18 @@ var executeTools = async (ctx) => {
701
810
  approvalCallback,
702
811
  parallel = false,
703
812
  retryCount = 0,
704
- approvalId
813
+ approvalId,
814
+ executeOnApproval = false
705
815
  } = toolConfig;
706
- const approvalPromises = calls.map(async (call) => {
707
- if (requireApproval) {
708
- let approved;
709
- if (approvalCallback) {
710
- approved = await approvalCallback(call);
711
- } else {
712
- const response = await requestApproval(call, approvalId);
713
- approved = response.approved;
714
- }
715
- return { call, approved };
716
- } else {
717
- return { call, approved: true };
718
- }
719
- });
720
- const approvals = await Promise.all(approvalPromises);
721
816
  const updatedCounts = { ...ctx.toolCallCounts || {} };
722
- const runCall = async (call) => {
723
- const approval = approvals.find((a) => a.call.id === call.id);
724
- if (!approval?.approved) {
817
+ const runCall = async (call, approved) => {
818
+ if (!approved) {
725
819
  if (ctx.stream) {
726
- ctx.stream({ type: "tool_error", call, error: "Tool execution denied by user" });
820
+ ctx.stream({
821
+ type: "tool_error",
822
+ call,
823
+ error: "Tool execution denied by user"
824
+ });
727
825
  }
728
826
  return {
729
827
  call,
@@ -778,7 +876,51 @@ var executeTools = async (ctx) => {
778
876
  }
779
877
  return { call, result: { error } };
780
878
  };
781
- const results = parallel ? await Promise.all(calls.map(runCall)) : await runCallsSequentially(calls, runCall);
879
+ if (executeOnApproval && requireApproval) {
880
+ const resultPromises = calls.map(async (call) => {
881
+ let approved;
882
+ if (approvalCallback) {
883
+ approved = await approvalCallback(call);
884
+ } else {
885
+ const response = await requestApproval(call, approvalId);
886
+ approved = response.approved;
887
+ }
888
+ return runCall(call, approved);
889
+ });
890
+ const results2 = await Promise.all(resultPromises);
891
+ return {
892
+ ...ctx,
893
+ history: [
894
+ ...ctx.history,
895
+ ...results2.map(({ call, result }) => ({
896
+ role: "tool",
897
+ tool_call_id: call.id,
898
+ content: JSON.stringify(result)
899
+ }))
900
+ ],
901
+ toolCallCounts: updatedCounts
902
+ };
903
+ }
904
+ const approvalPromises = calls.map(async (call) => {
905
+ if (requireApproval) {
906
+ let approved;
907
+ if (approvalCallback) {
908
+ approved = await approvalCallback(call);
909
+ } else {
910
+ const response = await requestApproval(call, approvalId);
911
+ approved = response.approved;
912
+ }
913
+ return { call, approved };
914
+ } else {
915
+ return { call, approved: true };
916
+ }
917
+ });
918
+ const approvals = await Promise.all(approvalPromises);
919
+ const runCallWithApproval = async (call) => {
920
+ const approval = approvals.find((a) => a.call.id === call.id);
921
+ return runCall(call, approval?.approved ?? true);
922
+ };
923
+ const results = parallel ? await Promise.all(calls.map(runCallWithApproval)) : await runCallsSequentially(calls, runCallWithApproval);
782
924
  return {
783
925
  ...ctx,
784
926
  history: [
@@ -844,14 +986,15 @@ var createThread = (id, store) => {
844
986
  }
845
987
  };
846
988
  };
847
- var defaultStore = createMemoryStore();
848
989
  var threads = /* @__PURE__ */ new Map();
849
- var getOrCreateThread = (id, store = defaultStore) => {
850
- if (threads.has(id)) {
851
- return threads.get(id);
990
+ var getOrCreateThread = (id, store) => {
991
+ const cacheKey = store ? `${id}-${store}` : id;
992
+ if (threads.has(cacheKey)) {
993
+ return threads.get(cacheKey);
852
994
  }
853
- const thread = createThread(id, store);
854
- threads.set(id, thread);
995
+ const threadStore = store || createMemoryStore();
996
+ const thread = createThread(id, threadStore);
997
+ threads.set(cacheKey, thread);
855
998
  return thread;
856
999
  };
857
1000
 
@@ -1082,6 +1225,9 @@ var scopeContext = (config, ctx) => {
1082
1225
  scopedCtx.history = [{ role: "system", content: config.system }, ...scopedCtx.history];
1083
1226
  }
1084
1227
  }
1228
+ if (config.stream) {
1229
+ scopedCtx.stream = config.stream;
1230
+ }
1085
1231
  return scopedCtx;
1086
1232
  };
1087
1233
  var scope = (config, ...steps) => {
@@ -1103,6 +1249,59 @@ var scope = (config, ...steps) => {
1103
1249
  };
1104
1250
  };
1105
1251
  };
1252
+
1253
+ // src/utils/rateLimited.ts
1254
+ var rateLimited = (config) => (fn) => {
1255
+ const { rps, burst, concurrency } = config;
1256
+ let tokens = burst;
1257
+ let inFlight = 0;
1258
+ const queue = [];
1259
+ let intervalId = null;
1260
+ const refillTokens = () => {
1261
+ tokens = Math.min(tokens + 1, burst);
1262
+ processQueue();
1263
+ };
1264
+ const startInterval = () => {
1265
+ if (!intervalId) {
1266
+ intervalId = setInterval(refillTokens, 1e3 / rps);
1267
+ }
1268
+ };
1269
+ const stopInterval = () => {
1270
+ if (intervalId && queue.length === 0 && inFlight === 0) {
1271
+ clearInterval(intervalId);
1272
+ intervalId = null;
1273
+ }
1274
+ };
1275
+ const processQueue = () => {
1276
+ while (queue.length > 0 && tokens > 0 && inFlight < concurrency) {
1277
+ tokens--;
1278
+ inFlight++;
1279
+ const item = queue.shift();
1280
+ item.fn().then((result) => {
1281
+ inFlight--;
1282
+ item.resolve(result);
1283
+ processQueue();
1284
+ stopInterval();
1285
+ }).catch((error) => {
1286
+ inFlight--;
1287
+ item.reject(error);
1288
+ processQueue();
1289
+ stopInterval();
1290
+ });
1291
+ }
1292
+ };
1293
+ return (async (...args) => {
1294
+ return new Promise((resolve, reject) => {
1295
+ queue.push({
1296
+ fn: () => fn(...args),
1297
+ resolve,
1298
+ reject
1299
+ });
1300
+ startInterval();
1301
+ processQueue();
1302
+ });
1303
+ });
1304
+ };
1106
1305
  // Annotate the CommonJS export names for ESM import in node:
1107
1306
  0 && (module.exports = {
1108
1307
  Inherit,
@@ -1110,7 +1309,9 @@ var scope = (config, ...steps) => {
1110
1309
  compose,
1111
1310
  convertMCPSchemaToToolSchema,
1112
1311
  convertStandardSchemaToJsonSchema,
1312
+ convertStandardSchemaToSchemaProperties,
1113
1313
  createMCPTools,
1314
+ embed,
1114
1315
  everyNMessages,
1115
1316
  everyNTokens,
1116
1317
  generateApprovalToken,
@@ -1124,6 +1325,7 @@ var scope = (config, ...steps) => {
1124
1325
  onApprovalRequested,
1125
1326
  onApprovalResolved,
1126
1327
  parseModelName,
1328
+ rateLimited,
1127
1329
  removeApprovalListener,
1128
1330
  requestApproval,
1129
1331
  resolveApproval,