@threaded/ai 1.0.0 → 1.0.2

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.d.cts CHANGED
@@ -100,9 +100,10 @@ interface ScopeConfig {
100
100
  system?: string;
101
101
  silent?: boolean;
102
102
  until?: (ctx: ConversationContext) => boolean;
103
+ stream?: (event: StreamEvent) => void;
103
104
  }
104
105
  type StepFunction = (ctx: ConversationContext) => Promise<ConversationContext>;
105
- type ComposedFunction = (ctxOrMessage?: ConversationContext | string) => Promise<ConversationContext>;
106
+ type ComposedFunction = (ctxOrMessage: ConversationContext | string) => Promise<ConversationContext>;
106
107
  interface JsonSchema {
107
108
  name: string;
108
109
  schema: {
@@ -146,6 +147,34 @@ interface RetryOptions {
146
147
 
147
148
  declare const createMCPTools: (client: Client) => Promise<ToolConfig[]>;
148
149
 
150
+ declare const toolConfigToToolDefinition: (tool: ToolConfig) => ToolDefinition;
151
+ declare const parseModelName: (model: string) => ParsedModel;
152
+ declare const setKeys: (keys: ApiKeys) => void;
153
+ declare const getKey: (provider: string) => string;
154
+ declare const maxCalls: (toolConfig: ToolConfig, maxCalls: number) => ToolConfig;
155
+
156
+ /**
157
+ * @example
158
+ * // in-memory (default)
159
+ * const thread = getOrCreateThread('user-123');
160
+ *
161
+ * @example
162
+ * // sqlite
163
+ * const thread = getOrCreateThread('user-123', {
164
+ * async get(id) {
165
+ * const row = await db.get('SELECT messages FROM threads WHERE id = ?', id);
166
+ * return row ? JSON.parse(row.messages) : [];
167
+ * },
168
+ * async set(id, messages) {
169
+ * await db.run(
170
+ * 'INSERT OR REPLACE INTO threads (id, messages, updated_at) VALUES (?, ?, ?)',
171
+ * id,
172
+ * JSON.stringify(messages),
173
+ * Date.now()
174
+ * );
175
+ * }
176
+ * });
177
+ */
149
178
  declare const getOrCreateThread: (id: string, store?: ThreadStore) => Thread;
150
179
 
151
180
  declare const isStandardSchema: (schema: any) => schema is StandardSchema;
@@ -192,8 +221,8 @@ declare const when: (condition: (ctx: ConversationContext) => boolean, action: S
192
221
 
193
222
  declare const model: ({ model, schema, }?: {
194
223
  model?: string;
195
- schema?: any;
196
- }) => StepFunction;
224
+ schema?: JsonSchema | StandardSchema;
225
+ }) => ComposedFunction;
197
226
 
198
227
  /**
199
228
  * scope({}, retry({ times: 2 }, model(...)))
@@ -204,4 +233,4 @@ declare const scope: (config: ScopeConfig, ...steps: StepFunction[]) => StepFunc
204
233
 
205
234
  declare const compose: (...steps: StepFunction[]) => ComposedFunction;
206
235
 
207
- export { type ApiKeys, type ApprovalRequest, type ApprovalResponse, type ComposedFunction, type ConversationContext, Inherit, type JsonSchema, type Message, type ParsedModel, type ProviderConfig, type RetryOptions, type SchemaProperty, type ScopeConfig, type StandardSchema, type StepFunction, type StreamEvent, type Thread, type ThreadStore, type ToolCall, type ToolCallResult, type ToolConfig, type ToolDefinition, type ToolExecutionConfig, appendToLastRequest, compose, convertMCPSchemaToToolSchema, convertStandardSchemaToJsonSchema, createMCPTools, everyNMessages, everyNTokens, generateApprovalToken, getOrCreateThread, isStandardSchema, model, noToolsCalled, normalizeSchema, onApprovalRequested, onApprovalResolved, removeApprovalListener, requestApproval, resolveApproval, retry, scope, tap, toolNotUsedInNTurns, toolWasCalled, when };
236
+ export { type ApiKeys, type ApprovalRequest, type ApprovalResponse, type ComposedFunction, type ConversationContext, Inherit, type JsonSchema, type Message, type ParsedModel, type ProviderConfig, type RetryOptions, type SchemaProperty, type ScopeConfig, type StandardSchema, type StepFunction, type StreamEvent, type Thread, type ThreadStore, type ToolCall, type ToolCallResult, type ToolConfig, type ToolDefinition, type ToolExecutionConfig, appendToLastRequest, compose, convertMCPSchemaToToolSchema, convertStandardSchemaToJsonSchema, createMCPTools, everyNMessages, everyNTokens, generateApprovalToken, getKey, getOrCreateThread, isStandardSchema, maxCalls, model, noToolsCalled, normalizeSchema, onApprovalRequested, onApprovalResolved, parseModelName, removeApprovalListener, requestApproval, resolveApproval, retry, scope, setKeys, tap, toolConfigToToolDefinition, toolNotUsedInNTurns, toolWasCalled, when };
package/dist/index.d.ts CHANGED
@@ -100,9 +100,10 @@ interface ScopeConfig {
100
100
  system?: string;
101
101
  silent?: boolean;
102
102
  until?: (ctx: ConversationContext) => boolean;
103
+ stream?: (event: StreamEvent) => void;
103
104
  }
104
105
  type StepFunction = (ctx: ConversationContext) => Promise<ConversationContext>;
105
- type ComposedFunction = (ctxOrMessage?: ConversationContext | string) => Promise<ConversationContext>;
106
+ type ComposedFunction = (ctxOrMessage: ConversationContext | string) => Promise<ConversationContext>;
106
107
  interface JsonSchema {
107
108
  name: string;
108
109
  schema: {
@@ -146,6 +147,34 @@ interface RetryOptions {
146
147
 
147
148
  declare const createMCPTools: (client: Client) => Promise<ToolConfig[]>;
148
149
 
150
+ declare const toolConfigToToolDefinition: (tool: ToolConfig) => ToolDefinition;
151
+ declare const parseModelName: (model: string) => ParsedModel;
152
+ declare const setKeys: (keys: ApiKeys) => void;
153
+ declare const getKey: (provider: string) => string;
154
+ declare const maxCalls: (toolConfig: ToolConfig, maxCalls: number) => ToolConfig;
155
+
156
+ /**
157
+ * @example
158
+ * // in-memory (default)
159
+ * const thread = getOrCreateThread('user-123');
160
+ *
161
+ * @example
162
+ * // sqlite
163
+ * const thread = getOrCreateThread('user-123', {
164
+ * async get(id) {
165
+ * const row = await db.get('SELECT messages FROM threads WHERE id = ?', id);
166
+ * return row ? JSON.parse(row.messages) : [];
167
+ * },
168
+ * async set(id, messages) {
169
+ * await db.run(
170
+ * 'INSERT OR REPLACE INTO threads (id, messages, updated_at) VALUES (?, ?, ?)',
171
+ * id,
172
+ * JSON.stringify(messages),
173
+ * Date.now()
174
+ * );
175
+ * }
176
+ * });
177
+ */
149
178
  declare const getOrCreateThread: (id: string, store?: ThreadStore) => Thread;
150
179
 
151
180
  declare const isStandardSchema: (schema: any) => schema is StandardSchema;
@@ -192,8 +221,8 @@ declare const when: (condition: (ctx: ConversationContext) => boolean, action: S
192
221
 
193
222
  declare const model: ({ model, schema, }?: {
194
223
  model?: string;
195
- schema?: any;
196
- }) => StepFunction;
224
+ schema?: JsonSchema | StandardSchema;
225
+ }) => ComposedFunction;
197
226
 
198
227
  /**
199
228
  * scope({}, retry({ times: 2 }, model(...)))
@@ -204,4 +233,4 @@ declare const scope: (config: ScopeConfig, ...steps: StepFunction[]) => StepFunc
204
233
 
205
234
  declare const compose: (...steps: StepFunction[]) => ComposedFunction;
206
235
 
207
- export { type ApiKeys, type ApprovalRequest, type ApprovalResponse, type ComposedFunction, type ConversationContext, Inherit, type JsonSchema, type Message, type ParsedModel, type ProviderConfig, type RetryOptions, type SchemaProperty, type ScopeConfig, type StandardSchema, type StepFunction, type StreamEvent, type Thread, type ThreadStore, type ToolCall, type ToolCallResult, type ToolConfig, type ToolDefinition, type ToolExecutionConfig, appendToLastRequest, compose, convertMCPSchemaToToolSchema, convertStandardSchemaToJsonSchema, createMCPTools, everyNMessages, everyNTokens, generateApprovalToken, getOrCreateThread, isStandardSchema, model, noToolsCalled, normalizeSchema, onApprovalRequested, onApprovalResolved, removeApprovalListener, requestApproval, resolveApproval, retry, scope, tap, toolNotUsedInNTurns, toolWasCalled, when };
236
+ export { type ApiKeys, type ApprovalRequest, type ApprovalResponse, type ComposedFunction, type ConversationContext, Inherit, type JsonSchema, type Message, type ParsedModel, type ProviderConfig, type RetryOptions, type SchemaProperty, type ScopeConfig, type StandardSchema, type StepFunction, type StreamEvent, type Thread, type ThreadStore, type ToolCall, type ToolCallResult, type ToolConfig, type ToolDefinition, type ToolExecutionConfig, appendToLastRequest, compose, convertMCPSchemaToToolSchema, convertStandardSchemaToJsonSchema, createMCPTools, everyNMessages, everyNTokens, generateApprovalToken, getKey, getOrCreateThread, isStandardSchema, maxCalls, model, noToolsCalled, normalizeSchema, onApprovalRequested, onApprovalResolved, parseModelName, removeApprovalListener, requestApproval, resolveApproval, retry, scope, setKeys, tap, toolConfigToToolDefinition, toolNotUsedInNTurns, toolWasCalled, when };
package/dist/index.js CHANGED
@@ -135,6 +135,9 @@ var parseModelName = (model2) => {
135
135
  };
136
136
  };
137
137
  var globalKeys = {};
138
+ var setKeys = (keys) => {
139
+ globalKeys = { ...globalKeys, ...keys };
140
+ };
138
141
  var getKey = (provider) => {
139
142
  const key = globalKeys[provider.toLowerCase()];
140
143
  if (!key) {
@@ -142,8 +145,28 @@ var getKey = (provider) => {
142
145
  }
143
146
  return key;
144
147
  };
148
+ var maxCalls = (toolConfig, maxCalls2) => ({
149
+ ...toolConfig,
150
+ _maxCalls: maxCalls2
151
+ });
145
152
 
146
153
  // src/providers/openai.ts
154
+ var appendToolCalls = (toolCalls, tcchunklist) => {
155
+ for (const tcchunk of tcchunklist) {
156
+ while (toolCalls.length <= tcchunk.index) {
157
+ toolCalls.push({
158
+ id: "",
159
+ type: "function",
160
+ function: { name: "", arguments: "" }
161
+ });
162
+ }
163
+ const tc = toolCalls[tcchunk.index];
164
+ tc.id += tcchunk.id || "";
165
+ tc.function.name += tcchunk.function?.name || "";
166
+ tc.function.arguments += tcchunk.function?.arguments || "";
167
+ }
168
+ return toolCalls;
169
+ };
147
170
  var callOpenAI = async (config, ctx) => {
148
171
  const { model: model2, instructions, schema } = config;
149
172
  const apiKey = getKey("openai") || process.env.OPENAI_API_KEY;
@@ -210,17 +233,19 @@ var handleOpenAIStream = async (response, ctx) => {
210
233
  const decoder = new TextDecoder();
211
234
  let fullContent = "";
212
235
  let toolCalls = [];
213
- const toolCallsBuffer = {};
236
+ let buffer = "";
214
237
  try {
215
238
  while (true) {
216
239
  const { done, value } = await reader.read();
217
240
  if (done) break;
218
- const chunk = decoder.decode(value);
219
- const lines = chunk.split("\n");
241
+ buffer += decoder.decode(value, { stream: true });
242
+ const lines = buffer.split("\n");
243
+ buffer = lines.pop() || "";
220
244
  for (const line of lines) {
221
245
  if (line.startsWith("data: ")) {
222
- const data = line.slice(6);
246
+ const data = line.slice(6).trim();
223
247
  if (data === "[DONE]") continue;
248
+ if (!data) continue;
224
249
  try {
225
250
  const parsed = JSON.parse(data);
226
251
  const delta = parsed.choices?.[0]?.delta;
@@ -231,25 +256,7 @@ var handleOpenAIStream = async (response, ctx) => {
231
256
  }
232
257
  }
233
258
  if (delta?.tool_calls) {
234
- for (const toolCall of delta.tool_calls) {
235
- const { index } = toolCall;
236
- if (!toolCallsBuffer[index]) {
237
- toolCallsBuffer[index] = {
238
- id: toolCall.id || "",
239
- type: "function",
240
- function: { name: "", arguments: "" }
241
- };
242
- }
243
- if (toolCall.id) {
244
- toolCallsBuffer[index].id = toolCall.id;
245
- }
246
- if (toolCall.function?.name) {
247
- toolCallsBuffer[index].function.name += toolCall.function.name;
248
- }
249
- if (toolCall.function?.arguments) {
250
- toolCallsBuffer[index].function.arguments += toolCall.function.arguments;
251
- }
252
- }
259
+ toolCalls = appendToolCalls(toolCalls, delta.tool_calls);
253
260
  }
254
261
  } catch (e) {
255
262
  }
@@ -259,7 +266,6 @@ var handleOpenAIStream = async (response, ctx) => {
259
266
  } finally {
260
267
  reader.releaseLock();
261
268
  }
262
- toolCalls = Object.values(toolCallsBuffer);
263
269
  const msg = {
264
270
  role: "assistant",
265
271
  content: fullContent
@@ -361,15 +367,18 @@ var handleAnthropicStream = async (response, ctx) => {
361
367
  const decoder = new TextDecoder();
362
368
  let fullContent = "";
363
369
  const toolCalls = [];
370
+ let buffer = "";
364
371
  try {
365
372
  while (true) {
366
373
  const { done, value } = await reader.read();
367
374
  if (done) break;
368
- const chunk = decoder.decode(value);
369
- const lines = chunk.split("\n");
375
+ buffer += decoder.decode(value, { stream: true });
376
+ const lines = buffer.split("\n");
377
+ buffer = lines.pop() || "";
370
378
  for (const line of lines) {
371
379
  if (line.startsWith("data: ")) {
372
- const data = line.slice(6);
380
+ const data = line.slice(6).trim();
381
+ if (!data) continue;
373
382
  try {
374
383
  const parsed = JSON.parse(data);
375
384
  if (parsed.type === "content_block_delta" && parsed.delta?.text) {
@@ -498,15 +507,18 @@ var handleGoogleStream = async (response, ctx) => {
498
507
  const decoder = new TextDecoder();
499
508
  let fullContent = "";
500
509
  const toolCalls = [];
510
+ let buffer = "";
501
511
  try {
502
512
  while (true) {
503
513
  const { done, value } = await reader.read();
504
514
  if (done) break;
505
- const chunk = decoder.decode(value);
506
- const lines = chunk.split("\n");
515
+ buffer += decoder.decode(value, { stream: true });
516
+ const lines = buffer.split("\n");
517
+ buffer = lines.pop() || "";
507
518
  for (const line of lines) {
508
519
  if (line.startsWith("data: ")) {
509
- const data = line.slice(6);
520
+ const data = line.slice(6).trim();
521
+ if (!data) continue;
510
522
  try {
511
523
  const parsed = JSON.parse(data);
512
524
  const candidate = parsed.candidates?.[0];
@@ -614,10 +626,16 @@ var model = ({
614
626
  schema
615
627
  } = {}) => {
616
628
  return async (ctxOrMessage) => {
617
- const ctx = typeof ctxOrMessage === "string" ? {
618
- history: [{ role: "user", content: ctxOrMessage }],
619
- tools: []
620
- } : ctxOrMessage;
629
+ const ctx = typeof ctxOrMessage === "string" ? (
630
+ // model()("hello!");
631
+ {
632
+ history: [{ role: "user", content: ctxOrMessage }],
633
+ tools: []
634
+ }
635
+ ) : (
636
+ // model()(/* few shot / history */);
637
+ ctxOrMessage
638
+ );
621
639
  const normalizedSchema = schema ? normalizeSchema(schema) : void 0;
