@lowire/loop 0.0.9 → 0.0.11

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
@@ -14,31 +14,32 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import type * as types from './types';
17
+ type PromiseOrValue<T> = T | Promise<T>;
17
18
  export type LoopEvents = {
18
19
  onBeforeTurn?: (params: {
19
20
  conversation: types.Conversation;
20
21
  totalUsage: types.Usage;
21
- budgetTokens: number;
22
- }) => Promise<'break' | 'continue' | void>;
22
+ budgetTokens?: number;
23
+ }) => PromiseOrValue<'continue' | 'break' | void>;
23
24
  onAfterTurn?: (params: {
24
25
  assistantMessage: types.AssistantMessage;
25
26
  totalUsage: types.Usage;
26
- budgetTokens: number;
27
- }) => Promise<'break' | 'continue' | void>;
27
+ budgetTokens?: number;
28
+ }) => PromiseOrValue<'continue' | 'break' | void>;
28
29
  onBeforeToolCall?: (params: {
29
30
  assistantMessage: types.AssistantMessage;
30
31
  toolCall: types.ToolCallContentPart;
31
- }) => Promise<'allowed' | 'disallowed' | void>;
32
+ }) => PromiseOrValue<'continue' | 'break' | 'disallow' | void>;
32
33
  onAfterToolCall?: (params: {
33
34
  assistantMessage: types.AssistantMessage;
34
35
  toolCall: types.ToolCallContentPart;
35
36
  result: types.ToolResult;
36
- }) => Promise<'allowed' | 'disallowed' | void>;
37
+ }) => PromiseOrValue<'continue' | 'break' | 'disallow' | void>;
37
38
  onToolCallError?: (params: {
38
39
  assistantMessage: types.AssistantMessage;
39
40
  toolCall: types.ToolCallContentPart;
40
41
  error: Error;
41
- }) => Promise<void>;
42
+ }) => PromiseOrValue<'continue' | 'break' | void>;
42
43
  };
43
44
  export type LoopOptions = types.CompletionOptions & LoopEvents & {
44
45
  tools?: types.Tool[];
@@ -67,3 +68,4 @@ export declare class Loop {
67
68
  private _summarizeConversation;
68
69
  cache(): types.ReplayCache;
69
70
  }
71
+ export {};
package/lib/loop.js CHANGED
@@ -43,12 +43,12 @@ class Loop {
43
43
  tools: allTools,
44
44
  };
45
45
  const debug = options.debug;
46
- let budgetTokens = options.maxTokens ?? 100_000;
46
+ let budgetTokens = options.maxTokens;
47
47
  const totalUsage = { input: 0, output: 0 };
48
48
  debug?.('lowire:loop')(`Starting ${this._provider.name} loop`, task);
49
49
  const maxTurns = options.maxTurns || 100;
50
50
  for (let turns = 0; turns < maxTurns; ++turns) {
51
- if (budgetTokens <= 0)
51
+ if (options.maxTokens && budgetTokens !== undefined && budgetTokens <= 0)
52
52
  throw new Error(`Budget tokens ${options.maxTokens} exhausted`);
53
53
  debug?.('lowire:loop')(`Turn ${turns + 1} of (max ${maxTurns})`);
54
54
  const caches = options.cache ? {
@@ -68,7 +68,8 @@ class Loop {
68
68
  const intent = assistantMessage.content.filter(part => part.type === 'text').map(part => part.text).join('\n');
69
69
  totalUsage.input += usage.input;
70
70
  totalUsage.output += usage.output;
71
- budgetTokens -= usage.input + usage.output;
71
+ if (budgetTokens !== undefined)
72
+ budgetTokens -= usage.input + usage.output;
72
73
  debug?.('lowire:loop')('Usage', `input: ${usage.input}, output: ${usage.output}`);
73
74
  debug?.('lowire:loop')('Assistant', intent, JSON.stringify(assistantMessage.content, null, 2));
74
75
  const afterStatus = await options.onAfterTurn?.({ assistantMessage, totalUsage, budgetTokens });
@@ -86,7 +87,9 @@ class Loop {
86
87
  if (name === 'report_result')
87
88
  return { result: args, status: 'ok', usage: totalUsage, turns };
88
89
  const status = await options.onBeforeToolCall?.({ assistantMessage, toolCall });
89
- if (status === 'disallowed') {
90
+ if (status === 'break')
91
+ return { status: 'break', usage: totalUsage, turns };
92
+ if (status === 'disallow') {
90
93
  toolCall.result = {
91
94
  content: [{ type: 'text', text: 'Tool call is disallowed.' }],
92
95
  isError: true,
@@ -108,7 +111,9 @@ class Loop {
108
111
  const text = result.content.filter(part => part.type === 'text').map(part => part.text).join('\n');
109
112
  debug?.('lowire:loop')('Tool result', text, JSON.stringify(result, null, 2));
110
113
  const status = await options.onAfterToolCall?.({ assistantMessage, toolCall, result });
111
- if (status === 'disallowed') {
114
+ if (status === 'break')
115
+ return { status: 'break', usage: totalUsage, turns };
116
+ if (status === 'disallow') {
112
117
  toolCall.result = {
113
118
  content: [{ type: 'text', text: 'Tool result is disallowed to be reported.' }],
114
119
  isError: true,
@@ -119,7 +124,9 @@ class Loop {
119
124
  }
120
125
  catch (error) {
121
126
  const errorMessage = `Error while executing tool "${name}": ${error instanceof Error ? error.message : String(error)}\n\nPlease try to recover and complete the task.`;
122
- await options.onToolCallError?.({ assistantMessage, toolCall, error });
127
+ const status = await options.onToolCallError?.({ assistantMessage, toolCall, error });
128
+ if (status === 'break')
129
+ return { status: 'break', usage: totalUsage, turns };
123
130
  toolCall.result = {
124
131
  content: [{ type: 'text', text: errorMessage }],
125
132
  isError: true,
@@ -19,7 +19,7 @@ exports.Anthropic = void 0;
19
19
  class Anthropic {
20
20
  name = 'anthropic';
21
21
  async complete(conversation, options) {
22
- const maxTokens = Math.min(options.maxTokens ?? 32768, 32768);
22
+ const maxTokens = Math.min(options.maxTokens ?? 32_768, 32_768);
23
23
  const response = await create({
24
24
  model: options.model,
25
25
  max_tokens: maxTokens,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowire/loop",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "description": "Small agentic loop",
5
5
  "repository": {
6
6
  "type": "git",