@rovn-ai/agent 1.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/README.md ADDED
@@ -0,0 +1,495 @@
1
+ # @rovn-platform/sdk
2
+
3
+ **AI Agent Governance SDK** -- manage, monitor, and govern your AI agents.
4
+
5
+ The official TypeScript/JavaScript SDK for [Rovn](https://rovn.io), the AI Agent Command Center. Zero runtime dependencies -- uses only the Fetch API.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @rovn-platform/sdk
11
+ ```
12
+
13
+ Works with Node.js 18+ (Fetch API required), Deno, Bun, and modern browsers.
14
+
15
+ ## Quick Start (5 minutes)
16
+
17
+ ```typescript
18
+ import { RovnAgent, RovnError } from '@rovn-platform/sdk';
19
+
20
+ // 1. Register your agent
21
+ const { agent, id, apiKey } = await RovnAgent.register(
22
+ 'https://your-rovn-instance.com',
23
+ {
24
+ name: 'my-data-pipeline',
25
+ description: 'Processes and analyzes customer data',
26
+ type: 'data_pipeline',
27
+ capabilities: ['read_database', 'write_reports', 'send_email'],
28
+ }
29
+ );
30
+ // Save apiKey -- you will need it to reconnect later
31
+
32
+ // 2. Send an activity
33
+ await agent.logActivity('Processed 1,200 records', {
34
+ type: 'data_processing',
35
+ metadata: { records: 1200, duration_ms: 4500 },
36
+ });
37
+
38
+ // 3. Check if an action is allowed (pre-flight)
39
+ const check = await agent.checkAction('send_email', {
40
+ urgency: 'high',
41
+ cost: 0.05,
42
+ });
43
+
44
+ if (check.allowed) {
45
+ console.log('Go ahead!');
46
+ } else if (check.needs_approval) {
47
+ // 4. Request approval from the owner
48
+ const approvalId = await agent.requestApproval({
49
+ type: 'action',
50
+ title: 'Send 500 marketing emails',
51
+ description: 'Campaign targeting new signups from last week',
52
+ urgency: 'high',
53
+ });
54
+ console.log(`Waiting for approval: ${approvalId}`);
55
+ }
56
+
57
+ // 5. Get your report card
58
+ const report = await agent.getReportCard({ days: 7 });
59
+ console.log('Trust:', report.trust);
60
+ console.log('Recommendations:', report.recommendations);
61
+ ```
62
+
63
+ ## All Available Methods
64
+
65
+ ### Registration & Info
66
+
67
+ ```typescript
68
+ // Register a new agent (static method, no API key needed)
69
+ const { agent, id, apiKey } = await RovnAgent.register(
70
+ baseUrl: string,
71
+ options: {
72
+ name: string;
73
+ description?: string;
74
+ type?: string;
75
+ capabilities?: string[];
76
+ owner_email?: string;
77
+ metadata?: Record<string, unknown>;
78
+ }
79
+ );
80
+
81
+ // Connect with an existing API key
82
+ const agent = new RovnAgent({
83
+ baseUrl: 'https://...',
84
+ apiKey: 'rovn_...',
85
+ fireAndForget: false, // optional, default false
86
+ });
87
+
88
+ // Get agent info (auto-discovers agentId on first call)
89
+ const info: AgentInfo = await agent.getInfo();
90
+ // info.id, info.name, info.description, info.status, info.type,
91
+ // info.approved, info.capabilities, info.metadata, info.created_at,
92
+ // info.updated_at, info.last_seen_at
93
+ ```
94
+
95
+ ### Events & Activities
96
+
97
+ ```typescript
98
+ // Log an activity
99
+ await agent.logActivity(
100
+ title: string,
101
+ options?: { type?: string; description?: string; metadata?: Record<string, unknown> }
102
+ );
103
+
104
+ // Update agent status
105
+ await agent.updateStatus(status); // 'active' | 'idle' | 'busy' | 'offline' | 'error'
106
+
107
+ // Share structured data with the owner
108
+ await agent.shareData(title: string, content: Record<string, unknown>, type?: string);
109
+
110
+ // Send a raw event (low-level)
111
+ await agent.sendEvent(event: WebhookEvent, data: Record<string, unknown>);
112
+ ```
113
+
114
+ ### Tasks
115
+
116
+ ```typescript
117
+ // Get assigned tasks
118
+ const tasks: Task[] = await agent.getTasks({ status?: string, limit?: number });
119
+ // Each task has: id, agent_id, owner_id, title, description, status,
120
+ // priority, result, scheduled_at, started_at, completed_at
121
+
122
+ // Update a task's status
123
+ await agent.updateTaskStatus(taskId: string, status: string, result?: Record<string, unknown>);
124
+ ```
125
+
126
+ ### Messages & Chat
127
+
128
+ ```typescript
129
+ // Send a message to the owner
130
+ await agent.sendMessage(
131
+ content: string,
132
+ options?: { message_type?: string; metadata?: Record<string, unknown> }
133
+ );
134
+
135
+ // Respond to an owner command
136
+ await agent.respondToCommand(
137
+ commandId: string, status: string, response?: Record<string, unknown>
138
+ );
139
+
140
+ // Send a message to another agent
141
+ await agent.sendPeerMessage(
142
+ toAgentId: string,
143
+ content: string,
144
+ options?: { message_type?: string; metadata?: Record<string, unknown> }
145
+ );
146
+
147
+ // Get peer messages
148
+ const messages: PeerMessage[] = await agent.getPeerMessages({
149
+ direction?: 'inbox' | 'outbox' | 'all',
150
+ limit?: number,
151
+ });
152
+ ```
153
+
154
+ ### Approvals
155
+
156
+ ```typescript
157
+ // Request approval from the owner
158
+ const approvalId: string | undefined = await agent.requestApproval({
159
+ type: string,
160
+ title: string,
161
+ description?: string,
162
+ urgency?: 'low' | 'medium' | 'high' | 'critical',
163
+ metadata?: Record<string, unknown>,
164
+ });
165
+
166
+ // Poll all approvals
167
+ const approvals: ApprovalRequest[] = await agent.getApprovals({
168
+ status?: string,
169
+ limit?: number,
170
+ });
171
+
172
+ // Poll a specific approval by ID
173
+ const approval: ApprovalRequest = await agent.pollApproval(approvalId: string);
174
+ // approval.id, approval.status, approval.decided_at, approval.decision_note
175
+ ```
176
+
177
+ ### Guardrails & Constraints
178
+
179
+ ```typescript
180
+ // Get all guardrails set by the owner
181
+ const guardrails: Guardrail[] = await agent.getGuardrails();
182
+ // Each guardrail: id, metric, limit_value, current_value, window, action, enabled
183
+
184
+ // Check remaining budget for a specific metric (cached for 60s)
185
+ const remaining: number | null = await agent.getGuardrailRemaining(metric: string);
186
+
187
+ // Clear the guardrail cache to force a fresh fetch
188
+ agent.invalidateGuardrailCache();
189
+
190
+ // Declare self-constraints before starting a task
191
+ const constraint: Constraint = await agent.declareConstraint(
192
+ task: string,
193
+ constraints: Record<string, unknown>, // e.g. { max_api_calls: 100, max_cost_usd: 5.0 }
194
+ );
195
+
196
+ // Update actual usage against a declared constraint
197
+ const result = await agent.updateConstraint(
198
+ constraintId: string,
199
+ actualUsage: Record<string, unknown>, // e.g. { api_calls: 45, cost_usd: 2.10 }
200
+ completed: boolean = false,
201
+ );
202
+
203
+ // Get all constraints for this agent
204
+ const constraints: Constraint[] = await agent.getConstraints();
205
+ ```
206
+
207
+ ### Trust Score
208
+
209
+ ```typescript
210
+ const trust: TrustScoreResult = await agent.getTrustScore();
211
+ // trust.score (0-100), trust.grade ('A' through 'F'), trust.breakdown, trust.computed_at
212
+ ```
213
+
214
+ ### Pre-flight Check
215
+
216
+ ```typescript
217
+ // "Can I do this?" -- checks policies, guardrails, and earned autonomy
218
+ const check: CheckResult = await agent.checkAction(
219
+ action: string,
220
+ options?: {
221
+ urgency?: string;
222
+ cost?: number;
223
+ data_fields?: string[];
224
+ }
225
+ );
226
+ // check.allowed -- true if the action can proceed
227
+ // check.needs_approval -- true if the action requires owner approval
228
+ // check.would_auto_approve -- true if earned autonomy would auto-approve
229
+ // check.checks -- array of individual check results
230
+ // check.summary -- human-readable summary
231
+ ```
232
+
233
+ ### Report Card
234
+
235
+ ```typescript
236
+ const report: ReportCard = await agent.getReportCard({ days?: number });
237
+ // report.agent -- { id, name }
238
+ // report.period -- time period string
239
+ // report.productivity -- productivity metrics
240
+ // report.reliability -- reliability metrics
241
+ // report.compliance -- compliance metrics
242
+ // report.trust -- trust metrics
243
+ // report.recommendations -- array of improvement suggestions
244
+ ```
245
+
246
+ ### SSE Connection (Real-Time)
247
+
248
+ ```typescript
249
+ agent.connect(
250
+ handler: (event: SSEEventType, data: Record<string, unknown>) => void,
251
+ options?: {
252
+ agentId?: string; // override auto-discovered ID
253
+ onConnect?: () => void;
254
+ onDisconnect?: () => void;
255
+ reconnect?: boolean; // default true, auto-reconnect on disconnect
256
+ }
257
+ );
258
+
259
+ // Example handler
260
+ agent.connect((event, data) => {
261
+ // event is one of: 'connected', 'command', 'approval_response',
262
+ // 'interrupt', 'agent_updated', 'peer_message'
263
+ switch (event) {
264
+ case 'command':
265
+ console.log('Owner command:', data);
266
+ break;
267
+ case 'approval_response':
268
+ console.log(`Approval ${data.id}: ${data.status}`);
269
+ break;
270
+ case 'interrupt':
271
+ agent.disconnect();
272
+ break;
273
+ }
274
+ });
275
+
276
+ // Stop listening
277
+ agent.disconnect();
278
+ ```
279
+
280
+ ### Flush & Close
281
+
282
+ ```typescript
283
+ // Flush all queued events (blocks until drained)
284
+ await agent.flush();
285
+
286
+ // Graceful shutdown: disconnect SSE + flush pending events
287
+ await agent.close();
288
+ ```
289
+
290
+ ## Advanced Features
291
+
292
+ ### Fire-and-Forget Mode
293
+
294
+ Events are queued in memory and sent asynchronously with automatic retry. Your code never awaits HTTP calls for event delivery.
295
+
296
+ ```typescript
297
+ const agent = new RovnAgent({
298
+ baseUrl: 'https://...',
299
+ apiKey: 'rovn_...',
300
+ fireAndForget: true,
301
+ });
302
+
303
+ await agent.logActivity('Instant return, sent in background');
304
+ await agent.sendMessage('Also queued');
305
+ await agent.updateStatus('busy');
306
+
307
+ // Flush before shutdown to guarantee delivery
308
+ await agent.flush();
309
+
310
+ // Or use close() for full graceful shutdown
311
+ await agent.close();
312
+ ```
313
+
314
+ Even in synchronous mode, transient failures (5xx, network errors) are automatically queued for retry instead of throwing.
315
+
316
+ ### Error Handling
317
+
318
+ All API errors throw `RovnError` with structured information.
319
+
320
+ ```typescript
321
+ import { RovnError } from '@rovn-platform/sdk';
322
+
323
+ try {
324
+ await agent.logActivity('test');
325
+ } catch (err) {
326
+ if (err instanceof RovnError) {
327
+ console.error(err.message); // human-readable message
328
+ console.error(err.statusCode); // HTTP status code (0 for network errors)
329
+ console.error(err.errorCode); // server error code (e.g. 'rate_limited')
330
+ }
331
+ }
332
+ ```
333
+
334
+ Common error scenarios:
335
+ - **`statusCode === 0`** -- Network error (connection refused, DNS failure)
336
+ - **`statusCode === 401`** -- Invalid or expired API key
337
+ - **`statusCode === 429`** -- Rate limited (auto-retried)
338
+ - **`statusCode >= 500`** -- Server error (auto-retried)
339
+ - **`errorCode === 'AGENT_ID_MISSING'`** -- Call `getInfo()` or `register()` first
340
+
341
+ ### Retry Behavior
342
+
343
+ The SDK automatically retries on:
344
+ - Network errors (fetch failures, connection reset)
345
+ - HTTP 429 (rate limit)
346
+ - HTTP 5xx (server errors)
347
+
348
+ Retry uses exponential backoff: 1s, 2s, 4s, 8s, ... up to 30s max. Non-retryable errors (4xx except 429) cause the event to be dropped from the queue.
349
+
350
+ In fire-and-forget mode, failed events stay at the front of the queue and are retried with backoff. In synchronous mode, retryable failures are automatically queued for background retry instead of throwing.
351
+
352
+ The event queue holds up to 10,000 events. When full, the oldest event is dropped to make room.
353
+
354
+ ### Guardrail Cache
355
+
356
+ `getGuardrailRemaining()` caches the guardrail list for 60 seconds to avoid excessive API calls. The cache is automatically refreshed when stale.
357
+
358
+ ```typescript
359
+ // First call fetches from API
360
+ const remaining = await agent.getGuardrailRemaining('api_calls'); // -> 950
361
+
362
+ // Subsequent calls within 60s use the cache
363
+ const remaining2 = await agent.getGuardrailRemaining('api_calls'); // -> 950 (cached)
364
+
365
+ // Force a fresh fetch
366
+ agent.invalidateGuardrailCache();
367
+ const remaining3 = await agent.getGuardrailRemaining('api_calls'); // -> 947 (fresh)
368
+ ```
369
+
370
+ ## Complete Workflow Example
371
+
372
+ ```typescript
373
+ import { RovnAgent, RovnError } from '@rovn-platform/sdk';
374
+
375
+ const ROVN_URL = 'https://your-rovn-instance.com';
376
+ const API_KEY = 'rovn_abc123...';
377
+
378
+ async function main() {
379
+ // Connect to Rovn
380
+ const agent = new RovnAgent({ baseUrl: ROVN_URL, apiKey: API_KEY });
381
+ const info = await agent.getInfo();
382
+ console.log(`Agent: ${info.name} (status: ${info.status})`);
383
+
384
+ // Check trust score
385
+ const trust = await agent.getTrustScore();
386
+ console.log(`Trust: ${trust.grade} (${trust.score}/100)`);
387
+
388
+ // Check if we're allowed to send emails
389
+ const check = await agent.checkAction('send_email', { cost: 2.5 });
390
+
391
+ if (!check.allowed) {
392
+ if (check.needs_approval) {
393
+ // Request approval and wait
394
+ const approvalId = await agent.requestApproval({
395
+ type: 'action',
396
+ title: 'Send weekly report emails',
397
+ description: '150 emails to subscribers, estimated cost $2.50',
398
+ urgency: 'medium',
399
+ });
400
+ console.log(`Approval requested: ${approvalId}`);
401
+
402
+ // Poll until decided
403
+ let approval;
404
+ do {
405
+ await new Promise(r => setTimeout(r, 5000));
406
+ approval = await agent.pollApproval(approvalId!);
407
+ } while (approval.status === 'pending');
408
+
409
+ if (approval.status !== 'approved') {
410
+ console.log(`Denied: ${approval.decision_note}`);
411
+ return;
412
+ }
413
+ } else {
414
+ console.log(`Blocked: ${check.summary}`);
415
+ return;
416
+ }
417
+ }
418
+
419
+ // Declare constraints for the task
420
+ const constraint = await agent.declareConstraint(
421
+ 'send_weekly_emails',
422
+ { max_emails: 200, max_cost_usd: 5.0 },
423
+ );
424
+
425
+ // Do the work
426
+ await agent.updateStatus('busy');
427
+ await agent.logActivity('Sending weekly report emails', { type: 'email_campaign' });
428
+
429
+ const emailsSent = 150;
430
+ const cost = 2.35;
431
+
432
+ // Report actual usage
433
+ await agent.updateConstraint(
434
+ constraint.id,
435
+ { emails: emailsSent, cost_usd: cost },
436
+ true, // completed
437
+ );
438
+
439
+ // Notify owner
440
+ await agent.sendMessage(
441
+ `Weekly emails sent: ${emailsSent} emails, $${cost.toFixed(2)}`,
442
+ { metadata: { emails_sent: emailsSent, cost_usd: cost } },
443
+ );
444
+
445
+ await agent.updateStatus('idle');
446
+
447
+ // Check report card
448
+ const report = await agent.getReportCard({ days: 7 });
449
+ console.log('Recommendations:', report.recommendations);
450
+
451
+ // Graceful shutdown
452
+ await agent.close();
453
+ }
454
+
455
+ main().catch(console.error);
456
+ ```
457
+
458
+ ## Exported Types
459
+
460
+ All types are importable from the package:
461
+
462
+ ```typescript
463
+ import {
464
+ RovnAgent, // Client class
465
+ RovnError, // Error class
466
+ type RovnConfig, // Constructor config
467
+ type AgentInfo, // Agent profile
468
+ type Task, // Assigned task
469
+ type PeerMessage, // Inter-agent message
470
+ type Guardrail, // Usage limit
471
+ type Constraint, // Self-constraint declaration
472
+ type ApprovalRequest, // Approval request/response
473
+ type TrustScoreResult, // Trust score result
474
+ type CheckResult, // Pre-flight check result
475
+ type ReportCard, // Performance report card
476
+ type AgentStatus, // 'active' | 'idle' | 'busy' | 'offline' | 'error'
477
+ type TaskStatus, // 'pending' | 'in_progress' | 'completed' | 'cancelled' | 'failed'
478
+ type TaskPriority, // 'low' | 'medium' | 'high' | 'urgent'
479
+ type WebhookEvent, // Event type union
480
+ type SSEEventType, // SSE event type union
481
+ type SSEHandler, // SSE handler function type
482
+ type GuardrailWindow, // 'hourly' | 'daily' | 'weekly' | 'monthly'
483
+ type GuardrailAction, // 'warn' | 'block' | 'approval_required'
484
+ } from '@rovn-platform/sdk';
485
+ ```
486
+
487
+ ## Requirements
488
+
489
+ - Node.js 18+ (Fetch API), Deno, Bun, or modern browsers
490
+ - TypeScript 5+ (for type definitions)
491
+ - Zero runtime dependencies
492
+
493
+ ## License
494
+
495
+ MIT
@@ -0,0 +1,303 @@
1
+ export type RovnConfig = {
2
+ baseUrl: string;
3
+ apiKey: string;
4
+ /** When true, sendEvent queues events and returns immediately without awaiting the HTTP call. */
5
+ fireAndForget?: boolean;
6
+ };
7
+ export type WebhookEvent = 'activity' | 'task_update' | 'message' | 'status' | 'share_data' | 'command_response' | 'approval_request' | 'peer_message';
8
+ export type SSEEventType = 'connected' | 'command' | 'approval_response' | 'interrupt' | 'agent_updated' | 'peer_message';
9
+ export type SSEHandler = (event: SSEEventType, data: Record<string, unknown>) => void;
10
+ export type AgentStatus = 'active' | 'idle' | 'busy' | 'offline' | 'error';
11
+ export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'cancelled' | 'failed';
12
+ export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent';
13
+ export type GuardrailWindow = 'hourly' | 'daily' | 'weekly' | 'monthly';
14
+ export type GuardrailAction = 'warn' | 'block' | 'approval_required';
15
+ export interface AgentInfo {
16
+ id: string;
17
+ name: string;
18
+ description: string | null;
19
+ status: AgentStatus;
20
+ type: string;
21
+ approved: boolean;
22
+ capabilities: string[] | null;
23
+ metadata: Record<string, unknown> | null;
24
+ created_at: string;
25
+ updated_at: string;
26
+ last_seen_at: string | null;
27
+ }
28
+ export interface Task {
29
+ id: string;
30
+ agent_id: string;
31
+ owner_id: string;
32
+ title: string;
33
+ description: string | null;
34
+ status: TaskStatus;
35
+ priority: TaskPriority;
36
+ result: Record<string, unknown> | null;
37
+ scheduled_at: string | null;
38
+ started_at: string | null;
39
+ completed_at: string | null;
40
+ created_at: string;
41
+ updated_at: string;
42
+ }
43
+ export interface PeerMessage {
44
+ id: string;
45
+ from_agent_id: string;
46
+ to_agent_id: string;
47
+ content: string;
48
+ message_type: string;
49
+ metadata: Record<string, unknown> | null;
50
+ read_at: string | null;
51
+ created_at: string;
52
+ from_agent_name?: string;
53
+ to_agent_name?: string;
54
+ }
55
+ export interface Guardrail {
56
+ id: string;
57
+ agent_id: string;
58
+ owner_id: string;
59
+ metric: string;
60
+ limit_value: number;
61
+ current_value: number;
62
+ window: GuardrailWindow;
63
+ action: GuardrailAction;
64
+ enabled: boolean;
65
+ created_at: string;
66
+ updated_at: string;
67
+ }
68
+ export interface Constraint {
69
+ id: string;
70
+ agent_id: string;
71
+ task: string;
72
+ constraints: Record<string, unknown>;
73
+ actual_usage: Record<string, unknown> | null;
74
+ compliance: string;
75
+ started_at: string;
76
+ completed_at: string | null;
77
+ }
78
+ export interface ApprovalRequest {
79
+ id: string;
80
+ agent_id: string;
81
+ type: string;
82
+ title: string;
83
+ status: string;
84
+ urgency: string;
85
+ description: string | null;
86
+ decided_at: string | null;
87
+ decided_by: string | null;
88
+ decision_note: string | null;
89
+ created_at: string;
90
+ }
91
+ export interface TrustScoreResult {
92
+ agent_id: string;
93
+ score: number;
94
+ grade: string;
95
+ breakdown: Record<string, unknown>;
96
+ computed_at: string;
97
+ }
98
+ export interface CheckResult {
99
+ action: string;
100
+ allowed: boolean;
101
+ needs_approval: boolean;
102
+ would_auto_approve: boolean;
103
+ checks: Array<{
104
+ check: string;
105
+ passed: boolean;
106
+ detail: string;
107
+ }>;
108
+ summary: string;
109
+ }
110
+ export interface ReportCard {
111
+ agent: {
112
+ id: string;
113
+ name: string;
114
+ };
115
+ period: string;
116
+ productivity: Record<string, unknown>;
117
+ reliability: Record<string, unknown>;
118
+ compliance: Record<string, unknown>;
119
+ trust: Record<string, unknown>;
120
+ recommendations: string[];
121
+ }
122
+ export declare class RovnError extends Error {
123
+ readonly statusCode: number;
124
+ readonly errorCode: string | undefined;
125
+ constructor(message: string, statusCode: number, errorCode?: string);
126
+ }
127
+ export declare class RovnAgent {
128
+ private baseUrl;
129
+ private apiKey;
130
+ private agentId;
131
+ private sseController;
132
+ private fireAndForget;
133
+ private eventQueue;
134
+ private flushTimer;
135
+ private flushing;
136
+ private guardrailCache;
137
+ constructor(config: RovnConfig);
138
+ private headers;
139
+ /**
140
+ * Throws RovnError if agentId has not been set yet.
141
+ * Call register(), getInfo(), or connect({ agentId }) first.
142
+ */
143
+ private ensureAgentId;
144
+ private request;
145
+ /**
146
+ * Returns true if the error looks like a network / transient failure
147
+ * (as opposed to a 4xx client error that retrying won't fix).
148
+ */
149
+ private isRetryable;
150
+ private enqueue;
151
+ private scheduleFlush;
152
+ /**
153
+ * Internal drain loop — sends queued events one by one.
154
+ * On failure, re-queues the event at the front with incremented retry count
155
+ * and waits with exponential backoff before trying again.
156
+ */
157
+ private drainQueue;
158
+ static register(baseUrl: string, options: {
159
+ name: string;
160
+ description?: string;
161
+ type?: string;
162
+ capabilities?: string[];
163
+ owner_email?: string;
164
+ metadata?: Record<string, unknown>;
165
+ }): Promise<{
166
+ agent: RovnAgent;
167
+ id: string;
168
+ apiKey: string;
169
+ }>;
170
+ getInfo(): Promise<AgentInfo>;
171
+ sendEvent(event: WebhookEvent, data: Record<string, unknown>): Promise<Record<string, unknown> | void>;
172
+ logActivity(title: string, options?: {
173
+ type?: string;
174
+ description?: string;
175
+ metadata?: Record<string, unknown>;
176
+ }): Promise<void>;
177
+ updateTaskStatus(taskId: string, status: string, result?: Record<string, unknown>): Promise<void>;
178
+ sendMessage(content: string, options?: {
179
+ message_type?: string;
180
+ metadata?: Record<string, unknown>;
181
+ }): Promise<void>;
182
+ updateStatus(status: AgentStatus): Promise<void>;
183
+ shareData(title: string, content: Record<string, unknown>, type?: string): Promise<void>;
184
+ respondToCommand(commandId: string, status: string, response?: Record<string, unknown>): Promise<void>;
185
+ requestApproval(options: {
186
+ type: string;
187
+ title: string;
188
+ description?: string;
189
+ urgency?: 'low' | 'medium' | 'high' | 'critical';
190
+ metadata?: Record<string, unknown>;
191
+ }): Promise<string | undefined>;
192
+ sendPeerMessage(toAgentId: string, content: string, options?: {
193
+ message_type?: string;
194
+ metadata?: Record<string, unknown>;
195
+ }): Promise<void>;
196
+ /**
197
+ * Sends all queued events. Resolves when the queue is drained.
198
+ * Useful before shutdown or when you need to guarantee delivery.
199
+ */
200
+ flush(): Promise<void>;
201
+ /**
202
+ * Graceful shutdown: disconnects SSE, flushes pending events.
203
+ */
204
+ close(): Promise<void>;
205
+ connect(handler: SSEHandler, options?: {
206
+ agentId?: string;
207
+ onConnect?: () => void;
208
+ onDisconnect?: () => void;
209
+ reconnect?: boolean;
210
+ }): void;
211
+ disconnect(): void;
212
+ getTasks(options?: {
213
+ status?: string;
214
+ limit?: number;
215
+ }): Promise<Task[]>;
216
+ getPeerMessages(options?: {
217
+ direction?: 'inbox' | 'outbox' | 'all';
218
+ limit?: number;
219
+ }): Promise<PeerMessage[]>;
220
+ getGuardrails(): Promise<Guardrail[]>;
221
+ /**
222
+ * Returns how many units remain before hitting the guardrail limit for the
223
+ * given metric. Returns `null` if no guardrail matches.
224
+ *
225
+ * Results are cached for 60 seconds to reduce API calls.
226
+ */
227
+ getGuardrailRemaining(metric: string): Promise<number | null>;
228
+ /** Clears the cached guardrail data so the next call fetches fresh values. */
229
+ invalidateGuardrailCache(): void;
230
+ declareConstraint(task: string, constraints: Record<string, unknown>): Promise<Constraint>;
231
+ updateConstraint(constraintId: string, actualUsage: Record<string, unknown>, completed?: boolean): Promise<Record<string, unknown>>;
232
+ getConstraints(): Promise<Constraint[]>;
233
+ getTrustScore(): Promise<TrustScoreResult>;
234
+ getApprovals(options?: {
235
+ status?: string;
236
+ limit?: number;
237
+ }): Promise<ApprovalRequest[]>;
238
+ pollApproval(approvalId: string): Promise<ApprovalRequest>;
239
+ checkAction(action: string, options?: {
240
+ urgency?: string;
241
+ cost?: number;
242
+ data_fields?: string[];
243
+ }): Promise<CheckResult>;
244
+ getReportCard(options?: {
245
+ days?: number;
246
+ }): Promise<ReportCard>;
247
+ }
248
+ /**
249
+ * Wraps any function with Rovn governance: pre-flight policy check + activity reporting.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * import { RovnAgent, createGovernedTool } from 'rovn-sdk';
254
+ *
255
+ * const agent = new RovnAgent({ baseUrl: '...', apiKey: '...' });
256
+ * await agent.getInfo();
257
+ *
258
+ * const search = createGovernedTool(agent, {
259
+ * name: 'search',
260
+ * actionName: 'db_read',
261
+ * fn: async (query: string) => `Results for ${query}`,
262
+ * });
263
+ *
264
+ * const result = await search('my query');
265
+ * ```
266
+ */
267
+ export declare function createGovernedTool<TArgs extends unknown[], TResult>(agent: RovnAgent, options: {
268
+ name: string;
269
+ actionName?: string;
270
+ fn: (...args: TArgs) => TResult | Promise<TResult>;
271
+ }): (...args: TArgs) => Promise<TResult>;
272
+ /**
273
+ * Creates a middleware-style wrapper for Vercel AI SDK that reports
274
+ * each generation to Rovn and checks policies.
275
+ *
276
+ * @example
277
+ * ```ts
278
+ * import { RovnAgent, createVercelAIMiddleware } from 'rovn-sdk';
279
+ *
280
+ * const agent = new RovnAgent({ baseUrl: '...', apiKey: '...' });
281
+ * await agent.getInfo();
282
+ *
283
+ * const middleware = createVercelAIMiddleware(agent);
284
+ *
285
+ * // Wrap your doGenerate or doStream calls:
286
+ * const result = await middleware.wrapGenerate(async () => {
287
+ * return await model.doGenerate({ prompt: '...' });
288
+ * });
289
+ * ```
290
+ */
291
+ export declare function createVercelAIMiddleware(agent: RovnAgent, options?: {
292
+ actionName?: string;
293
+ }): {
294
+ /**
295
+ * Wraps a doGenerate call with policy check + activity reporting.
296
+ */
297
+ wrapGenerate<T>(fn: () => Promise<T>): Promise<T>;
298
+ /**
299
+ * Wraps a doStream call with policy check + activity reporting.
300
+ */
301
+ wrapStream<T>(fn: () => Promise<T>): Promise<T>;
302
+ };
303
+ export default RovnAgent;
package/dist/index.js ADDED
@@ -0,0 +1,586 @@
1
+ "use strict";
2
+ // ==================== Types ====================
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RovnAgent = exports.RovnError = void 0;
5
+ exports.createGovernedTool = createGovernedTool;
6
+ exports.createVercelAIMiddleware = createVercelAIMiddleware;
7
+ // ==================== Error ====================
8
+ class RovnError extends Error {
9
+ statusCode;
10
+ errorCode;
11
+ constructor(message, statusCode, errorCode) {
12
+ super(message);
13
+ this.name = 'RovnError';
14
+ this.statusCode = statusCode;
15
+ this.errorCode = errorCode;
16
+ }
17
+ }
18
+ exports.RovnError = RovnError;
19
+ // ==================== Client ====================
20
+ const MAX_QUEUE_SIZE = 10_000;
21
+ const MAX_BACKOFF_MS = 30_000;
22
+ const GUARDRAIL_CACHE_TTL_MS = 60_000;
23
+ class RovnAgent {
24
+ baseUrl;
25
+ apiKey;
26
+ agentId = null;
27
+ sseController = null;
28
+ fireAndForget;
29
+ // Event queue for fire-and-forget and offline resilience
30
+ eventQueue = [];
31
+ flushTimer = null;
32
+ flushing = false;
33
+ // Guardrail cache
34
+ guardrailCache = null;
35
+ constructor(config) {
36
+ this.baseUrl = config.baseUrl.replace(/\/$/, '');
37
+ this.apiKey = config.apiKey;
38
+ this.fireAndForget = config.fireAndForget ?? false;
39
+ }
40
+ // ==================== Private Helpers ====================
41
+ headers() {
42
+ return {
43
+ 'Content-Type': 'application/json',
44
+ Authorization: `Bearer ${this.apiKey}`,
45
+ };
46
+ }
47
+ /**
48
+ * Throws RovnError if agentId has not been set yet.
49
+ * Call register(), getInfo(), or connect({ agentId }) first.
50
+ */
51
+ ensureAgentId() {
52
+ if (!this.agentId) {
53
+ throw new RovnError('agentId is required. Call register(), getInfo(), or connect({ agentId }) first.', 0, 'AGENT_ID_MISSING');
54
+ }
55
+ }
56
+ async request(method, path, body) {
57
+ const res = await fetch(`${this.baseUrl}${path}`, {
58
+ method,
59
+ headers: this.headers(),
60
+ body: body ? JSON.stringify(body) : undefined,
61
+ });
62
+ if (!res.ok && res.headers.get('content-type')?.includes('text/html')) {
63
+ throw new RovnError(`Server error: ${res.status} ${res.statusText}`, res.status);
64
+ }
65
+ const data = await res.json();
66
+ if (!data.success) {
67
+ throw new RovnError(data.error || `Request failed: ${method} ${path}`, res.status, data.code);
68
+ }
69
+ return data.data;
70
+ }
71
+ /**
72
+ * Returns true if the error looks like a network / transient failure
73
+ * (as opposed to a 4xx client error that retrying won't fix).
74
+ */
75
+ isRetryable(err) {
76
+ if (err instanceof RovnError) {
77
+ // Retry on 5xx and 429; don't retry on 4xx client errors
78
+ return err.statusCode >= 500 || err.statusCode === 429;
79
+ }
80
+ // Network errors (TypeError from fetch), AbortError, etc. are retryable
81
+ return true;
82
+ }
83
+ // ==================== Event Queue Internals ====================
84
+ enqueue(event, data) {
85
+ if (this.eventQueue.length >= MAX_QUEUE_SIZE) {
86
+ // Drop the oldest event to make room
87
+ this.eventQueue.shift();
88
+ }
89
+ this.eventQueue.push({ event, data, retries: 0 });
90
+ this.scheduleFlush();
91
+ }
92
+ scheduleFlush() {
93
+ if (this.flushTimer || this.flushing)
94
+ return;
95
+ this.flushTimer = setTimeout(() => {
96
+ this.flushTimer = null;
97
+ this.drainQueue();
98
+ }, 0);
99
+ }
100
+ /**
101
+ * Internal drain loop — sends queued events one by one.
102
+ * On failure, re-queues the event at the front with incremented retry count
103
+ * and waits with exponential backoff before trying again.
104
+ */
105
+ async drainQueue() {
106
+ if (this.flushing)
107
+ return;
108
+ this.flushing = true;
109
+ try {
110
+ while (this.eventQueue.length > 0) {
111
+ const item = this.eventQueue[0];
112
+ try {
113
+ await this.request('POST', '/api/webhook/agent', { event: item.event, data: item.data });
114
+ // Success — remove from queue
115
+ this.eventQueue.shift();
116
+ }
117
+ catch (err) {
118
+ if (this.isRetryable(err)) {
119
+ // Exponential backoff: 1s, 2s, 4s, 8s, …, max 30s
120
+ const delayMs = Math.min(1000 * Math.pow(2, item.retries), MAX_BACKOFF_MS);
121
+ item.retries++;
122
+ await new Promise(resolve => setTimeout(resolve, delayMs));
123
+ // The item is still at the front of the queue; loop will retry it
124
+ }
125
+ else {
126
+ // Non-retryable error — drop the event and move on
127
+ this.eventQueue.shift();
128
+ }
129
+ }
130
+ }
131
+ }
132
+ finally {
133
+ this.flushing = false;
134
+ }
135
+ }
136
+ // ==================== Registration ====================
137
+ static async register(baseUrl, options) {
138
+ const res = await fetch(`${baseUrl.replace(/\/$/, '')}/api/agents/register`, {
139
+ method: 'POST',
140
+ headers: { 'Content-Type': 'application/json' },
141
+ body: JSON.stringify(options),
142
+ });
143
+ const data = await res.json();
144
+ if (!data.success)
145
+ throw new RovnError(data.error || 'Registration failed', res.status);
146
+ const agent = new RovnAgent({ baseUrl, apiKey: data.data.api_key });
147
+ agent.agentId = data.data.id;
148
+ return { agent, id: data.data.id, apiKey: data.data.api_key };
149
+ }
150
+ // ==================== Agent Info ====================
151
+ async getInfo() {
152
+ if (!this.agentId) {
153
+ const info = await this.request('GET', '/api/agents/me');
154
+ this.agentId = info.id;
155
+ return info;
156
+ }
157
+ return this.request('GET', `/api/agents/${this.agentId}`);
158
+ }
159
+ // ==================== Webhook (unified event endpoint) ====================
160
+ async sendEvent(event, data) {
161
+ if (this.fireAndForget) {
162
+ this.enqueue(event, data);
163
+ return;
164
+ }
165
+ try {
166
+ return await this.request('POST', '/api/webhook/agent', { event, data });
167
+ }
168
+ catch (err) {
169
+ if (this.isRetryable(err)) {
170
+ // Queue for retry with backoff
171
+ this.enqueue(event, data);
172
+ return;
173
+ }
174
+ throw err;
175
+ }
176
+ }
177
+ async logActivity(title, options) {
178
+ await this.sendEvent('activity', { title, ...options });
179
+ }
180
+ async updateTaskStatus(taskId, status, result) {
181
+ await this.sendEvent('task_update', { task_id: taskId, status, result });
182
+ }
183
+ async sendMessage(content, options) {
184
+ await this.sendEvent('message', { content, ...options });
185
+ }
186
+ async updateStatus(status) {
187
+ await this.sendEvent('status', { status });
188
+ }
189
+ async shareData(title, content, type) {
190
+ await this.sendEvent('share_data', { title, content, type });
191
+ }
192
+ async respondToCommand(commandId, status, response) {
193
+ await this.sendEvent('command_response', { command_id: commandId, status, response });
194
+ }
195
+ async requestApproval(options) {
196
+ // Always send synchronously to guarantee approval_id, even in fire-and-forget mode
197
+ const result = await this.request('POST', '/api/webhook/agent', { event: 'approval_request', data: options });
198
+ return result?.approval_id;
199
+ }
200
+ async sendPeerMessage(toAgentId, content, options) {
201
+ await this.sendEvent('peer_message', { to_agent_id: toAgentId, content, ...options });
202
+ }
203
+ // ==================== Flush ====================
204
+ /**
205
+ * Sends all queued events. Resolves when the queue is drained.
206
+ * Useful before shutdown or when you need to guarantee delivery.
207
+ */
208
+ async flush() {
209
+ // Cancel any pending scheduled flush
210
+ if (this.flushTimer) {
211
+ clearTimeout(this.flushTimer);
212
+ this.flushTimer = null;
213
+ }
214
+ // If already flushing, wait for it to finish
215
+ if (this.flushing) {
216
+ // Spin-wait until the current drain completes
217
+ while (this.flushing) {
218
+ await new Promise(resolve => setTimeout(resolve, 50));
219
+ }
220
+ // If items were added during the wait, drain again
221
+ if (this.eventQueue.length > 0) {
222
+ await this.drainQueue();
223
+ }
224
+ return;
225
+ }
226
+ if (this.eventQueue.length > 0) {
227
+ await this.drainQueue();
228
+ }
229
+ }
230
+ // ==================== Close ====================
231
+ /**
232
+ * Graceful shutdown: disconnects SSE, flushes pending events.
233
+ */
234
+ async close() {
235
+ this.disconnect();
236
+ await this.flush();
237
+ }
238
+ // ==================== SSE Stream ====================
239
+ connect(handler, options) {
240
+ const targetId = options?.agentId ?? this.agentId;
241
+ if (targetId)
242
+ this.agentId = targetId;
243
+ if (!this.agentId) {
244
+ throw new RovnError('agentId is required. Call register() or getInfo() first, or pass agentId in options.', 0);
245
+ }
246
+ const reconnect = options?.reconnect !== false;
247
+ let lastEventId = null;
248
+ const doConnect = async () => {
249
+ this.sseController = new AbortController();
250
+ try {
251
+ const headers = { ...this.headers() };
252
+ if (lastEventId)
253
+ headers['Last-Event-ID'] = lastEventId;
254
+ const response = await fetch(`${this.baseUrl}/api/agents/${this.agentId}/stream`, {
255
+ headers,
256
+ signal: this.sseController.signal,
257
+ });
258
+ if (!response.ok || !response.body) {
259
+ throw new RovnError(`SSE connection failed: ${response.status}`, response.status);
260
+ }
261
+ options?.onConnect?.();
262
+ const reader = response.body.getReader();
263
+ const decoder = new TextDecoder();
264
+ let buffer = '';
265
+ while (true) {
266
+ const { done, value } = await reader.read();
267
+ if (done)
268
+ break;
269
+ buffer += decoder.decode(value, { stream: true });
270
+ const lines = buffer.split('\n');
271
+ buffer = lines.pop() || '';
272
+ let eventType = '';
273
+ let eventData = '';
274
+ for (const line of lines) {
275
+ if (line.startsWith('id: ')) {
276
+ lastEventId = line.slice(4);
277
+ }
278
+ else if (line.startsWith('event: ')) {
279
+ eventType = line.slice(7);
280
+ }
281
+ else if (line.startsWith('data: ')) {
282
+ eventData = line.slice(6);
283
+ }
284
+ else if (line === '' && eventType && eventData) {
285
+ try {
286
+ const parsed = JSON.parse(eventData);
287
+ handler(eventType, parsed);
288
+ }
289
+ catch {
290
+ // ignore parse errors
291
+ }
292
+ eventType = '';
293
+ eventData = '';
294
+ }
295
+ }
296
+ }
297
+ }
298
+ catch (err) {
299
+ if (err.name === 'AbortError')
300
+ return;
301
+ options?.onDisconnect?.();
302
+ if (reconnect) {
303
+ await new Promise(r => setTimeout(r, 3000));
304
+ doConnect();
305
+ }
306
+ }
307
+ };
308
+ doConnect();
309
+ }
310
+ disconnect() {
311
+ this.sseController?.abort();
312
+ this.sseController = null;
313
+ }
314
+ // ==================== Tasks ====================
315
+ async getTasks(options) {
316
+ this.ensureAgentId();
317
+ const params = new URLSearchParams();
318
+ if (options?.status)
319
+ params.set('status', options.status);
320
+ if (options?.limit)
321
+ params.set('limit', String(options.limit));
322
+ const qs = params.toString();
323
+ return this.request('GET', `/api/agents/${this.agentId}/tasks${qs ? '?' + qs : ''}`);
324
+ }
325
+ // ==================== Peer Messages ====================
326
+ async getPeerMessages(options) {
327
+ this.ensureAgentId();
328
+ const params = new URLSearchParams();
329
+ if (options?.direction)
330
+ params.set('direction', options.direction);
331
+ if (options?.limit)
332
+ params.set('limit', String(options.limit));
333
+ const qs = params.toString();
334
+ return this.request('GET', `/api/agents/${this.agentId}/peer${qs ? '?' + qs : ''}`);
335
+ }
336
+ // ==================== Guardrails ====================
337
+ async getGuardrails() {
338
+ this.ensureAgentId();
339
+ return this.request('GET', `/api/agents/${this.agentId}/guardrails`);
340
+ }
341
+ // ==================== Guardrail Helper ====================
342
+ /**
343
+ * Returns how many units remain before hitting the guardrail limit for the
344
+ * given metric. Returns `null` if no guardrail matches.
345
+ *
346
+ * Results are cached for 60 seconds to reduce API calls.
347
+ */
348
+ async getGuardrailRemaining(metric) {
349
+ this.ensureAgentId();
350
+ const now = Date.now();
351
+ if (!this.guardrailCache || now - this.guardrailCache.cachedAt > GUARDRAIL_CACHE_TTL_MS) {
352
+ this.guardrailCache = {
353
+ data: await this.request('GET', `/api/agents/${this.agentId}/guardrails`),
354
+ cachedAt: now,
355
+ };
356
+ }
357
+ const guardrail = this.guardrailCache.data.find(g => g.metric === metric);
358
+ if (!guardrail)
359
+ return null;
360
+ return guardrail.limit_value - guardrail.current_value;
361
+ }
362
+ /** Clears the cached guardrail data so the next call fetches fresh values. */
363
+ invalidateGuardrailCache() {
364
+ this.guardrailCache = null;
365
+ }
366
+ // ==================== Constraints (Self-Constraint Declaration) ====================
367
+ async declareConstraint(task, constraints) {
368
+ this.ensureAgentId();
369
+ return this.request('POST', `/api/agents/${this.agentId}/constraints`, {
370
+ task,
371
+ constraints,
372
+ });
373
+ }
374
+ async updateConstraint(constraintId, actualUsage, completed = false) {
375
+ this.ensureAgentId();
376
+ return this.request('PATCH', `/api/agents/${this.agentId}/constraints`, {
377
+ constraint_id: constraintId,
378
+ actual_usage: actualUsage,
379
+ completed,
380
+ });
381
+ }
382
+ async getConstraints() {
383
+ this.ensureAgentId();
384
+ return this.request('GET', `/api/agents/${this.agentId}/constraints`);
385
+ }
386
+ // ==================== Trust Score ====================
387
+ async getTrustScore() {
388
+ this.ensureAgentId();
389
+ return this.request('GET', `/api/agents/${this.agentId}/trust-score`);
390
+ }
391
+ // ==================== Approvals (Polling) ====================
392
+ async getApprovals(options) {
393
+ const params = new URLSearchParams();
394
+ if (options?.status)
395
+ params.set('status', options.status);
396
+ if (options?.limit)
397
+ params.set('limit', String(options.limit));
398
+ const qs = params.toString();
399
+ const data = await this.request('GET', `/api/approvals${qs ? '?' + qs : ''}`);
400
+ return data.approvals ?? data;
401
+ }
402
+ async pollApproval(approvalId) {
403
+ return this.request('GET', `/api/approvals/${approvalId}`);
404
+ }
405
+ // ==================== Pre-flight Check ("Can I Do This?") ====================
406
+ async checkAction(action, options) {
407
+ this.ensureAgentId();
408
+ const params = new URLSearchParams({ action });
409
+ if (options?.urgency)
410
+ params.set('urgency', options.urgency);
411
+ if (options?.cost !== undefined)
412
+ params.set('cost', String(options.cost));
413
+ if (options?.data_fields)
414
+ params.set('data_fields', options.data_fields.join(','));
415
+ return this.request('GET', `/api/agents/${this.agentId}/check?${params}`);
416
+ }
417
+ // ==================== Report Card ====================
418
+ async getReportCard(options) {
419
+ this.ensureAgentId();
420
+ const params = new URLSearchParams();
421
+ if (options?.days)
422
+ params.set('days', String(options.days));
423
+ const qs = params.toString();
424
+ return this.request('GET', `/api/agents/${this.agentId}/report-card${qs ? '?' + qs : ''}`);
425
+ }
426
+ }
427
+ exports.RovnAgent = RovnAgent;
428
+ // ==================== LangChain-style Tool Wrapper ====================
429
+ /**
430
+ * Wraps any function with Rovn governance: pre-flight policy check + activity reporting.
431
+ *
432
+ * @example
433
+ * ```ts
434
+ * import { RovnAgent, createGovernedTool } from 'rovn-sdk';
435
+ *
436
+ * const agent = new RovnAgent({ baseUrl: '...', apiKey: '...' });
437
+ * await agent.getInfo();
438
+ *
439
+ * const search = createGovernedTool(agent, {
440
+ * name: 'search',
441
+ * actionName: 'db_read',
442
+ * fn: async (query: string) => `Results for ${query}`,
443
+ * });
444
+ *
445
+ * const result = await search('my query');
446
+ * ```
447
+ */
448
+ function createGovernedTool(agent, options) {
449
+ const actionName = options.actionName ?? options.name;
450
+ return async (...args) => {
451
+ // Pre-flight check
452
+ try {
453
+ const check = await agent.checkAction(actionName);
454
+ if (!check.allowed) {
455
+ await agent.logActivity(`Blocked: ${actionName}`, {
456
+ type: 'policy_block',
457
+ description: check.summary,
458
+ });
459
+ throw new RovnError(`Action '${actionName}' blocked by policy: ${check.summary}`, 403, 'POLICY_BLOCKED');
460
+ }
461
+ }
462
+ catch (err) {
463
+ if (err instanceof RovnError)
464
+ throw err;
465
+ // If check fails (network, etc.), allow execution but continue
466
+ }
467
+ // Execute
468
+ try {
469
+ const result = await options.fn(...args);
470
+ await agent.logActivity(`Executed: ${actionName}`, {
471
+ type: 'tool_execution',
472
+ description: `Tool '${options.name}' completed successfully`,
473
+ });
474
+ return result;
475
+ }
476
+ catch (err) {
477
+ if (err instanceof RovnError)
478
+ throw err;
479
+ await agent.logActivity(`Failed: ${actionName}`, {
480
+ type: 'tool_error',
481
+ description: `Tool '${options.name}' failed: ${err}`,
482
+ });
483
+ throw err;
484
+ }
485
+ };
486
+ }
487
+ // ==================== Vercel AI SDK Middleware ====================
488
+ /**
489
+ * Creates a middleware-style wrapper for Vercel AI SDK that reports
490
+ * each generation to Rovn and checks policies.
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * import { RovnAgent, createVercelAIMiddleware } from 'rovn-sdk';
495
+ *
496
+ * const agent = new RovnAgent({ baseUrl: '...', apiKey: '...' });
497
+ * await agent.getInfo();
498
+ *
499
+ * const middleware = createVercelAIMiddleware(agent);
500
+ *
501
+ * // Wrap your doGenerate or doStream calls:
502
+ * const result = await middleware.wrapGenerate(async () => {
503
+ * return await model.doGenerate({ prompt: '...' });
504
+ * });
505
+ * ```
506
+ */
507
+ function createVercelAIMiddleware(agent, options) {
508
+ const actionName = options?.actionName ?? 'ai_generate';
509
+ return {
510
+ /**
511
+ * Wraps a doGenerate call with policy check + activity reporting.
512
+ */
513
+ async wrapGenerate(fn) {
514
+ // Pre-flight check
515
+ try {
516
+ const check = await agent.checkAction(actionName);
517
+ if (!check.allowed) {
518
+ await agent.logActivity(`Blocked: ${actionName}`, {
519
+ type: 'policy_block',
520
+ description: check.summary,
521
+ });
522
+ throw new RovnError(`Action '${actionName}' blocked by policy: ${check.summary}`, 403, 'POLICY_BLOCKED');
523
+ }
524
+ }
525
+ catch (err) {
526
+ if (err instanceof RovnError)
527
+ throw err;
528
+ }
529
+ const start = Date.now();
530
+ try {
531
+ const result = await fn();
532
+ const duration = Date.now() - start;
533
+ await agent.logActivity(`AI Generate completed`, {
534
+ type: 'ai_generate',
535
+ description: `Generation took ${duration}ms`,
536
+ metadata: { duration_ms: duration },
537
+ });
538
+ return result;
539
+ }
540
+ catch (err) {
541
+ if (err instanceof RovnError)
542
+ throw err;
543
+ const duration = Date.now() - start;
544
+ await agent.logActivity(`AI Generate failed`, {
545
+ type: 'ai_error',
546
+ description: `Generation failed after ${duration}ms: ${err}`,
547
+ });
548
+ throw err;
549
+ }
550
+ },
551
+ /**
552
+ * Wraps a doStream call with policy check + activity reporting.
553
+ */
554
+ async wrapStream(fn) {
555
+ try {
556
+ const check = await agent.checkAction(actionName);
557
+ if (!check.allowed) {
558
+ await agent.logActivity(`Blocked: ${actionName} (stream)`, {
559
+ type: 'policy_block',
560
+ description: check.summary,
561
+ });
562
+ throw new RovnError(`Action '${actionName}' blocked by policy: ${check.summary}`, 403, 'POLICY_BLOCKED');
563
+ }
564
+ }
565
+ catch (err) {
566
+ if (err instanceof RovnError)
567
+ throw err;
568
+ }
569
+ try {
570
+ const result = await fn();
571
+ await agent.logActivity(`AI Stream started`, { type: 'ai_stream' });
572
+ return result;
573
+ }
574
+ catch (err) {
575
+ if (err instanceof RovnError)
576
+ throw err;
577
+ await agent.logActivity(`AI Stream failed`, {
578
+ type: 'ai_error',
579
+ description: `Stream failed: ${err}`,
580
+ });
581
+ throw err;
582
+ }
583
+ },
584
+ };
585
+ }
586
+ exports.default = RovnAgent;
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@rovn-ai/agent",
3
+ "version": "1.1.0",
4
+ "description": "TypeScript SDK for Rovn Agent OS",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch"
21
+ },
22
+ "keywords": ["ai-agent", "agent-os", "rovn"],
23
+ "license": "MIT",
24
+ "devDependencies": {
25
+ "typescript": "^5"
26
+ }
27
+ }