vessels-sdk 0.8.0 → 0.11.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 +24 -1
- package/dist/index.cjs +108 -5
- package/dist/index.d.cts +75 -3
- package/dist/index.d.ts +75 -3
- package/dist/index.js +108 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,6 +144,29 @@ for (const r of results) {
|
|
|
144
144
|
|
|
145
145
|
---
|
|
146
146
|
|
|
147
|
+
### `vessels.validatePush(payload)` / `vessels.validatePushMany(payload)`
|
|
148
|
+
|
|
149
|
+
Check that a payload complies with the required syntax **without sending anything**. Useful for confirming an agent's message is well-formed in a test or dry run before it reaches a human. This runs the **same schema the server enforces**, so a payload that passes here is exactly one the API will accept.
|
|
150
|
+
|
|
151
|
+
Returns `{ valid: boolean, errors: string[], details? }` — `errors` is a list of readable `field.path: message` lines; `details` is Zod's `flatten()` output (`{ formErrors, fieldErrors }`).
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const check = vessels.validatePush({
|
|
155
|
+
vessel: 'booking-123',
|
|
156
|
+
message: 'New booking',
|
|
157
|
+
interaction: { type: 'approval', prompt: 'Confirm?' },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (!check.valid) {
|
|
161
|
+
console.error(check.errors);
|
|
162
|
+
// e.g. ['suggestions: Array must contain at most 5 element(s)']
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
You don't have to call this explicitly: `push()` and `pushMany()` run the same check internally and throw a `VesselsValidationError` **before** the network request, so a malformed payload fails fast without burning an API call. Call `validatePush` directly when you want a result object instead of an exception.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
147
170
|
### `vessels.editMessage(messageId, patch)`
|
|
148
171
|
|
|
149
172
|
Edit an existing agent-sent message in place. The message re-renders via Supabase Realtime without reloading.
|
|
@@ -416,7 +439,7 @@ Three typed error classes are exported alongside `Vessels`:
|
|
|
416
439
|
| Class | HTTP status | Description |
|
|
417
440
|
|-------|-------------|-------------|
|
|
418
441
|
| `VesselsAuthError` | 401 | Invalid or revoked API key |
|
|
419
|
-
| `VesselsValidationError` | 400 | Bad request payload. Check `.details` for field-level errors. |
|
|
442
|
+
| `VesselsValidationError` | 400 | Bad request payload. Also thrown **locally, before the request** by `push()`/`pushMany()` when the payload fails client-side validation. Check `.details` for field-level errors. |
|
|
420
443
|
| `VesselsRateLimitError` | 429 | Rate limit exceeded. Check `.retryAfter` (seconds) before retrying. |
|
|
421
444
|
|
|
422
445
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -70,9 +70,17 @@ var InteractionSchema = zod.z.discriminatedUnion("type", [
|
|
|
70
70
|
ConfirmPreviewInteractionSchema
|
|
71
71
|
]);
|
|
72
72
|
var AgentActivityTypeSchema = zod.z.enum(["thinking", "searching", "tool_use", "browsing", "processing"]);
|
|
73
|
+
var AgentTodoStatusSchema = zod.z.enum(["pending", "in_progress", "done"]);
|
|
74
|
+
var AgentTodoInputSchema = zod.z.object({
|
|
75
|
+
label: zod.z.string().min(1).max(200),
|
|
76
|
+
status: AgentTodoStatusSchema.optional()
|
|
77
|
+
});
|
|
73
78
|
var AgentActivitySchema = zod.z.object({
|
|
74
|
-
type: AgentActivityTypeSchema,
|
|
75
|
-
label: zod.z.string().max(200).optional()
|
|
79
|
+
type: AgentActivityTypeSchema.optional(),
|
|
80
|
+
label: zod.z.string().max(200).optional(),
|
|
81
|
+
todos: zod.z.array(AgentTodoInputSchema).max(50).optional()
|
|
82
|
+
}).refine((d) => d.type != null || d.todos != null, {
|
|
83
|
+
message: "agentActivity requires `type` (a step) or `todos` (a plan)"
|
|
76
84
|
});
|
|
77
85
|
var CardFieldSchema = zod.z.object({
|
|
78
86
|
label: zod.z.string().min(1),
|
|
@@ -87,7 +95,7 @@ var AttachmentSchema = zod.z.discriminatedUnion("type", [
|
|
|
87
95
|
zod.z.object({ type: zod.z.literal("file"), url: zod.z.string().url(), filename: zod.z.string().optional() })
|
|
88
96
|
]);
|
|
89
97
|
var VesselStatusSchema = zod.z.enum(["active", "waiting", "resolved"]);
|
|
90
|
-
zod.z.object({
|
|
98
|
+
var PushPayloadSchema = zod.z.object({
|
|
91
99
|
message: zod.z.string().min(1).max(1e4).optional(),
|
|
92
100
|
vessel: zod.z.string().optional(),
|
|
93
101
|
vesselTitle: zod.z.string().optional(),
|
|
@@ -107,7 +115,7 @@ zod.z.object({
|
|
|
107
115
|
}).refine((d) => d.message || d.agentActivity, {
|
|
108
116
|
message: "Either message or agentActivity is required"
|
|
109
117
|
});
|
|
110
|
-
zod.z.object({
|
|
118
|
+
var PushManyPayloadSchema = zod.z.object({
|
|
111
119
|
vessels: zod.z.array(zod.z.string().min(1)).min(1).max(100),
|
|
112
120
|
message: zod.z.string().min(1).max(1e4),
|
|
113
121
|
vesselTitle: zod.z.string().optional(),
|
|
@@ -218,10 +226,21 @@ var WebhookVesselCreatedPayloadSchema = zod.z.object({
|
|
|
218
226
|
})
|
|
219
227
|
})
|
|
220
228
|
});
|
|
229
|
+
var WebhookMessageCancelledPayloadSchema = zod.z.object({
|
|
230
|
+
event: zod.z.literal("message.cancelled"),
|
|
231
|
+
vessel_id: zod.z.string(),
|
|
232
|
+
workspace_id: zod.z.string(),
|
|
233
|
+
timestamp: zod.z.string(),
|
|
234
|
+
data: zod.z.object({
|
|
235
|
+
message_id: zod.z.string(),
|
|
236
|
+
vessel: WebhookVesselSchema
|
|
237
|
+
})
|
|
238
|
+
});
|
|
221
239
|
var WebhookPayloadSchema = zod.z.discriminatedUnion("event", [
|
|
222
240
|
WebhookInteractionResponsePayloadSchema,
|
|
223
241
|
WebhookUserMessagePayloadSchema,
|
|
224
|
-
WebhookVesselCreatedPayloadSchema
|
|
242
|
+
WebhookVesselCreatedPayloadSchema,
|
|
243
|
+
WebhookMessageCancelledPayloadSchema
|
|
225
244
|
]);
|
|
226
245
|
|
|
227
246
|
// src/index.ts
|
|
@@ -260,6 +279,13 @@ var VesselsConflictError = class extends Error {
|
|
|
260
279
|
this.name = "VesselsConflictError";
|
|
261
280
|
}
|
|
262
281
|
};
|
|
282
|
+
function formatZodError(error) {
|
|
283
|
+
const errors = error.issues.map((i) => {
|
|
284
|
+
const path = i.path.join(".") || "(payload)";
|
|
285
|
+
return `${path}: ${i.message}`;
|
|
286
|
+
});
|
|
287
|
+
return { errors, details: error.flatten() };
|
|
288
|
+
}
|
|
263
289
|
var Vessels = class {
|
|
264
290
|
apiKey;
|
|
265
291
|
baseUrl;
|
|
@@ -292,8 +318,33 @@ var Vessels = class {
|
|
|
292
318
|
}
|
|
293
319
|
return res;
|
|
294
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* Check a push payload against the schema the server enforces, WITHOUT sending
|
|
323
|
+
* anything. Use this to confirm an agent's message complies before it goes
|
|
324
|
+
* live (e.g. in a test or a dry-run). `push()` runs the same check internally.
|
|
325
|
+
*/
|
|
326
|
+
validatePush(payload) {
|
|
327
|
+
const { idempotencyKey: _ignored, ...body } = payload;
|
|
328
|
+
const result = PushPayloadSchema.safeParse(body);
|
|
329
|
+
if (result.success) return { valid: true, errors: [] };
|
|
330
|
+
const { errors, details } = formatZodError(result.error);
|
|
331
|
+
return { valid: false, errors, details };
|
|
332
|
+
}
|
|
333
|
+
/** Like {@link validatePush}, but for a `pushMany()` broadcast payload. */
|
|
334
|
+
validatePushMany(payload) {
|
|
335
|
+
const { idempotencyKey: _ignored, ...body } = payload;
|
|
336
|
+
const result = PushManyPayloadSchema.safeParse(body);
|
|
337
|
+
if (result.success) return { valid: true, errors: [] };
|
|
338
|
+
const { errors, details } = formatZodError(result.error);
|
|
339
|
+
return { valid: false, errors, details };
|
|
340
|
+
}
|
|
295
341
|
async push(payload) {
|
|
296
342
|
const { idempotencyKey, ...body } = payload;
|
|
343
|
+
const check = PushPayloadSchema.safeParse(body);
|
|
344
|
+
if (!check.success) {
|
|
345
|
+
const { errors, details } = formatZodError(check.error);
|
|
346
|
+
throw new VesselsValidationError(`Invalid push payload \u2014 ${errors.join("; ")}`, details);
|
|
347
|
+
}
|
|
297
348
|
const res = await this._fetch(`${this.baseUrl}/api/v1/push`, {
|
|
298
349
|
method: "POST",
|
|
299
350
|
headers: {
|
|
@@ -319,6 +370,11 @@ var Vessels = class {
|
|
|
319
370
|
}
|
|
320
371
|
async pushMany(payload) {
|
|
321
372
|
const { idempotencyKey, ...body } = payload;
|
|
373
|
+
const check = PushManyPayloadSchema.safeParse(body);
|
|
374
|
+
if (!check.success) {
|
|
375
|
+
const { errors, details } = formatZodError(check.error);
|
|
376
|
+
throw new VesselsValidationError(`Invalid pushMany payload \u2014 ${errors.join("; ")}`, details);
|
|
377
|
+
}
|
|
322
378
|
const res = await this._fetch(`${this.baseUrl}/api/v1/push/many`, {
|
|
323
379
|
method: "POST",
|
|
324
380
|
headers: {
|
|
@@ -362,6 +418,44 @@ var Vessels = class {
|
|
|
362
418
|
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
363
419
|
return { ok: true };
|
|
364
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Narrate a working message: declare a plan, file steps under each task as you
|
|
423
|
+
* go, and seal it when done. Sugar over {@link editMessage} — it tracks the
|
|
424
|
+
* todo list locally and PATCHes the full list each update (the server is
|
|
425
|
+
* authoritative and reconciles by label). The message must already exist; get
|
|
426
|
+
* its id from {@link push}.
|
|
427
|
+
*/
|
|
428
|
+
activity(messageId) {
|
|
429
|
+
let todos = [];
|
|
430
|
+
const sendTodos = () => this.editMessage(messageId, { agentActivity: { todos } });
|
|
431
|
+
return {
|
|
432
|
+
plan: async (tasks) => {
|
|
433
|
+
todos = tasks.map(
|
|
434
|
+
(t) => typeof t === "string" ? { label: t, status: "pending" } : { label: t.label, status: t.status ?? "pending" }
|
|
435
|
+
);
|
|
436
|
+
await sendTodos();
|
|
437
|
+
},
|
|
438
|
+
start: async (label) => {
|
|
439
|
+
todos = todos.map(
|
|
440
|
+
(t) => t.label === label ? { ...t, status: "in_progress" } : t.status === "in_progress" ? { ...t, status: "done" } : t
|
|
441
|
+
);
|
|
442
|
+
if (!todos.some((t) => t.label === label)) todos.push({ label, status: "in_progress" });
|
|
443
|
+
await sendTodos();
|
|
444
|
+
},
|
|
445
|
+
step: async (type, label) => {
|
|
446
|
+
await this.editMessage(messageId, { agentActivity: { type, label } });
|
|
447
|
+
},
|
|
448
|
+
complete: async (label) => {
|
|
449
|
+
todos = todos.map(
|
|
450
|
+
(t) => (label ? t.label === label : t.status === "in_progress") ? { ...t, status: "done" } : t
|
|
451
|
+
);
|
|
452
|
+
await sendTodos();
|
|
453
|
+
},
|
|
454
|
+
done: async () => {
|
|
455
|
+
await this.editMessage(messageId, { agentActivity: null });
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
365
459
|
/**
|
|
366
460
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
367
461
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -547,6 +641,15 @@ var Vessels = class {
|
|
|
547
641
|
user: raw.data.user ?? null
|
|
548
642
|
};
|
|
549
643
|
}
|
|
644
|
+
if (raw.event === "message.cancelled") {
|
|
645
|
+
return {
|
|
646
|
+
id: raw.data.message_id,
|
|
647
|
+
type: "message.cancelled",
|
|
648
|
+
timestamp: raw.timestamp,
|
|
649
|
+
vessel,
|
|
650
|
+
messageId: raw.data.message_id
|
|
651
|
+
};
|
|
652
|
+
}
|
|
550
653
|
if (raw.event === "message.user") {
|
|
551
654
|
return {
|
|
552
655
|
id: raw.data.message_id,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _vessels_types from '@vessels/types';
|
|
2
|
-
export { AgentActivity, AgentActivityType, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyOptions, PushManyPayload, PushOptions, PushPayload, TextInputInteraction, WebhookInteractionResponsePayload, WebhookInteractionResponsePayloadSchema, WebhookPayload, WebhookPayloadSchema, WebhookUserMessagePayload, WebhookUserMessagePayloadSchema, WebhookVesselCreatedPayload, WebhookVesselCreatedPayloadSchema } from '@vessels/types';
|
|
2
|
+
export { AgentActivity, AgentActivityType, AgentTodoInput, AgentTodoStatus, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyOptions, PushManyPayload, PushOptions, PushPayload, TextInputInteraction, WebhookInteractionResponsePayload, WebhookInteractionResponsePayloadSchema, WebhookPayload, WebhookPayloadSchema, WebhookUserMessagePayload, WebhookUserMessagePayloadSchema, WebhookVesselCreatedPayload, WebhookVesselCreatedPayloadSchema } from '@vessels/types';
|
|
3
3
|
|
|
4
4
|
declare const AgentActivityTypes: {
|
|
5
5
|
readonly thinking: "thinking";
|
|
@@ -23,11 +23,53 @@ declare class VesselsRateLimitError extends Error {
|
|
|
23
23
|
declare class VesselsConflictError extends Error {
|
|
24
24
|
constructor(message: string);
|
|
25
25
|
}
|
|
26
|
+
/** Result of a client-side payload check via {@link Vessels.validatePush}. */
|
|
27
|
+
interface ValidationResult {
|
|
28
|
+
/** True when the payload satisfies the schema the server enforces. */
|
|
29
|
+
valid: boolean;
|
|
30
|
+
/** Human-readable `field.path: message` lines — empty when valid. */
|
|
31
|
+
errors: string[];
|
|
32
|
+
/** Zod `flatten()` output (field + form errors) — present only when invalid. */
|
|
33
|
+
details?: {
|
|
34
|
+
formErrors: string[];
|
|
35
|
+
fieldErrors: Record<string, string[]>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
26
38
|
interface VesselsConfig {
|
|
27
39
|
apiKey: string;
|
|
28
40
|
baseUrl?: string;
|
|
29
41
|
debug?: boolean;
|
|
30
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A stateful handle for narrating a working message — the plan and the work it
|
|
45
|
+
* produces resolve into one artifact. Steps emitted while a task is active are
|
|
46
|
+
* filed under it server-side. Obtain one with {@link Vessels.activity}.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* const act = vessels.activity(messageId);
|
|
50
|
+
* await act.plan(['Check availability', 'Draft email', 'Send to customer']);
|
|
51
|
+
* await act.start('Check availability'); // → in_progress, step target
|
|
52
|
+
* await act.step('searching', 'Querying calendar');
|
|
53
|
+
* await act.start('Draft email'); // auto-finishes the prior task
|
|
54
|
+
* await act.step('tool_use', 'Sending via SendGrid');
|
|
55
|
+
* await act.done(); // seals everything
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
interface ActivityHandle {
|
|
59
|
+
/** Declare (or replace) the plan. Tasks default to `pending`. */
|
|
60
|
+
plan(tasks: Array<string | {
|
|
61
|
+
label: string;
|
|
62
|
+
status?: _vessels_types.AgentTodoStatus;
|
|
63
|
+
}>): Promise<void>;
|
|
64
|
+
/** Mark a task in-progress (creating it if new); any other in-progress task is finished. */
|
|
65
|
+
start(label: string): Promise<void>;
|
|
66
|
+
/** Append a step, filed under the active task. */
|
|
67
|
+
step(type: _vessels_types.AgentActivityType, label?: string): Promise<void>;
|
|
68
|
+
/** Finish a task by label, or the current in-progress task if omitted. */
|
|
69
|
+
complete(label?: string): Promise<void>;
|
|
70
|
+
/** Seal the activity — finishes the open step and any in-progress task. */
|
|
71
|
+
done(): Promise<void>;
|
|
72
|
+
}
|
|
31
73
|
interface PushResponse {
|
|
32
74
|
ok: true;
|
|
33
75
|
messageId: string;
|
|
@@ -116,6 +158,20 @@ interface VesselCreatedEvent {
|
|
|
116
158
|
content: string | null;
|
|
117
159
|
};
|
|
118
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Fired when a human taps "Stop" on an in-progress (working) agent message in
|
|
163
|
+
* the app. Vessels does NOT force-stop anything — you decide what to do (abort
|
|
164
|
+
* a task, stop polling, ignore it). Delivery is synchronous: the app tells the
|
|
165
|
+
* human whether your handler accepted it (return 2xx) so they know cancel is
|
|
166
|
+
* wired up. `messageId` is the working agent message the human wants stopped.
|
|
167
|
+
*/
|
|
168
|
+
interface MessageCancelledEvent {
|
|
169
|
+
id: string;
|
|
170
|
+
type: 'message.cancelled';
|
|
171
|
+
timestamp: string;
|
|
172
|
+
vessel: VesselContext;
|
|
173
|
+
messageId: string;
|
|
174
|
+
}
|
|
119
175
|
/** A message in a vessel, as returned by getMessages — the human-facing record. */
|
|
120
176
|
interface Message {
|
|
121
177
|
id: string;
|
|
@@ -140,11 +196,27 @@ declare class Vessels {
|
|
|
140
196
|
private _debug;
|
|
141
197
|
constructor(config: VesselsConfig);
|
|
142
198
|
private _fetch;
|
|
199
|
+
/**
|
|
200
|
+
* Check a push payload against the schema the server enforces, WITHOUT sending
|
|
201
|
+
* anything. Use this to confirm an agent's message complies before it goes
|
|
202
|
+
* live (e.g. in a test or a dry-run). `push()` runs the same check internally.
|
|
203
|
+
*/
|
|
204
|
+
validatePush(payload: _vessels_types.PushOptions): ValidationResult;
|
|
205
|
+
/** Like {@link validatePush}, but for a `pushMany()` broadcast payload. */
|
|
206
|
+
validatePushMany(payload: _vessels_types.PushManyOptions): ValidationResult;
|
|
143
207
|
push(payload: _vessels_types.PushOptions): Promise<PushResponse>;
|
|
144
208
|
pushMany(payload: _vessels_types.PushManyOptions): Promise<PushManyResult>;
|
|
145
209
|
editMessage(messageId: string, patch: _vessels_types.MessagePatch): Promise<{
|
|
146
210
|
ok: true;
|
|
147
211
|
}>;
|
|
212
|
+
/**
|
|
213
|
+
* Narrate a working message: declare a plan, file steps under each task as you
|
|
214
|
+
* go, and seal it when done. Sugar over {@link editMessage} — it tracks the
|
|
215
|
+
* todo list locally and PATCHes the full list each update (the server is
|
|
216
|
+
* authoritative and reconciles by label). The message must already exist; get
|
|
217
|
+
* its id from {@link push}.
|
|
218
|
+
*/
|
|
219
|
+
activity(messageId: string): ActivityHandle;
|
|
148
220
|
/**
|
|
149
221
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
150
222
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -207,7 +279,7 @@ declare class Vessels {
|
|
|
207
279
|
}): _vessels_types.ConfirmPreviewInteraction;
|
|
208
280
|
poll(options?: PollOptions): Promise<PollResponse>;
|
|
209
281
|
verifyWebhook(body: string, signature: string, webhookSecret: string): Promise<boolean>;
|
|
210
|
-
parseWebhookEvent(body: string, signature: string, webhookSecret: string): Promise<InteractionResponseEvent | UserMessageEvent | VesselCreatedEvent | null>;
|
|
282
|
+
parseWebhookEvent(body: string, signature: string, webhookSecret: string): Promise<InteractionResponseEvent | UserMessageEvent | VesselCreatedEvent | MessageCancelledEvent | null>;
|
|
211
283
|
}
|
|
212
284
|
|
|
213
|
-
export { AgentActivityTypes, type InteractionResponseEvent, type Message, type OriginMessage, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type VesselContext, type VesselCreatedEvent, Vessels, VesselsAuthError, type VesselsConfig, VesselsConflictError, VesselsRateLimitError, VesselsValidationError };
|
|
285
|
+
export { type ActivityHandle, AgentActivityTypes, type InteractionResponseEvent, type Message, type MessageCancelledEvent, type OriginMessage, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type ValidationResult, type VesselContext, type VesselCreatedEvent, Vessels, VesselsAuthError, type VesselsConfig, VesselsConflictError, VesselsRateLimitError, VesselsValidationError };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _vessels_types from '@vessels/types';
|
|
2
|
-
export { AgentActivity, AgentActivityType, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyOptions, PushManyPayload, PushOptions, PushPayload, TextInputInteraction, WebhookInteractionResponsePayload, WebhookInteractionResponsePayloadSchema, WebhookPayload, WebhookPayloadSchema, WebhookUserMessagePayload, WebhookUserMessagePayloadSchema, WebhookVesselCreatedPayload, WebhookVesselCreatedPayloadSchema } from '@vessels/types';
|
|
2
|
+
export { AgentActivity, AgentActivityType, AgentTodoInput, AgentTodoStatus, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, ConfirmPreviewInteraction, Interaction, MessagePatch, PushManyOptions, PushManyPayload, PushOptions, PushPayload, TextInputInteraction, WebhookInteractionResponsePayload, WebhookInteractionResponsePayloadSchema, WebhookPayload, WebhookPayloadSchema, WebhookUserMessagePayload, WebhookUserMessagePayloadSchema, WebhookVesselCreatedPayload, WebhookVesselCreatedPayloadSchema } from '@vessels/types';
|
|
3
3
|
|
|
4
4
|
declare const AgentActivityTypes: {
|
|
5
5
|
readonly thinking: "thinking";
|
|
@@ -23,11 +23,53 @@ declare class VesselsRateLimitError extends Error {
|
|
|
23
23
|
declare class VesselsConflictError extends Error {
|
|
24
24
|
constructor(message: string);
|
|
25
25
|
}
|
|
26
|
+
/** Result of a client-side payload check via {@link Vessels.validatePush}. */
|
|
27
|
+
interface ValidationResult {
|
|
28
|
+
/** True when the payload satisfies the schema the server enforces. */
|
|
29
|
+
valid: boolean;
|
|
30
|
+
/** Human-readable `field.path: message` lines — empty when valid. */
|
|
31
|
+
errors: string[];
|
|
32
|
+
/** Zod `flatten()` output (field + form errors) — present only when invalid. */
|
|
33
|
+
details?: {
|
|
34
|
+
formErrors: string[];
|
|
35
|
+
fieldErrors: Record<string, string[]>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
26
38
|
interface VesselsConfig {
|
|
27
39
|
apiKey: string;
|
|
28
40
|
baseUrl?: string;
|
|
29
41
|
debug?: boolean;
|
|
30
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A stateful handle for narrating a working message — the plan and the work it
|
|
45
|
+
* produces resolve into one artifact. Steps emitted while a task is active are
|
|
46
|
+
* filed under it server-side. Obtain one with {@link Vessels.activity}.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* const act = vessels.activity(messageId);
|
|
50
|
+
* await act.plan(['Check availability', 'Draft email', 'Send to customer']);
|
|
51
|
+
* await act.start('Check availability'); // → in_progress, step target
|
|
52
|
+
* await act.step('searching', 'Querying calendar');
|
|
53
|
+
* await act.start('Draft email'); // auto-finishes the prior task
|
|
54
|
+
* await act.step('tool_use', 'Sending via SendGrid');
|
|
55
|
+
* await act.done(); // seals everything
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
interface ActivityHandle {
|
|
59
|
+
/** Declare (or replace) the plan. Tasks default to `pending`. */
|
|
60
|
+
plan(tasks: Array<string | {
|
|
61
|
+
label: string;
|
|
62
|
+
status?: _vessels_types.AgentTodoStatus;
|
|
63
|
+
}>): Promise<void>;
|
|
64
|
+
/** Mark a task in-progress (creating it if new); any other in-progress task is finished. */
|
|
65
|
+
start(label: string): Promise<void>;
|
|
66
|
+
/** Append a step, filed under the active task. */
|
|
67
|
+
step(type: _vessels_types.AgentActivityType, label?: string): Promise<void>;
|
|
68
|
+
/** Finish a task by label, or the current in-progress task if omitted. */
|
|
69
|
+
complete(label?: string): Promise<void>;
|
|
70
|
+
/** Seal the activity — finishes the open step and any in-progress task. */
|
|
71
|
+
done(): Promise<void>;
|
|
72
|
+
}
|
|
31
73
|
interface PushResponse {
|
|
32
74
|
ok: true;
|
|
33
75
|
messageId: string;
|
|
@@ -116,6 +158,20 @@ interface VesselCreatedEvent {
|
|
|
116
158
|
content: string | null;
|
|
117
159
|
};
|
|
118
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Fired when a human taps "Stop" on an in-progress (working) agent message in
|
|
163
|
+
* the app. Vessels does NOT force-stop anything — you decide what to do (abort
|
|
164
|
+
* a task, stop polling, ignore it). Delivery is synchronous: the app tells the
|
|
165
|
+
* human whether your handler accepted it (return 2xx) so they know cancel is
|
|
166
|
+
* wired up. `messageId` is the working agent message the human wants stopped.
|
|
167
|
+
*/
|
|
168
|
+
interface MessageCancelledEvent {
|
|
169
|
+
id: string;
|
|
170
|
+
type: 'message.cancelled';
|
|
171
|
+
timestamp: string;
|
|
172
|
+
vessel: VesselContext;
|
|
173
|
+
messageId: string;
|
|
174
|
+
}
|
|
119
175
|
/** A message in a vessel, as returned by getMessages — the human-facing record. */
|
|
120
176
|
interface Message {
|
|
121
177
|
id: string;
|
|
@@ -140,11 +196,27 @@ declare class Vessels {
|
|
|
140
196
|
private _debug;
|
|
141
197
|
constructor(config: VesselsConfig);
|
|
142
198
|
private _fetch;
|
|
199
|
+
/**
|
|
200
|
+
* Check a push payload against the schema the server enforces, WITHOUT sending
|
|
201
|
+
* anything. Use this to confirm an agent's message complies before it goes
|
|
202
|
+
* live (e.g. in a test or a dry-run). `push()` runs the same check internally.
|
|
203
|
+
*/
|
|
204
|
+
validatePush(payload: _vessels_types.PushOptions): ValidationResult;
|
|
205
|
+
/** Like {@link validatePush}, but for a `pushMany()` broadcast payload. */
|
|
206
|
+
validatePushMany(payload: _vessels_types.PushManyOptions): ValidationResult;
|
|
143
207
|
push(payload: _vessels_types.PushOptions): Promise<PushResponse>;
|
|
144
208
|
pushMany(payload: _vessels_types.PushManyOptions): Promise<PushManyResult>;
|
|
145
209
|
editMessage(messageId: string, patch: _vessels_types.MessagePatch): Promise<{
|
|
146
210
|
ok: true;
|
|
147
211
|
}>;
|
|
212
|
+
/**
|
|
213
|
+
* Narrate a working message: declare a plan, file steps under each task as you
|
|
214
|
+
* go, and seal it when done. Sugar over {@link editMessage} — it tracks the
|
|
215
|
+
* todo list locally and PATCHes the full list each update (the server is
|
|
216
|
+
* authoritative and reconciles by label). The message must already exist; get
|
|
217
|
+
* its id from {@link push}.
|
|
218
|
+
*/
|
|
219
|
+
activity(messageId: string): ActivityHandle;
|
|
148
220
|
/**
|
|
149
221
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
150
222
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -207,7 +279,7 @@ declare class Vessels {
|
|
|
207
279
|
}): _vessels_types.ConfirmPreviewInteraction;
|
|
208
280
|
poll(options?: PollOptions): Promise<PollResponse>;
|
|
209
281
|
verifyWebhook(body: string, signature: string, webhookSecret: string): Promise<boolean>;
|
|
210
|
-
parseWebhookEvent(body: string, signature: string, webhookSecret: string): Promise<InteractionResponseEvent | UserMessageEvent | VesselCreatedEvent | null>;
|
|
282
|
+
parseWebhookEvent(body: string, signature: string, webhookSecret: string): Promise<InteractionResponseEvent | UserMessageEvent | VesselCreatedEvent | MessageCancelledEvent | null>;
|
|
211
283
|
}
|
|
212
284
|
|
|
213
|
-
export { AgentActivityTypes, type InteractionResponseEvent, type Message, type OriginMessage, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type VesselContext, type VesselCreatedEvent, Vessels, VesselsAuthError, type VesselsConfig, VesselsConflictError, VesselsRateLimitError, VesselsValidationError };
|
|
285
|
+
export { type ActivityHandle, AgentActivityTypes, type InteractionResponseEvent, type Message, type MessageCancelledEvent, type OriginMessage, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type UserMessageEvent, type ValidationResult, type VesselContext, type VesselCreatedEvent, Vessels, VesselsAuthError, type VesselsConfig, VesselsConflictError, VesselsRateLimitError, VesselsValidationError };
|
package/dist/index.js
CHANGED
|
@@ -68,9 +68,17 @@ var InteractionSchema = z.discriminatedUnion("type", [
|
|
|
68
68
|
ConfirmPreviewInteractionSchema
|
|
69
69
|
]);
|
|
70
70
|
var AgentActivityTypeSchema = z.enum(["thinking", "searching", "tool_use", "browsing", "processing"]);
|
|
71
|
+
var AgentTodoStatusSchema = z.enum(["pending", "in_progress", "done"]);
|
|
72
|
+
var AgentTodoInputSchema = z.object({
|
|
73
|
+
label: z.string().min(1).max(200),
|
|
74
|
+
status: AgentTodoStatusSchema.optional()
|
|
75
|
+
});
|
|
71
76
|
var AgentActivitySchema = z.object({
|
|
72
|
-
type: AgentActivityTypeSchema,
|
|
73
|
-
label: z.string().max(200).optional()
|
|
77
|
+
type: AgentActivityTypeSchema.optional(),
|
|
78
|
+
label: z.string().max(200).optional(),
|
|
79
|
+
todos: z.array(AgentTodoInputSchema).max(50).optional()
|
|
80
|
+
}).refine((d) => d.type != null || d.todos != null, {
|
|
81
|
+
message: "agentActivity requires `type` (a step) or `todos` (a plan)"
|
|
74
82
|
});
|
|
75
83
|
var CardFieldSchema = z.object({
|
|
76
84
|
label: z.string().min(1),
|
|
@@ -85,7 +93,7 @@ var AttachmentSchema = z.discriminatedUnion("type", [
|
|
|
85
93
|
z.object({ type: z.literal("file"), url: z.string().url(), filename: z.string().optional() })
|
|
86
94
|
]);
|
|
87
95
|
var VesselStatusSchema = z.enum(["active", "waiting", "resolved"]);
|
|
88
|
-
z.object({
|
|
96
|
+
var PushPayloadSchema = z.object({
|
|
89
97
|
message: z.string().min(1).max(1e4).optional(),
|
|
90
98
|
vessel: z.string().optional(),
|
|
91
99
|
vesselTitle: z.string().optional(),
|
|
@@ -105,7 +113,7 @@ z.object({
|
|
|
105
113
|
}).refine((d) => d.message || d.agentActivity, {
|
|
106
114
|
message: "Either message or agentActivity is required"
|
|
107
115
|
});
|
|
108
|
-
z.object({
|
|
116
|
+
var PushManyPayloadSchema = z.object({
|
|
109
117
|
vessels: z.array(z.string().min(1)).min(1).max(100),
|
|
110
118
|
message: z.string().min(1).max(1e4),
|
|
111
119
|
vesselTitle: z.string().optional(),
|
|
@@ -216,10 +224,21 @@ var WebhookVesselCreatedPayloadSchema = z.object({
|
|
|
216
224
|
})
|
|
217
225
|
})
|
|
218
226
|
});
|
|
227
|
+
var WebhookMessageCancelledPayloadSchema = z.object({
|
|
228
|
+
event: z.literal("message.cancelled"),
|
|
229
|
+
vessel_id: z.string(),
|
|
230
|
+
workspace_id: z.string(),
|
|
231
|
+
timestamp: z.string(),
|
|
232
|
+
data: z.object({
|
|
233
|
+
message_id: z.string(),
|
|
234
|
+
vessel: WebhookVesselSchema
|
|
235
|
+
})
|
|
236
|
+
});
|
|
219
237
|
var WebhookPayloadSchema = z.discriminatedUnion("event", [
|
|
220
238
|
WebhookInteractionResponsePayloadSchema,
|
|
221
239
|
WebhookUserMessagePayloadSchema,
|
|
222
|
-
WebhookVesselCreatedPayloadSchema
|
|
240
|
+
WebhookVesselCreatedPayloadSchema,
|
|
241
|
+
WebhookMessageCancelledPayloadSchema
|
|
223
242
|
]);
|
|
224
243
|
|
|
225
244
|
// src/index.ts
|
|
@@ -258,6 +277,13 @@ var VesselsConflictError = class extends Error {
|
|
|
258
277
|
this.name = "VesselsConflictError";
|
|
259
278
|
}
|
|
260
279
|
};
|
|
280
|
+
function formatZodError(error) {
|
|
281
|
+
const errors = error.issues.map((i) => {
|
|
282
|
+
const path = i.path.join(".") || "(payload)";
|
|
283
|
+
return `${path}: ${i.message}`;
|
|
284
|
+
});
|
|
285
|
+
return { errors, details: error.flatten() };
|
|
286
|
+
}
|
|
261
287
|
var Vessels = class {
|
|
262
288
|
apiKey;
|
|
263
289
|
baseUrl;
|
|
@@ -290,8 +316,33 @@ var Vessels = class {
|
|
|
290
316
|
}
|
|
291
317
|
return res;
|
|
292
318
|
}
|
|
319
|
+
/**
|
|
320
|
+
* Check a push payload against the schema the server enforces, WITHOUT sending
|
|
321
|
+
* anything. Use this to confirm an agent's message complies before it goes
|
|
322
|
+
* live (e.g. in a test or a dry-run). `push()` runs the same check internally.
|
|
323
|
+
*/
|
|
324
|
+
validatePush(payload) {
|
|
325
|
+
const { idempotencyKey: _ignored, ...body } = payload;
|
|
326
|
+
const result = PushPayloadSchema.safeParse(body);
|
|
327
|
+
if (result.success) return { valid: true, errors: [] };
|
|
328
|
+
const { errors, details } = formatZodError(result.error);
|
|
329
|
+
return { valid: false, errors, details };
|
|
330
|
+
}
|
|
331
|
+
/** Like {@link validatePush}, but for a `pushMany()` broadcast payload. */
|
|
332
|
+
validatePushMany(payload) {
|
|
333
|
+
const { idempotencyKey: _ignored, ...body } = payload;
|
|
334
|
+
const result = PushManyPayloadSchema.safeParse(body);
|
|
335
|
+
if (result.success) return { valid: true, errors: [] };
|
|
336
|
+
const { errors, details } = formatZodError(result.error);
|
|
337
|
+
return { valid: false, errors, details };
|
|
338
|
+
}
|
|
293
339
|
async push(payload) {
|
|
294
340
|
const { idempotencyKey, ...body } = payload;
|
|
341
|
+
const check = PushPayloadSchema.safeParse(body);
|
|
342
|
+
if (!check.success) {
|
|
343
|
+
const { errors, details } = formatZodError(check.error);
|
|
344
|
+
throw new VesselsValidationError(`Invalid push payload \u2014 ${errors.join("; ")}`, details);
|
|
345
|
+
}
|
|
295
346
|
const res = await this._fetch(`${this.baseUrl}/api/v1/push`, {
|
|
296
347
|
method: "POST",
|
|
297
348
|
headers: {
|
|
@@ -317,6 +368,11 @@ var Vessels = class {
|
|
|
317
368
|
}
|
|
318
369
|
async pushMany(payload) {
|
|
319
370
|
const { idempotencyKey, ...body } = payload;
|
|
371
|
+
const check = PushManyPayloadSchema.safeParse(body);
|
|
372
|
+
if (!check.success) {
|
|
373
|
+
const { errors, details } = formatZodError(check.error);
|
|
374
|
+
throw new VesselsValidationError(`Invalid pushMany payload \u2014 ${errors.join("; ")}`, details);
|
|
375
|
+
}
|
|
320
376
|
const res = await this._fetch(`${this.baseUrl}/api/v1/push/many`, {
|
|
321
377
|
method: "POST",
|
|
322
378
|
headers: {
|
|
@@ -360,6 +416,44 @@ var Vessels = class {
|
|
|
360
416
|
if (!res.ok) throw new Error(data.error ?? `HTTP ${res.status}`);
|
|
361
417
|
return { ok: true };
|
|
362
418
|
}
|
|
419
|
+
/**
|
|
420
|
+
* Narrate a working message: declare a plan, file steps under each task as you
|
|
421
|
+
* go, and seal it when done. Sugar over {@link editMessage} — it tracks the
|
|
422
|
+
* todo list locally and PATCHes the full list each update (the server is
|
|
423
|
+
* authoritative and reconciles by label). The message must already exist; get
|
|
424
|
+
* its id from {@link push}.
|
|
425
|
+
*/
|
|
426
|
+
activity(messageId) {
|
|
427
|
+
let todos = [];
|
|
428
|
+
const sendTodos = () => this.editMessage(messageId, { agentActivity: { todos } });
|
|
429
|
+
return {
|
|
430
|
+
plan: async (tasks) => {
|
|
431
|
+
todos = tasks.map(
|
|
432
|
+
(t) => typeof t === "string" ? { label: t, status: "pending" } : { label: t.label, status: t.status ?? "pending" }
|
|
433
|
+
);
|
|
434
|
+
await sendTodos();
|
|
435
|
+
},
|
|
436
|
+
start: async (label) => {
|
|
437
|
+
todos = todos.map(
|
|
438
|
+
(t) => t.label === label ? { ...t, status: "in_progress" } : t.status === "in_progress" ? { ...t, status: "done" } : t
|
|
439
|
+
);
|
|
440
|
+
if (!todos.some((t) => t.label === label)) todos.push({ label, status: "in_progress" });
|
|
441
|
+
await sendTodos();
|
|
442
|
+
},
|
|
443
|
+
step: async (type, label) => {
|
|
444
|
+
await this.editMessage(messageId, { agentActivity: { type, label } });
|
|
445
|
+
},
|
|
446
|
+
complete: async (label) => {
|
|
447
|
+
todos = todos.map(
|
|
448
|
+
(t) => (label ? t.label === label : t.status === "in_progress") ? { ...t, status: "done" } : t
|
|
449
|
+
);
|
|
450
|
+
await sendTodos();
|
|
451
|
+
},
|
|
452
|
+
done: async () => {
|
|
453
|
+
await this.editMessage(messageId, { agentActivity: null });
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
363
457
|
/**
|
|
364
458
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
365
459
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -545,6 +639,15 @@ var Vessels = class {
|
|
|
545
639
|
user: raw.data.user ?? null
|
|
546
640
|
};
|
|
547
641
|
}
|
|
642
|
+
if (raw.event === "message.cancelled") {
|
|
643
|
+
return {
|
|
644
|
+
id: raw.data.message_id,
|
|
645
|
+
type: "message.cancelled",
|
|
646
|
+
timestamp: raw.timestamp,
|
|
647
|
+
vessel,
|
|
648
|
+
messageId: raw.data.message_id
|
|
649
|
+
};
|
|
650
|
+
}
|
|
548
651
|
if (raw.event === "message.user") {
|
|
549
652
|
return {
|
|
550
653
|
id: raw.data.message_id,
|