@mast-ai/core 0.1.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/LICENSE +193 -0
- package/README.md +55 -0
- package/dist/adapter/index.d.ts +52 -0
- package/dist/adapter/index.js +3 -0
- package/dist/adapter/urp.d.ts +54 -0
- package/dist/adapter/urp.js +80 -0
- package/dist/agent.d.ts +9 -0
- package/dist/agent.js +19 -0
- package/dist/agentTool.d.ts +26 -0
- package/dist/agentTool.js +37 -0
- package/dist/conversation.d.ts +22 -0
- package/dist/conversation.js +64 -0
- package/dist/error.d.ts +14 -0
- package/dist/error.js +24 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +14 -0
- package/dist/runner.d.ts +74 -0
- package/dist/runner.js +216 -0
- package/dist/tool.d.ts +77 -0
- package/dist/tool.js +57 -0
- package/dist/transport/http.d.ts +21 -0
- package/dist/transport/http.js +108 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +3 -0
- package/package.json +33 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Current version of the `@mast-ai/core` package. */
|
|
2
|
+
export declare const VERSION = "0.1.0";
|
|
3
|
+
export * from './types';
|
|
4
|
+
export * from './error';
|
|
5
|
+
export * from './tool';
|
|
6
|
+
export * from './agent';
|
|
7
|
+
export * from './agentTool';
|
|
8
|
+
export * from './runner';
|
|
9
|
+
export * from './conversation';
|
|
10
|
+
export * from './adapter/index';
|
|
11
|
+
export * from './adapter/urp';
|
|
12
|
+
export * from './transport/http';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Copyright 2026 Andre Cipriani Bandarra
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/** Current version of the `@mast-ai/core` package. */
|
|
4
|
+
export const VERSION = '0.1.0';
|
|
5
|
+
export * from './types';
|
|
6
|
+
export * from './error';
|
|
7
|
+
export * from './tool';
|
|
8
|
+
export * from './agent';
|
|
9
|
+
export * from './agentTool';
|
|
10
|
+
export * from './runner';
|
|
11
|
+
export * from './conversation';
|
|
12
|
+
export * from './adapter/index';
|
|
13
|
+
export * from './adapter/urp';
|
|
14
|
+
export * from './transport/http';
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AgentConfig, AgentEvent, AgentResult, Message } from './types';
|
|
2
|
+
import type { LlmAdapter } from './adapter/index';
|
|
3
|
+
import { type ToolProvider } from './tool';
|
|
4
|
+
import { Conversation } from './conversation';
|
|
5
|
+
type StreamExecutor = (input: string, history: Message[], signal?: AbortSignal, onToolEvent?: (toolName: string, event: AgentEvent) => void) => AsyncIterable<AgentEvent>;
|
|
6
|
+
/**
|
|
7
|
+
* Fluent builder for a single agent run.
|
|
8
|
+
*
|
|
9
|
+
* Obtain an instance from {@link AgentRunner.runBuilder} rather than
|
|
10
|
+
* constructing one directly.
|
|
11
|
+
*/
|
|
12
|
+
export declare class RunBuilder {
|
|
13
|
+
private readonly agent;
|
|
14
|
+
private readonly execute;
|
|
15
|
+
private _history;
|
|
16
|
+
private _signal?;
|
|
17
|
+
private _onToolEvent?;
|
|
18
|
+
constructor(agent: AgentConfig, execute: StreamExecutor);
|
|
19
|
+
/** Prepend prior conversation turns. */
|
|
20
|
+
history(messages: Message[]): this;
|
|
21
|
+
/** Attach an AbortSignal to cancel the run and any in-flight tool calls. */
|
|
22
|
+
signal(signal: AbortSignal): this;
|
|
23
|
+
/**
|
|
24
|
+
* Register a callback that receives events emitted by tools running
|
|
25
|
+
* sub-agents. `toolName` identifies which tool fired the event.
|
|
26
|
+
* Note: convenience methods on {@link AgentRunner} do not surface tool
|
|
27
|
+
* events — use `runBuilder().onToolEvent(...).runStream()` for that.
|
|
28
|
+
*/
|
|
29
|
+
onToolEvent(handler: (toolName: string, event: AgentEvent) => void): this;
|
|
30
|
+
/** Executes the run and returns a stream of {@link AgentEvent} objects. */
|
|
31
|
+
runStream(input: string): AsyncIterable<AgentEvent>;
|
|
32
|
+
/** Executes the run and returns the final text output. */
|
|
33
|
+
run(input: string): Promise<AgentResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Executes the run and parses the final output as JSON into `T`.
|
|
36
|
+
*
|
|
37
|
+
* Throws {@link AgentError} if the output cannot be parsed.
|
|
38
|
+
*/
|
|
39
|
+
runTyped<T>(input: string): Promise<T>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Core execution engine that drives the agent agentic loop.
|
|
43
|
+
*
|
|
44
|
+
* Pair an {@link LlmAdapter} with a {@link ToolRegistry} and use
|
|
45
|
+
* {@link AgentRunner.runBuilder} (multi-turn) or the convenience methods
|
|
46
|
+
* {@link AgentRunner.run} / {@link AgentRunner.runStream} (single-turn).
|
|
47
|
+
*/
|
|
48
|
+
export declare class AgentRunner {
|
|
49
|
+
/** The LLM adapter used to generate responses. */
|
|
50
|
+
readonly adapter: LlmAdapter;
|
|
51
|
+
/** Provider of tools the runner may invoke on behalf of agents. */
|
|
52
|
+
readonly registry: ToolProvider;
|
|
53
|
+
constructor(
|
|
54
|
+
/** The LLM adapter used to generate responses. */
|
|
55
|
+
adapter: LlmAdapter,
|
|
56
|
+
/** Provider of tools the runner may invoke on behalf of agents. */
|
|
57
|
+
registry?: ToolProvider);
|
|
58
|
+
/** Creates a Conversation that automatically tracks history across turns. */
|
|
59
|
+
conversation(agent: AgentConfig): Conversation;
|
|
60
|
+
/** Primary entry point for multi-turn use. */
|
|
61
|
+
runBuilder(agent: AgentConfig): RunBuilder;
|
|
62
|
+
/** Single-turn convenience methods (delegate to runBuilder). */
|
|
63
|
+
runStream(agent: AgentConfig, input: string): AsyncIterable<AgentEvent>;
|
|
64
|
+
/** Single-turn convenience method — runs the agent and returns the final text output. */
|
|
65
|
+
run(agent: AgentConfig, input: string): Promise<AgentResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Single-turn convenience method — runs the agent and parses the output as JSON into `T`.
|
|
68
|
+
*
|
|
69
|
+
* Throws {@link AgentError} if parsing fails.
|
|
70
|
+
*/
|
|
71
|
+
runTyped<T>(agent: AgentConfig, input: string): Promise<T>;
|
|
72
|
+
private executeStream;
|
|
73
|
+
}
|
|
74
|
+
export {};
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Copyright 2026 Andre Cipriani Bandarra
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { ToolRegistry } from './tool';
|
|
4
|
+
import { AgentError } from './error';
|
|
5
|
+
import { Conversation } from './conversation';
|
|
6
|
+
/**
|
|
7
|
+
* Fluent builder for a single agent run.
|
|
8
|
+
*
|
|
9
|
+
* Obtain an instance from {@link AgentRunner.runBuilder} rather than
|
|
10
|
+
* constructing one directly.
|
|
11
|
+
*/
|
|
12
|
+
export class RunBuilder {
|
|
13
|
+
agent;
|
|
14
|
+
execute;
|
|
15
|
+
_history = [];
|
|
16
|
+
_signal;
|
|
17
|
+
_onToolEvent;
|
|
18
|
+
constructor(agent, execute) {
|
|
19
|
+
this.agent = agent;
|
|
20
|
+
this.execute = execute;
|
|
21
|
+
}
|
|
22
|
+
/** Prepend prior conversation turns. */
|
|
23
|
+
history(messages) {
|
|
24
|
+
this._history = [...messages];
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
/** Attach an AbortSignal to cancel the run and any in-flight tool calls. */
|
|
28
|
+
signal(signal) {
|
|
29
|
+
this._signal = signal;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Register a callback that receives events emitted by tools running
|
|
34
|
+
* sub-agents. `toolName` identifies which tool fired the event.
|
|
35
|
+
* Note: convenience methods on {@link AgentRunner} do not surface tool
|
|
36
|
+
* events — use `runBuilder().onToolEvent(...).runStream()` for that.
|
|
37
|
+
*/
|
|
38
|
+
onToolEvent(handler) {
|
|
39
|
+
this._onToolEvent = handler;
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
/** Executes the run and returns a stream of {@link AgentEvent} objects. */
|
|
43
|
+
runStream(input) {
|
|
44
|
+
return this.execute(input, this._history, this._signal, this._onToolEvent);
|
|
45
|
+
}
|
|
46
|
+
/** Executes the run and returns the final text output. */
|
|
47
|
+
async run(input) {
|
|
48
|
+
let output = '';
|
|
49
|
+
for await (const event of this.runStream(input)) {
|
|
50
|
+
if (event.type === 'text_delta') {
|
|
51
|
+
output += event.delta;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { output };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Executes the run and parses the final output as JSON into `T`.
|
|
58
|
+
*
|
|
59
|
+
* Throws {@link AgentError} if the output cannot be parsed.
|
|
60
|
+
*/
|
|
61
|
+
async runTyped(input) {
|
|
62
|
+
const result = await this.run(input);
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(result.output);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new AgentError('Failed to parse structured output', err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Core execution engine that drives the agent agentic loop.
|
|
73
|
+
*
|
|
74
|
+
* Pair an {@link LlmAdapter} with a {@link ToolRegistry} and use
|
|
75
|
+
* {@link AgentRunner.runBuilder} (multi-turn) or the convenience methods
|
|
76
|
+
* {@link AgentRunner.run} / {@link AgentRunner.runStream} (single-turn).
|
|
77
|
+
*/
|
|
78
|
+
export class AgentRunner {
|
|
79
|
+
adapter;
|
|
80
|
+
registry;
|
|
81
|
+
constructor(
|
|
82
|
+
/** The LLM adapter used to generate responses. */
|
|
83
|
+
adapter,
|
|
84
|
+
/** Provider of tools the runner may invoke on behalf of agents. */
|
|
85
|
+
registry = new ToolRegistry()) {
|
|
86
|
+
this.adapter = adapter;
|
|
87
|
+
this.registry = registry;
|
|
88
|
+
}
|
|
89
|
+
/** Creates a Conversation that automatically tracks history across turns. */
|
|
90
|
+
conversation(agent) {
|
|
91
|
+
return new Conversation(this, agent);
|
|
92
|
+
}
|
|
93
|
+
/** Primary entry point for multi-turn use. */
|
|
94
|
+
runBuilder(agent) {
|
|
95
|
+
return new RunBuilder(agent, (input, history, signal, onToolEvent) => this.executeStream(agent, input, history, signal, onToolEvent));
|
|
96
|
+
}
|
|
97
|
+
/** Single-turn convenience methods (delegate to runBuilder). */
|
|
98
|
+
runStream(agent, input) {
|
|
99
|
+
return this.runBuilder(agent).runStream(input);
|
|
100
|
+
}
|
|
101
|
+
/** Single-turn convenience method — runs the agent and returns the final text output. */
|
|
102
|
+
run(agent, input) {
|
|
103
|
+
return this.runBuilder(agent).run(input);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Single-turn convenience method — runs the agent and parses the output as JSON into `T`.
|
|
107
|
+
*
|
|
108
|
+
* Throws {@link AgentError} if parsing fails.
|
|
109
|
+
*/
|
|
110
|
+
runTyped(agent, input) {
|
|
111
|
+
return this.runBuilder(agent).runTyped(input);
|
|
112
|
+
}
|
|
113
|
+
async *executeStream(agent, input, history, signal, onToolEvent) {
|
|
114
|
+
// Use the explicit tool list when provided; otherwise expose all registered tools.
|
|
115
|
+
const toolDefinitions = agent.tools
|
|
116
|
+
? agent.tools.map((toolName) => {
|
|
117
|
+
const tool = this.registry.getTool(toolName);
|
|
118
|
+
if (!tool) {
|
|
119
|
+
throw new AgentError(`Tool '${toolName}' requested by agent '${agent.name}' is not registered.`);
|
|
120
|
+
}
|
|
121
|
+
return tool.definition();
|
|
122
|
+
})
|
|
123
|
+
: this.registry.getTools();
|
|
124
|
+
// Clone history and add new user message
|
|
125
|
+
const currentHistory = [
|
|
126
|
+
...history,
|
|
127
|
+
{ role: 'user', content: { type: 'text', text: input } },
|
|
128
|
+
];
|
|
129
|
+
while (true) {
|
|
130
|
+
if (signal?.aborted) {
|
|
131
|
+
throw new AgentError('Run aborted', signal.reason);
|
|
132
|
+
}
|
|
133
|
+
const request = {
|
|
134
|
+
system: agent.instructions,
|
|
135
|
+
messages: currentHistory,
|
|
136
|
+
tools: toolDefinitions,
|
|
137
|
+
outputSchema: agent.outputSchema,
|
|
138
|
+
signal,
|
|
139
|
+
};
|
|
140
|
+
let finalOutput = '';
|
|
141
|
+
const toolCalls = [];
|
|
142
|
+
if (this.adapter.generateStream) {
|
|
143
|
+
const stream = this.adapter.generateStream(request);
|
|
144
|
+
for await (const chunk of stream) {
|
|
145
|
+
if (signal?.aborted) {
|
|
146
|
+
throw new AgentError('Run aborted', signal.reason);
|
|
147
|
+
}
|
|
148
|
+
if (chunk.type === 'text_delta') {
|
|
149
|
+
finalOutput += chunk.delta;
|
|
150
|
+
yield { type: 'text_delta', delta: chunk.delta };
|
|
151
|
+
}
|
|
152
|
+
else if (chunk.type === 'thinking') {
|
|
153
|
+
yield { type: 'thinking', delta: chunk.delta };
|
|
154
|
+
}
|
|
155
|
+
else if (chunk.type === 'tool_call') {
|
|
156
|
+
toolCalls.push(chunk.toolCall);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const response = await this.adapter.generate(request);
|
|
162
|
+
if (response.text) {
|
|
163
|
+
finalOutput = response.text;
|
|
164
|
+
yield { type: 'text_delta', delta: finalOutput };
|
|
165
|
+
}
|
|
166
|
+
if (response.toolCalls) {
|
|
167
|
+
toolCalls.push(...response.toolCalls);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (toolCalls.length > 0) {
|
|
171
|
+
for (const call of toolCalls) {
|
|
172
|
+
yield { type: 'tool_call_started', name: call.name, args: call.args };
|
|
173
|
+
}
|
|
174
|
+
const toolResults = await Promise.all(toolCalls.map(async (call) => {
|
|
175
|
+
const tool = this.registry.getTool(call.name);
|
|
176
|
+
if (!tool) {
|
|
177
|
+
return {
|
|
178
|
+
call,
|
|
179
|
+
result: `Error: Tool '${call.name}' not found.`,
|
|
180
|
+
error: true,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const result = await tool.call(call.args, {
|
|
185
|
+
signal,
|
|
186
|
+
onEvent: onToolEvent ? (event) => onToolEvent(call.name, event) : undefined,
|
|
187
|
+
});
|
|
188
|
+
return { call, result, error: false };
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
192
|
+
return { call, result: `Error executing tool: ${message}`, error: true };
|
|
193
|
+
}
|
|
194
|
+
}));
|
|
195
|
+
const resultMessages = [];
|
|
196
|
+
for (const { call, result, error } of toolResults) {
|
|
197
|
+
yield { type: 'tool_call_completed', name: call.name, result, error };
|
|
198
|
+
resultMessages.push({
|
|
199
|
+
role: 'user',
|
|
200
|
+
content: { type: 'tool_result', id: call.id, name: call.name, result },
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
// Assistant tool_calls message must precede tool result messages in history.
|
|
204
|
+
currentHistory.push({
|
|
205
|
+
role: 'assistant',
|
|
206
|
+
content: { type: 'tool_calls', calls: toolCalls },
|
|
207
|
+
});
|
|
208
|
+
currentHistory.push(...resultMessages);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
currentHistory.push({ role: 'assistant', content: { type: 'text', text: finalOutput } });
|
|
212
|
+
yield { type: 'done', output: finalOutput, history: currentHistory };
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
package/dist/tool.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AgentEvent } from './types';
|
|
2
|
+
/** Static description of a tool that is sent to the model so it can decide when to call it. */
|
|
3
|
+
export interface ToolDefinition {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
/** JSON Schema object describing the tool's arguments. */
|
|
7
|
+
parameters: Record<string, unknown>;
|
|
8
|
+
/** Whether the tool reads or modifies state. Used for scope-based filtering. */
|
|
9
|
+
scope: 'read' | 'write';
|
|
10
|
+
/** When true, the tool author declares this tool is sensitive and requires human confirmation before executing. */
|
|
11
|
+
requiresApproval?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Runtime context passed to every {@link Tool.call} invocation. */
|
|
14
|
+
export interface ToolContext {
|
|
15
|
+
/** Forwarded from the runner; allows long-running tools to be cancelled. */
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
/**
|
|
18
|
+
* Called by tools that internally run sub-agents to surface child events to
|
|
19
|
+
* the parent runner's consumer. Simple tools can ignore this entirely.
|
|
20
|
+
* Tools should filter out {@link AgentEvent} `done` events before forwarding
|
|
21
|
+
* to avoid leaking child conversation history to the parent's consumers.
|
|
22
|
+
*/
|
|
23
|
+
onEvent?: (event: AgentEvent) => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A callable tool that can be registered with a {@link ToolRegistry}.
|
|
27
|
+
*
|
|
28
|
+
* @typeParam TArgs - Shape of the arguments object the model passes to `call`.
|
|
29
|
+
* @typeParam TResult - Value returned by `call` and forwarded to the model as the tool result.
|
|
30
|
+
*/
|
|
31
|
+
export interface Tool<TArgs = unknown, TResult = unknown> {
|
|
32
|
+
/** Returns the static description used to advertise this tool to the model. */
|
|
33
|
+
definition(): ToolDefinition;
|
|
34
|
+
/** Executes the tool with the given arguments. */
|
|
35
|
+
call(args: TArgs, context: ToolContext): Promise<TResult>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read-only interface for accessing tool definitions and resolving tools by name.
|
|
39
|
+
* Passed to {@link AgentRunner} so that scope-based filtering is handled by the
|
|
40
|
+
* provider rather than the runner.
|
|
41
|
+
*/
|
|
42
|
+
export interface ToolProvider {
|
|
43
|
+
/** Returns the definitions of all accessible tools. */
|
|
44
|
+
getTools(): ToolDefinition[];
|
|
45
|
+
/** Returns the tool registered under `name`, or `undefined` if not found or out of scope. */
|
|
46
|
+
getTool(name: string): Tool | undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Holds all tools available to an {@link AgentRunner} and resolves them by name during execution. */
|
|
49
|
+
export declare class ToolRegistry implements ToolProvider {
|
|
50
|
+
private _tools;
|
|
51
|
+
/**
|
|
52
|
+
* Registers a tool. Throws if a tool with the same name is already registered.
|
|
53
|
+
* Returns `this` for chaining.
|
|
54
|
+
*/
|
|
55
|
+
register(tool: Tool): this;
|
|
56
|
+
/** Removes the tool registered under `name`. No-op if not found. */
|
|
57
|
+
unregister(name: string): void;
|
|
58
|
+
/** Returns the tool registered under `name`, or `undefined` if not found. */
|
|
59
|
+
getTool(name: string): Tool | undefined;
|
|
60
|
+
/** Returns the definitions of all registered tools for inclusion in an adapter request. */
|
|
61
|
+
getTools(): ToolDefinition[];
|
|
62
|
+
/** Returns a live read-only view filtered to tools with `scope: 'read'`. */
|
|
63
|
+
readOnly(): ToolRegistryView;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* A live filtered projection of a {@link ToolRegistry}.
|
|
67
|
+
*
|
|
68
|
+
* Changes to the parent registry are automatically reflected — no copy is made.
|
|
69
|
+
* Only tools whose `scope` matches the view's scope are visible.
|
|
70
|
+
*/
|
|
71
|
+
export declare class ToolRegistryView implements ToolProvider {
|
|
72
|
+
private readonly parent;
|
|
73
|
+
private readonly scope;
|
|
74
|
+
constructor(parent: ToolRegistry, scope: 'read' | 'write');
|
|
75
|
+
getTools(): ToolDefinition[];
|
|
76
|
+
getTool(name: string): Tool | undefined;
|
|
77
|
+
}
|
package/dist/tool.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Copyright 2026 Andre Cipriani Bandarra
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/** Holds all tools available to an {@link AgentRunner} and resolves them by name during execution. */
|
|
4
|
+
export class ToolRegistry {
|
|
5
|
+
_tools = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Registers a tool. Throws if a tool with the same name is already registered.
|
|
8
|
+
* Returns `this` for chaining.
|
|
9
|
+
*/
|
|
10
|
+
register(tool) {
|
|
11
|
+
const name = tool.definition().name;
|
|
12
|
+
if (this._tools.has(name)) {
|
|
13
|
+
throw new Error(`Tool '${name}' is already registered.`);
|
|
14
|
+
}
|
|
15
|
+
this._tools.set(name, tool);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
/** Removes the tool registered under `name`. No-op if not found. */
|
|
19
|
+
unregister(name) {
|
|
20
|
+
this._tools.delete(name);
|
|
21
|
+
}
|
|
22
|
+
/** Returns the tool registered under `name`, or `undefined` if not found. */
|
|
23
|
+
getTool(name) {
|
|
24
|
+
return this._tools.get(name);
|
|
25
|
+
}
|
|
26
|
+
/** Returns the definitions of all registered tools for inclusion in an adapter request. */
|
|
27
|
+
getTools() {
|
|
28
|
+
return Array.from(this._tools.values()).map((t) => t.definition());
|
|
29
|
+
}
|
|
30
|
+
/** Returns a live read-only view filtered to tools with `scope: 'read'`. */
|
|
31
|
+
readOnly() {
|
|
32
|
+
return new ToolRegistryView(this, 'read');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A live filtered projection of a {@link ToolRegistry}.
|
|
37
|
+
*
|
|
38
|
+
* Changes to the parent registry are automatically reflected — no copy is made.
|
|
39
|
+
* Only tools whose `scope` matches the view's scope are visible.
|
|
40
|
+
*/
|
|
41
|
+
export class ToolRegistryView {
|
|
42
|
+
parent;
|
|
43
|
+
scope;
|
|
44
|
+
constructor(parent, scope) {
|
|
45
|
+
this.parent = parent;
|
|
46
|
+
this.scope = scope;
|
|
47
|
+
}
|
|
48
|
+
getTools() {
|
|
49
|
+
return this.parent.getTools().filter((def) => def.scope === this.scope);
|
|
50
|
+
}
|
|
51
|
+
getTool(name) {
|
|
52
|
+
const tool = this.parent.getTool(name);
|
|
53
|
+
if (!tool)
|
|
54
|
+
return undefined;
|
|
55
|
+
return tool.definition().scope === this.scope ? tool : undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { UrpRequest, UrpResponse, UrpTransport, UrpStreamChunk } from '../adapter/urp';
|
|
2
|
+
/** Configuration for {@link HttpTransport}. */
|
|
3
|
+
export interface HttpTransportOptions {
|
|
4
|
+
/** Endpoint URL that accepts URP-formatted POST requests. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** Additional headers merged into every request (e.g. `Authorization`). */
|
|
7
|
+
headers?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* {@link UrpTransport} implementation that sends URP requests over HTTP/HTTPS.
|
|
11
|
+
*
|
|
12
|
+
* Supports both non-streaming (JSON) and streaming (NDJSON / SSE) responses.
|
|
13
|
+
*/
|
|
14
|
+
export declare class HttpTransport implements UrpTransport {
|
|
15
|
+
private options;
|
|
16
|
+
constructor(options: HttpTransportOptions);
|
|
17
|
+
/** {@inheritDoc UrpTransport.send} */
|
|
18
|
+
send(request: UrpRequest, signal?: AbortSignal): Promise<UrpResponse>;
|
|
19
|
+
/** {@inheritDoc UrpTransport.sendStream} */
|
|
20
|
+
sendStream(request: UrpRequest, signal?: AbortSignal): AsyncIterable<UrpStreamChunk>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright 2026 Andre Cipriani Bandarra
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { AdapterError } from '../error';
|
|
4
|
+
/**
|
|
5
|
+
* {@link UrpTransport} implementation that sends URP requests over HTTP/HTTPS.
|
|
6
|
+
*
|
|
7
|
+
* Supports both non-streaming (JSON) and streaming (NDJSON / SSE) responses.
|
|
8
|
+
*/
|
|
9
|
+
export class HttpTransport {
|
|
10
|
+
options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
/** {@inheritDoc UrpTransport.send} */
|
|
15
|
+
async send(request, signal) {
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetch(this.options.url, {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
...(this.options.headers || {}),
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify(request),
|
|
24
|
+
signal,
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new AdapterError(`HTTP request failed with status ${response.status}`, response.status);
|
|
28
|
+
}
|
|
29
|
+
const data = await response.json();
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof AdapterError) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
throw new AdapterError(`Failed to send request: ${error instanceof Error ? error.message : String(error)}`, undefined, error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** {@inheritDoc UrpTransport.sendStream} */
|
|
40
|
+
async *sendStream(request, signal) {
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = await fetch(this.options.url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
Accept: 'text/event-stream, application/x-ndjson, application/json',
|
|
48
|
+
...(this.options.headers || {}),
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify({ ...request, stream: true }),
|
|
51
|
+
signal,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new AdapterError(`Failed to send streaming request: ${error instanceof Error ? error.message : String(error)}`, undefined, error);
|
|
56
|
+
}
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new AdapterError(`HTTP streaming request failed with status ${response.status}`, response.status);
|
|
59
|
+
}
|
|
60
|
+
if (!response.body) {
|
|
61
|
+
throw new AdapterError('Response body is empty');
|
|
62
|
+
}
|
|
63
|
+
const reader = response.body.getReader();
|
|
64
|
+
const decoder = new TextDecoder();
|
|
65
|
+
let buffer = '';
|
|
66
|
+
try {
|
|
67
|
+
while (true) {
|
|
68
|
+
const { done, value } = await reader.read();
|
|
69
|
+
if (done)
|
|
70
|
+
break;
|
|
71
|
+
buffer += decoder.decode(value, { stream: true });
|
|
72
|
+
const lines = buffer.split('\n');
|
|
73
|
+
buffer = lines.pop() || '';
|
|
74
|
+
for (const line of lines) {
|
|
75
|
+
const trimmed = line.trim();
|
|
76
|
+
if (!trimmed)
|
|
77
|
+
continue;
|
|
78
|
+
// Support both simple NDJSON and SSE (data: ...)
|
|
79
|
+
const jsonStr = trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed;
|
|
80
|
+
if (jsonStr === '[DONE]')
|
|
81
|
+
continue;
|
|
82
|
+
try {
|
|
83
|
+
yield JSON.parse(jsonStr);
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
throw new AdapterError(`Failed to parse URP stream chunk: ${jsonStr}`, undefined, e);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Handle remaining buffer
|
|
91
|
+
if (buffer.trim()) {
|
|
92
|
+
const trimmed = buffer.trim();
|
|
93
|
+
const jsonStr = trimmed.startsWith('data: ') ? trimmed.slice(6) : trimmed;
|
|
94
|
+
if (jsonStr !== '[DONE]') {
|
|
95
|
+
try {
|
|
96
|
+
yield JSON.parse(jsonStr);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
throw new AdapterError(`Failed to parse URP stream chunk: ${jsonStr}`, undefined, e);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
reader.releaseLock();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the role of a message sender.
|
|
3
|
+
*/
|
|
4
|
+
export type Role = 'user' | 'assistant';
|
|
5
|
+
/**
|
|
6
|
+
* A single tool call issued by the assistant.
|
|
7
|
+
*/
|
|
8
|
+
export interface ToolCall {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
args: unknown;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Discriminated union of possible message content types.
|
|
15
|
+
*/
|
|
16
|
+
export type MessageContent = {
|
|
17
|
+
type: 'text';
|
|
18
|
+
text: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: 'tool_calls';
|
|
21
|
+
calls: ToolCall[];
|
|
22
|
+
} | {
|
|
23
|
+
type: 'tool_result';
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
result: unknown;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A single message in a conversation history.
|
|
30
|
+
*/
|
|
31
|
+
export interface Message {
|
|
32
|
+
role: Role;
|
|
33
|
+
content: MessageContent;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Events emitted by the AgentRunner during execution.
|
|
37
|
+
*/
|
|
38
|
+
export type AgentEvent = {
|
|
39
|
+
type: 'tool_call_started';
|
|
40
|
+
name: string;
|
|
41
|
+
args: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'tool_call_completed';
|
|
44
|
+
name: string;
|
|
45
|
+
result: unknown;
|
|
46
|
+
error?: boolean;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'text_delta';
|
|
49
|
+
delta: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'thinking';
|
|
52
|
+
delta: string;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'done';
|
|
55
|
+
output: string;
|
|
56
|
+
history: Message[];
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Pure data blueprint for an agent.
|
|
60
|
+
*/
|
|
61
|
+
export interface AgentConfig {
|
|
62
|
+
name: string;
|
|
63
|
+
instructions: string;
|
|
64
|
+
tools?: string[];
|
|
65
|
+
outputSchema?: Record<string, unknown>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The final result of an agent run.
|
|
69
|
+
*/
|
|
70
|
+
export interface AgentResult {
|
|
71
|
+
output: string;
|
|
72
|
+
}
|
package/dist/types.js
ADDED