@threaded/ai 1.0.0

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 ADDED
@@ -0,0 +1,1122 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Inherit: () => Inherit,
24
+ appendToLastRequest: () => appendToLastRequest,
25
+ compose: () => compose,
26
+ convertMCPSchemaToToolSchema: () => convertMCPSchemaToToolSchema,
27
+ convertStandardSchemaToJsonSchema: () => convertStandardSchemaToJsonSchema,
28
+ createMCPTools: () => createMCPTools,
29
+ everyNMessages: () => everyNMessages,
30
+ everyNTokens: () => everyNTokens,
31
+ generateApprovalToken: () => generateApprovalToken,
32
+ getOrCreateThread: () => getOrCreateThread,
33
+ isStandardSchema: () => isStandardSchema,
34
+ model: () => model,
35
+ noToolsCalled: () => noToolsCalled,
36
+ normalizeSchema: () => normalizeSchema,
37
+ onApprovalRequested: () => onApprovalRequested,
38
+ onApprovalResolved: () => onApprovalResolved,
39
+ removeApprovalListener: () => removeApprovalListener,
40
+ requestApproval: () => requestApproval,
41
+ resolveApproval: () => resolveApproval,
42
+ retry: () => retry,
43
+ scope: () => scope,
44
+ tap: () => tap,
45
+ toolNotUsedInNTurns: () => toolNotUsedInNTurns,
46
+ toolWasCalled: () => toolWasCalled,
47
+ when: () => when
48
+ });
49
+ module.exports = __toCommonJS(index_exports);
50
+
51
+ // src/schema.ts
52
+ var isStandardSchema = (schema) => {
53
+ return schema && typeof schema === "object" && "~standard" in schema;
54
+ };
55
+ var convertStandardSchemaToJsonSchema = (standardSchema, name = "Schema") => {
56
+ try {
57
+ const zodModule = require("zod");
58
+ if (zodModule && typeof zodModule.toJSONSchema === "function") {
59
+ const jsonSchema = zodModule.toJSONSchema(standardSchema);
60
+ return {
61
+ name,
62
+ schema: jsonSchema
63
+ };
64
+ }
65
+ } catch (error) {
66
+ }
67
+ throw new Error(
68
+ "Standard Schema conversion requires zod v4+ with toJSONSchema support. Please install zod@^4.0.0 or provide a JsonSchema object instead."
69
+ );
70
+ };
71
+ var convertMCPSchemaToToolSchema = (mcpSchema) => {
72
+ if (!mcpSchema?.properties) return {};
73
+ const result = {};
74
+ for (const [key, value] of Object.entries(mcpSchema.properties)) {
75
+ const prop = value;
76
+ result[key] = {
77
+ type: prop.type || "string",
78
+ description: prop.description || "",
79
+ optional: !mcpSchema.required?.includes(key)
80
+ };
81
+ }
82
+ return result;
83
+ };
84
+ function normalizeSchema(schema, name) {
85
+ if (isStandardSchema(schema)) {
86
+ return convertStandardSchemaToJsonSchema(schema, name);
87
+ }
88
+ return schema;
89
+ }
90
+
91
+ // src/mcp.ts
92
+ var createMCPTools = async (client) => {
93
+ const serverInfo = client.getServerVersion();
94
+ const serverName = serverInfo?.name;
95
+ if (!serverName) {
96
+ console.error("MCP server has no name? Skipping tool creation.");
97
+ return [];
98
+ }
99
+ const toolsResponse = await client.listTools();
100
+ return toolsResponse.tools.map((mcpTool) => {
101
+ const prefixedName = `${serverName}_${mcpTool.name}`;
102
+ return {
103
+ name: prefixedName,
104
+ description: `[${serverName}] ${mcpTool.description || ""}`,
105
+ schema: convertMCPSchemaToToolSchema(mcpTool.inputSchema),
106
+ execute: async (args) => {
107
+ const result = await client.callTool({
108
+ name: mcpTool.name,
109
+ arguments: args
110
+ });
111
+ return result.content && Array.isArray(result.content) && result.content[0]?.text || JSON.stringify(result);
112
+ }
113
+ };
114
+ });
115
+ };
116
+
117
+ // src/types.ts
118
+ var Inherit = /* @__PURE__ */ ((Inherit2) => {
119
+ Inherit2[Inherit2["Nothing"] = 0] = "Nothing";
120
+ Inherit2[Inherit2["Conversation"] = 1] = "Conversation";
121
+ Inherit2[Inherit2["Tools"] = 2] = "Tools";
122
+ Inherit2[Inherit2["All"] = 3] = "All";
123
+ return Inherit2;
124
+ })(Inherit || {});
125
+
126
+ // src/utils.ts
127
+ var toolConfigToToolDefinition = (tool) => {
128
+ const properties = {};
129
+ const required = [];
130
+ for (const [key, prop] of Object.entries(tool.schema)) {
131
+ properties[key] = convertSchemaProperty(prop);
132
+ if (!prop.optional) {
133
+ required.push(key);
134
+ }
135
+ }
136
+ return {
137
+ type: "function",
138
+ function: {
139
+ name: tool.name,
140
+ description: tool.description,
141
+ parameters: {
142
+ type: "object",
143
+ properties,
144
+ ...required.length > 0 && { required }
145
+ }
146
+ }
147
+ };
148
+ };
149
+ var convertSchemaProperty = (prop) => {
150
+ const result = {
151
+ type: prop.type
152
+ };
153
+ if (prop.description) {
154
+ result.description = prop.description;
155
+ }
156
+ if (prop.enum) {
157
+ result.enum = prop.enum;
158
+ }
159
+ if (prop.items) {
160
+ result.items = convertSchemaProperty(prop.items);
161
+ }
162
+ if (prop.properties) {
163
+ result.properties = {};
164
+ for (const [key, childProp] of Object.entries(prop.properties)) {
165
+ result.properties[key] = convertSchemaProperty(childProp);
166
+ }
167
+ }
168
+ return result;
169
+ };
170
+ var parseModelName = (model2) => {
171
+ const parts = model2.split("/");
172
+ if (parts.length === 1) {
173
+ return { provider: "huggingface", model: parts[0] };
174
+ }
175
+ return {
176
+ provider: parts[0],
177
+ model: parts.slice(1).join("/")
178
+ };
179
+ };
180
+ var globalKeys = {};
181
+ var getKey = (provider) => {
182
+ const key = globalKeys[provider.toLowerCase()];
183
+ if (!key) {
184
+ throw new Error(`No API key configured for provider: ${provider}`);
185
+ }
186
+ return key;
187
+ };
188
+
189
+ // src/providers/openai.ts
190
+ var callOpenAI = async (config, ctx) => {
191
+ const { model: model2, instructions, schema } = config;
192
+ const apiKey = getKey("openai") || process.env.OPENAI_API_KEY;
193
+ if (!apiKey) {
194
+ throw new Error("OpenAI API key not found");
195
+ }
196
+ const messages = [];
197
+ if (instructions) {
198
+ messages.push({ role: "system", content: instructions });
199
+ }
200
+ messages.push(...ctx.history);
201
+ const body = {
202
+ model: model2,
203
+ messages,
204
+ stream: !!ctx.stream
205
+ };
206
+ if (schema) {
207
+ body.response_format = {
208
+ type: "json_schema",
209
+ json_schema: {
210
+ name: schema.name,
211
+ schema: { ...schema.schema, additionalProperties: false },
212
+ strict: true
213
+ }
214
+ };
215
+ }
216
+ if (ctx.tools && ctx.tools.length > 0) {
217
+ body.tools = ctx.tools;
218
+ body.tool_choice = "auto";
219
+ }
220
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
221
+ method: "POST",
222
+ headers: {
223
+ "Content-Type": "application/json",
224
+ Authorization: `Bearer ${apiKey}`
225
+ },
226
+ body: JSON.stringify(body)
227
+ });
228
+ if (!response.ok) {
229
+ const error = await response.text();
230
+ throw new Error(`OpenAI API error: ${error}`);
231
+ }
232
+ if (ctx.stream) {
233
+ return handleOpenAIStream(response, ctx);
234
+ }
235
+ const data = await response.json();
236
+ const choice = data.choices[0];
237
+ const { message } = choice;
238
+ const msg = {
239
+ role: "assistant",
240
+ content: message.content || ""
241
+ };
242
+ if (message.tool_calls) {
243
+ msg.tool_calls = message.tool_calls;
244
+ }
245
+ return {
246
+ ...ctx,
247
+ lastResponse: msg,
248
+ history: [...ctx.history, msg]
249
+ };
250
+ };
251
+ var handleOpenAIStream = async (response, ctx) => {
252
+ const reader = response.body.getReader();
253
+ const decoder = new TextDecoder();
254
+ let fullContent = "";
255
+ let toolCalls = [];
256
+ const toolCallsBuffer = {};
257
+ try {
258
+ while (true) {
259
+ const { done, value } = await reader.read();
260
+ if (done) break;
261
+ const chunk = decoder.decode(value);
262
+ const lines = chunk.split("\n");
263
+ for (const line of lines) {
264
+ if (line.startsWith("data: ")) {
265
+ const data = line.slice(6);
266
+ if (data === "[DONE]") continue;
267
+ try {
268
+ const parsed = JSON.parse(data);
269
+ const delta = parsed.choices?.[0]?.delta;
270
+ if (delta?.content) {
271
+ fullContent += delta.content;
272
+ if (ctx.stream) {
273
+ ctx.stream({ type: "content", content: delta.content });
274
+ }
275
+ }
276
+ if (delta?.tool_calls) {
277
+ for (const toolCall of delta.tool_calls) {
278
+ const { index } = toolCall;
279
+ if (!toolCallsBuffer[index]) {
280
+ toolCallsBuffer[index] = {
281
+ id: toolCall.id || "",
282
+ type: "function",
283
+ function: { name: "", arguments: "" }
284
+ };
285
+ }
286
+ if (toolCall.id) {
287
+ toolCallsBuffer[index].id = toolCall.id;
288
+ }
289
+ if (toolCall.function?.name) {
290
+ toolCallsBuffer[index].function.name += toolCall.function.name;
291
+ }
292
+ if (toolCall.function?.arguments) {
293
+ toolCallsBuffer[index].function.arguments += toolCall.function.arguments;
294
+ }
295
+ }
296
+ }
297
+ } catch (e) {
298
+ }
299
+ }
300
+ }
301
+ }
302
+ } finally {
303
+ reader.releaseLock();
304
+ }
305
+ toolCalls = Object.values(toolCallsBuffer);
306
+ const msg = {
307
+ role: "assistant",
308
+ content: fullContent
309
+ };
310
+ if (toolCalls.length > 0) {
311
+ msg.tool_calls = toolCalls;
312
+ }
313
+ return {
314
+ ...ctx,
315
+ lastResponse: msg,
316
+ history: [...ctx.history, msg]
317
+ };
318
+ };
319
+
320
+ // src/providers/anthropic.ts
321
+ var callAnthropic = async (config, ctx) => {
322
+ const { model: model2, instructions, schema } = config;
323
+ const apiKey = getKey("anthropic") || process.env.ANTHROPIC_API_KEY;
324
+ if (!apiKey) {
325
+ throw new Error("Anthropic API key not found");
326
+ }
327
+ const messages = [...ctx.history];
328
+ let system = instructions;
329
+ if (messages[0]?.role === "system") {
330
+ system = messages[0].content;
331
+ messages.shift();
332
+ }
333
+ if (schema) {
334
+ const schemaPrompt = `
335
+
336
+ You must respond with valid JSON that matches this schema:
337
+ ${JSON.stringify(
338
+ schema.schema,
339
+ null,
340
+ 2
341
+ )}
342
+
343
+ Return only the JSON object, no other text or formatting.`;
344
+ system = system ? system + schemaPrompt : schemaPrompt.slice(2);
345
+ }
346
+ const body = {
347
+ model: model2,
348
+ messages,
349
+ max_tokens: 4096,
350
+ stream: !!ctx.stream
351
+ };
352
+ if (system) {
353
+ body.system = system;
354
+ }
355
+ if (ctx.tools && ctx.tools.length > 0) {
356
+ body.tools = ctx.tools.map((tool) => ({
357
+ name: tool.function.name,
358
+ description: tool.function.description,
359
+ input_schema: tool.function.parameters
360
+ }));
361
+ }
362
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
363
+ method: "POST",
364
+ headers: {
365
+ "Content-Type": "application/json",
366
+ "x-api-key": apiKey,
367
+ "anthropic-version": "2023-06-01"
368
+ },
369
+ body: JSON.stringify(body)
370
+ });
371
+ if (!response.ok) {
372
+ const error = await response.text();
373
+ throw new Error(`Anthropic API error: ${error}`);
374
+ }
375
+ if (ctx.stream) {
376
+ return handleAnthropicStream(response, ctx);
377
+ }
378
+ const data = await response.json();
379
+ const content = data.content[0];
380
+ const msg = {
381
+ role: "assistant",
382
+ content: content.type === "text" ? content.text : ""
383
+ };
384
+ if (content.type === "tool_use") {
385
+ msg.tool_calls = [
386
+ {
387
+ id: content.id,
388
+ type: "function",
389
+ function: {
390
+ name: content.name,
391
+ arguments: JSON.stringify(content.input)
392
+ }
393
+ }
394
+ ];
395
+ }
396
+ return {
397
+ ...ctx,
398
+ lastResponse: msg,
399
+ history: [...ctx.history, msg]
400
+ };
401
+ };
402
+ var handleAnthropicStream = async (response, ctx) => {
403
+ const reader = response.body.getReader();
404
+ const decoder = new TextDecoder();
405
+ let fullContent = "";
406
+ const toolCalls = [];
407
+ try {
408
+ while (true) {
409
+ const { done, value } = await reader.read();
410
+ if (done) break;
411
+ const chunk = decoder.decode(value);
412
+ const lines = chunk.split("\n");
413
+ for (const line of lines) {
414
+ if (line.startsWith("data: ")) {
415
+ const data = line.slice(6);
416
+ try {
417
+ const parsed = JSON.parse(data);
418
+ if (parsed.type === "content_block_delta" && parsed.delta?.text) {
419
+ fullContent += parsed.delta.text;
420
+ if (ctx.stream) {
421
+ ctx.stream({ type: "content", content: parsed.delta.text });
422
+ }
423
+ }
424
+ if (parsed.type === "content_block_start" && parsed.content_block?.type === "tool_use") {
425
+ const toolUse = parsed.content_block;
426
+ toolCalls.push({
427
+ id: toolUse.id,
428
+ type: "function",
429
+ function: {
430
+ name: toolUse.name,
431
+ arguments: JSON.stringify(toolUse.input)
432
+ }
433
+ });
434
+ }
435
+ } catch (e) {
436
+ }
437
+ }
438
+ }
439
+ }
440
+ } finally {
441
+ reader.releaseLock();
442
+ }
443
+ const msg = {
444
+ role: "assistant",
445
+ content: fullContent
446
+ };
447
+ if (toolCalls.length > 0) {
448
+ msg.tool_calls = toolCalls;
449
+ }
450
+ return {
451
+ ...ctx,
452
+ lastResponse: msg,
453
+ history: [...ctx.history, msg]
454
+ };
455
+ };
456
+
457
+ // src/providers/google.ts
458
+ var callGoogle = async (config, ctx) => {
459
+ const { model: model2, instructions } = config;
460
+ const apiKey = getKey("google") || process.env.GOOGLE_AI_API_KEY;
461
+ if (!apiKey) {
462
+ throw new Error("Google API key not found");
463
+ }
464
+ const contents = [];
465
+ if (instructions) {
466
+ contents.push({
467
+ role: "user",
468
+ parts: [{ text: instructions }]
469
+ });
470
+ contents.push({
471
+ role: "model",
472
+ parts: [{ text: "I understand." }]
473
+ });
474
+ }
475
+ for (const msg2 of ctx.history) {
476
+ const role = msg2.role === "assistant" ? "model" : "user";
477
+ contents.push({
478
+ role,
479
+ parts: [{ text: msg2.content }]
480
+ });
481
+ }
482
+ const body = {
483
+ contents
484
+ };
485
+ if (ctx.tools && ctx.tools.length > 0) {
486
+ body.tools = [
487
+ {
488
+ function_declarations: ctx.tools.map((tool) => ({
489
+ name: tool.function.name,
490
+ description: tool.function.description,
491
+ parameters: tool.function.parameters
492
+ }))
493
+ }
494
+ ];
495
+ }
496
+ const endpoint = ctx.stream ? "streamGenerateContent" : "generateContent";
497
+ const response = await fetch(
498
+ `https://generativelanguage.googleapis.com/v1beta/models/${model2}:${endpoint}?key=${apiKey}`,
499
+ {
500
+ method: "POST",
501
+ headers: {
502
+ "Content-Type": "application/json"
503
+ },
504
+ body: JSON.stringify(body)
505
+ }
506
+ );
507
+ if (!response.ok) {
508
+ const error = await response.text();
509
+ throw new Error(`Google API error: ${error}`);
510
+ }
511
+ if (ctx.stream) {
512
+ return handleGoogleStream(response, ctx);
513
+ }
514
+ const data = await response.json();
515
+ const candidate = data.candidates[0];
516
+ const part = candidate.content.parts[0];
517
+ const msg = {
518
+ role: "assistant",
519
+ content: part.text || ""
520
+ };
521
+ if (part.functionCall) {
522
+ msg.tool_calls = [
523
+ {
524
+ id: Math.random().toString(36).substring(2, 9),
525
+ type: "function",
526
+ function: {
527
+ name: part.functionCall.name,
528
+ arguments: JSON.stringify(part.functionCall.args)
529
+ }
530
+ }
531
+ ];
532
+ }
533
+ return {
534
+ ...ctx,
535
+ lastResponse: msg,
536
+ history: [...ctx.history, msg]
537
+ };
538
+ };
539
+ var handleGoogleStream = async (response, ctx) => {
540
+ const reader = response.body.getReader();
541
+ const decoder = new TextDecoder();
542
+ let fullContent = "";
543
+ const toolCalls = [];
544
+ try {
545
+ while (true) {
546
+ const { done, value } = await reader.read();
547
+ if (done) break;
548
+ const chunk = decoder.decode(value);
549
+ const lines = chunk.split("\n");
550
+ for (const line of lines) {
551
+ if (line.startsWith("data: ")) {
552
+ const data = line.slice(6);
553
+ try {
554
+ const parsed = JSON.parse(data);
555
+ const candidate = parsed.candidates?.[0];
556
+ const part = candidate?.content?.parts?.[0];
557
+ if (part?.text) {
558
+ fullContent += part.text;
559
+ if (ctx.stream) {
560
+ ctx.stream({ type: "content", content: part.text });
561
+ }
562
+ }
563
+ if (part?.functionCall) {
564
+ toolCalls.push({
565
+ id: Math.random().toString(36).substring(2, 9),
566
+ type: "function",
567
+ function: {
568
+ name: part.functionCall.name,
569
+ arguments: JSON.stringify(part.functionCall.args)
570
+ }
571
+ });
572
+ }
573
+ } catch (e) {
574
+ }
575
+ }
576
+ }
577
+ }
578
+ } finally {
579
+ reader.releaseLock();
580
+ }
581
+ const msg = {
582
+ role: "assistant",
583
+ content: fullContent
584
+ };
585
+ if (toolCalls.length > 0) {
586
+ msg.tool_calls = toolCalls;
587
+ }
588
+ return {
589
+ ...ctx,
590
+ lastResponse: msg,
591
+ history: [...ctx.history, msg]
592
+ };
593
+ };
594
+
595
+ // src/providers/huggingface.ts
596
+ var callHuggingFace = async (config, ctx) => {
597
+ throw new Error(
598
+ "Hugging Face provider not yet implemented. Use openai/, anthropic/, or google/ prefixes."
599
+ );
600
+ };
601
+
602
+ // src/providers/index.ts
603
+ var callProvider = async (config, ctx) => {
604
+ const { provider, model: model2 } = parseModelName(config.model);
605
+ const providerConfig = { ...config, model: model2 };
606
+ switch (provider.toLowerCase()) {
607
+ case "openai":
608
+ return callOpenAI(providerConfig, ctx);
609
+ case "anthropic":
610
+ return callAnthropic(providerConfig, ctx);
611
+ case "google":
612
+ return callGoogle(providerConfig, ctx);
613
+ case "huggingface":
614
+ default:
615
+ return callHuggingFace(providerConfig, ctx);
616
+ }
617
+ };
618
+
619
+ // src/approval.ts
620
+ var import_events = require("events");
621
+ var state = {
622
+ resolvers: /* @__PURE__ */ new Map(),
623
+ emitter: new import_events.EventEmitter()
624
+ };
625
+ var generateApprovalToken = () => {
626
+ return `approval_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
627
+ };
628
+ var requestApproval = async (toolCall, approvalId) => {
629
+ const id = generateApprovalToken();
630
+ const request = { id, toolCall, approvalId };
631
+ state.emitter.emit("approvalRequested", request);
632
+ return new Promise((resolve) => {
633
+ state.resolvers.set(id, resolve);
634
+ });
635
+ };
636
+ var resolveApproval = (response) => {
637
+ const resolver = state.resolvers.get(response.id);
638
+ if (!resolver) return false;
639
+ state.resolvers.delete(response.id);
640
+ resolver(response);
641
+ state.emitter.emit("approvalResolved", response);
642
+ return true;
643
+ };
644
+ var onApprovalRequested = (listener) => {
645
+ state.emitter.on("approvalRequested", listener);
646
+ };
647
+ var onApprovalResolved = (listener) => {
648
+ state.emitter.on("approvalResolved", listener);
649
+ };
650
+ var removeApprovalListener = (event, listener) => {
651
+ state.emitter.removeListener(event, listener);
652
+ };
653
+
654
+ // src/composition/model.ts
655
+ var model = ({
656
+ model: model2 = "openai/gpt-4o-mini",
657
+ schema
658
+ } = {}) => {
659
+ return async (ctxOrMessage) => {
660
+ const ctx = typeof ctxOrMessage === "string" ? {
661
+ history: [{ role: "user", content: ctxOrMessage }],
662
+ tools: []
663
+ } : ctxOrMessage;
664
+ const normalizedSchema = schema ? normalizeSchema(schema) : void 0;
665
+ const systemMessage = ctx.history.find((m) => m.role === "system");
666
+ const instructions = systemMessage?.content;
667
+ let currentCtx = ctx;
668
+ do {
669
+ currentCtx = await callProvider(
670
+ { model: model2, instructions, schema: normalizedSchema },
671
+ currentCtx
672
+ );
673
+ if (currentCtx.lastResponse?.tool_calls && currentCtx.tools?.length) {
674
+ currentCtx = await executeTools(currentCtx);
675
+ }
676
+ } while (currentCtx.lastResponse?.tool_calls && currentCtx.tools?.length);
677
+ return currentCtx;
678
+ };
679
+ };
680
+ var executeTools = async (ctx) => {
681
+ const calls = ctx.lastResponse?.tool_calls || [];
682
+ if (!calls.length) return ctx;
683
+ if (ctx.stream) {
684
+ ctx.stream({ type: "tool_calls_ready", calls });
685
+ }
686
+ const toolConfig = ctx.toolConfig || {};
687
+ const {
688
+ requireApproval = false,
689
+ approvalCallback,
690
+ parallel = false,
691
+ retryCount = 0,
692
+ approvalId
693
+ } = toolConfig;
694
+ const approvalPromises = calls.map(async (call) => {
695
+ if (requireApproval) {
696
+ let approved;
697
+ if (approvalCallback) {
698
+ approved = await approvalCallback(call);
699
+ } else {
700
+ const response = await requestApproval(call, approvalId);
701
+ approved = response.approved;
702
+ }
703
+ return { call, approved };
704
+ } else {
705
+ return { call, approved: true };
706
+ }
707
+ });
708
+ const approvals = await Promise.all(approvalPromises);
709
+ const updatedCounts = { ...ctx.toolCallCounts || {} };
710
+ const runCall = async (call) => {
711
+ const approval = approvals.find((a) => a.call.id === call.id);
712
+ if (!approval?.approved) {
713
+ if (ctx.stream) {
714
+ ctx.stream({ type: "tool_error", call, error: "Tool execution denied by user" });
715
+ }
716
+ return {
717
+ call,
718
+ result: { error: "Tool execution denied by user" }
719
+ };
720
+ }
721
+ const toolName = call.function.name;
722
+ const limits = ctx.toolLimits || {};
723
+ const maxCalls = limits[toolName];
724
+ const currentCount = updatedCounts[toolName] || 0;
725
+ if (maxCalls && currentCount >= maxCalls) {
726
+ const error2 = `Tool ${toolName} has reached its limit of ${maxCalls} calls`;
727
+ if (ctx.stream) {
728
+ ctx.stream({ type: "tool_error", call, error: error2 });
729
+ }
730
+ return {
731
+ call,
732
+ result: { error: error2 }
733
+ };
734
+ }
735
+ updatedCounts[toolName] = currentCount + 1;
736
+ if (ctx.stream) {
737
+ ctx.stream({ type: "tool_executing", call });
738
+ }
739
+ let lastError;
740
+ for (let i = 0; i <= retryCount; i++) {
741
+ try {
742
+ const executor = ctx.toolExecutors?.[call.function.name];
743
+ if (!executor) {
744
+ throw new Error(`Tool executor not found: ${call.function.name}`);
745
+ }
746
+ let args = {};
747
+ try {
748
+ args = call.function.arguments ? JSON.parse(call.function.arguments) : {};
749
+ } catch (e) {
750
+ throw new Error(
751
+ `Invalid JSON arguments for tool ${call.function.name}: ${call.function.arguments}`
752
+ );
753
+ }
754
+ const result = await executor(args);
755
+ if (ctx.stream) {
756
+ ctx.stream({ type: "tool_complete", call, result });
757
+ }
758
+ return { call, result };
759
+ } catch (e) {
760
+ lastError = e;
761
+ }
762
+ }
763
+ const error = lastError.message;
764
+ if (ctx.stream) {
765
+ ctx.stream({ type: "tool_error", call, error });
766
+ }
767
+ return { call, result: { error } };
768
+ };
769
+ const results = parallel ? await Promise.all(calls.map(runCall)) : await runCallsSequentially(calls, runCall);
770
+ return {
771
+ ...ctx,
772
+ history: [
773
+ ...ctx.history,
774
+ ...results.map(({ call, result }) => ({
775
+ role: "tool",
776
+ tool_call_id: call.id,
777
+ content: JSON.stringify(result)
778
+ }))
779
+ ],
780
+ toolCallCounts: updatedCounts
781
+ };
782
+ };
783
+ var runCallsSequentially = async (calls, runCall) => {
784
+ const results = [];
785
+ for (const call of calls) {
786
+ results.push(await runCall(call));
787
+ }
788
+ return results;
789
+ };
790
+
791
+ // src/thread.ts
792
+ var createMemoryStore = () => {
793
+ const store = /* @__PURE__ */ new Map();
794
+ return {
795
+ async get(threadId) {
796
+ return store.get(threadId) || [];
797
+ },
798
+ async set(threadId, messages) {
799
+ store.set(threadId, messages);
800
+ }
801
+ };
802
+ };
803
+ var createThread = (id, store) => {
804
+ return {
805
+ id,
806
+ store,
807
+ async generate(workflow) {
808
+ const history = await store.get(id);
809
+ const initialContext = {
810
+ history,
811
+ tools: [],
812
+ toolExecutors: {},
813
+ toolLimits: {},
814
+ toolCallCounts: {}
815
+ };
816
+ const finalContext = await workflow(initialContext);
817
+ await store.set(id, finalContext.history);
818
+ return finalContext;
819
+ },
820
+ async message(content, workflow) {
821
+ const history = await store.get(id);
822
+ const initialContext = {
823
+ history: [...history, { role: "user", content }],
824
+ tools: [],
825
+ toolExecutors: {},
826
+ toolLimits: {},
827
+ toolCallCounts: {}
828
+ };
829
+ const finalContext = await (workflow || model())(initialContext);
830
+ await store.set(id, finalContext.history);
831
+ return finalContext;
832
+ }
833
+ };
834
+ };
835
+ var defaultStore = createMemoryStore();
836
+ var threads = /* @__PURE__ */ new Map();
837
+ var getOrCreateThread = (id, store = defaultStore) => {
838
+ if (threads.has(id)) {
839
+ return threads.get(id);
840
+ }
841
+ const thread = createThread(id, store);
842
+ threads.set(id, thread);
843
+ return thread;
844
+ };
845
+
846
+ // src/composition/when.ts
847
+ var when = (condition, action) => {
848
+ return async (ctx) => {
849
+ if (condition(ctx)) {
850
+ return await action(ctx);
851
+ }
852
+ return ctx;
853
+ };
854
+ };
855
+
856
+ // src/helpers.ts
857
+ var noToolsCalled = () => (ctx) => {
858
+ return !ctx.lastResponse?.tool_calls || ctx.lastResponse.tool_calls.length === 0;
859
+ };
860
+ var everyNMessages = (n, step) => {
861
+ let lastTriggeredAt = 0;
862
+ return when(
863
+ (ctx) => Math.floor(ctx.history.length / n) > Math.floor(lastTriggeredAt / n),
864
+ async (ctx) => {
865
+ lastTriggeredAt = ctx.history.length;
866
+ return await step(ctx);
867
+ }
868
+ );
869
+ };
870
+ var everyNTokens = (n, step) => {
871
+ let lastTriggeredAt = 0;
872
+ return when(
873
+ (ctx) => {
874
+ const totalTokens = ctx.history.reduce(
875
+ (acc, msg) => acc + Math.ceil(msg.content.length / 4),
876
+ 0
877
+ );
878
+ return Math.floor(totalTokens / n) > Math.floor(lastTriggeredAt / n);
879
+ },
880
+ async (ctx) => {
881
+ const totalTokens = ctx.history.reduce(
882
+ (acc, msg) => acc + Math.ceil(msg.content.length / 4),
883
+ 0
884
+ );
885
+ lastTriggeredAt = totalTokens;
886
+ return await step(ctx);
887
+ }
888
+ );
889
+ };
890
+ var appendToLastRequest = (content) => {
891
+ return async (ctx) => {
892
+ let lastUserIndex = -1;
893
+ for (let i = ctx.history.length - 1; i >= 0; i--) {
894
+ if (ctx.history[i].role === "user") {
895
+ lastUserIndex = i;
896
+ break;
897
+ }
898
+ }
899
+ if (lastUserIndex === -1) return ctx;
900
+ const newHistory = [...ctx.history];
901
+ newHistory[lastUserIndex] = {
902
+ ...newHistory[lastUserIndex],
903
+ content: newHistory[lastUserIndex].content + content
904
+ };
905
+ return {
906
+ ...ctx,
907
+ history: newHistory
908
+ };
909
+ };
910
+ };
911
+ var toolNotUsedInNTurns = ({ toolName, times }, step) => {
912
+ let turnsSinceLastUsed = 0;
913
+ let lastProcessedTurn = -1;
914
+ return when((ctx) => {
915
+ const currentTurn = getCurrentTurn(ctx);
916
+ if (currentTurn === lastProcessedTurn) return false;
917
+ lastProcessedTurn = currentTurn;
918
+ const toolUsedInTurn = wasToolUsedInCurrentTurn(ctx, toolName);
919
+ if (toolUsedInTurn) {
920
+ turnsSinceLastUsed = 0;
921
+ return false;
922
+ } else {
923
+ turnsSinceLastUsed++;
924
+ return turnsSinceLastUsed >= times;
925
+ }
926
+ }, step);
927
+ };
928
+ var getCurrentTurn = (ctx) => {
929
+ let turns = 0;
930
+ for (const msg of ctx.history) {
931
+ if (msg.role === "user") turns++;
932
+ }
933
+ return turns;
934
+ };
935
+ var wasToolUsedInCurrentTurn = (ctx, toolName) => {
936
+ let lastUserIndex = -1;
937
+ for (let i = ctx.history.length - 1; i >= 0; i--) {
938
+ if (ctx.history[i].role === "user") {
939
+ lastUserIndex = i;
940
+ break;
941
+ }
942
+ }
943
+ if (lastUserIndex === -1) return false;
944
+ for (let i = lastUserIndex + 1; i < ctx.history.length; i++) {
945
+ const msg = ctx.history[i];
946
+ if (msg.role === "assistant" && ctx.lastResponse?.tool_calls) {
947
+ return ctx.lastResponse.tool_calls.some(
948
+ (call) => call.function.name === toolName
949
+ );
950
+ }
951
+ }
952
+ return false;
953
+ };
954
+ var toolWasCalled = (name) => (ctx) => {
955
+ return !!ctx.lastResponse?.tool_calls && ctx.lastResponse.tool_calls.some((call) => call.function.name === name);
956
+ };
957
+
958
+ // src/composition/tap.ts
959
+ var tap = (fn) => {
960
+ return async (ctx) => {
961
+ await fn(ctx);
962
+ return ctx;
963
+ };
964
+ };
965
+
966
+ // src/composition/retry.ts
967
+ var retry = ({ times = 3 } = {}, step) => {
968
+ return async (ctx) => {
969
+ let err;
970
+ for (let i = 0; i < times; i++) {
971
+ try {
972
+ return await step(ctx);
973
+ } catch (e) {
974
+ err = e;
975
+ }
976
+ }
977
+ throw err;
978
+ };
979
+ };
980
+
981
+ // src/composition/compose.ts
982
+ var enrichContext = (ctx) => {
983
+ const lastUserMessage = [...ctx.history].reverse().find((msg) => msg.role === "user");
984
+ return {
985
+ ...ctx,
986
+ lastRequest: lastUserMessage
987
+ };
988
+ };
989
+ var compose = (...steps) => {
990
+ return async (ctxOrMessage) => {
991
+ let initialContext;
992
+ if (typeof ctxOrMessage === "string") {
993
+ initialContext = {
994
+ history: [{ role: "user", content: ctxOrMessage }],
995
+ tools: [],
996
+ toolExecutors: {},
997
+ toolLimits: {},
998
+ toolCallCounts: {}
999
+ };
1000
+ } else {
1001
+ initialContext = ctxOrMessage || {
1002
+ history: [],
1003
+ tools: [],
1004
+ toolExecutors: {},
1005
+ toolLimits: {},
1006
+ toolCallCounts: {}
1007
+ };
1008
+ }
1009
+ let next = enrichContext(initialContext);
1010
+ for (const step of steps) {
1011
+ next = await step(enrichContext(next));
1012
+ }
1013
+ return next;
1014
+ };
1015
+ };
1016
+
1017
+ // src/composition/scope.ts
1018
+ var scopeContext = (config, ctx) => {
1019
+ const inherit = config.inherit ?? 1 /* Conversation */;
1020
+ let scopedCtx = {
1021
+ history: [],
1022
+ tools: [],
1023
+ toolExecutors: {},
1024
+ toolLimits: {},
1025
+ toolCallCounts: {}
1026
+ };
1027
+ if (inherit & 1 /* Conversation */) {
1028
+ scopedCtx.history = ctx.history;
1029
+ scopedCtx.lastResponse = ctx.lastResponse;
1030
+ scopedCtx.lastRequest = ctx.lastRequest;
1031
+ }
1032
+ if (inherit & 2 /* Tools */) {
1033
+ scopedCtx.tools = [...ctx.tools || []];
1034
+ scopedCtx.toolExecutors = { ...ctx.toolExecutors || {} };
1035
+ scopedCtx.toolLimits = { ...ctx.toolLimits || {} };
1036
+ scopedCtx.toolCallCounts = { ...ctx.toolCallCounts || {} };
1037
+ scopedCtx.toolConfig = ctx.toolConfig ? { ...ctx.toolConfig } : void 0;
1038
+ }
1039
+ scopedCtx.stream = ctx.stream;
1040
+ if (config.tools) {
1041
+ const toolDefinitions = config.tools.map(toolConfigToToolDefinition);
1042
+ const toolExecutors = config.tools.reduce(
1043
+ (acc, tool) => {
1044
+ acc[tool.name] = tool.execute;
1045
+ return acc;
1046
+ },
1047
+ {}
1048
+ );
1049
+ const toolLimits = config.tools.reduce(
1050
+ (acc, tool) => {
1051
+ if (tool._maxCalls) {
1052
+ acc[tool.name] = tool._maxCalls;
1053
+ }
1054
+ return acc;
1055
+ },
1056
+ {}
1057
+ );
1058
+ scopedCtx.tools = toolDefinitions;
1059
+ scopedCtx.toolExecutors = toolExecutors;
1060
+ scopedCtx.toolLimits = toolLimits;
1061
+ }
1062
+ if (config.toolConfig) {
1063
+ scopedCtx.toolConfig = { ...config.toolConfig };
1064
+ }
1065
+ if (config.system) {
1066
+ const [first, ...rest] = scopedCtx.history;
1067
+ if (first?.role === "system") {
1068
+ scopedCtx.history = [{ role: "system", content: config.system }, ...rest];
1069
+ } else {
1070
+ scopedCtx.history = [{ role: "system", content: config.system }, ...scopedCtx.history];
1071
+ }
1072
+ }
1073
+ return scopedCtx;
1074
+ };
1075
+ var scope = (config, ...steps) => {
1076
+ return async (ctx) => {
1077
+ let scopedCtx = scopeContext(config, ctx);
1078
+ if (config.until) {
1079
+ do {
1080
+ scopedCtx = await compose(...steps)(scopedCtx);
1081
+ } while (!config.until(scopedCtx));
1082
+ } else {
1083
+ scopedCtx = await compose(...steps)(scopedCtx);
1084
+ }
1085
+ return {
1086
+ ...ctx,
1087
+ history: config.silent ? ctx.history : scopedCtx.history,
1088
+ lastResponse: config.silent ? ctx.lastResponse : scopedCtx.lastResponse,
1089
+ lastRequest: config.silent ? ctx.lastRequest : scopedCtx.lastRequest,
1090
+ stopReason: config.silent ? ctx.stopReason : scopedCtx.stopReason
1091
+ };
1092
+ };
1093
+ };
1094
+ // Annotate the CommonJS export names for ESM import in node:
1095
+ 0 && (module.exports = {
1096
+ Inherit,
1097
+ appendToLastRequest,
1098
+ compose,
1099
+ convertMCPSchemaToToolSchema,
1100
+ convertStandardSchemaToJsonSchema,
1101
+ createMCPTools,
1102
+ everyNMessages,
1103
+ everyNTokens,
1104
+ generateApprovalToken,
1105
+ getOrCreateThread,
1106
+ isStandardSchema,
1107
+ model,
1108
+ noToolsCalled,
1109
+ normalizeSchema,
1110
+ onApprovalRequested,
1111
+ onApprovalResolved,
1112
+ removeApprovalListener,
1113
+ requestApproval,
1114
+ resolveApproval,
1115
+ retry,
1116
+ scope,
1117
+ tap,
1118
+ toolNotUsedInNTurns,
1119
+ toolWasCalled,
1120
+ when
1121
+ });
1122
+ //# sourceMappingURL=index.cjs.map