@arki-moe/agent-ts 5.0.0 → 5.2.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/README.md +5 -0
- package/dist/abort.d.ts +5 -0
- package/dist/abort.js +22 -0
- package/dist/adapter/openai.js +11 -3
- package/dist/adapter/openrouter.js +11 -3
- package/dist/adapter/selfhost_chat_completions.d.ts +2 -0
- package/dist/adapter/selfhost_chat_completions.js +156 -0
- package/dist/adapter/sse.d.ts +1 -1
- package/dist/adapter/sse.js +9 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -1
- package/dist/types.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ console.log(agent.context);
|
|
|
47
47
|
|---------|----------|----------|
|
|
48
48
|
| `openai` | `apiKey` (or `OPENAI_API_KEY` env), `model` | `system`, `baseUrl` |
|
|
49
49
|
| `openrouter` | `apiKey` (or `OPENROUTER_API_KEY` env), `model` | `system`, `baseUrl`, `httpReferer`, `title` |
|
|
50
|
+
| `selfhost_chat_completions` | `apiKey` (or `SELFHOST_API_KEY` env), `model` | `system`, `baseUrl` |
|
|
50
51
|
|
|
51
52
|
When `apiKey` is not provided in config, adapters read from the corresponding environment variable. An error is thrown only when both are missing.
|
|
52
53
|
|
|
@@ -66,6 +67,8 @@ When `apiKey` is not provided in config, adapters read from the corresponding en
|
|
|
66
67
|
| `system` | `string` | Optional system prompt |
|
|
67
68
|
| `endCondition` | `(context, last) => boolean` | Stop condition for `run`. Defaults to `last.role === Role.Ai` |
|
|
68
69
|
| `onStream` | `(textDelta: string) => void \| Promise<void>` | Stream hook for AI text only. When provided, adapters use SSE streaming and still return the final `Message[]`. |
|
|
70
|
+
| `isAbort` | `() => boolean \| Promise<boolean>` | Abort hook polled during `run`; return `true` to stop early and return partial results. |
|
|
71
|
+
| `toolChoice` | `"auto" \| "required" \| "none" \| object` | Tool choice passed to adapters. Defaults to `auto` when tools are provided. |
|
|
69
72
|
| `onToolCall` | `(message, args, agent) => boolean \| void \| Promise<boolean \| void>` | Called before each tool execution; return `false` to skip tool execution and `onToolResult` |
|
|
70
73
|
| `onToolResult` | `(message, agent) => void \| Promise<void>` | Called after each tool execution (`message.role === Role.ToolResult`) |
|
|
71
74
|
|
|
@@ -75,6 +78,8 @@ When `apiKey` is not provided in config, adapters read from the corresponding en
|
|
|
75
78
|
|
|
76
79
|
`tool.data` is a local JSON metadata bag for your own use and is not sent to adapters.
|
|
77
80
|
|
|
81
|
+
`isAbort` is polled on the hot path (including streaming). When it returns `true`, `agent.run` stops and returns whatever messages it has so far. Streaming abort returns a partial AI message with `isPartial: true`.
|
|
82
|
+
|
|
78
83
|
## Scripts
|
|
79
84
|
|
|
80
85
|
| Command | Description |
|
package/dist/abort.d.ts
ADDED
package/dist/abort.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAbortChecker = createAbortChecker;
|
|
4
|
+
function createAbortChecker(isAbort) {
|
|
5
|
+
let aborted = false;
|
|
6
|
+
const check = async () => {
|
|
7
|
+
if (!isAbort)
|
|
8
|
+
return false;
|
|
9
|
+
try {
|
|
10
|
+
const result = await Promise.resolve(isAbort());
|
|
11
|
+
if (result)
|
|
12
|
+
aborted = true;
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
aborted = true;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const isAborted = () => aborted;
|
|
21
|
+
return { check, isAborted };
|
|
22
|
+
}
|
package/dist/adapter/openai.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.openaiAdapter = openaiAdapter;
|
|
4
|
+
const abort_1 = require("../abort");
|
|
4
5
|
const types_1 = require("../types");
|
|
5
6
|
const sse_1 = require("./sse");
|
|
6
7
|
const ROLE_TO_OPENAI = {
|
|
@@ -30,7 +31,9 @@ async function openaiAdapter(config, context, tools) {
|
|
|
30
31
|
const baseUrl = config.baseUrl ?? "https://api.openai.com";
|
|
31
32
|
const apiKey = config.apiKey || process.env.OPENAI_API_KEY || "";
|
|
32
33
|
const model = config.model ?? "gpt-5-nano";
|
|
34
|
+
const toolChoice = config.toolChoice;
|
|
33
35
|
const onStream = config.onStream;
|
|
36
|
+
const { check, isAborted } = (0, abort_1.createAbortChecker)(config.isAbort);
|
|
34
37
|
if (!apiKey)
|
|
35
38
|
throw new Error("OpenAI adapter requires apiKey in config or OPENAI_API_KEY env");
|
|
36
39
|
const contextMessages = toOpenAIMessages(context);
|
|
@@ -42,9 +45,11 @@ async function openaiAdapter(config, context, tools) {
|
|
|
42
45
|
model,
|
|
43
46
|
messages,
|
|
44
47
|
tools: tools.length ? tools.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters ?? {} } })) : undefined,
|
|
45
|
-
tool_choice: tools.length ? "auto" : undefined,
|
|
48
|
+
tool_choice: tools.length ? (toolChoice ?? "auto") : undefined,
|
|
46
49
|
stream: onStream ? true : undefined,
|
|
47
50
|
};
|
|
51
|
+
if (await check())
|
|
52
|
+
return [];
|
|
48
53
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/chat/completions`, {
|
|
49
54
|
method: "POST",
|
|
50
55
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
@@ -106,7 +111,8 @@ async function openaiAdapter(config, context, tools) {
|
|
|
106
111
|
content += delta.content;
|
|
107
112
|
await Promise.resolve(onStream(delta.content));
|
|
108
113
|
}
|
|
109
|
-
|
|
114
|
+
await check();
|
|
115
|
+
}, check);
|
|
110
116
|
if (toolCalls.size > 0) {
|
|
111
117
|
return [...toolCalls.entries()]
|
|
112
118
|
.sort((a, b) => a[0] - b[0])
|
|
@@ -121,7 +127,9 @@ async function openaiAdapter(config, context, tools) {
|
|
|
121
127
|
};
|
|
122
128
|
});
|
|
123
129
|
}
|
|
124
|
-
|
|
130
|
+
if (!content)
|
|
131
|
+
return [];
|
|
132
|
+
return [{ role: types_1.Role.Ai, content, isPartial: isAborted() ? true : undefined }];
|
|
125
133
|
}
|
|
126
134
|
const text = await res.text();
|
|
127
135
|
let data;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.openrouterAdapter = openrouterAdapter;
|
|
4
|
+
const abort_1 = require("../abort");
|
|
4
5
|
const types_1 = require("../types");
|
|
5
6
|
const sse_1 = require("./sse");
|
|
6
7
|
const ROLE_TO_OPENROUTER = {
|
|
@@ -30,9 +31,11 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
30
31
|
const baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
31
32
|
const apiKey = config.apiKey || process.env.OPENROUTER_API_KEY || "";
|
|
32
33
|
const model = config.model ?? "gpt-5-nano";
|
|
34
|
+
const toolChoice = config.toolChoice;
|
|
33
35
|
const httpReferer = config.httpReferer;
|
|
34
36
|
const title = config.title;
|
|
35
37
|
const onStream = config.onStream;
|
|
38
|
+
const { check, isAborted } = (0, abort_1.createAbortChecker)(config.isAbort);
|
|
36
39
|
if (!apiKey)
|
|
37
40
|
throw new Error("OpenRouter adapter requires apiKey in config or OPENROUTER_API_KEY env");
|
|
38
41
|
const contextMessages = toOpenRouterMessages(context);
|
|
@@ -44,7 +47,7 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
44
47
|
model,
|
|
45
48
|
messages,
|
|
46
49
|
tools: tools.length ? tools.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters ?? {} } })) : undefined,
|
|
47
|
-
tool_choice: tools.length ? "auto" : undefined,
|
|
50
|
+
tool_choice: tools.length ? (toolChoice ?? "auto") : undefined,
|
|
48
51
|
stream: onStream ? true : undefined,
|
|
49
52
|
};
|
|
50
53
|
const headers = {
|
|
@@ -55,6 +58,8 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
55
58
|
headers["HTTP-Referer"] = httpReferer;
|
|
56
59
|
if (title)
|
|
57
60
|
headers["X-Title"] = title;
|
|
61
|
+
if (await check())
|
|
62
|
+
return [];
|
|
58
63
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
59
64
|
method: "POST",
|
|
60
65
|
headers,
|
|
@@ -116,7 +121,8 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
116
121
|
content += delta.content;
|
|
117
122
|
await Promise.resolve(onStream(delta.content));
|
|
118
123
|
}
|
|
119
|
-
|
|
124
|
+
await check();
|
|
125
|
+
}, check);
|
|
120
126
|
if (toolCalls.size > 0) {
|
|
121
127
|
return [...toolCalls.entries()]
|
|
122
128
|
.sort((a, b) => a[0] - b[0])
|
|
@@ -131,7 +137,9 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
131
137
|
};
|
|
132
138
|
});
|
|
133
139
|
}
|
|
134
|
-
|
|
140
|
+
if (!content)
|
|
141
|
+
return [];
|
|
142
|
+
return [{ role: types_1.Role.Ai, content, isPartial: isAborted() ? true : undefined }];
|
|
135
143
|
}
|
|
136
144
|
const text = await res.text();
|
|
137
145
|
let data;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selfhostChatCompletionsAdapter = selfhostChatCompletionsAdapter;
|
|
4
|
+
const abort_1 = require("../abort");
|
|
5
|
+
const types_1 = require("../types");
|
|
6
|
+
const sse_1 = require("./sse");
|
|
7
|
+
const ROLE_TO_OPENAI = {
|
|
8
|
+
[types_1.Role.System]: "system",
|
|
9
|
+
[types_1.Role.User]: "user",
|
|
10
|
+
[types_1.Role.Ai]: "assistant",
|
|
11
|
+
};
|
|
12
|
+
function toOpenAIMessages(context) {
|
|
13
|
+
return context.reduce((out, m) => {
|
|
14
|
+
if (m.role === types_1.Role.System || m.role === types_1.Role.User || m.role === types_1.Role.Ai)
|
|
15
|
+
return [...out, { role: ROLE_TO_OPENAI[m.role], content: m.content }];
|
|
16
|
+
if (m.role === types_1.Role.ToolResult)
|
|
17
|
+
return [...out, { role: "tool", content: m.content, tool_call_id: m.callId }];
|
|
18
|
+
if (m.role === types_1.Role.ToolCall) {
|
|
19
|
+
const tc = { id: m.callId, type: "function", function: { name: m.toolName, arguments: m.argsText ?? "{}" } };
|
|
20
|
+
const last = out[out.length - 1];
|
|
21
|
+
if (last?.tool_calls) {
|
|
22
|
+
last.tool_calls.push(tc);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
return [...out, { role: "assistant", tool_calls: [tc] }];
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}, []);
|
|
29
|
+
}
|
|
30
|
+
async function selfhostChatCompletionsAdapter(config, context, tools) {
|
|
31
|
+
const baseUrl = config.baseUrl ?? "http://localhost:1234";
|
|
32
|
+
const apiKey = config.apiKey || process.env.SELFHOST_API_KEY || "";
|
|
33
|
+
const model = config.model ?? "gpt-5-nano";
|
|
34
|
+
const toolChoice = config.toolChoice;
|
|
35
|
+
const onStream = config.onStream;
|
|
36
|
+
const { check, isAborted } = (0, abort_1.createAbortChecker)(config.isAbort);
|
|
37
|
+
if (!apiKey)
|
|
38
|
+
throw new Error("Self-host chat completions adapter requires apiKey in config or SELFHOST_API_KEY env");
|
|
39
|
+
const contextMessages = toOpenAIMessages(context);
|
|
40
|
+
const systemContent = config.system;
|
|
41
|
+
const messages = systemContent
|
|
42
|
+
? [{ role: "system", content: systemContent }, ...contextMessages]
|
|
43
|
+
: contextMessages;
|
|
44
|
+
const body = {
|
|
45
|
+
model,
|
|
46
|
+
messages,
|
|
47
|
+
tools: tools.length ? tools.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters ?? {} } })) : undefined,
|
|
48
|
+
tool_choice: tools.length ? (toolChoice ?? "auto") : undefined,
|
|
49
|
+
stream: onStream ? true : undefined,
|
|
50
|
+
};
|
|
51
|
+
if (await check())
|
|
52
|
+
return [];
|
|
53
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/v1/chat/completions`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
56
|
+
body: JSON.stringify(body),
|
|
57
|
+
});
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const text = await res.text();
|
|
60
|
+
let errMsg = `Self-host chat completions API HTTP ${res.status}`;
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse(text);
|
|
63
|
+
if (parsed?.error?.message)
|
|
64
|
+
errMsg = parsed.error.message;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
if (text)
|
|
68
|
+
errMsg += `: ${text.slice(0, 200)}`;
|
|
69
|
+
}
|
|
70
|
+
throw new Error(errMsg);
|
|
71
|
+
}
|
|
72
|
+
if (onStream) {
|
|
73
|
+
let content = "";
|
|
74
|
+
const toolCalls = new Map();
|
|
75
|
+
const upsertToolCall = (tc) => {
|
|
76
|
+
const index = typeof tc?.index === "number" ? tc.index : toolCalls.size;
|
|
77
|
+
let entry = toolCalls.get(index);
|
|
78
|
+
if (!entry) {
|
|
79
|
+
entry = { args: "" };
|
|
80
|
+
toolCalls.set(index, entry);
|
|
81
|
+
}
|
|
82
|
+
if (typeof tc?.id === "string")
|
|
83
|
+
entry.id = tc.id;
|
|
84
|
+
const fn = tc?.function;
|
|
85
|
+
if (fn) {
|
|
86
|
+
if (typeof fn.name === "string")
|
|
87
|
+
entry.name = fn.name;
|
|
88
|
+
if (typeof fn.arguments === "string")
|
|
89
|
+
entry.args += fn.arguments;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
await (0, sse_1.readSse)(res, async (dataLine) => {
|
|
93
|
+
if (dataLine === "[DONE]")
|
|
94
|
+
return;
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(dataLine);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new Error(`Self-host chat completions API returned invalid JSON: ${dataLine.slice(0, 200)}`);
|
|
101
|
+
}
|
|
102
|
+
const delta = parsed?.choices?.[0]?.delta;
|
|
103
|
+
if (!delta)
|
|
104
|
+
return;
|
|
105
|
+
if (Array.isArray(delta.tool_calls)) {
|
|
106
|
+
for (const tc of delta.tool_calls) {
|
|
107
|
+
upsertToolCall(tc);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (typeof delta.content === "string") {
|
|
111
|
+
content += delta.content;
|
|
112
|
+
await Promise.resolve(onStream(delta.content));
|
|
113
|
+
}
|
|
114
|
+
await check();
|
|
115
|
+
}, check);
|
|
116
|
+
if (toolCalls.size > 0) {
|
|
117
|
+
return [...toolCalls.entries()]
|
|
118
|
+
.sort((a, b) => a[0] - b[0])
|
|
119
|
+
.map(([index, tc]) => {
|
|
120
|
+
if (!tc.name)
|
|
121
|
+
throw new Error(`Self-host streaming tool call missing function name at index ${index}`);
|
|
122
|
+
return {
|
|
123
|
+
role: types_1.Role.ToolCall,
|
|
124
|
+
toolName: tc.name,
|
|
125
|
+
callId: tc.id ?? `call_${index}`,
|
|
126
|
+
argsText: tc.args.length ? tc.args : "{}",
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (!content)
|
|
131
|
+
return [];
|
|
132
|
+
return [{ role: types_1.Role.Ai, content, isPartial: isAborted() ? true : undefined }];
|
|
133
|
+
}
|
|
134
|
+
const text = await res.text();
|
|
135
|
+
let data;
|
|
136
|
+
try {
|
|
137
|
+
data = JSON.parse(text);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
throw new Error(`Self-host chat completions API returned invalid JSON: ${text.slice(0, 200)}`);
|
|
141
|
+
}
|
|
142
|
+
if (data.error)
|
|
143
|
+
throw new Error(`Self-host chat completions API error: ${data.error.message}`);
|
|
144
|
+
const msg = data.choices?.[0]?.message;
|
|
145
|
+
if (!msg)
|
|
146
|
+
throw new Error("Self-host chat completions API returned empty response");
|
|
147
|
+
if (msg.tool_calls?.length) {
|
|
148
|
+
return msg.tool_calls.map((tc) => ({
|
|
149
|
+
role: types_1.Role.ToolCall,
|
|
150
|
+
toolName: tc.function.name,
|
|
151
|
+
callId: tc.id,
|
|
152
|
+
argsText: tc.function.arguments ?? "{}",
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
return [{ role: types_1.Role.Ai, content: msg.content ?? "" }];
|
|
156
|
+
}
|
package/dist/adapter/sse.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function readSse(res: Response, onData: (data: string) => void | Promise<void>): Promise<void>;
|
|
1
|
+
export declare function readSse(res: Response, onData: (data: string) => void | Promise<void>, isAbort?: () => boolean | Promise<boolean>): Promise<void>;
|
package/dist/adapter/sse.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readSse = readSse;
|
|
4
|
-
async function readSse(res, onData) {
|
|
4
|
+
async function readSse(res, onData, isAbort) {
|
|
5
5
|
const body = res.body;
|
|
6
6
|
if (!body)
|
|
7
7
|
throw new Error("Response body is empty");
|
|
@@ -10,12 +10,16 @@ async function readSse(res, onData) {
|
|
|
10
10
|
let buffer = "";
|
|
11
11
|
let dataLines = [];
|
|
12
12
|
for (;;) {
|
|
13
|
+
if (isAbort && (await isAbort()))
|
|
14
|
+
return;
|
|
13
15
|
const { value, done } = await reader.read();
|
|
14
16
|
if (done)
|
|
15
17
|
break;
|
|
16
18
|
buffer += decoder.decode(value, { stream: true });
|
|
17
19
|
let newlineIndex = buffer.indexOf("\n");
|
|
18
20
|
while (newlineIndex !== -1) {
|
|
21
|
+
if (isAbort && (await isAbort()))
|
|
22
|
+
return;
|
|
19
23
|
let line = buffer.slice(0, newlineIndex);
|
|
20
24
|
buffer = buffer.slice(newlineIndex + 1);
|
|
21
25
|
if (line.endsWith("\r"))
|
|
@@ -34,6 +38,8 @@ async function readSse(res, onData) {
|
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
if (buffer) {
|
|
41
|
+
if (isAbort && (await isAbort()))
|
|
42
|
+
return;
|
|
37
43
|
let line = buffer;
|
|
38
44
|
if (line.endsWith("\r"))
|
|
39
45
|
line = line.slice(0, -1);
|
|
@@ -41,6 +47,8 @@ async function readSse(res, onData) {
|
|
|
41
47
|
dataLines.push(line.slice(5).trimStart());
|
|
42
48
|
}
|
|
43
49
|
if (dataLines.length > 0) {
|
|
50
|
+
if (isAbort && (await isAbort()))
|
|
51
|
+
return;
|
|
44
52
|
const data = dataLines.join("\n");
|
|
45
53
|
await onData(data);
|
|
46
54
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentConfig, AgentLike, Context, Message, Tool } from "./types";
|
|
2
2
|
export { openaiAdapter } from "./adapter/openai";
|
|
3
3
|
export { openrouterAdapter } from "./adapter/openrouter";
|
|
4
|
+
export { selfhostChatCompletionsAdapter } from "./adapter/selfhost_chat_completions";
|
|
4
5
|
export type { Adapter, AgentConfig, AgentLike, Context, Message, Tool } from "./types";
|
|
5
6
|
export { Role } from "./types";
|
|
6
7
|
export declare class Agent implements AgentLike {
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Agent = exports.Role = exports.openrouterAdapter = exports.openaiAdapter = void 0;
|
|
3
|
+
exports.Agent = exports.Role = exports.selfhostChatCompletionsAdapter = exports.openrouterAdapter = exports.openaiAdapter = void 0;
|
|
4
4
|
const openai_1 = require("./adapter/openai");
|
|
5
5
|
const openrouter_1 = require("./adapter/openrouter");
|
|
6
|
+
const selfhost_chat_completions_1 = require("./adapter/selfhost_chat_completions");
|
|
7
|
+
const abort_1 = require("./abort");
|
|
6
8
|
const types_1 = require("./types");
|
|
7
9
|
var openai_2 = require("./adapter/openai");
|
|
8
10
|
Object.defineProperty(exports, "openaiAdapter", { enumerable: true, get: function () { return openai_2.openaiAdapter; } });
|
|
9
11
|
var openrouter_2 = require("./adapter/openrouter");
|
|
10
12
|
Object.defineProperty(exports, "openrouterAdapter", { enumerable: true, get: function () { return openrouter_2.openrouterAdapter; } });
|
|
13
|
+
var selfhost_chat_completions_2 = require("./adapter/selfhost_chat_completions");
|
|
14
|
+
Object.defineProperty(exports, "selfhostChatCompletionsAdapter", { enumerable: true, get: function () { return selfhost_chat_completions_2.selfhostChatCompletionsAdapter; } });
|
|
11
15
|
var types_2 = require("./types");
|
|
12
16
|
Object.defineProperty(exports, "Role", { enumerable: true, get: function () { return types_2.Role; } });
|
|
13
17
|
const adapters = {
|
|
14
18
|
openai: openai_1.openaiAdapter,
|
|
15
19
|
openrouter: openrouter_1.openrouterAdapter,
|
|
20
|
+
selfhost_chat_completions: selfhost_chat_completions_1.selfhostChatCompletionsAdapter,
|
|
16
21
|
};
|
|
17
22
|
class Agent {
|
|
18
23
|
constructor(adapterName, config) {
|
|
@@ -30,6 +35,9 @@ class Agent {
|
|
|
30
35
|
}
|
|
31
36
|
async run(message) {
|
|
32
37
|
const all = [];
|
|
38
|
+
const { check } = (0, abort_1.createAbortChecker)(this.config.isAbort);
|
|
39
|
+
if (await check())
|
|
40
|
+
return all;
|
|
33
41
|
const sessionContext = [...this.context];
|
|
34
42
|
const persistToContext = (msgs) => {
|
|
35
43
|
this.context.push(...msgs);
|
|
@@ -41,6 +49,8 @@ class Agent {
|
|
|
41
49
|
pushToSession([userMessage]);
|
|
42
50
|
persistToContext([userMessage]);
|
|
43
51
|
const runAdapter = async () => {
|
|
52
|
+
if (await check())
|
|
53
|
+
return [];
|
|
44
54
|
const msgs = await this.adapter(this.config, sessionContext, this.tools);
|
|
45
55
|
pushToSession(msgs);
|
|
46
56
|
persistToContext(msgs);
|
|
@@ -48,7 +58,11 @@ class Agent {
|
|
|
48
58
|
return msgs;
|
|
49
59
|
};
|
|
50
60
|
let msgs = await runAdapter();
|
|
61
|
+
if (await check())
|
|
62
|
+
return all;
|
|
51
63
|
for (;;) {
|
|
64
|
+
if (await check())
|
|
65
|
+
return all;
|
|
52
66
|
const last = msgs[msgs.length - 1];
|
|
53
67
|
if (this.endCondition(sessionContext, last))
|
|
54
68
|
return all;
|
|
@@ -56,6 +70,8 @@ class Agent {
|
|
|
56
70
|
if (toolCalls.length === 0)
|
|
57
71
|
return all;
|
|
58
72
|
const results = await Promise.all(toolCalls.map(async (m) => {
|
|
73
|
+
if (await check())
|
|
74
|
+
return null;
|
|
59
75
|
const tool = this.tools.find((t) => t.name === m.toolName);
|
|
60
76
|
if (!tool)
|
|
61
77
|
throw new Error(`Tool "${m.toolName}" is not registered`);
|
package/dist/types.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type Message = {
|
|
|
14
14
|
} | {
|
|
15
15
|
role: Role.Ai;
|
|
16
16
|
content: string;
|
|
17
|
+
isPartial?: boolean;
|
|
17
18
|
} | {
|
|
18
19
|
role: Role.ToolCall;
|
|
19
20
|
toolName: string;
|
|
@@ -41,6 +42,8 @@ export type Tool = {
|
|
|
41
42
|
export type AgentConfig = {
|
|
42
43
|
endCondition?: (context: Message[], last: Message) => boolean;
|
|
43
44
|
onStream?: (textDelta: string) => void | Promise<void>;
|
|
45
|
+
isAbort?: () => boolean | Promise<boolean>;
|
|
46
|
+
toolChoice?: "auto" | "required" | "none" | Record<string, unknown>;
|
|
44
47
|
onToolCall?: (message: Extract<Message, {
|
|
45
48
|
role: Role.ToolCall;
|
|
46
49
|
}>, args: unknown, agent: AgentLike) => boolean | void | Promise<boolean | void>;
|