622
640
  const systemMessage = ctx.history.find((m) => m.role === "system");
623
641
  const instructions = systemMessage?.content;
@@ -668,7 +686,11 @@ var executeTools = async (ctx) => {
668
686
  const approval = approvals.find((a) => a.call.id === call.id);
669
687
  if (!approval?.approved) {
670
688
  if (ctx.stream) {
671
- ctx.stream({ type: "tool_error", call, error: "Tool execution denied by user" });
689
+ ctx.stream({
690
+ type: "tool_error",
691
+ call,
692
+ error: "Tool execution denied by user"
693
+ });
672
694
  }
673
695
  return {
674
696
  call,
@@ -677,10 +699,10 @@ var executeTools = async (ctx) => {
677
699
  }
678
700
  const toolName = call.function.name;
679
701
  const limits = ctx.toolLimits || {};
680
- const maxCalls = limits[toolName];
702
+ const maxCalls2 = limits[toolName];
681
703
  const currentCount = updatedCounts[toolName] || 0;
682
- if (maxCalls && currentCount >= maxCalls) {
683
- const error2 = `Tool ${toolName} has reached its limit of ${maxCalls} calls`;
704
+ if (maxCalls2 && currentCount >= maxCalls2) {
705
+ const error2 = `Tool ${toolName} has reached its limit of ${maxCalls2} calls`;
684
706
  if (ctx.stream) {
685
707
  ctx.stream({ type: "tool_error", call, error: error2 });
686
708
  }
@@ -789,14 +811,15 @@ var createThread = (id, store) => {
789
811
  }
790
812
  };
791
813
  };
792
- var defaultStore = createMemoryStore();
793
814
  var threads = /* @__PURE__ */ new Map();
794
- var getOrCreateThread = (id, store = defaultStore) => {
795
- if (threads.has(id)) {
796
- return threads.get(id);
815
+ var getOrCreateThread = (id, store) => {
816
+ const cacheKey = store ? `${id}-${store}` : id;
817
+ if (threads.has(cacheKey)) {
818
+ return threads.get(cacheKey);
797
819
  }
798
- const thread = createThread(id, store);
799
- threads.set(id, thread);
820
+ const threadStore = store || createMemoryStore();
821
+ const thread = createThread(id, threadStore);
822
+ threads.set(cacheKey, thread);
800
823
  return thread;
801
824
  };
802
825
 
@@ -1027,6 +1050,9 @@ var scopeContext = (config, ctx) => {
1027
1050
  scopedCtx.history = [{ role: "system", content: config.system }, ...scopedCtx.history];
1028
1051
  }
1029
1052
  }
1053
+ if (config.stream) {
1054
+ scopedCtx.stream = config.stream;
1055
+ }
1030
1056
  return scopedCtx;
1031
1057
  };
1032
1058
  var scope = (config, ...steps) => {
@@ -1058,19 +1084,24 @@ export {
1058
1084
  everyNMessages,
1059
1085
  everyNTokens,
1060
1086
  generateApprovalToken,
1087
+ getKey,
1061
1088
  getOrCreateThread,
1062
1089
  isStandardSchema,
1090
+ maxCalls,
1063
1091
  model,
1064
1092
  noToolsCalled,
1065
1093
  normalizeSchema,
1066
1094
  onApprovalRequested,
1067
1095
  onApprovalResolved,
1096
+ parseModelName,
1068
1097
  removeApprovalListener,
1069
1098
  requestApproval,
1070
1099
  resolveApproval,
1071
1100
  retry,
1072
1101
  scope,
1102
+ setKeys,
1073
1103
  tap,
1104
+ toolConfigToToolDefinition,
1074
1105
  toolNotUsedInNTurns,
1075
1106
  toolWasCalled,
1076
1107
  when