@lowire/loop 0.0.17 → 0.0.19

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/lib/loop.d.ts CHANGED
@@ -20,26 +20,26 @@ export type LoopEvents = {
20
20
  conversation: types.Conversation;
21
21
  totalUsage: types.Usage;
22
22
  budgetTokens?: number;
23
- }) => PromiseOrValue<'continue' | 'break' | void>;
23
+ }) => PromiseOrValue<void>;
24
24
  onAfterTurn?: (params: {
25
25
  assistantMessage: types.AssistantMessage;
26
26
  totalUsage: types.Usage;
27
27
  budgetTokens?: number;
28
- }) => PromiseOrValue<'continue' | 'break' | void>;
28
+ }) => PromiseOrValue<void>;
29
29
  onBeforeToolCall?: (params: {
30
30
  assistantMessage: types.AssistantMessage;
31
31
  toolCall: types.ToolCallContentPart;
32
- }) => PromiseOrValue<'continue' | 'break' | 'disallow' | void>;
32
+ }) => PromiseOrValue<'disallow' | void>;
33
33
  onAfterToolCall?: (params: {
34
34
  assistantMessage: types.AssistantMessage;
35
35
  toolCall: types.ToolCallContentPart;
36
36
  result: types.ToolResult;
37
- }) => PromiseOrValue<'continue' | 'break' | 'disallow' | void>;
37
+ }) => PromiseOrValue<'disallow' | void>;
38
38
  onToolCallError?: (params: {
39
39
  assistantMessage: types.AssistantMessage;
40
40
  toolCall: types.ToolCallContentPart;
41
41
  error: Error;
42
- }) => PromiseOrValue<'continue' | 'break' | void>;
42
+ }) => PromiseOrValue<void>;
43
43
  };
