vessels-sdk 0.2.0 → 0.3.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 CHANGED
@@ -1,25 +1,25 @@
1
- # @vessels/sdk
1
+ # vessels-sdk
2
2
 
3
- Node.js SDK for [Vessels](https://vessels.app) — push messages from your agent to your phone and verify webhook callbacks.
3
+ Node.js SDK for [Vessels](https://vessels-two.vercel.app) — let your agent reach you.
4
+
5
+ Vessels is the communication layer between AI agents and their human operators. Your agent pushes structured messages to a vessel; the human responds via the web or mobile app; your agent receives the answer via polling or webhooks.
4
6
 
5
7
  ## Installation
6
8
 
7
9
  ```bash
8
- npm install @vessels/sdk
10
+ npm install vessels-sdk
9
11
  ```
10
12
 
11
- ## Usage
12
-
13
- ### Push a message with a card and approval interaction
13
+ ## Quick start
14
14
 
15
15
  ```typescript
16
- import { Vessels } from '@vessels/sdk';
16
+ import { Vessels } from 'vessels-sdk';
17
17
 
18
18
  const vessels = new Vessels({ apiKey: process.env.VESSELS_API_KEY! });
19
19
 
20
- const response = await vessels.push({
21
- thread: 'booking-123',
22
- threadTitle: 'Sarah Martinez',
20
+ const { messageId, vesselId } = await vessels.push({
21
+ vessel: 'booking-123',
22
+ vesselTitle: 'Sarah Martinez — Saturday Booking',
23
23
  message: 'New booking request received.',
24
24
  card: {
25
25
  title: 'Booking Details',
@@ -34,17 +34,299 @@ const response = await vessels.push({
34
34
  approveLabel: 'Confirm',
35
35
  rejectLabel: 'Decline',
36
36
  }),
37
+ vesselStatus: 'waiting',
37
38
  });
39
+ ```
40
+
41
+ Get your API key from Settings → API Keys in the [Vessels app](https://vessels-two.vercel.app), or via the CLI:
42
+
43
+ ```bash
44
+ npm install -g vessels
45
+ vessels login
46
+ vessels keys create
47
+ ```
48
+
49
+ ---
50
+
51
+ ## API reference
52
+
53
+ ### `new Vessels(config)`
54
+
55
+ | Option | Type | Default | Description |
56
+ |--------|------|---------|-------------|
57
+ | `apiKey` | `string` | required | Your `vsl_` prefixed API key |
58
+ | `baseUrl` | `string` | `https://vessels-two.vercel.app` | Override for local dev or self-hosted |
59
+
60
+ ---
61
+
62
+ ### `vessels.push(payload)`
63
+
64
+ Push a message to a vessel. Creates the vessel if it doesn't exist yet.
65
+
66
+ Returns `{ ok: true, messageId: string, vesselId: string, createdAt: string }`.
67
+
68
+ **Full payload:**
69
+
70
+ | Field | Type | Description |
71
+ |-------|------|-------------|
72
+ | `message` | `string` | **Required.** The message text. |
73
+ | `vessel` | `string` | Your external ID for this vessel (e.g. `booking-123`). Creates the vessel on first use. |
74
+ | `vesselTitle` | `string` | Human-readable name shown in the vessel list. Set on creation; updates on subsequent pushes. |
75
+ | `vesselStatus` | `'active' \| 'waiting' \| 'resolved'` | Status badge shown in the vessel list. `waiting` = amber, `resolved` = green. |
76
+ | `labels` | `string[]` | Tags for filtering in the dashboard. Max 10, 50 chars each. Replaces the existing set on every push — send all labels you want, not just new ones. |
77
+ | `metadata` | `object` | Arbitrary JSON stored on the vessel, passed through in webhook callbacks. |
78
+ | `card` | `Card` | Structured key-value info attached to this message. `{ title: string, fields: [{ label, value }] }` |
79
+ | `interaction` | `Interaction` | Interactive prompt for the human (one of 5 types — see helpers below). Max one per message; immutable after the human responds. |
80
+ | `pinCard` | `Card \| null` | Persistent card pinned to the vessel header. Always visible above the message stream. Replaces any existing pinned card. Pass `null` to clear. |
81
+ | `attachments` | `Attachment[]` | Images or files to show in the message. Max 10. You host the files; Vessels renders them. `[{ type: 'image' \| 'file', url: string, filename?: string }]` |
82
+ | `suggestions` | `string[]` | Quick-reply chips shown below the message. Max 5. Tapping fills the text input. Disappear after the user sends any message. |
83
+ | `previewUrl` | `string` | URL used by `confirm_preview` interactions. |
84
+ | `notify` | `boolean` | Whether to send a push notification. Default `true`. |
85
+
86
+ ```typescript
87
+ await vessels.push({
88
+ vessel: 'booking-123',
89
+ vesselTitle: 'Sarah Martinez',
90
+ message: 'Ready for review.',
91
+ vesselStatus: 'waiting',
92
+ labels: ['golf', 'vip'],
93
+ metadata: { bookingRef: 'BK-001' },
94
+ card: {
95
+ title: 'Booking',
96
+ fields: [{ label: 'Date', value: 'Saturday 14 June' }],
97
+ },
98
+ pinCard: {
99
+ title: 'Current Status',
100
+ fields: [{ label: 'Status', value: 'Awaiting confirmation' }],
101
+ },
102
+ attachments: [
103
+ { type: 'image', url: 'https://example.com/photo.jpg' },
104
+ { type: 'file', url: 'https://example.com/report.pdf', filename: 'Report.pdf' },
105
+ ],
106
+ suggestions: ['Looks good', 'Need changes'],
107
+ interaction: vessels.approval({ prompt: 'Approve?' }),
108
+ });
109
+ ```
110
+
111
+ ---
112
+
113
+ ### `vessels.pushMany(payload)`
114
+
115
+ Push the same message to multiple vessels at once. Max 100 vessels per call. Each vessel gets its own independent copy — interactions are responded to individually.
116
+
117
+ Returns `{ ok: true, results: Array<{ vessel, messageId, vesselId, error? }> }`.
118
+
119
+ ```typescript
120
+ const { results } = await vessels.pushMany({
121
+ vessels: ['booking-101', 'booking-102', 'booking-103'],
122
+ message: 'The Windmill Course is closed this Saturday due to maintenance.',
123
+ interaction: vessels.approval({
124
+ prompt: 'Send cancellation email to this client?',
125
+ approveLabel: 'Send',
126
+ rejectLabel: 'Skip',
127
+ }),
128
+ vesselStatus: 'waiting',
129
+ });
130
+
131
+ for (const r of results) {
132
+ if (r.error) console.error(`Failed for ${r.vessel}:`, r.error);
133
+ else console.log(`Pushed to ${r.vessel} — message ${r.messageId}`);
134
+ }
135
+ ```
136
+
137
+ `pushMany` accepts the same fields as `push`, except `vessel` and `vesselTitle` are replaced by `vessels` (an array of external IDs). Vessel titles are not settable via `pushMany` — use per-vessel `push` calls for that.
138
+
139
+ ---
140
+
141
+ ### `vessels.editMessage(messageId, patch)`
142
+
143
+ Edit an existing agent-sent message in place. The message re-renders via Supabase Realtime without reloading.
144
+
145
+ Updatable fields: `content`, `card`, `attachments`, `suggestions`. Interactions are immutable — create a new message to re-ask.
146
+
147
+ Only agent-sourced messages can be edited. Returns `{ ok: true }`.
148
+
149
+ ```typescript
150
+ const { messageId } = await vessels.push({
151
+ vessel: 'batch-job-1',
152
+ message: 'Processing bookings... 0 of 50 complete',
153
+ card: { title: 'Progress', fields: [{ label: 'Progress', value: '0 / 50' }] },
154
+ });
155
+
156
+ // Update as work proceeds:
157
+ await vessels.editMessage(messageId, {
158
+ content: 'Processing bookings... 24 of 50 complete',
159
+ card: { title: 'Progress', fields: [{ label: 'Progress', value: '24 / 50' }] },
160
+ });
161
+
162
+ // Final update:
163
+ await vessels.editMessage(messageId, {
164
+ content: 'All 50 bookings processed.',
165
+ card: {
166
+ title: 'Batch Complete',
167
+ fields: [{ label: 'Processed', value: '50' }, { label: 'Errors', value: '0' }],
168
+ },
169
+ });
170
+ ```
171
+
172
+ ---
173
+
174
+ ### Interaction helpers
175
+
176
+ All five helpers return a typed interaction object to pass as `payload.interaction`. There is a fixed set of five types — no custom types.
177
+
178
+ #### `vessels.approval(opts)`
179
+
180
+ A yes/no decision. Optionally require a reason on rejection.
181
+
182
+ ```typescript
183
+ vessels.approval({
184
+ prompt: 'Send the invoice?',
185
+ approveLabel: 'Send', // default: 'Approve'
186
+ rejectLabel: 'Cancel', // default: 'Reject'
187
+ reasonRequired: false, // if true, rejection requires a reason
188
+ metadata: { invoiceId: '42' }, // returned in poll event and webhook
189
+ })
190
+ ```
191
+
192
+ Response shape: `{ action: 'approved' | 'rejected', reason?: string }`
193
+
194
+ #### `vessels.choice(opts)`
195
+
196
+ Pick one option from a list. Optionally allow a free-text custom value.
197
+
198
+ ```typescript
199
+ vessels.choice({
200
+ prompt: 'Which time slot works?',
201
+ options: [
202
+ { id: '9am', label: '9:00 AM' },
203
+ { id: '2pm', label: '2:00 PM' },
204
+ { id: '5pm', label: '5:00 PM' },
205
+ ],
206
+ allowCustom: true,
207
+ customPlaceholder: 'Type a different time...',
208
+ })
209
+ ```
210
+
211
+ Response shape: `{ selected: string, customValue?: string | null }`
212
+
213
+ #### `vessels.checklist(opts)`
214
+
215
+ Pick one or more items from a list.
216
+
217
+ ```typescript
218
+ vessels.checklist({
219
+ prompt: 'Which documents are needed?',
220
+ options: [
221
+ { id: 'id', label: 'Photo ID', checked: true },
222
+ { id: 'proof', label: 'Proof of address' },
223
+ { id: 'contract', label: 'Signed contract' },
224
+ ],
225
+ minSelections: 1,
226
+ submitLabel: 'Confirm selection',
227
+ })
228
+ ```
229
+
230
+ Response shape: `{ selected: string[] }` — array of selected option IDs.
38
231
 
39
- console.log(response.messageId); // the created message ID
40
- console.log(response.threadId); // the upserted thread ID
232
+ #### `vessels.textInput(opts)`
233
+
234
+ Free-form text from the human.
235
+
236
+ ```typescript
237
+ vessels.textInput({
238
+ prompt: 'Why is the booking being cancelled?',
239
+ placeholder: 'Enter reason...',
240
+ multiline: true,
241
+ submitLabel: 'Submit',
242
+ })
41
243
  ```
42
244
 
43
- ### Verify a webhook in an Express handler
245
+ Response shape: `{ text: string }`
246
+
247
+ #### `vessels.confirmPreview(opts)`
248
+
249
+ Approve or reject after reviewing an external preview (draft email, document, image).
250
+
251
+ ```typescript
252
+ vessels.confirmPreview({
253
+ prompt: 'Review the draft email before sending.',
254
+ previewUrl: 'https://your-app.com/drafts/123',
255
+ previewLabel: 'View draft',
256
+ approveLabel: 'Send',
257
+ rejectLabel: 'Edit',
258
+ reasonRequiredOnReject: true,
259
+ })
260
+ ```
261
+
262
+ Response shape: `{ action: 'approved' | 'rejected', reason?: string }`
263
+
264
+ ---
265
+
266
+ ### `vessels.poll(options?)`
267
+
268
+ Fetch pending events. Uses a server-side cursor per API key — each call returns only new events since the last acknowledged poll.
269
+
270
+ Returns `{ ok: true, events: PollEvent[], hasMore: boolean }`.
271
+
272
+ | Option | Type | Default | Description |
273
+ |--------|------|---------|-------------|
274
+ | `ack` | `boolean` | `true` | Advance the cursor so these events aren't returned again |
275
+ | `limit` | `number` | `50` | Max events per call |
276
+ | `since` | `string` | — | ISO timestamp — overrides the cursor for this call only |
277
+
278
+ Poll events are normalised to camelCase by the SDK.
279
+
280
+ ```typescript
281
+ const { events, hasMore } = await vessels.poll({ ack: true });
282
+
283
+ for (const event of events) {
284
+ if (event.type === 'interaction.response') {
285
+ // event.interactionType — 'approval' | 'choice' | 'checklist' | 'text_input' | 'confirm_preview'
286
+ // event.response — response shape depends on interactionType (see above)
287
+ // event.interactionMetadata — metadata object you passed when creating the interaction, or null
288
+ // event.messageId — UUID of the message containing the interaction
289
+ // event.vessel.externalId — your original vessel string ('booking-123')
290
+ // event.vessel.id — vessel UUID
291
+ // event.vessel.labels — string[] of tags on this vessel
292
+ // event.user — { id, email } of the responding user, or null
293
+
294
+ if (event.interactionType === 'approval' && event.response.action === 'approved') {
295
+ await confirmBooking(event.vessel.externalId);
296
+ }
297
+ }
298
+
299
+ if (event.type === 'message.user') {
300
+ // event.message.content — what the human typed
301
+ // event.vessel.externalId — your original vessel string
302
+ const reply = await generateReply(event.message.content);
303
+ await vessels.push({ vessel: event.vessel.externalId, message: reply });
304
+ }
305
+ }
306
+
307
+ // For high volume, page through all pending events
308
+ if (hasMore) {
309
+ // Call poll() again immediately
310
+ }
311
+ ```
312
+
313
+ Polling is a complete alternative to webhooks. Use it to get started, or when you can't receive inbound HTTP.
314
+
315
+ ---
316
+
317
+ ### `vessels.verifyWebhook(body, signature, webhookSecret)`
318
+
319
+ Verify the `X-Vessels-Signature` header on incoming webhook requests. Uses constant-time HMAC comparison.
320
+
321
+ - `body` — raw request body as a string, **before** `JSON.parse`
322
+ - `signature` — the `X-Vessels-Signature` header value (format: `sha256=<hex>`)
323
+ - `webhookSecret` — the per-endpoint secret shown when you created the webhook in Settings → Webhooks. **Not the same as your API key.**
324
+
325
+ Returns `true` if the signature is valid, `false` otherwise.
44
326
 
45
327
  ```typescript
46
328
  import express from 'express';
47
- import { Vessels } from '@vessels/sdk';
329
+ import { Vessels } from 'vessels-sdk';
48
330
 
49
331
  const vessels = new Vessels({ apiKey: process.env.VESSELS_API_KEY! });
50
332
  const app = express();
@@ -52,56 +334,142 @@ const app = express();
52
334
  app.post(
53
335
  '/webhooks/vessels',
54
336
  express.raw({ type: 'application/json' }),
55
- (req, res) => {
56
- const signature = req.headers['x-vessels-signature'] as string;
337
+ async (req, res) => {
57
338
  const body = req.body.toString('utf8');
339
+ const signature = req.headers['x-vessels-signature'] as string;
58
340
 
59
- if (!vessels.verifyWebhook(body, signature)) {
60
- return res.status(401).json({ error: 'Invalid signature' });
61
- }
341
+ const valid = vessels.verifyWebhook(body, signature, process.env.VESSELS_WEBHOOK_SECRET!);
342
+ if (!valid) return res.status(401).json({ error: 'Invalid signature' });
62
343
 
63
- const event = JSON.parse(body);
344
+ const payload = JSON.parse(body);
345
+ // payload.event — 'interaction.response' | 'message.user'
346
+ // Webhook bodies use snake_case (vessel_id, external_id, interaction_type)
64
347
 
65
- if (event.type === 'interaction.response') {
66
- const { interactionType, response } = event.data;
67
- console.log(`User responded to ${interactionType}:`, response);
68
- // e.g. { action: 'approved' } for an approval card
348
+ if (payload.event === 'interaction.response') {
349
+ const { interaction_type, response, vessel, metadata } = payload.data;
350
+ // vessel.external_id your original vessel string
351
+ if (interaction_type === 'approval' && response.action === 'approved') {
352
+ await confirmBooking(vessel.external_id);
353
+ }
69
354
  }
70
355
 
71
- res.status(200).json({ received: true });
356
+ if (payload.event === 'message.user') {
357
+ const { content, vessel, context } = payload.data;
358
+ // context — last 10 messages in the vessel, oldest first (convenience, not canonical state)
359
+ const reply = await generateReply(content, context);
360
+ await vessels.push({ vessel: vessel.external_id, message: reply });
361
+ }
362
+
363
+ res.json({ ok: true });
72
364
  },
73
365
  );
366
+ ```
367
+
368
+ **Webhook payload shapes** (snake_case — raw HTTP/JSON layer):
369
+
370
+ ```json
371
+ // interaction.response
372
+ {
373
+ "event": "interaction.response",
374
+ "vessel_id": "uuid",
375
+ "workspace_id": "uuid",
376
+ "data": {
377
+ "message_id": "uuid",
378
+ "interaction_type": "approval",
379
+ "response": { "action": "approved" },
380
+ "response_id": "uuid",
381
+ "metadata": { "invoiceId": "42" },
382
+ "vessel": { "id": "uuid", "external_id": "booking-123", "title": "Sarah Martinez", "metadata": {} }
383
+ },
384
+ "timestamp": "2024-01-01T00:00:00.000Z"
385
+ }
74
386
 
75
- app.listen(3000);
387
+ // message.user
388
+ {
389
+ "event": "message.user",
390
+ "vessel_id": "uuid",
391
+ "workspace_id": "uuid",
392
+ "data": {
393
+ "message_id": "uuid",
394
+ "content": "I want to reschedule",
395
+ "vessel": { "id": "uuid", "external_id": "booking-123", "title": "Sarah Martinez", "metadata": {} },
396
+ "context": [{ "source": "agent", "content": "...", "created_at": "..." }]
397
+ },
398
+ "timestamp": "2024-01-01T00:00:00.000Z"
399
+ }
76
400
  ```
77
401
 
78
- ## API
402
+ Retries: 3× on failure with backoff (1s, 10s, 60s). All deliveries logged in Settings → Logs.
79
403
 
80
- ### `new Vessels(config)`
404
+ ---
81
405
 
82
- | Option | Type | Default | Description |
83
- |--------|------|---------|-------------|
84
- | `apiKey` | `string` | required | Your `vsl_` prefixed API key |
85
- | `baseUrl` | `string` | `https://vessels.app` | Override for self-hosted or local dev |
406
+ ## Error handling
86
407
 
87
- ### `vessels.push(payload)`
408
+ Three typed error classes are exported alongside `Vessels`:
88
409
 
89
- Push a message to a thread. Returns `{ ok, messageId, threadId, createdAt }`.
410
+ | Class | HTTP status | Description |
411
+ |-------|-------------|-------------|
412
+ | `VesselsAuthError` | 401 | Invalid or revoked API key |
413
+ | `VesselsValidationError` | 400 | Bad request payload. Check `.details` for field-level errors. |
414
+ | `VesselsRateLimitError` | 429 | Rate limit exceeded. Check `.retryAfter` (seconds) before retrying. |
90
415
 
91
- Throws `VesselsAuthError` (401), `VesselsRateLimitError` (429), or `VesselsValidationError` (400) on failure.
416
+ ```typescript
417
+ import { Vessels, VesselsAuthError, VesselsRateLimitError, VesselsValidationError } from 'vessels-sdk';
92
418
 
93
- ### Interaction helpers
419
+ try {
420
+ await vessels.push({ vessel: 'booking-123', message: 'hello' });
421
+ } catch (err) {
422
+ if (err instanceof VesselsAuthError) {
423
+ console.error('Check your API key — it may be revoked');
424
+ } else if (err instanceof VesselsRateLimitError) {
425
+ console.error(`Rate limited — retry after ${err.retryAfter}s`);
426
+ } else if (err instanceof VesselsValidationError) {
427
+ console.error('Bad payload:', err.details);
428
+ } else {
429
+ throw err;
430
+ }
431
+ }
432
+ ```
433
+
434
+ ---
435
+
436
+ ## Naming conventions
437
+
438
+ The SDK and push payload use **camelCase** (JavaScript convention): `vesselTitle`, `vesselStatus`, `pinCard`, `vesselId`, `messageId`.
94
439
 
95
- All helpers return a typed interaction object to pass as `payload.interaction`.
440
+ Webhook POST bodies use **snake_case** (raw HTTP/JSON layer): `vessel_id`, `external_id`, `interaction_type`.
441
+
442
+ Poll events are normalised to **camelCase** by the SDK: `event.vessel.externalId`, `event.interactionType`, `event.messageId`.
443
+
444
+ Rule of thumb: anything you **write** (push calls, SDK methods) is camelCase. Anything you **read** from a raw webhook POST body is snake_case.
445
+
446
+ ---
447
+
448
+ ## TypeScript types
449
+
450
+ All types used by the SDK are exported directly from `vessels-sdk`:
451
+
452
+ ```typescript
453
+ import type {
454
+ PushPayload,
455
+ PushManyPayload,
456
+ MessagePatch,
457
+ Attachment,
458
+ Interaction,
459
+ Card,
460
+ ApprovalInteraction,
461
+ ChoiceInteraction,
462
+ ChecklistInteraction,
463
+ TextInputInteraction,
464
+ ConfirmPreviewInteraction,
465
+ } from 'vessels-sdk';
466
+ ```
96
467
 
97
- | Method | Card type |
98
- |--------|-----------|
99
- | `vessels.approval(opts)` | Yes/no decision |
100
- | `vessels.choice(opts)` | Pick one option |
101
- | `vessels.checklist(opts)` | Pick multiple options |
102
- | `vessels.textInput(opts)` | Free-form text |
103
- | `vessels.confirmPreview(opts)` | Approve with external preview link |
468
+ ---
104
469
 
105
- ### `vessels.verifyWebhook(body, signature)`
470
+ ## Links
106
471
 
107
- Verifies the `X-Vessels-Signature` header using HMAC-SHA256. Pass the **raw** request body as a string. Returns `true` if valid.
472
+ - Dashboard: [https://vessels-two.vercel.app](https://vessels-two.vercel.app)
473
+ - Full integration reference: [https://vessels-two.vercel.app/docs](https://vessels-two.vercel.app/docs)
474
+ - CLI: `npm install -g vessels`
475
+ - npm: [https://www.npmjs.com/package/vessels-sdk](https://www.npmjs.com/package/vessels-sdk)
package/dist/index.cjs CHANGED
@@ -3,6 +3,13 @@
3
3
  var crypto = require('crypto');
4
4
 
5
5
  // src/index.ts
6
+ var AgentActivityTypes = {
7
+ thinking: "thinking",
8
+ searching: "searching",
9
+ toolUse: "tool_use",
10
+ browsing: "browsing",
11
+ processing: "processing"
12
+ };
6
13
  var VesselsAuthError = class extends Error {
7
14
  constructor(message) {
8
15
  super(message);
@@ -28,12 +35,37 @@ var VesselsRateLimitError = class extends Error {
28
35
  var Vessels = class {
29
36
  apiKey;
30
37
  baseUrl;
38
+ _debug;
31
39
  constructor(config) {
32
40
  this.apiKey = config.apiKey;
33
41
  this.baseUrl = config.baseUrl?.replace(/\/$/, "") ?? "https://vessels-two.vercel.app";
42
+ this._debug = config.debug ?? false;
43
+ }
44
+ async _fetch(url, init) {
45
+ if (this._debug) {
46
+ const method = (init.method ?? "GET").toUpperCase();
47
+ console.log(`[Vessels] \u2192 ${method} ${url}`);
48
+ if (init.body) {
49
+ try {
50
+ console.log("[Vessels] body:", JSON.parse(init.body));
51
+ } catch {
52
+ }
53
+ }
54
+ }
55
+ const res = await fetch(url, init);
56
+ if (this._debug) {
57
+ const clone = res.clone();
58
+ try {
59
+ const body = await clone.json();
60
+ console.log(`[Vessels] \u2190 ${res.status}`, body);
61
+ } catch {
62
+ console.log(`[Vessels] \u2190 ${res.status}`);
63
+ }
64
+ }
65
+ return res;
34
66
  }
35
67
  async push(payload) {
36
- const res = await fetch(`${this.baseUrl}/api/v1/push`, {
68
+ const res = await this._fetch(`${this.baseUrl}/api/v1/push`, {
37
69
  method: "POST",
38
70
  headers: {
39
71
  "Content-Type": "application/json",
@@ -54,7 +86,7 @@ var Vessels = class {
54
86
  };
55
87
  }
56
88
  async pushMany(payload) {
57
- const res = await fetch(`${this.baseUrl}/api/v1/push/many`, {
89
+ const res = await this._fetch(`${this.baseUrl}/api/v1/push/many`, {
58
90
  method: "POST",
59
91
  headers: {
60
92
  "Content-Type": "application/json",
@@ -78,7 +110,7 @@ var Vessels = class {
78
110
  };
79
111
  }
80
112
  async editMessage(messageId, patch) {
81
- const res = await fetch(`${this.baseUrl}/api/v1/messages/${messageId}`, {
113
+ const res = await this._fetch(`${this.baseUrl}/api/v1/messages/${messageId}`, {
82
114
  method: "PATCH",
83
115
  headers: {
84
116
  "Content-Type": "application/json",
@@ -116,7 +148,7 @@ var Vessels = class {
116
148
  if (since) params.set("since", since);
117
149
  params.set("limit", String(limit));
118
150
  params.set("ack", String(ack));
119
- const res = await fetch(`${this.baseUrl}/api/v1/poll?${params}`, {
151
+ const res = await this._fetch(`${this.baseUrl}/api/v1/poll?${params}`, {
120
152
  headers: { "Authorization": `Bearer ${this.apiKey}` }
121
153
  });
122
154
  const data = await res.json();
@@ -170,6 +202,7 @@ var Vessels = class {
170
202
  }
171
203
  };
172
204
 
205
+ exports.AgentActivityTypes = AgentActivityTypes;
173
206
  exports.Vessels = Vessels;
174
207
  exports.VesselsAuthError = VesselsAuthError;
175
208
  exports.VesselsRateLimitError = VesselsRateLimitError;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,13 @@
1
1
  import * as _vessels_types from '@vessels/types';
2
- export { ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyPayload, PushPayload, TextInputInteraction } from '@vessels/types';
2
+ export { AgentActivity, AgentActivityType, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyPayload, PushPayload, TextInputInteraction } from '@vessels/types';
3
3
 
4
+ declare const AgentActivityTypes: {
5
+ readonly thinking: "thinking";
6
+ readonly searching: "searching";
7
+ readonly toolUse: "tool_use";
8
+ readonly browsing: "browsing";
9
+ readonly processing: "processing";
10
+ };
4
11
  declare class VesselsAuthError extends Error {
5
12
  constructor(message: string);
6
13
  }
@@ -15,6 +22,7 @@ declare class VesselsRateLimitError extends Error {
15
22
  interface VesselsConfig {
16
23
  apiKey: string;
17
24
  baseUrl?: string;
25
+ debug?: boolean;
18
26
  }
19
27
  interface PushResponse {
20
28
  ok: true;
@@ -76,7 +84,9 @@ interface PollResponse {
76
84
  declare class Vessels {
77
85
  private apiKey;
78
86
  private baseUrl;
87
+ private _debug;
79
88
  constructor(config: VesselsConfig);
89
+ private _fetch;
80
90
  push(payload: _vessels_types.PushPayload): Promise<PushResponse>;
81
91
  pushMany(payload: _vessels_types.PushManyPayload): Promise<PushManyResult>;
82
92
  editMessage(messageId: string, patch: _vessels_types.MessagePatch): Promise<{
@@ -130,4 +140,4 @@ declare class Vessels {
130
140
  verifyWebhook(body: string, signature: string, webhookSecret: string): boolean;
131
141
  }
132
142
 
133
- export { type InteractionResponseEvent, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type VesselContext, Vessels, VesselsAuthError, type VesselsConfig, VesselsRateLimitError, VesselsValidationError };
143
+ export { AgentActivityTypes, type InteractionResponseEvent, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type VesselContext, Vessels, VesselsAuthError, type VesselsConfig, VesselsRateLimitError, VesselsValidationError };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,13 @@
1
1
  import * as _vessels_types from '@vessels/types';
2
- export { ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyPayload, PushPayload, TextInputInteraction } from '@vessels/types';
2
+ export { AgentActivity, AgentActivityType, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyPayload, PushPayload, TextInputInteraction } from '@vessels/types';
3
3
 
4
+ declare const AgentActivityTypes: {
5
+ readonly thinking: "thinking";
6
+ readonly searching: "searching";
7
+ readonly toolUse: "tool_use";
8
+ readonly browsing: "browsing";
9
+ readonly processing: "processing";
10
+ };
4
11
  declare class VesselsAuthError extends Error {
5
12
  constructor(message: string);
6
13
  }
@@ -15,6 +22,7 @@ declare class VesselsRateLimitError extends Error {
15
22
  interface VesselsConfig {
16
23
  apiKey: string;
17
24
  baseUrl?: string;
25
+ debug?: boolean;
18
26
  }
19
27
  interface PushResponse {
20
28
  ok: true;
@@ -76,7 +84,9 @@ interface PollResponse {
76
84
  declare class Vessels {
77
85
  private apiKey;
78
86
  private baseUrl;
87
+ private _debug;
79
88
  constructor(config: VesselsConfig);
89
+ private _fetch;
80
90
  push(payload: _vessels_types.PushPayload): Promise<PushResponse>;
81
91
  pushMany(payload: _vessels_types.PushManyPayload): Promise<PushManyResult>;
82
92
  editMessage(messageId: string, patch: _vessels_types.MessagePatch): Promise<{
@@ -130,4 +140,4 @@ declare class Vessels {
130
140
  verifyWebhook(body: string, signature: string, webhookSecret: string): boolean;
131
141
  }
132
142
 
133
- export { type InteractionResponseEvent, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type VesselContext, Vessels, VesselsAuthError, type VesselsConfig, VesselsRateLimitError, VesselsValidationError };
143
+ export { AgentActivityTypes, type InteractionResponseEvent, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type VesselContext, Vessels, VesselsAuthError, type VesselsConfig, VesselsRateLimitError, VesselsValidationError };
package/dist/index.js CHANGED
@@ -1,6 +1,13 @@
1
1
  import { createHmac } from 'crypto';
2
2
 
3
3
  // src/index.ts
4
+ var AgentActivityTypes = {
5
+ thinking: "thinking",
6
+ searching: "searching",
7
+ toolUse: "tool_use",
8
+ browsing: "browsing",
9
+ processing: "processing"
10
+ };
4
11
  var VesselsAuthError = class extends Error {
5
12
  constructor(message) {
6
13
  super(message);
@@ -26,12 +33,37 @@ var VesselsRateLimitError = class extends Error {
26
33
  var Vessels = class {
27
34
  apiKey;
28
35
  baseUrl;
36
+ _debug;
29
37
  constructor(config) {
30
38
  this.apiKey = config.apiKey;
31
39
  this.baseUrl = config.baseUrl?.replace(/\/$/, "") ?? "https://vessels-two.vercel.app";
40
+ this._debug = config.debug ?? false;
41
+ }
42
+ async _fetch(url, init) {
43
+ if (this._debug) {
44
+ const method = (init.method ?? "GET").toUpperCase();
45
+ console.log(`[Vessels] \u2192 ${method} ${url}`);
46
+ if (init.body) {
47
+ try {
48
+ console.log("[Vessels] body:", JSON.parse(init.body));
49
+ } catch {
50
+ }
51
+ }
52
+ }
53
+ const res = await fetch(url, init);
54
+ if (this._debug) {
55
+ const clone = res.clone();
56
+ try {
57
+ const body = await clone.json();
58
+ console.log(`[Vessels] \u2190 ${res.status}`, body);
59
+ } catch {
60
+ console.log(`[Vessels] \u2190 ${res.status}`);
61
+ }
62
+ }
63
+ return res;
32
64
  }
33
65
  async push(payload) {
34
- const res = await fetch(`${this.baseUrl}/api/v1/push`, {
66
+ const res = await this._fetch(`${this.baseUrl}/api/v1/push`, {
35
67
  method: "POST",
36
68
  headers: {
37
69
  "Content-Type": "application/json",
@@ -52,7 +84,7 @@ var Vessels = class {
52
84
  };
53
85
  }
54
86
  async pushMany(payload) {
55
- const res = await fetch(`${this.baseUrl}/api/v1/push/many`, {
87
+ const res = await this._fetch(`${this.baseUrl}/api/v1/push/many`, {
56
88
  method: "POST",
57
89
  headers: {
58
90
  "Content-Type": "application/json",
@@ -76,7 +108,7 @@ var Vessels = class {
76
108
  };
77
109
  }
78
110
  async editMessage(messageId, patch) {
79
- const res = await fetch(`${this.baseUrl}/api/v1/messages/${messageId}`, {
111
+ const res = await this._fetch(`${this.baseUrl}/api/v1/messages/${messageId}`, {
80
112
  method: "PATCH",
81
113
  headers: {
82
114
  "Content-Type": "application/json",
@@ -114,7 +146,7 @@ var Vessels = class {
114
146
  if (since) params.set("since", since);
115
147
  params.set("limit", String(limit));
116
148
  params.set("ack", String(ack));
117
- const res = await fetch(`${this.baseUrl}/api/v1/poll?${params}`, {
149
+ const res = await this._fetch(`${this.baseUrl}/api/v1/poll?${params}`, {
118
150
  headers: { "Authorization": `Bearer ${this.apiKey}` }
119
151
  });
120
152
  const data = await res.json();
@@ -168,4 +200,4 @@ var Vessels = class {
168
200
  }
169
201
  };
170
202
 
171
- export { Vessels, VesselsAuthError, VesselsRateLimitError, VesselsValidationError };
203
+ export { AgentActivityTypes, Vessels, VesselsAuthError, VesselsRateLimitError, VesselsValidationError };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vessels-sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Let your agent reach you. Official Vessels SDK.",
5
5
  "type": "module",
6
6
  "exports": {