@arki-moe/agent-ts 1.0.0 → 1.0.2
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 +42 -4
- package/dist/adapter/openai.js +2 -2
- package/dist/adapter/openrouter.d.ts +2 -0
- package/dist/adapter/openrouter.js +95 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +22 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
# agent
|
|
1
|
+
# agent-ts
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@arki-moe/agent-ts) [](https://www.npmjs.com/package/@arki-moe/agent-ts)
|
|
2
4
|
|
|
3
5
|
Minimal Agent library, zero dependencies
|
|
4
6
|
|
|
5
7
|
## Usage Example
|
|
6
8
|
|
|
7
9
|
```ts
|
|
8
|
-
import { Agent, Role, type Tool } from "agent-ts";
|
|
10
|
+
import { Agent, Role, type Tool } from "@arki-moe/agent-ts";
|
|
9
11
|
|
|
10
12
|
const getTimeTool: Tool = {
|
|
11
13
|
name: "get_time",
|
|
@@ -33,10 +35,46 @@ console.log(msgs2);
|
|
|
33
35
|
console.log(agent.context);
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
## Supported Adapters
|
|
39
|
+
|
|
40
|
+
| Adapter | Required | Optional |
|
|
41
|
+
|---------|----------|----------|
|
|
42
|
+
| `openai` | `apiKey` (or `OPENAI_API_KEY` env), `model` | `system`, `baseUrl` |
|
|
43
|
+
| `openrouter` | `apiKey` (or `OPENROUTER_API_KEY` env), `model` | `system`, `baseUrl`, `httpReferer`, `title` |
|
|
44
|
+
|
|
45
|
+
When `apiKey` is not provided in config, adapters read from the corresponding environment variable. An error is thrown only when both are missing.
|
|
46
|
+
|
|
36
47
|
## API
|
|
37
48
|
|
|
38
49
|
- `Agent(adapterName, config)` - Create Agent, config contains `apiKey`, `model`, `system` (optional), etc.
|
|
39
50
|
- `agent.context` - Public property, complete conversation history
|
|
40
51
|
- `agent.registerTool(tool)` - Register tool
|
|
41
|
-
- `agent.step(message)` - Call model once, returns new `Message[]`
|
|
42
|
-
- `agent.run(message)` - Execute tool chain automatically, returns all new `Message[]`
|
|
52
|
+
- `agent.step(message?)` - Call model once, returns new `Message[]`
|
|
53
|
+
- `agent.run(message, endCondition?)` - Execute tool chain automatically, returns all new `Message[]`
|
|
54
|
+
- `agent.fork()` - Create a new agent with a copied context
|
|
55
|
+
|
|
56
|
+
`agent.step` and `agent.run` always append new messages to `agent.context`.
|
|
57
|
+
`endCondition` receives `(context, last)` and stops the run when it returns `true`. Defaults to `last.role === Role.Ai`.
|
|
58
|
+
|
|
59
|
+
`agent.fork()` shallow-copies the context array, but message objects are shared. This means:
|
|
60
|
+
- Shallow copy: `forked.context !== agent.context`, so pushing new messages does not affect the other agent.
|
|
61
|
+
- Shared messages: modifying a message object in one context will be visible in the other.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const forked = agent.fork();
|
|
67
|
+
forked.context.push({ role: Role.User, content: "hi" });
|
|
68
|
+
// agent.context length is unchanged
|
|
69
|
+
|
|
70
|
+
forked.context[0].content = "changed";
|
|
71
|
+
// agent.context[0].content is also "changed" because messages are shared
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Scripts
|
|
75
|
+
|
|
76
|
+
| Command | Description |
|
|
77
|
+
|---------|--------------|
|
|
78
|
+
| `pnpm run build` | Compile TypeScript to `dist/` |
|
|
79
|
+
| `pnpm run check` | Type check (no emit) |
|
|
80
|
+
| `pnpm test` | Run tests (API key read from env var for real API tests) |
|
package/dist/adapter/openai.js
CHANGED
|
@@ -27,10 +27,10 @@ function toOpenAIMessages(context) {
|
|
|
27
27
|
}
|
|
28
28
|
async function openaiAdapter(config, context, tools) {
|
|
29
29
|
const baseUrl = config.baseUrl ?? "https://api.openai.com";
|
|
30
|
-
const apiKey = config.apiKey
|
|
30
|
+
const apiKey = config.apiKey || process.env.OPENAI_API_KEY || "";
|
|
31
31
|
const model = config.model ?? "gpt-5-nano";
|
|
32
32
|
if (!apiKey)
|
|
33
|
-
throw new Error("OpenAI adapter requires apiKey in config");
|
|
33
|
+
throw new Error("OpenAI adapter requires apiKey in config or OPENAI_API_KEY env");
|
|
34
34
|
const contextMessages = toOpenAIMessages(context);
|
|
35
35
|
const systemContent = config.system;
|
|
36
36
|
const messages = systemContent
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.openrouterAdapter = openrouterAdapter;
|
|
4
|
+
const types_1 = require("../types");
|
|
5
|
+
const ROLE_TO_OPENROUTER = {
|
|
6
|
+
[types_1.Role.System]: "system",
|
|
7
|
+
[types_1.Role.User]: "user",
|
|
8
|
+
[types_1.Role.Ai]: "assistant",
|
|
9
|
+
};
|
|
10
|
+
function toOpenRouterMessages(context) {
|
|
11
|
+
return context.reduce((out, m) => {
|
|
12
|
+
if (m.role === types_1.Role.System || m.role === types_1.Role.User || m.role === types_1.Role.Ai)
|
|
13
|
+
return [...out, { role: ROLE_TO_OPENROUTER[m.role], content: m.content }];
|
|
14
|
+
if (m.role === types_1.Role.ToolResult)
|
|
15
|
+
return [...out, { role: "tool", content: m.content, tool_call_id: m.callId }];
|
|
16
|
+
if (m.role === types_1.Role.ToolCall) {
|
|
17
|
+
const tc = { id: m.callId, type: "function", function: { name: m.toolName, arguments: m.argsText ?? "{}" } };
|
|
18
|
+
const last = out[out.length - 1];
|
|
19
|
+
if (last?.tool_calls) {
|
|
20
|
+
last.tool_calls.push(tc);
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
return [...out, { role: "assistant", tool_calls: [tc] }];
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}, []);
|
|
27
|
+
}
|
|
28
|
+
async function openrouterAdapter(config, context, tools) {
|
|
29
|
+
const baseUrl = config.baseUrl ?? "https://openrouter.ai/api/v1";
|
|
30
|
+
const apiKey = config.apiKey || process.env.OPENROUTER_API_KEY || "";
|
|
31
|
+
const model = config.model ?? "gpt-5-nano";
|
|
32
|
+
const httpReferer = config.httpReferer;
|
|
33
|
+
const title = config.title;
|
|
34
|
+
if (!apiKey)
|
|
35
|
+
throw new Error("OpenRouter adapter requires apiKey in config or OPENROUTER_API_KEY env");
|
|
36
|
+
const contextMessages = toOpenRouterMessages(context);
|
|
37
|
+
const systemContent = config.system;
|
|
38
|
+
const messages = systemContent
|
|
39
|
+
? [{ role: "system", content: systemContent }, ...contextMessages]
|
|
40
|
+
: contextMessages;
|
|
41
|
+
const body = {
|
|
42
|
+
model,
|
|
43
|
+
messages,
|
|
44
|
+
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,
|
|
46
|
+
};
|
|
47
|
+
const headers = {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
Authorization: `Bearer ${apiKey}`,
|
|
50
|
+
};
|
|
51
|
+
if (httpReferer)
|
|
52
|
+
headers["HTTP-Referer"] = httpReferer;
|
|
53
|
+
if (title)
|
|
54
|
+
headers["X-Title"] = title;
|
|
55
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers,
|
|
58
|
+
body: JSON.stringify(body),
|
|
59
|
+
});
|
|
60
|
+
const text = await res.text();
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
let errMsg = `OpenRouter API HTTP ${res.status}`;
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(text);
|
|
65
|
+
if (parsed?.error?.message)
|
|
66
|
+
errMsg = parsed.error.message;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
if (text)
|
|
70
|
+
errMsg += `: ${text.slice(0, 200)}`;
|
|
71
|
+
}
|
|
72
|
+
throw new Error(errMsg);
|
|
73
|
+
}
|
|
74
|
+
let data;
|
|
75
|
+
try {
|
|
76
|
+
data = JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
throw new Error(`OpenRouter API returned invalid JSON: ${text.slice(0, 200)}`);
|
|
80
|
+
}
|
|
81
|
+
if (data.error)
|
|
82
|
+
throw new Error(`OpenRouter API error: ${data.error.message}`);
|
|
83
|
+
const msg = data.choices?.[0]?.message;
|
|
84
|
+
if (!msg)
|
|
85
|
+
throw new Error("OpenRouter API returned empty response");
|
|
86
|
+
if (msg.tool_calls?.length) {
|
|
87
|
+
return msg.tool_calls.map((tc) => ({
|
|
88
|
+
role: types_1.Role.ToolCall,
|
|
89
|
+
toolName: tc.function.name,
|
|
90
|
+
callId: tc.id,
|
|
91
|
+
argsText: tc.function.arguments ?? "{}",
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
return [{ role: types_1.Role.Ai, content: msg.content ?? "" }];
|
|
95
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { Context, Message, Tool } from "./types";
|
|
2
2
|
export { openaiAdapter } from "./adapter/openai";
|
|
3
|
+
export { openrouterAdapter } from "./adapter/openrouter";
|
|
3
4
|
export type { Adapter, Context, Message, Tool } from "./types";
|
|
4
5
|
export { Role } from "./types";
|
|
5
6
|
export declare class Agent {
|
|
6
7
|
context: Context;
|
|
7
8
|
private adapter;
|
|
9
|
+
private adapterName;
|
|
8
10
|
private config;
|
|
9
11
|
private tools;
|
|
10
12
|
constructor(adapterName: string, config: Record<string, unknown>);
|
|
11
13
|
registerTool(tool: Tool): void;
|
|
12
|
-
step(message
|
|
13
|
-
run(message: Message): Promise<Message[]>;
|
|
14
|
+
step(message?: Message): Promise<Message[]>;
|
|
15
|
+
run(message: Message, endCondition?: (context: Message[], last: Message) => boolean): Promise<Message[]>;
|
|
16
|
+
fork(): Agent;
|
|
14
17
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Agent = exports.Role = exports.openaiAdapter = void 0;
|
|
3
|
+
exports.Agent = exports.Role = exports.openrouterAdapter = exports.openaiAdapter = void 0;
|
|
4
4
|
const openai_1 = require("./adapter/openai");
|
|
5
|
+
const openrouter_1 = require("./adapter/openrouter");
|
|
5
6
|
const types_1 = require("./types");
|
|
6
7
|
var openai_2 = require("./adapter/openai");
|
|
7
8
|
Object.defineProperty(exports, "openaiAdapter", { enumerable: true, get: function () { return openai_2.openaiAdapter; } });
|
|
9
|
+
var openrouter_2 = require("./adapter/openrouter");
|
|
10
|
+
Object.defineProperty(exports, "openrouterAdapter", { enumerable: true, get: function () { return openrouter_2.openrouterAdapter; } });
|
|
8
11
|
var types_2 = require("./types");
|
|
9
12
|
Object.defineProperty(exports, "Role", { enumerable: true, get: function () { return types_2.Role; } });
|
|
10
13
|
const adapters = {
|
|
11
14
|
openai: openai_1.openaiAdapter,
|
|
15
|
+
openrouter: openrouter_1.openrouterAdapter,
|
|
12
16
|
};
|
|
13
17
|
class Agent {
|
|
14
18
|
constructor(adapterName, config) {
|
|
15
19
|
this.context = [];
|
|
16
20
|
this.tools = [];
|
|
21
|
+
this.adapterName = adapterName;
|
|
17
22
|
this.adapter = adapters[adapterName] ?? (() => { throw new Error(`Adapter "${adapterName}" not found`); })();
|
|
18
23
|
this.config = config;
|
|
19
24
|
}
|
|
@@ -21,20 +26,20 @@ class Agent {
|
|
|
21
26
|
this.tools.push(tool);
|
|
22
27
|
}
|
|
23
28
|
async step(message) {
|
|
24
|
-
|
|
29
|
+
if (message) {
|
|
30
|
+
this.context.push(message);
|
|
31
|
+
}
|
|
25
32
|
const msgs = await this.adapter(this.config, this.context, this.tools);
|
|
26
33
|
this.context.push(...msgs);
|
|
27
34
|
return msgs;
|
|
28
35
|
}
|
|
29
|
-
async run(message) {
|
|
30
|
-
this.context.push(message);
|
|
36
|
+
async run(message, endCondition = (_context, last) => last.role === types_1.Role.Ai) {
|
|
31
37
|
const all = [];
|
|
38
|
+
let msgs = await this.step(message);
|
|
39
|
+
all.push(...msgs);
|
|
32
40
|
for (;;) {
|
|
33
|
-
const msgs = await this.adapter(this.config, this.context, this.tools);
|
|
34
|
-
this.context.push(...msgs);
|
|
35
|
-
all.push(...msgs);
|
|
36
41
|
const last = msgs[msgs.length - 1];
|
|
37
|
-
if (
|
|
42
|
+
if (endCondition(this.context, last))
|
|
38
43
|
return all;
|
|
39
44
|
for (const m of msgs) {
|
|
40
45
|
if (m.role !== types_1.Role.ToolCall)
|
|
@@ -57,7 +62,16 @@ class Agent {
|
|
|
57
62
|
this.context.push(result);
|
|
58
63
|
all.push(result);
|
|
59
64
|
}
|
|
65
|
+
msgs = await this.step();
|
|
66
|
+
all.push(...msgs);
|
|
60
67
|
}
|
|
61
68
|
}
|
|
69
|
+
fork() {
|
|
70
|
+
const agent = new Agent(this.adapterName, this.config);
|
|
71
|
+
agent.adapter = this.adapter;
|
|
72
|
+
agent.tools = [...this.tools];
|
|
73
|
+
agent.context = [...this.context];
|
|
74
|
+
return agent;
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
exports.Agent = Agent;
|