44
44
  export type LoopOptions = types.CompletionOptions & LoopEvents & {
45
45
  tools?: types.Tool[];
@@ -56,6 +56,7 @@ export declare class Loop {
56
56
  constructor(options: LoopOptions);
57
57
  run(task: string, runOptions?: Omit<LoopOptions, 'model' | 'api' | 'apiKey'> & {
58
58
  model?: string;
59
+ abortController?: AbortController;
59
60
  }): Promise<{
60
61
  result?: types.ToolResult;
61
62
  status: 'ok' | 'break';
package/lib/loop.js CHANGED
@@ -29,6 +29,7 @@ class Loop {
29
29
  }
30
30
  async run(task, runOptions = {}) {
31
31
  const options = { ...this._loopOptions, ...runOptions };
32
+ const abortController = runOptions.abortController;
32
33
  const allTools = [...(options.tools || []).map(wrapToolWithIsDone)];
33
34
  const conversation = {
34
35
  systemPrompt,
@@ -51,13 +52,14 @@ class Loop {
51
52
  output: this._cacheOutput,
52
53
  } : undefined;
53
54
  const summarizedConversation = options.summarize ? this._summarizeConversation(task, conversation, options) : conversation;
54
- const beforeStatus = await options.onBeforeTurn?.({ conversation: summarizedConversation, totalUsage, budgetTokens });
55
- if (beforeStatus === 'break')
55
+ await options.onBeforeTurn?.({ conversation: summarizedConversation, totalUsage, budgetTokens });
56
+ if (abortController?.signal.aborted)
56
57
  return { status: 'break', usage: totalUsage, turns };
57
58
  debug?.('lowire:loop')(`Request`, JSON.stringify({ ...summarizedConversation, tools: `${summarizedConversation.tools.length} tools` }, null, 2));
58
59
  const { result: assistantMessage, usage } = await (0, cache_1.cachedComplete)(this._provider, summarizedConversation, caches, {
59
60
  ...options,
60
61
  maxTokens: budgetTokens,
62
+ signal: abortController?.signal,
61
63
  });
62
64
  const intent = assistantMessage.content.filter(part => part.type === 'text').map(part => part.text).join('\n');
63
65
  totalUsage.input += usage.input;
@@ -66,8 +68,8 @@ class Loop {
66
68
  budgetTokens -= usage.input + usage.output;
67
69
  debug?.('lowire:loop')('Usage', `input: ${usage.input}, output: ${usage.output}`);
68
70
  debug?.('lowire:loop')('Assistant', intent, JSON.stringify(assistantMessage.content, null, 2));
69
- const afterStatus = await options.onAfterTurn?.({ assistantMessage, totalUsage, budgetTokens });
70
- if (afterStatus === 'break')
71
+ await options.onAfterTurn?.({ assistantMessage, totalUsage, budgetTokens });
72
+ if (abortController?.signal.aborted)
71
73
  return { status: 'break', usage: totalUsage, turns };
72
74
  conversation.messages.push(assistantMessage);
73
75
  const toolCalls = assistantMessage.content.filter(part => part.type === 'tool_call');
@@ -79,7 +81,7 @@ class Loop {
79
81
  const { name, arguments: args } = toolCall;
80
82
  debug?.('lowire:loop')('Call tool', name, JSON.stringify(args, null, 2));
81
83
  const status = await options.onBeforeToolCall?.({ assistantMessage, toolCall });
82
- if (status === 'break')
84
+ if (abortController?.signal.aborted)
83
85
  return { status: 'break', usage: totalUsage, turns };
84
86
  if (status === 'disallow') {
85
87
  toolCall.result = {
@@ -103,7 +105,7 @@ class Loop {
103
105
  const text = result.content.filter(part => part.type === 'text').map(part => part.text).join('\n');
104
106
  debug?.('lowire:loop')('Tool result', text, JSON.stringify(result, null, 2));
105
107
  const status = await options.onAfterToolCall?.({ assistantMessage, toolCall, result });
106
- if (status === 'break')
108
+ if (abortController?.signal.aborted)
107
109
  return { status: 'break', usage: totalUsage, turns };
108
110
  if (status === 'disallow') {
109
111
  toolCall.result = {
@@ -118,8 +120,8 @@ class Loop {
118
120
  }
119
121
  catch (error) {
120
122
  const errorMessage = `Error while executing tool "${name}": ${error instanceof Error ? error.message : String(error)}\n\nPlease try to recover and complete the task.`;
121
- const status = await options.onToolCallError?.({ assistantMessage, toolCall, error });
122
- if (status === 'break')
123
+ await options.onToolCallError?.({ assistantMessage, toolCall, error });
124
+ if (abortController?.signal.aborted)
123
125
  return { status: 'break', usage: totalUsage, turns };
124
126
  toolCall.result = {
125
127
  content: [{ type: 'text', text: errorMessage }],
@@ -128,8 +130,6 @@ class Loop {
128
130
  }
129
131
  }
130
132
  }
131
- if (options.summarize)
132
- return this._summarizeConversation(task, conversation, options);
133
133
  throw new Error('Failed to perform step, max attempts reached');
134
134
  }
135
135
  _summarizeConversation(task, conversation, options) {
@@ -52,7 +52,8 @@ async function create(createParams, options) {
52
52
  const response = await fetch(options.apiEndpoint ?? `https://api.anthropic.com/v1/messages`, {
53
53
  method: 'POST',
54
54
  headers,
55
- body: JSON.stringify(createParams)
55
+ body: JSON.stringify(createParams),
56
+ signal: options.signal,
56
57
  });
57
58
  if (!response.ok) {
58
59
  options.debug?.('lowire:anthropic')('Response:', response.status);
@@ -52,7 +52,8 @@ async function create(model, createParams, options) {
52
52
  'Content-Type': 'application/json',
53
53
  'x-goog-api-key': options.apiKey,
54
54
  },
55
- body: JSON.stringify(createParams)
55
+ body: JSON.stringify(createParams),
56
+ signal: options.signal,
56
57
  });
57
58
  if (!response.ok) {
58
59
  options.debug?.('lowire:google')('Response:', response.status);
@@ -72,7 +72,8 @@ async function create(createParams, options) {
72
72
  const response = await fetch(options.apiEndpoint ?? `https://api.openai.com/v1/responses`, {
73
73
  method: 'POST',
74
74
  headers,
75
- body: JSON.stringify(createParams)
75
+ body: JSON.stringify(createParams),
76
+ signal: options.signal,
76
77
  });
77
78
  if (!response.ok) {
78
79
  options.debug?.('lowire:openai-responses')('Response:', response.status);
@@ -71,6 +71,7 @@ async function create(createParams, options) {
71
71
  method: 'POST',
72
72
  headers,
73
73
  body: JSON.stringify(createParams),
74
+ signal: options.signal,
74
75
  });
75
76
  if (!response.ok) {
76
77
  options.debug?.('lowire:openai')('Response:', response.status);
package/lib/types.d.ts CHANGED
@@ -97,6 +97,7 @@ export type CompletionOptions = {
97
97
  maxTokens?: number;
98
98
  reasoning?: 'none' | 'medium' | 'high';
99
99
  temperature?: number;
100
+ signal?: AbortSignal;
100
101
  debug?: Debug;
101
102
  };
102
103
  export interface Provider {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowire/loop",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Small agentic loop",
5
5
  "repository": {
6
6
  "type": "git",