@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 +6 -5
- package/lib/loop.js +10 -10
- package/lib/providers/anthropic.js +2 -1
- package/lib/providers/google.js +2 -1
- package/lib/providers/openai.js +2 -1
- package/lib/providers/openaiCompatible.js +1 -0
- package/lib/types.d.ts +1 -0
- package/package.json +1 -1
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<
|
|
23
|
+
}) => PromiseOrValue<void>;
|
|
24
24
|
onAfterTurn?: (params: {
|
|
25
25
|
assistantMessage: types.AssistantMessage;
|
|
26
26
|
totalUsage: types.Usage;
|
|
27
27
|
budgetTokens?: number;
|
|
28
|
-
}) => PromiseOrValue<
|
|
28
|
+
}) => PromiseOrValue<void>;
|
|
29
29
|
onBeforeToolCall?: (params: {
|
|
30
30
|
assistantMessage: types.AssistantMessage;
|
|
31
31
|
toolCall: types.ToolCallContentPart;
|
|
32
|
-
}) => PromiseOrValue<'
|
|
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<'
|
|
37
|
+
}) => PromiseOrValue<'disallow' | void>;
|
|
38
38
|
onToolCallError?: (params: {
|
|
39
39
|
assistantMessage: types.AssistantMessage;
|
|
40
40
|
toolCall: types.ToolCallContentPart;
|
|
41
41
|
error: Error;
|
|
42
|
-
}) => PromiseOrValue<
|
|
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
|
-
|
|
55
|
-
if (
|
|
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
|
-
|
|
70
|
-
if (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
122
|
-
if (
|
|
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);
|
package/lib/providers/google.js
CHANGED
|
@@ -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);
|
package/lib/providers/openai.js
CHANGED
|
@@ -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);
|
package/lib/types.d.ts
CHANGED