@open1s/ezbos 1.0.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/src/agent.ts ADDED
@@ -0,0 +1,404 @@
1
+ import * as jsbos from '@open1s/jsbos';
2
+ import { jsbos as jsbosDefault, InternalToolDef } from './tool.js';
3
+ import { HookEvent, HookCallback, mergeHooks } from './hook.js';
4
+ import { PluginHandlers, mergePlugins } from './plugin.js';
5
+ import { SkillDef } from './skills.js';
6
+
7
+ const DEFAULT_MODEL = 'nvidia/meta/llama-3.1-8b-instruct';
8
+ const DEFAULT_BASE_URL = 'https://integrate.api.nvidia.com/v1';
9
+
10
+ export class AgentBuilder {
11
+ private _inner: jsbos.Agent | null = null;
12
+ private _tools: InternalToolDef[] = [];
13
+ private _hooks: Array<{ event: HookEvent, callback: HookCallback }> = [];
14
+ private _plugins: PluginHandlers[] = [];
15
+ private _mcp: Array<{ type: 'process' | 'http', namespace: string, command?: string, args?: string[], url?: string }> = [];
16
+ private _skillsDirs: string[] = [];
17
+ private _inlineSkills: SkillDef[] = [];
18
+ private _config: {
19
+ name: string;
20
+ model: string;
21
+ baseUrl: string;
22
+ apiKey?: string;
23
+ systemPrompt: string;
24
+ temperature: number;
25
+ timeoutSecs: number;
26
+ maxTokens?: number;
27
+ circuitBreakerMaxFailures?: number;
28
+ circuitBreakerCooldownSecs?: number;
29
+ rateLimitCapacity?: number;
30
+ rateLimitWindowSecs?: number;
31
+ rateLimitMaxRetries?: number;
32
+ };
33
+
34
+ constructor(name: string, options: {
35
+ model?: string;
36
+ baseUrl?: string;
37
+ apiKey?: string;
38
+ systemPrompt?: string;
39
+ temperature?: number;
40
+ timeoutSecs?: number;
41
+ maxTokens?: number;
42
+ circuitBreakerMaxFailures?: number;
43
+ circuitBreakerCooldownSecs?: number;
44
+ rateLimitCapacity?: number;
45
+ rateLimitWindowSecs?: number;
46
+ rateLimitMaxRetries?: number;
47
+ } = {}) {
48
+ this._config = {
49
+ name,
50
+ model: options.model || DEFAULT_MODEL,
51
+ baseUrl: options.baseUrl || DEFAULT_BASE_URL,
52
+ apiKey: options.apiKey,
53
+ systemPrompt: options.systemPrompt || 'You are a helpful assistant.',
54
+ temperature: options.temperature ?? 0.7,
55
+ timeoutSecs: options.timeoutSecs || 120,
56
+ maxTokens: options.maxTokens ?? 4096,
57
+ circuitBreakerMaxFailures: options.circuitBreakerMaxFailures,
58
+ circuitBreakerCooldownSecs: options.circuitBreakerCooldownSecs,
59
+ rateLimitCapacity: options.rateLimitCapacity,
60
+ rateLimitWindowSecs: options.rateLimitWindowSecs,
61
+ rateLimitMaxRetries: options.rateLimitMaxRetries,
62
+ };
63
+ }
64
+
65
+ with_model(model: string): this {
66
+ this._config.model = model;
67
+ return this;
68
+ }
69
+
70
+ with_baseUrl(url: string): this {
71
+ this._config.baseUrl = url;
72
+ return this;
73
+ }
74
+
75
+ with_apiKey(key: string): this {
76
+ this._config.apiKey = key;
77
+ return this;
78
+ }
79
+
80
+ with_systemPrompt(prompt: string): this {
81
+ this._config.systemPrompt = prompt;
82
+ return this;
83
+ }
84
+
85
+ with_prompt(prompt: string): this {
86
+ return this.with_systemPrompt(prompt);
87
+ }
88
+
89
+ with_temperature(temp: number): this {
90
+ this._config.temperature = temp;
91
+ return this;
92
+ }
93
+
94
+ with_timeout(secs: number): this {
95
+ this._config.timeoutSecs = secs;
96
+ return this;
97
+ }
98
+
99
+ with_maxTokens(tokens: number): this {
100
+ this._config.maxTokens = tokens;
101
+ return this;
102
+ }
103
+
104
+ with_tools(...tools: any[]): this {
105
+ for (const t of tools) {
106
+ if (t && typeof t === 'object' && 'name' in t && 'description' in t && 'callback' in t) {
107
+ this._tools.push(t as InternalToolDef);
108
+ } else if (typeof t === 'function' && (t as any).toolDef) {
109
+ this._tools.push((t as any).toolDef);
110
+ }
111
+ }
112
+ return this;
113
+ }
114
+
115
+ register(...tools: any[]): this {
116
+ return this.with_tools(...tools);
117
+ }
118
+
119
+ with_hooks(...sources: any[]): this {
120
+ const merged = mergeHooks(...sources);
121
+ this._hooks.push(...merged);
122
+ return this;
123
+ }
124
+
125
+ with_plugins(...sources: any[]): this {
126
+ const merged = mergePlugins(...sources);
127
+ this._plugins.push(...merged);
128
+ return this;
129
+ }
130
+
131
+ with_mcp_process(namespace: string, command: string, args: string[]): this {
132
+ this._mcp.push({ type: 'process', namespace, command, args });
133
+ return this;
134
+ }
135
+
136
+ with_mcp_http(namespace: string, url: string): this {
137
+ this._mcp.push({ type: 'http', namespace, url });
138
+ return this;
139
+ }
140
+
141
+ with_skills_dir(dirPath: string): this {
142
+ this._skillsDirs.push(dirPath);
143
+ return this;
144
+ }
145
+
146
+ with_skills(...skills: SkillDef[]): this {
147
+ this._inlineSkills.push(...skills);
148
+ return this;
149
+ }
150
+
151
+ with_resilience(opts: {
152
+ circuitBreakerMaxFailures?: number;
153
+ circuitBreakerCooldownSecs?: number;
154
+ rateLimitCapacity?: number;
155
+ rateLimitWindowSecs?: number;
156
+ rateLimitMaxRetries?: number;
157
+ }): this {
158
+ if (opts.circuitBreakerMaxFailures !== undefined) {
159
+ this._config.circuitBreakerMaxFailures = opts.circuitBreakerMaxFailures;
160
+ }
161
+ if (opts.circuitBreakerCooldownSecs !== undefined) {
162
+ this._config.circuitBreakerCooldownSecs = opts.circuitBreakerCooldownSecs;
163
+ }
164
+ if (opts.rateLimitCapacity !== undefined) {
165
+ this._config.rateLimitCapacity = opts.rateLimitCapacity;
166
+ }
167
+ if (opts.rateLimitWindowSecs !== undefined) {
168
+ this._config.rateLimitWindowSecs = opts.rateLimitWindowSecs;
169
+ }
170
+ if (opts.rateLimitMaxRetries !== undefined) {
171
+ this._config.rateLimitMaxRetries = opts.rateLimitMaxRetries;
172
+ }
173
+ return this;
174
+ }
175
+
176
+ async start(): Promise<Agent> {
177
+ this._inner = await jsbos.Agent.create(this._config as any);
178
+
179
+ for (const tool of this._tools) {
180
+ this._inner.addTool(
181
+ tool.name,
182
+ tool.description,
183
+ JSON.stringify(tool.schema.properties || {}),
184
+ JSON.stringify(tool.schema),
185
+ (err: any, args: any) => {
186
+ if (err) return String(err);
187
+ try {
188
+ return tool.callback(args);
189
+ } catch (e: any) {
190
+ return String(e);
191
+ }
192
+ }
193
+ );
194
+ }
195
+
196
+ for (const { event, callback } of this._hooks) {
197
+ this._inner.registerHook(event, async (e, ctx) => {
198
+ if (e) return 'continue';
199
+ return await callback(ctx);
200
+ });
201
+ }
202
+
203
+ for (const plugin of this._plugins) {
204
+ this._inner.registerPlugin(
205
+ plugin.name || 'plugin',
206
+ plugin.on_llm_request ? ((err: any, arg: any) => {
207
+ if (err) return;
208
+ return plugin.on_llm_request!(arg);
209
+ }) : undefined,
210
+ plugin.on_llm_response ? ((err: any, arg: any) => {
211
+ if (err) return;
212
+ return plugin.on_llm_response!(arg);
213
+ }) : undefined,
214
+ plugin.on_tool_call ? ((err: any, arg: any) => {
215
+ if (err) return;
216
+ return plugin.on_tool_call!(arg);
217
+ }) : undefined,
218
+ plugin.on_tool_result ? ((err: any, arg: any) => {
219
+ if (err) return;
220
+ return plugin.on_tool_result!(arg);
221
+ }) : undefined
222
+ );
223
+ }
224
+
225
+ for (const mcp of this._mcp) {
226
+ if (mcp.type === 'process' && mcp.command && mcp.args) {
227
+ await this._inner.addMcpServer(mcp.namespace, mcp.command, mcp.args);
228
+ } else if (mcp.type === 'http' && mcp.url) {
229
+ await this._inner.addMcpServerHttp(mcp.namespace, mcp.url);
230
+ }
231
+ }
232
+
233
+ let systemPrompt = this._config.systemPrompt || '';
234
+
235
+ for (const dir of this._skillsDirs) {
236
+ await this._inner.registerSkillsFromDir(dir);
237
+ }
238
+
239
+ for (const skill of this._inlineSkills) {
240
+ const addition = `\n\n# Skill: ${skill.name}\n${skill.content}`;
241
+ systemPrompt += addition;
242
+ }
243
+
244
+ if (systemPrompt !== this._config.systemPrompt) {
245
+ this._config.systemPrompt = systemPrompt;
246
+ const newAgent = await this.rebuildAgentWithPrompt(systemPrompt);
247
+ this._inner = newAgent;
248
+ }
249
+
250
+ return new Agent(this._inner!);
251
+ }
252
+
253
+ private async rebuildAgentWithPrompt(newPrompt: string): Promise<jsbos.Agent> {
254
+ const newAgent = await jsbos.Agent.create({
255
+ name: this._config.name,
256
+ model: this._config.model,
257
+ baseUrl: this._config.baseUrl,
258
+ apiKey: this._config.apiKey || '',
259
+ systemPrompt: newPrompt,
260
+ temperature: this._config.temperature,
261
+ timeoutSecs: this._config.timeoutSecs,
262
+ maxTokens: this._config.maxTokens,
263
+ });
264
+ for (const tool of this._tools) {
265
+ newAgent.addTool(
266
+ tool.name,
267
+ tool.description,
268
+ JSON.stringify(tool.schema.properties || {}),
269
+ JSON.stringify(tool.schema),
270
+ (err: any, args: any) => {
271
+ if (err) return String(err);
272
+ try {
273
+ return tool.callback(args);
274
+ } catch (e: any) {
275
+ return String(e);
276
+ }
277
+ }
278
+ );
279
+ }
280
+ return newAgent;
281
+ }
282
+
283
+ async ask(prompt: string): Promise<string> {
284
+ if (!this._inner) await this.start();
285
+ return this._inner!.react(prompt);
286
+ }
287
+
288
+ async runSimple(prompt: string): Promise<string> {
289
+ if (!this._inner) await this.start();
290
+ return this._inner!.runSimple(prompt);
291
+ }
292
+
293
+ async react(task: string): Promise<string> {
294
+ if (!this._inner) await this.start();
295
+ return this._inner!.react(task);
296
+ }
297
+ }
298
+
299
+ export class Agent {
300
+ constructor(private _inner: jsbos.Agent) {}
301
+
302
+ async run(task: string): Promise<string> {
303
+ return this._inner.runSimple(task);
304
+ }
305
+
306
+ async ask(prompt: string): Promise<string> {
307
+ return this._inner.react(prompt);
308
+ }
309
+
310
+ async runSimple(prompt: string): Promise<string> {
311
+ return this._inner.runSimple(prompt);
312
+ }
313
+
314
+ async react(task: string): Promise<string> {
315
+ return this._inner.react(task);
316
+ }
317
+
318
+ async compactSession(): Promise<void> {
319
+ const sessionJson = this._inner.getSessionJson();
320
+
321
+ const compactedJson = await this.ask(`Compact this session JSON. Preserve all important context, facts, and decisions while removing redundant messages. Return ONLY valid JSON. Use exact role names: "System", "User", "Assistant", "AssistantToolCall", "ToolResult".\n\nSession JSON:\n${sessionJson}`);
322
+
323
+ let cleaned = compactedJson.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
324
+
325
+ cleaned = cleaned.replace(/"user"/g, '"User"')
326
+ .replace(/"assistant"/g, '"Assistant"')
327
+ .replace(/"system"/g, '"System"')
328
+ .replace(/"toolresult"/g, '"ToolResult"')
329
+ .replace(/"assistanttoolcall"/g, '"AssistantToolCall"');
330
+
331
+ this._inner.restoreSessionJson(cleaned);
332
+ }
333
+
334
+ saveSession(path: string): void {
335
+ this._inner.saveSession(path);
336
+ }
337
+
338
+ restoreSession(path: string): void {
339
+ this._inner.restoreSessionFromFile(path);
340
+ }
341
+
342
+ clearSession(): void {
343
+ this._inner.clearSession();
344
+ }
345
+
346
+ exportSession(): string {
347
+ return this._inner.getSessionJson();
348
+ }
349
+
350
+ importSession(json: string): void {
351
+ this._inner.restoreSessionJson(json);
352
+ }
353
+
354
+ stream(task: string, onToken: (token: any) => void): Promise<void> {
355
+ return this._inner.stream(task, (err, token) => {
356
+ if (err) {
357
+ onToken({ type: 'Error', error: err.message });
358
+ } else {
359
+ onToken(token);
360
+ }
361
+ });
362
+ }
363
+
364
+ async streamCollect(task: string): Promise<any[]> {
365
+ const tokens: any[] = [];
366
+ await new Promise<void>((resolve) => {
367
+ this.stream(task, token => {
368
+ tokens.push(token);
369
+ if (token.type === 'Done') {
370
+ resolve();
371
+ }
372
+ });
373
+ });
374
+ return tokens;
375
+ }
376
+
377
+ get tools(): string[] {
378
+ return this._inner.listTools();
379
+ }
380
+
381
+ get config(): any {
382
+ return this._inner.config();
383
+ }
384
+
385
+ get inner(): jsbos.Agent {
386
+ return this._inner;
387
+ }
388
+
389
+ async listMcpTools(): Promise<any[]> {
390
+ return this._inner.listMcpTools();
391
+ }
392
+
393
+ get metrics(): jsbos.PerfSnapshot {
394
+ return this._inner.getPerfMetrics();
395
+ }
396
+
397
+ resetMetrics(): void {
398
+ this._inner.resetPerfMetrics();
399
+ }
400
+
401
+ async close(): Promise<void> {
402
+ this._inner.close();
403
+ }
404
+ }