@easynet/agent-tool-hub 1.0.7 → 1.0.9
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/dist/ToolRegistry-wObXj92k.d.cts +196 -0
- package/dist/ToolRegistry-wObXj92k.d.ts +196 -0
- package/dist/chunk-GBNFC46A.js +147 -0
- package/dist/chunk-GBNFC46A.js.map +1 -0
- package/dist/chunk-OOGCRR3P.cjs +151 -0
- package/dist/chunk-OOGCRR3P.cjs.map +1 -0
- package/dist/index.cjs +17 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/llm-export.cjs +20 -0
- package/dist/llm-export.cjs.map +1 -0
- package/dist/llm-export.d.cts +94 -0
- package/dist/llm-export.d.ts +94 -0
- package/dist/llm-export.js +3 -0
- package/dist/llm-export.js.map +1 -0
- package/dist/{toolhub-runtime-Du3j3gS3.d.cts → toolhub-runtime-DqZOyihh.d.cts} +2 -196
- package/dist/{toolhub-runtime-Du3j3gS3.d.ts → toolhub-runtime-EP-3hIOW.d.ts} +2 -196
- package/dist/toolhub-runtime.d.cts +2 -1
- package/dist/toolhub-runtime.d.ts +2 -1
- package/package.json +2 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget constraints for a tool invocation.
|
|
3
|
+
*/
|
|
4
|
+
interface BudgetConfig {
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
maxToolCalls?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Execution context passed from agent-orchestra.
|
|
11
|
+
* Contains permissions, budget, and observability context.
|
|
12
|
+
*/
|
|
13
|
+
interface ExecContext {
|
|
14
|
+
requestId: string;
|
|
15
|
+
taskId: string;
|
|
16
|
+
/** Allowed capabilities for this invocation */
|
|
17
|
+
permissions: Capability[];
|
|
18
|
+
budget?: BudgetConfig;
|
|
19
|
+
/** OpenTelemetry-compatible trace ID */
|
|
20
|
+
traceId?: string;
|
|
21
|
+
userId?: string;
|
|
22
|
+
/** Optional: enable dry-run mode for two-phase commit */
|
|
23
|
+
dryRun?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Tool invocation intent from agent-orchestra.
|
|
27
|
+
* Represents what the agent wants to do (untrusted input).
|
|
28
|
+
*/
|
|
29
|
+
interface ToolIntent {
|
|
30
|
+
/** ToolSpec.name reference */
|
|
31
|
+
tool: string;
|
|
32
|
+
/** Untrusted input arguments */
|
|
33
|
+
args: unknown;
|
|
34
|
+
/** Human-readable purpose for audit trail */
|
|
35
|
+
purpose: string;
|
|
36
|
+
/** Idempotency key: recommended format requestId:taskId:tool */
|
|
37
|
+
idempotencyKey?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Unified tool kinds supported by the tools package.
|
|
42
|
+
*/
|
|
43
|
+
type ToolKind = "mcp" | "langchain" | "n8n" | "comfyui" | "skill" | "core";
|
|
44
|
+
/**
|
|
45
|
+
* Capability declarations for tools.
|
|
46
|
+
* Used by PolicyEngine for permission gating.
|
|
47
|
+
*/
|
|
48
|
+
type Capability = "read:web" | "read:fs" | "write:fs" | "read:db" | "write:db" | "network" | "gpu" | "workflow" | "danger:destructive";
|
|
49
|
+
/**
|
|
50
|
+
* Cost hints for tools, used by Budget and routing.
|
|
51
|
+
*/
|
|
52
|
+
interface CostHints {
|
|
53
|
+
latencyMsP50?: number;
|
|
54
|
+
latencyMsP95?: number;
|
|
55
|
+
isAsync?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Unified tool specification.
|
|
59
|
+
* All tool types (MCP, LangChain, n8n, ComfyUI, SKILL) are described by this interface.
|
|
60
|
+
*/
|
|
61
|
+
interface ToolSpec {
|
|
62
|
+
/** Globally unique name, recommended format: namespace/name */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Semver version */
|
|
65
|
+
version: string;
|
|
66
|
+
/** Tool kind determines which adapter handles execution */
|
|
67
|
+
kind: ToolKind;
|
|
68
|
+
description?: string;
|
|
69
|
+
tags?: string[];
|
|
70
|
+
/** JSON Schema for input validation */
|
|
71
|
+
inputSchema: object;
|
|
72
|
+
/** JSON Schema for output validation */
|
|
73
|
+
outputSchema: object;
|
|
74
|
+
/** Required capabilities for this tool */
|
|
75
|
+
capabilities: Capability[];
|
|
76
|
+
costHints?: CostHints;
|
|
77
|
+
/** Adapter-specific: endpoint URL (MCP/n8n/ComfyUI) */
|
|
78
|
+
endpoint?: string;
|
|
79
|
+
/** Adapter-specific: resource identifier (workflowId, promptId, etc.) */
|
|
80
|
+
resourceId?: string;
|
|
81
|
+
/** Adapter-specific: implementation reference (LangChain Tool instance, skill handler) */
|
|
82
|
+
impl?: unknown;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Unified adapter interface.
|
|
86
|
+
* Each protocol adapter (MCP, LangChain, n8n, ComfyUI, SKILL) implements this.
|
|
87
|
+
*/
|
|
88
|
+
interface ToolAdapter {
|
|
89
|
+
kind: ToolKind;
|
|
90
|
+
/** Optional: supports dynamic tool discovery */
|
|
91
|
+
listTools?(): Promise<ToolSpec[]>;
|
|
92
|
+
/** Execute the tool with validated args */
|
|
93
|
+
invoke(spec: ToolSpec, args: unknown, ctx: ExecContext): Promise<{
|
|
94
|
+
result: unknown;
|
|
95
|
+
raw?: unknown;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Evidence attached to a tool result for audit trail.
|
|
101
|
+
*/
|
|
102
|
+
interface Evidence {
|
|
103
|
+
type: "tool" | "file" | "url" | "text" | "metric";
|
|
104
|
+
ref: string;
|
|
105
|
+
summary: string;
|
|
106
|
+
createdAt: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Error information in a tool result.
|
|
110
|
+
*/
|
|
111
|
+
interface ToolError {
|
|
112
|
+
kind?: "TOOL_NOT_FOUND" | "INPUT_SCHEMA_INVALID" | "POLICY_DENIED" | "BUDGET_EXCEEDED" | "TIMEOUT" | "UPSTREAM_ERROR" | "OUTPUT_SCHEMA_INVALID" | "PATH_OUTSIDE_SANDBOX" | "FILE_TOO_LARGE" | "HTTP_DISALLOWED_HOST" | "HTTP_TIMEOUT" | "HTTP_TOO_LARGE";
|
|
113
|
+
message: string;
|
|
114
|
+
details?: unknown;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Unified tool result returned to agent-orchestra.
|
|
118
|
+
* Always structured, never throws raw exceptions.
|
|
119
|
+
*/
|
|
120
|
+
interface ToolResult {
|
|
121
|
+
ok: boolean;
|
|
122
|
+
result?: unknown;
|
|
123
|
+
evidence: Evidence[];
|
|
124
|
+
error?: ToolError;
|
|
125
|
+
/** Raw response for debugging (can be disabled in production) */
|
|
126
|
+
raw?: unknown;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Search query for tools.
|
|
131
|
+
*/
|
|
132
|
+
interface ToolSearchQuery {
|
|
133
|
+
/** Text search in name/description/tags */
|
|
134
|
+
text?: string;
|
|
135
|
+
/** Filter by tool kind */
|
|
136
|
+
kind?: ToolKind;
|
|
137
|
+
/** Filter by required capabilities */
|
|
138
|
+
capabilities?: Capability[];
|
|
139
|
+
/** Filter by tags */
|
|
140
|
+
tags?: string[];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Tool Registry: manages tool registration, lookup, and search.
|
|
144
|
+
* Supports both static registration and dynamic discovery via adapters.
|
|
145
|
+
*/
|
|
146
|
+
declare class ToolRegistry {
|
|
147
|
+
private readonly tools;
|
|
148
|
+
private readonly tagIndex;
|
|
149
|
+
private readonly kindIndex;
|
|
150
|
+
/**
|
|
151
|
+
* Register a single tool spec.
|
|
152
|
+
* Overwrites if same name already exists.
|
|
153
|
+
*/
|
|
154
|
+
register(spec: ToolSpec): void;
|
|
155
|
+
/**
|
|
156
|
+
* Register multiple tool specs at once.
|
|
157
|
+
*/
|
|
158
|
+
bulkRegister(specs: ToolSpec[]): void;
|
|
159
|
+
/**
|
|
160
|
+
* Unregister a tool by name.
|
|
161
|
+
*/
|
|
162
|
+
unregister(name: string): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Get a tool spec by name.
|
|
165
|
+
*/
|
|
166
|
+
get(name: string): ToolSpec | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Check if a tool exists.
|
|
169
|
+
*/
|
|
170
|
+
has(name: string): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Search tools by query.
|
|
173
|
+
*/
|
|
174
|
+
search(query: ToolSearchQuery): ToolSpec[];
|
|
175
|
+
/**
|
|
176
|
+
* List all registered tool names.
|
|
177
|
+
*/
|
|
178
|
+
list(): string[];
|
|
179
|
+
/**
|
|
180
|
+
* Get count of registered tools.
|
|
181
|
+
*/
|
|
182
|
+
get size(): number;
|
|
183
|
+
/**
|
|
184
|
+
* Export a snapshot of all registered tools (for debugging/routing).
|
|
185
|
+
*/
|
|
186
|
+
snapshot(): ToolSpec[];
|
|
187
|
+
/**
|
|
188
|
+
* Clear all registered tools.
|
|
189
|
+
*/
|
|
190
|
+
clear(): void;
|
|
191
|
+
private validateSpec;
|
|
192
|
+
private indexTool;
|
|
193
|
+
private deindexTool;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { type BudgetConfig as B, type Capability as C, type Evidence as E, type ToolError as T, type ToolSpec as a, type ExecContext as b, ToolRegistry as c, type ToolAdapter as d, type ToolIntent as e, type ToolResult as f, type ToolKind as g, type CostHints as h, type ToolSearchQuery as i };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budget constraints for a tool invocation.
|
|
3
|
+
*/
|
|
4
|
+
interface BudgetConfig {
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
maxToolCalls?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Execution context passed from agent-orchestra.
|
|
11
|
+
* Contains permissions, budget, and observability context.
|
|
12
|
+
*/
|
|
13
|
+
interface ExecContext {
|
|
14
|
+
requestId: string;
|
|
15
|
+
taskId: string;
|
|
16
|
+
/** Allowed capabilities for this invocation */
|
|
17
|
+
permissions: Capability[];
|
|
18
|
+
budget?: BudgetConfig;
|
|
19
|
+
/** OpenTelemetry-compatible trace ID */
|
|
20
|
+
traceId?: string;
|
|
21
|
+
userId?: string;
|
|
22
|
+
/** Optional: enable dry-run mode for two-phase commit */
|
|
23
|
+
dryRun?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Tool invocation intent from agent-orchestra.
|
|
27
|
+
* Represents what the agent wants to do (untrusted input).
|
|
28
|
+
*/
|
|
29
|
+
interface ToolIntent {
|
|
30
|
+
/** ToolSpec.name reference */
|
|
31
|
+
tool: string;
|
|
32
|
+
/** Untrusted input arguments */
|
|
33
|
+
args: unknown;
|
|
34
|
+
/** Human-readable purpose for audit trail */
|
|
35
|
+
purpose: string;
|
|
36
|
+
/** Idempotency key: recommended format requestId:taskId:tool */
|
|
37
|
+
idempotencyKey?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Unified tool kinds supported by the tools package.
|
|
42
|
+
*/
|
|
43
|
+
type ToolKind = "mcp" | "langchain" | "n8n" | "comfyui" | "skill" | "core";
|
|
44
|
+
/**
|
|
45
|
+
* Capability declarations for tools.
|
|
46
|
+
* Used by PolicyEngine for permission gating.
|
|
47
|
+
*/
|
|
48
|
+
type Capability = "read:web" | "read:fs" | "write:fs" | "read:db" | "write:db" | "network" | "gpu" | "workflow" | "danger:destructive";
|
|
49
|
+
/**
|
|
50
|
+
* Cost hints for tools, used by Budget and routing.
|
|
51
|
+
*/
|
|
52
|
+
interface CostHints {
|
|
53
|
+
latencyMsP50?: number;
|
|
54
|
+
latencyMsP95?: number;
|
|
55
|
+
isAsync?: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Unified tool specification.
|
|
59
|
+
* All tool types (MCP, LangChain, n8n, ComfyUI, SKILL) are described by this interface.
|
|
60
|
+
*/
|
|
61
|
+
interface ToolSpec {
|
|
62
|
+
/** Globally unique name, recommended format: namespace/name */
|
|
63
|
+
name: string;
|
|
64
|
+
/** Semver version */
|
|
65
|
+
version: string;
|
|
66
|
+
/** Tool kind determines which adapter handles execution */
|
|
67
|
+
kind: ToolKind;
|
|
68
|
+
description?: string;
|
|
69
|
+
tags?: string[];
|
|
70
|
+
/** JSON Schema for input validation */
|
|
71
|
+
inputSchema: object;
|
|
72
|
+
/** JSON Schema for output validation */
|
|
73
|
+
outputSchema: object;
|
|
74
|
+
/** Required capabilities for this tool */
|
|
75
|
+
capabilities: Capability[];
|
|
76
|
+
costHints?: CostHints;
|
|
77
|
+
/** Adapter-specific: endpoint URL (MCP/n8n/ComfyUI) */
|
|
78
|
+
endpoint?: string;
|
|
79
|
+
/** Adapter-specific: resource identifier (workflowId, promptId, etc.) */
|
|
80
|
+
resourceId?: string;
|
|
81
|
+
/** Adapter-specific: implementation reference (LangChain Tool instance, skill handler) */
|
|
82
|
+
impl?: unknown;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Unified adapter interface.
|
|
86
|
+
* Each protocol adapter (MCP, LangChain, n8n, ComfyUI, SKILL) implements this.
|
|
87
|
+
*/
|
|
88
|
+
interface ToolAdapter {
|
|
89
|
+
kind: ToolKind;
|
|
90
|
+
/** Optional: supports dynamic tool discovery */
|
|
91
|
+
listTools?(): Promise<ToolSpec[]>;
|
|
92
|
+
/** Execute the tool with validated args */
|
|
93
|
+
invoke(spec: ToolSpec, args: unknown, ctx: ExecContext): Promise<{
|
|
94
|
+
result: unknown;
|
|
95
|
+
raw?: unknown;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Evidence attached to a tool result for audit trail.
|
|
101
|
+
*/
|
|
102
|
+
interface Evidence {
|
|
103
|
+
type: "tool" | "file" | "url" | "text" | "metric";
|
|
104
|
+
ref: string;
|
|
105
|
+
summary: string;
|
|
106
|
+
createdAt: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Error information in a tool result.
|
|
110
|
+
*/
|
|
111
|
+
interface ToolError {
|
|
112
|
+
kind?: "TOOL_NOT_FOUND" | "INPUT_SCHEMA_INVALID" | "POLICY_DENIED" | "BUDGET_EXCEEDED" | "TIMEOUT" | "UPSTREAM_ERROR" | "OUTPUT_SCHEMA_INVALID" | "PATH_OUTSIDE_SANDBOX" | "FILE_TOO_LARGE" | "HTTP_DISALLOWED_HOST" | "HTTP_TIMEOUT" | "HTTP_TOO_LARGE";
|
|
113
|
+
message: string;
|
|
114
|
+
details?: unknown;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Unified tool result returned to agent-orchestra.
|
|
118
|
+
* Always structured, never throws raw exceptions.
|
|
119
|
+
*/
|
|
120
|
+
interface ToolResult {
|
|
121
|
+
ok: boolean;
|
|
122
|
+
result?: unknown;
|
|
123
|
+
evidence: Evidence[];
|
|
124
|
+
error?: ToolError;
|
|
125
|
+
/** Raw response for debugging (can be disabled in production) */
|
|
126
|
+
raw?: unknown;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Search query for tools.
|
|
131
|
+
*/
|
|
132
|
+
interface ToolSearchQuery {
|
|
133
|
+
/** Text search in name/description/tags */
|
|
134
|
+
text?: string;
|
|
135
|
+
/** Filter by tool kind */
|
|
136
|
+
kind?: ToolKind;
|
|
137
|
+
/** Filter by required capabilities */
|
|
138
|
+
capabilities?: Capability[];
|
|
139
|
+
/** Filter by tags */
|
|
140
|
+
tags?: string[];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Tool Registry: manages tool registration, lookup, and search.
|
|
144
|
+
* Supports both static registration and dynamic discovery via adapters.
|
|
145
|
+
*/
|
|
146
|
+
declare class ToolRegistry {
|
|
147
|
+
private readonly tools;
|
|
148
|
+
private readonly tagIndex;
|
|
149
|
+
private readonly kindIndex;
|
|
150
|
+
/**
|
|
151
|
+
* Register a single tool spec.
|
|
152
|
+
* Overwrites if same name already exists.
|
|
153
|
+
*/
|
|
154
|
+
register(spec: ToolSpec): void;
|
|
155
|
+
/**
|
|
156
|
+
* Register multiple tool specs at once.
|
|
157
|
+
*/
|
|
158
|
+
bulkRegister(specs: ToolSpec[]): void;
|
|
159
|
+
/**
|
|
160
|
+
* Unregister a tool by name.
|
|
161
|
+
*/
|
|
162
|
+
unregister(name: string): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Get a tool spec by name.
|
|
165
|
+
*/
|
|
166
|
+
get(name: string): ToolSpec | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Check if a tool exists.
|
|
169
|
+
*/
|
|
170
|
+
has(name: string): boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Search tools by query.
|
|
173
|
+
*/
|
|
174
|
+
search(query: ToolSearchQuery): ToolSpec[];
|
|
175
|
+
/**
|
|
176
|
+
* List all registered tool names.
|
|
177
|
+
*/
|
|
178
|
+
list(): string[];
|
|
179
|
+
/**
|
|
180
|
+
* Get count of registered tools.
|
|
181
|
+
*/
|
|
182
|
+
get size(): number;
|
|
183
|
+
/**
|
|
184
|
+
* Export a snapshot of all registered tools (for debugging/routing).
|
|
185
|
+
*/
|
|
186
|
+
snapshot(): ToolSpec[];
|
|
187
|
+
/**
|
|
188
|
+
* Clear all registered tools.
|
|
189
|
+
*/
|
|
190
|
+
clear(): void;
|
|
191
|
+
private validateSpec;
|
|
192
|
+
private indexTool;
|
|
193
|
+
private deindexTool;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { type BudgetConfig as B, type Capability as C, type Evidence as E, type ToolError as T, type ToolSpec as a, type ExecContext as b, ToolRegistry as c, type ToolAdapter as d, type ToolIntent as e, type ToolResult as f, type ToolKind as g, type CostHints as h, type ToolSearchQuery as i };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// src/llm/OpenAICompatibleClient.ts
|
|
2
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
3
|
+
function createOpenAICompatibleClient(baseUrl, model, apiKey) {
|
|
4
|
+
return new OpenAICompatibleClient({ baseUrl, model, apiKey });
|
|
5
|
+
}
|
|
6
|
+
var OpenAICompatibleClient = class {
|
|
7
|
+
baseUrl;
|
|
8
|
+
model;
|
|
9
|
+
apiKey;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
12
|
+
this.model = config.model;
|
|
13
|
+
this.apiKey = config.apiKey;
|
|
14
|
+
}
|
|
15
|
+
async chat(messages, options) {
|
|
16
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
17
|
+
const raw = await this.request(
|
|
18
|
+
{
|
|
19
|
+
model: this.model,
|
|
20
|
+
messages: messages.map((m) => ({ role: m.role, content: m.content }))
|
|
21
|
+
},
|
|
22
|
+
timeoutMs
|
|
23
|
+
);
|
|
24
|
+
const choices = raw.choices;
|
|
25
|
+
const content = Array.isArray(choices) && choices[0]?.message?.content != null ? String(choices[0].message.content) : "";
|
|
26
|
+
return { content, raw };
|
|
27
|
+
}
|
|
28
|
+
async chatWithTools(messages, tools, options) {
|
|
29
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
30
|
+
const raw = await this.request(
|
|
31
|
+
{
|
|
32
|
+
model: this.model,
|
|
33
|
+
messages: messages.map((m) => this.serializeMessage(m)),
|
|
34
|
+
tools
|
|
35
|
+
},
|
|
36
|
+
timeoutMs
|
|
37
|
+
);
|
|
38
|
+
const choices = raw.choices;
|
|
39
|
+
const msg = Array.isArray(choices) ? choices[0]?.message : void 0;
|
|
40
|
+
return {
|
|
41
|
+
message: {
|
|
42
|
+
role: "assistant",
|
|
43
|
+
content: msg?.content ?? null,
|
|
44
|
+
tool_calls: msg?.tool_calls
|
|
45
|
+
},
|
|
46
|
+
raw
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
buildHeaders() {
|
|
50
|
+
const headers = {
|
|
51
|
+
"Content-Type": "application/json"
|
|
52
|
+
};
|
|
53
|
+
if (this.apiKey) headers["Authorization"] = `Bearer ${this.apiKey}`;
|
|
54
|
+
return headers;
|
|
55
|
+
}
|
|
56
|
+
serializeMessage(m) {
|
|
57
|
+
if (m.role === "tool")
|
|
58
|
+
return { role: "tool", content: m.content, tool_call_id: m.tool_call_id };
|
|
59
|
+
if (m.role === "assistant" && "tool_calls" in m && m.tool_calls?.length)
|
|
60
|
+
return { role: "assistant", content: m.content ?? null, tool_calls: m.tool_calls };
|
|
61
|
+
return { role: m.role, content: m.content };
|
|
62
|
+
}
|
|
63
|
+
async request(body, timeoutMs) {
|
|
64
|
+
const url = `${this.baseUrl}/chat/completions`;
|
|
65
|
+
const controller = new AbortController();
|
|
66
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
67
|
+
let response;
|
|
68
|
+
try {
|
|
69
|
+
response = await fetch(url, {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: this.buildHeaders(),
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
signal: controller.signal
|
|
74
|
+
});
|
|
75
|
+
} catch (err) {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
if (err instanceof Error && err.name === "AbortError")
|
|
78
|
+
throw new Error(`LLM request timed out after ${timeoutMs}ms`);
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
const raw = await response.json();
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
const errBody = typeof raw === "object" && raw !== null && "error" in raw ? raw.error : raw;
|
|
85
|
+
throw new Error(
|
|
86
|
+
`LLM API error ${response.status}: ${JSON.stringify(errBody)}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return raw;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/llm/ReActAgent.ts
|
|
94
|
+
var DEFAULT_PROMPT = "ReAct: Thought (reason) \u2192 Action (use tool or answer) \u2192 Observation (tool result). Repeat until you give the final answer. Same language as user.";
|
|
95
|
+
var ReActAgent = class {
|
|
96
|
+
constructor(llm, toolHub) {
|
|
97
|
+
this.llm = llm;
|
|
98
|
+
this.toolHub = toolHub;
|
|
99
|
+
}
|
|
100
|
+
async run(instruction, options = {}) {
|
|
101
|
+
const maxSteps = options.maxSteps ?? 10;
|
|
102
|
+
const tools = this.getTools();
|
|
103
|
+
const messages = [
|
|
104
|
+
{ role: "system", content: options.systemPrompt ?? DEFAULT_PROMPT },
|
|
105
|
+
{ role: "user", content: instruction }
|
|
106
|
+
];
|
|
107
|
+
let steps = 0;
|
|
108
|
+
let lastContent = "";
|
|
109
|
+
while (steps < maxSteps) {
|
|
110
|
+
const { message } = await this.llm.chatWithTools(
|
|
111
|
+
messages,
|
|
112
|
+
tools,
|
|
113
|
+
options.timeoutMs ? { timeoutMs: options.timeoutMs } : void 0
|
|
114
|
+
);
|
|
115
|
+
steps++;
|
|
116
|
+
messages.push({ ...message, role: "assistant" });
|
|
117
|
+
if (message.content) lastContent = message.content;
|
|
118
|
+
if (!message.tool_calls?.length)
|
|
119
|
+
return { content: lastContent, steps };
|
|
120
|
+
for (const tc of message.tool_calls) {
|
|
121
|
+
const args = this.parseArgs(tc.function.arguments);
|
|
122
|
+
const result = await this.toolHub.invokeTool(tc.function.name, args);
|
|
123
|
+
const content = result.ok ? JSON.stringify(result.result) : JSON.stringify({ ok: false, error: result.error?.message ?? "Tool failed" });
|
|
124
|
+
messages.push({ role: "tool", content: `Observation: ${content}`, tool_call_id: tc.id });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return { content: lastContent || "Reached max steps.", steps };
|
|
128
|
+
}
|
|
129
|
+
getTools() {
|
|
130
|
+
return this.toolHub.getRegistry().snapshot().map((s) => ({
|
|
131
|
+
type: "function",
|
|
132
|
+
function: { name: s.name, description: s.description ?? "", parameters: s.inputSchema }
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
parseArgs(json) {
|
|
136
|
+
if (!json) return {};
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(json);
|
|
139
|
+
} catch {
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export { OpenAICompatibleClient, ReActAgent, createOpenAICompatibleClient };
|
|
146
|
+
//# sourceMappingURL=chunk-GBNFC46A.js.map
|
|
147
|
+
//# sourceMappingURL=chunk-GBNFC46A.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/llm/OpenAICompatibleClient.ts","../src/llm/ReActAgent.ts"],"names":[],"mappings":"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","file":"chunk-GBNFC46A.js","sourcesContent":["/**\n * Minimal client for OpenAI-compatible chat completions API.\n * Use createOpenAICompatibleClient(baseUrl, model, apiKey?) and then .chat(messages) or .chatWithTools(messages, tools).\n */\n\nexport interface ChatMessage {\n role: \"system\" | \"user\" | \"assistant\";\n content: string;\n}\n\nexport interface ChatOptions {\n /** Request timeout in milliseconds. Default 60000. */\n timeoutMs?: number;\n}\n\nexport interface ChatResult {\n content: string;\n raw: unknown;\n}\n\nexport interface OpenAIToolDefinition {\n type: \"function\";\n function: {\n name: string;\n description?: string;\n parameters?: object;\n };\n}\n\nexport interface AssistantMessageWithToolCalls {\n role: \"assistant\";\n content?: string | null;\n tool_calls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>;\n}\n\nexport interface ChatWithToolsResult {\n message: AssistantMessageWithToolCalls;\n raw: unknown;\n}\n\nexport interface OpenAICompatibleClientConfig {\n baseUrl: string;\n model: string;\n apiKey?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000;\n\nexport function createOpenAICompatibleClient(\n baseUrl: string,\n model: string,\n apiKey?: string\n): OpenAICompatibleClient {\n return new OpenAICompatibleClient({ baseUrl, model, apiKey });\n}\n\nexport class OpenAICompatibleClient {\n private readonly baseUrl: string;\n private readonly model: string;\n private readonly apiKey?: string;\n\n constructor(config: OpenAICompatibleClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n this.model = config.model;\n this.apiKey = config.apiKey;\n }\n\n async chat(\n messages: ChatMessage[],\n options?: ChatOptions\n ): Promise<ChatResult> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const raw = await this.request(\n {\n model: this.model,\n messages: messages.map((m) => ({ role: m.role, content: m.content })),\n },\n timeoutMs\n );\n const choices = (raw as { choices?: Array<{ message?: { content?: string } }> }).choices;\n const content =\n Array.isArray(choices) && choices[0]?.message?.content != null\n ? String(choices[0].message.content)\n : \"\";\n return { content, raw };\n }\n\n async chatWithTools(\n messages: Array<\n | ChatMessage\n | { role: \"tool\"; content: string; tool_call_id: string }\n | (Omit<AssistantMessageWithToolCalls, \"role\"> & { role: \"assistant\" })\n >,\n tools: OpenAIToolDefinition[],\n options?: ChatOptions\n ): Promise<ChatWithToolsResult> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const raw = await this.request(\n {\n model: this.model,\n messages: messages.map((m) => this.serializeMessage(m)),\n tools,\n },\n timeoutMs\n );\n const choices = (raw as {\n choices?: Array<{\n message?: {\n content?: string | null;\n tool_calls?: AssistantMessageWithToolCalls[\"tool_calls\"];\n };\n }>;\n }).choices;\n const msg = Array.isArray(choices) ? choices[0]?.message : undefined;\n return {\n message: {\n role: \"assistant\",\n content: msg?.content ?? null,\n tool_calls: msg?.tool_calls,\n },\n raw,\n };\n }\n\n private buildHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n if (this.apiKey) headers[\"Authorization\"] = `Bearer ${this.apiKey}`;\n return headers;\n }\n\n private serializeMessage(\n m:\n | ChatMessage\n | { role: \"tool\"; content: string; tool_call_id: string }\n | (AssistantMessageWithToolCalls & { role: \"assistant\" })\n ): object {\n if (m.role === \"tool\")\n return { role: \"tool\", content: m.content, tool_call_id: m.tool_call_id };\n if (m.role === \"assistant\" && \"tool_calls\" in m && m.tool_calls?.length)\n return { role: \"assistant\", content: m.content ?? null, tool_calls: m.tool_calls };\n return { role: m.role, content: (m as ChatMessage).content };\n }\n\n private async request(body: object, timeoutMs: number): Promise<unknown> {\n const url = `${this.baseUrl}/chat/completions`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: this.buildHeaders(),\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n } catch (err) {\n clearTimeout(timer);\n if (err instanceof Error && err.name === \"AbortError\")\n throw new Error(`LLM request timed out after ${timeoutMs}ms`);\n throw err;\n }\n clearTimeout(timer);\n const raw = (await response.json()) as unknown;\n if (!response.ok) {\n const errBody =\n typeof raw === \"object\" && raw !== null && \"error\" in (raw as object)\n ? (raw as { error: unknown }).error\n : raw;\n throw new Error(\n `LLM API error ${response.status}: ${JSON.stringify(errBody)}`\n );\n }\n return raw;\n }\n}\n","/**\n * ReAct agent: LLM + tool hub, Thought–Action–Observation loop.\n * Use: new ReActAgent(llm, toolHub).run(instruction).\n */\n\nimport type { ToolRegistry } from \"../registry/ToolRegistry.js\";\nimport type { ToolSpec } from \"../types/ToolSpec.js\";\nimport type { ToolResult } from \"../types/ToolResult.js\";\nimport type {\n OpenAICompatibleClient,\n OpenAIToolDefinition,\n AssistantMessageWithToolCalls,\n ChatMessage,\n} from \"./OpenAICompatibleClient.js\";\n\nexport interface ReActAgentToolHub {\n getRegistry(): ToolRegistry;\n invokeTool(toolName: string, args: unknown): Promise<ToolResult>;\n}\n\nexport interface ReActAgentRunOptions {\n systemPrompt?: string;\n maxSteps?: number;\n timeoutMs?: number;\n}\n\nexport interface ReActAgentRunResult {\n content: string;\n steps: number;\n}\n\ntype AgentMessage =\n | ChatMessage\n | { role: \"tool\"; content: string; tool_call_id: string }\n | (AssistantMessageWithToolCalls & { role: \"assistant\" });\n\nconst DEFAULT_PROMPT =\n \"ReAct: Thought (reason) → Action (use tool or answer) → Observation (tool result). Repeat until you give the final answer. Same language as user.\";\n\nexport class ReActAgent {\n constructor(\n private readonly llm: OpenAICompatibleClient,\n private readonly toolHub: ReActAgentToolHub\n ) {}\n\n async run(\n instruction: string,\n options: ReActAgentRunOptions = {}\n ): Promise<ReActAgentRunResult> {\n const maxSteps = options.maxSteps ?? 10;\n const tools = this.getTools();\n const messages: AgentMessage[] = [\n { role: \"system\", content: options.systemPrompt ?? DEFAULT_PROMPT },\n { role: \"user\", content: instruction },\n ];\n let steps = 0;\n let lastContent = \"\";\n\n while (steps < maxSteps) {\n const { message } = await this.llm.chatWithTools(\n messages,\n tools,\n options.timeoutMs ? { timeoutMs: options.timeoutMs } : undefined\n );\n steps++;\n messages.push({ ...message, role: \"assistant\" });\n if (message.content) lastContent = message.content;\n if (!message.tool_calls?.length)\n return { content: lastContent, steps };\n\n for (const tc of message.tool_calls) {\n const args = this.parseArgs(tc.function.arguments);\n const result = await this.toolHub.invokeTool(tc.function.name, args);\n const content = result.ok\n ? JSON.stringify(result.result)\n : JSON.stringify({ ok: false, error: result.error?.message ?? \"Tool failed\" });\n messages.push({ role: \"tool\", content: `Observation: ${content}`, tool_call_id: tc.id });\n }\n }\n return { content: lastContent || \"Reached max steps.\", steps };\n }\n\n private getTools(): OpenAIToolDefinition[] {\n return this.toolHub.getRegistry().snapshot().map((s: ToolSpec) => ({\n type: \"function\" as const,\n function: { name: s.name, description: s.description ?? \"\", parameters: s.inputSchema },\n }));\n }\n\n private parseArgs(json: string | undefined): unknown {\n if (!json) return {};\n try {\n return JSON.parse(json);\n } catch {\n return {};\n }\n }\n}\n"]}
|