@arki-moe/agent-ts 5.1.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 +2 -0
- package/dist/adapter/openai.js +2 -1
- package/dist/adapter/openrouter.js +2 -1
- package/dist/adapter/selfhost_chat_completions.d.ts +2 -0
- package/dist/adapter/selfhost_chat_completions.js +156 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -1
- package/dist/types.d.ts +1 -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
|
|
|
@@ -67,6 +68,7 @@ When `apiKey` is not provided in config, adapters read from the corresponding en
|
|
|
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[]`. |
|
|
69
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. |
|
|
70
72
|
| `onToolCall` | `(message, args, agent) => boolean \| void \| Promise<boolean \| void>` | Called before each tool execution; return `false` to skip tool execution and `onToolResult` |
|
|
71
73
|
| `onToolResult` | `(message, agent) => void \| Promise<void>` | Called after each tool execution (`message.role === Role.ToolResult`) |
|
|
72
74
|
|
package/dist/adapter/openai.js
CHANGED
|
@@ -31,6 +31,7 @@ async function openaiAdapter(config, context, tools) {
|
|
|
31
31
|
const baseUrl = config.baseUrl ?? "https://api.openai.com";
|
|
32
32
|
const apiKey = config.apiKey || process.env.OPENAI_API_KEY || "";
|
|
33
33
|
const model = config.model ?? "gpt-5-nano";
|
|
34
|
+
const toolChoice = config.toolChoice;
|
|
34
35
|
const onStream = config.onStream;
|
|
35
36
|
const { check, isAborted } = (0, abort_1.createAbortChecker)(config.isAbort);
|
|
36
37
|
if (!apiKey)
|
|
@@ -44,7 +45,7 @@ async function openaiAdapter(config, context, tools) {
|
|
|
44
45
|
model,
|
|
45
46
|
messages,
|
|
46
47
|
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,
|
|
48
|
+
tool_choice: tools.length ? (toolChoice ?? "auto") : undefined,
|
|
48
49
|
stream: onStream ? true : undefined,
|
|
49
50
|
};
|
|
50
51
|
if (await check())
|
|
@@ -31,6 +31,7 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
31
31
|
const baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
32
32
|
const apiKey = config.apiKey || process.env.OPENROUTER_API_KEY || "";
|
|
33
33
|
const model = config.model ?? "gpt-5-nano";
|
|
34
|
+
const toolChoice = config.toolChoice;
|
|
34
35
|
const httpReferer = config.httpReferer;
|
|
35
36
|
const title = config.title;
|
|
36
37
|
const onStream = config.onStream;
|
|
@@ -46,7 +47,7 @@ async function openrouterAdapter(config, context, tools) {
|
|
|
46
47
|
model,
|
|
47
48
|
messages,
|
|
48
49
|
tools: tools.length ? tools.map((t) => ({ type: "function", function: { name: t.name, description: t.description, parameters: t.parameters ?? {} } })) : undefined,
|
|
49
|
-
tool_choice: tools.length ? "auto" : undefined,
|
|
50
|
+
tool_choice: tools.length ? (toolChoice ?? "auto") : undefined,
|
|
50
51
|
stream: onStream ? true : undefined,
|
|
51
52
|
};
|
|
52
53
|
const headers = {
|
|
@@ -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/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,19 +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");
|
|
6
7
|
const abort_1 = require("./abort");
|
|
7
8
|
const types_1 = require("./types");
|
|
8
9
|
var openai_2 = require("./adapter/openai");
|
|
9
10
|
Object.defineProperty(exports, "openaiAdapter", { enumerable: true, get: function () { return openai_2.openaiAdapter; } });
|
|
10
11
|
var openrouter_2 = require("./adapter/openrouter");
|
|
11
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; } });
|
|
12
15
|
var types_2 = require("./types");
|
|
13
16
|
Object.defineProperty(exports, "Role", { enumerable: true, get: function () { return types_2.Role; } });
|
|
14
17
|
const adapters = {
|
|
15
18
|
openai: openai_1.openaiAdapter,
|
|
16
19
|
openrouter: openrouter_1.openrouterAdapter,
|
|
20
|
+
selfhost_chat_completions: selfhost_chat_completions_1.selfhostChatCompletionsAdapter,
|
|
17
21
|
};
|
|
18
22
|
class Agent {
|
|
19
23
|
constructor(adapterName, config) {
|
package/dist/types.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export type AgentConfig = {
|
|
|
43
43
|
endCondition?: (context: Message[], last: Message) => boolean;
|
|
44
44
|
onStream?: (textDelta: string) => void | Promise<void>;
|
|
45
45
|
isAbort?: () => boolean | Promise<boolean>;
|
|
46
|
+
toolChoice?: "auto" | "required" | "none" | Record<string, unknown>;
|
|
46
47
|
onToolCall?: (message: Extract<Message, {
|
|
47
48
|
role: Role.ToolCall;
|
|
48
49
|
}>, args: unknown, agent: AgentLike) => boolean | void | Promise<boolean | void>;
|