vessels-sdk 0.11.0 → 0.13.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 +11 -13
- package/dist/index.cjs +138 -29
- package/dist/index.d.cts +74 -11
- package/dist/index.d.ts +74 -11
- package/dist/index.js +138 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,11 +82,11 @@ Returns `{ ok: true, messageId: string, vesselId: string, createdAt: string }`.
|
|
|
82
82
|
| `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. |
|
|
83
83
|
| `metadata` | `object` | Arbitrary JSON stored on the vessel, passed through in webhook callbacks. |
|
|
84
84
|
| `card` | `Card` | Structured key-value info attached to this message. `{ title: string, fields: [{ label, value }] }` |
|
|
85
|
-
| `interaction` | `Interaction` | Interactive prompt for the human (one of
|
|
85
|
+
| `interaction` | `Interaction` | Interactive prompt for the human (one of 4 types — see helpers below). Max one per message; immutable after the human responds. |
|
|
86
86
|
| `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. |
|
|
87
87
|
| `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 }]` |
|
|
88
88
|
| `suggestions` | `string[]` | Quick-reply chips shown below the message. Max 5. Tapping fills the text input. Disappear after the user sends any message. |
|
|
89
|
-
| `previewUrl` | `string` | URL
|
|
89
|
+
| `previewUrl` | `string` | A single URL rendered as a tappable link card below the message. Presentation only — no response. Compose with any interaction. |
|
|
90
90
|
| `notify` | `boolean` | Whether to send a push notification. Default `true`. |
|
|
91
91
|
|
|
92
92
|
```typescript
|
|
@@ -273,19 +273,18 @@ vessels.textInput({
|
|
|
273
273
|
|
|
274
274
|
Response shape: `{ text: string }`
|
|
275
275
|
|
|
276
|
-
#### `
|
|
276
|
+
#### Review-and-decide (the old `confirmPreview`)
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
There is no separate preview interaction. To have the human review something then decide,
|
|
279
|
+
attach a [`previewUrl`](#push) (a link card) to the message and use `approval`:
|
|
279
280
|
|
|
280
281
|
```typescript
|
|
281
|
-
vessels.
|
|
282
|
-
|
|
282
|
+
await vessels.push({
|
|
283
|
+
vessel: 'draft-123',
|
|
284
|
+
message: 'Draft email ready for review.',
|
|
283
285
|
previewUrl: 'https://your-app.com/drafts/123',
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
rejectLabel: 'Edit',
|
|
287
|
-
reasonRequiredOnReject: true,
|
|
288
|
-
})
|
|
286
|
+
interaction: vessels.approval({ prompt: 'Send this email?', approveLabel: 'Send', rejectLabel: 'Edit' }),
|
|
287
|
+
});
|
|
289
288
|
```
|
|
290
289
|
|
|
291
290
|
Response shape: `{ action: 'approved' | 'rejected', reason?: string }`
|
|
@@ -311,7 +310,7 @@ const { events, hasMore } = await vessels.poll({ ack: true });
|
|
|
311
310
|
|
|
312
311
|
for (const event of events) {
|
|
313
312
|
if (event.type === 'interaction.response') {
|
|
314
|
-
// event.interactionType — 'approval' | 'choice' | 'checklist' | 'text_input'
|
|
313
|
+
// event.interactionType — 'approval' | 'choice' | 'checklist' | 'text_input'
|
|
315
314
|
// event.response — response shape depends on interactionType (see above)
|
|
316
315
|
// event.interactionMetadata — metadata object you passed when creating the interaction, or null
|
|
317
316
|
// event.messageId — UUID of the message containing the interaction
|
|
@@ -490,7 +489,6 @@ import type {
|
|
|
490
489
|
ChoiceInteraction,
|
|
491
490
|
ChecklistInteraction,
|
|
492
491
|
TextInputInteraction,
|
|
493
|
-
ConfirmPreviewInteraction,
|
|
494
492
|
} from 'vessels-sdk';
|
|
495
493
|
```
|
|
496
494
|
|
package/dist/index.cjs
CHANGED
|
@@ -8,8 +8,7 @@ var InteractionTypeSchema = zod.z.enum([
|
|
|
8
8
|
"approval",
|
|
9
9
|
"choice",
|
|
10
10
|
"checklist",
|
|
11
|
-
"text_input"
|
|
12
|
-
"confirm_preview"
|
|
11
|
+
"text_input"
|
|
13
12
|
]);
|
|
14
13
|
var ApprovalInteractionSchema = zod.z.object({
|
|
15
14
|
type: zod.z.literal("approval"),
|
|
@@ -52,22 +51,11 @@ var TextInputInteractionSchema = zod.z.object({
|
|
|
52
51
|
submitLabel: zod.z.string().optional(),
|
|
53
52
|
metadata: zod.z.record(zod.z.unknown()).optional()
|
|
54
53
|
});
|
|
55
|
-
var ConfirmPreviewInteractionSchema = zod.z.object({
|
|
56
|
-
type: zod.z.literal("confirm_preview"),
|
|
57
|
-
prompt: zod.z.string().min(1),
|
|
58
|
-
previewUrl: zod.z.string().url(),
|
|
59
|
-
previewLabel: zod.z.string().optional(),
|
|
60
|
-
approveLabel: zod.z.string().optional(),
|
|
61
|
-
rejectLabel: zod.z.string().optional(),
|
|
62
|
-
reasonRequiredOnReject: zod.z.boolean().optional(),
|
|
63
|
-
metadata: zod.z.record(zod.z.unknown()).optional()
|
|
64
|
-
});
|
|
65
54
|
var InteractionSchema = zod.z.discriminatedUnion("type", [
|
|
66
55
|
ApprovalInteractionSchema,
|
|
67
56
|
ChoiceInteractionSchema,
|
|
68
57
|
ChecklistInteractionSchema,
|
|
69
|
-
TextInputInteractionSchema
|
|
70
|
-
ConfirmPreviewInteractionSchema
|
|
58
|
+
TextInputInteractionSchema
|
|
71
59
|
]);
|
|
72
60
|
var AgentActivityTypeSchema = zod.z.enum(["thinking", "searching", "tool_use", "browsing", "processing"]);
|
|
73
61
|
var AgentTodoStatusSchema = zod.z.enum(["pending", "in_progress", "done"]);
|
|
@@ -87,7 +75,10 @@ var CardFieldSchema = zod.z.object({
|
|
|
87
75
|
value: zod.z.string()
|
|
88
76
|
});
|
|
89
77
|
var CardSchema = zod.z.object({
|
|
90
|
-
|
|
78
|
+
// Optional: a glance-facts card under a surface takes its heading from the
|
|
79
|
+
// surface `title`, so a card title is redundant there. Still allowed (e.g. a
|
|
80
|
+
// standalone card on a bubble, or a pinned card).
|
|
81
|
+
title: zod.z.string().min(1).optional(),
|
|
91
82
|
fields: zod.z.array(CardFieldSchema)
|
|
92
83
|
});
|
|
93
84
|
var AttachmentSchema = zod.z.discriminatedUnion("type", [
|
|
@@ -95,6 +86,8 @@ var AttachmentSchema = zod.z.discriminatedUnion("type", [
|
|
|
95
86
|
zod.z.object({ type: zod.z.literal("file"), url: zod.z.string().url(), filename: zod.z.string().optional() })
|
|
96
87
|
]);
|
|
97
88
|
var VesselStatusSchema = zod.z.enum(["active", "waiting", "resolved"]);
|
|
89
|
+
var DisplaySchema = zod.z.enum(["bubble", "document"]);
|
|
90
|
+
var KindSchema = zod.z.enum(["bubble", "surface"]);
|
|
98
91
|
var PushPayloadSchema = zod.z.object({
|
|
99
92
|
message: zod.z.string().min(1).max(1e4).optional(),
|
|
100
93
|
vessel: zod.z.string().optional(),
|
|
@@ -111,9 +104,23 @@ var PushPayloadSchema = zod.z.object({
|
|
|
111
104
|
labels: zod.z.array(zod.z.string().min(1).max(50)).max(10).optional(),
|
|
112
105
|
attachments: zod.z.array(AttachmentSchema).max(10).optional(),
|
|
113
106
|
suggestions: zod.z.array(zod.z.string().min(1).max(500)).max(5).optional(),
|
|
114
|
-
agentActivity: AgentActivitySchema.optional()
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
agentActivity: AgentActivitySchema.optional(),
|
|
108
|
+
/**
|
|
109
|
+
* Live token-stream buffer — an ephemeral monospace block the human watches
|
|
110
|
+
* fill in real time (set it on the message you create, then keep replacing it
|
|
111
|
+
* via `PATCH /messages/:id`, and clear it with `null` when done). It is a live
|
|
112
|
+
* window, not a transcript: send the tail you want shown (the SDK trims to the
|
|
113
|
+
* last 8000 chars). Plaintext, like agentActivity. Vanishes when cleared.
|
|
114
|
+
*/
|
|
115
|
+
tokenStream: zod.z.string().max(8e3).optional(),
|
|
116
|
+
/** Bubble (chat) vs surface (composed artifact). Defaults from interaction/card. */
|
|
117
|
+
kind: KindSchema.optional(),
|
|
118
|
+
/** Surface heading. Ignored on bubbles. */
|
|
119
|
+
title: zod.z.string().max(200).optional(),
|
|
120
|
+
/** @deprecated legacy presentation hint — use `kind`. 'document' → surface. */
|
|
121
|
+
display: DisplaySchema.optional()
|
|
122
|
+
}).refine((d) => d.message || d.agentActivity || d.tokenStream, {
|
|
123
|
+
message: "One of message, agentActivity, or tokenStream is required"
|
|
117
124
|
});
|
|
118
125
|
var PushManyPayloadSchema = zod.z.object({
|
|
119
126
|
vessels: zod.z.array(zod.z.string().min(1)).min(1).max(100),
|
|
@@ -128,14 +135,26 @@ var PushManyPayloadSchema = zod.z.object({
|
|
|
128
135
|
metadata: zod.z.record(zod.z.unknown()).refine(
|
|
129
136
|
(v) => JSON.stringify(v).length < 16e3,
|
|
130
137
|
"metadata exceeds 16KB limit"
|
|
131
|
-
).optional()
|
|
138
|
+
).optional(),
|
|
139
|
+
kind: KindSchema.optional(),
|
|
140
|
+
title: zod.z.string().max(200).optional(),
|
|
141
|
+
/** @deprecated use `kind`. */
|
|
142
|
+
display: DisplaySchema.optional()
|
|
132
143
|
});
|
|
133
144
|
zod.z.object({
|
|
134
145
|
content: zod.z.string().min(1).max(1e4).optional(),
|
|
135
146
|
card: CardSchema.nullable().optional(),
|
|
136
147
|
attachments: zod.z.array(AttachmentSchema).max(10).nullable().optional(),
|
|
137
148
|
suggestions: zod.z.array(zod.z.string().min(1).max(500)).max(5).nullable().optional(),
|
|
138
|
-
agentActivity: AgentActivitySchema.nullable().optional()
|
|
149
|
+
agentActivity: AgentActivitySchema.nullable().optional(),
|
|
150
|
+
/** Replace the live token-stream window, or `null` to clear it (block vanishes). */
|
|
151
|
+
tokenStream: zod.z.string().max(8e3).nullable().optional(),
|
|
152
|
+
/** Switch kind: 'surface' = full-width artifact, 'bubble' or null = chat bubble. */
|
|
153
|
+
kind: KindSchema.nullable().optional(),
|
|
154
|
+
/** Update the surface heading, or `null` to clear it. */
|
|
155
|
+
title: zod.z.string().max(200).nullable().optional(),
|
|
156
|
+
/** @deprecated use `kind`. */
|
|
157
|
+
display: DisplaySchema.nullable().optional()
|
|
139
158
|
}).refine((d) => Object.values(d).some((v) => v !== void 0), {
|
|
140
159
|
message: "At least one field required"
|
|
141
160
|
});
|
|
@@ -153,16 +172,11 @@ var ChecklistResponseSchema = zod.z.object({
|
|
|
153
172
|
var TextInputResponseSchema = zod.z.object({
|
|
154
173
|
text: zod.z.string()
|
|
155
174
|
});
|
|
156
|
-
var ConfirmPreviewResponseSchema = zod.z.object({
|
|
157
|
-
action: zod.z.enum(["approved", "rejected"]),
|
|
158
|
-
reason: zod.z.string().optional()
|
|
159
|
-
});
|
|
160
175
|
zod.z.discriminatedUnion("interactionType", [
|
|
161
176
|
zod.z.object({ interactionType: zod.z.literal("approval"), response: ApprovalResponseSchema }),
|
|
162
177
|
zod.z.object({ interactionType: zod.z.literal("choice"), response: ChoiceResponseSchema }),
|
|
163
178
|
zod.z.object({ interactionType: zod.z.literal("checklist"), response: ChecklistResponseSchema }),
|
|
164
|
-
zod.z.object({ interactionType: zod.z.literal("text_input"), response: TextInputResponseSchema })
|
|
165
|
-
zod.z.object({ interactionType: zod.z.literal("confirm_preview"), response: ConfirmPreviewResponseSchema })
|
|
179
|
+
zod.z.object({ interactionType: zod.z.literal("text_input"), response: TextInputResponseSchema })
|
|
166
180
|
]);
|
|
167
181
|
var WebhookVesselSchema = zod.z.object({
|
|
168
182
|
id: zod.z.string(),
|
|
@@ -368,6 +382,31 @@ var Vessels = class {
|
|
|
368
382
|
replayed: res.headers.get("Idempotent-Replayed") === "true"
|
|
369
383
|
};
|
|
370
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Create a **surface** — a full-width composed artifact the human reviews and
|
|
387
|
+
* optionally acts on, rendered as one piece: a `title` heading, an optional
|
|
388
|
+
* `card` of glance-facts, a block-markdown `body` (tables, bullet/numbered
|
|
389
|
+
* lists, blockquotes, bold headings, links), and an optional `interaction`
|
|
390
|
+
* (the action bar). Use this for an email/message draft, a quote, an invoice
|
|
391
|
+
* review, a proposal, a report — anything substantial and structured.
|
|
392
|
+
*
|
|
393
|
+
* Sugar over {@link push}: it sets `kind: 'surface'` and maps `body` to the
|
|
394
|
+
* message content. For conversation, status updates and quick questions, use
|
|
395
|
+
* {@link push} instead (a chat **bubble** — the human just replies).
|
|
396
|
+
*
|
|
397
|
+
* @example
|
|
398
|
+
* await vessels.surface({
|
|
399
|
+
* vessel: 'inv-42',
|
|
400
|
+
* title: 'GreenTurf Invoice #GTL-2025-042',
|
|
401
|
+
* card: { fields: [{ label: 'Amount Due', value: '$7,400' }] },
|
|
402
|
+
* body: 'Invoice is **$1,200 over** the usual rate.\n\n| Item | Amount |\n|---|---:|\n| Overage | +$1,200 |',
|
|
403
|
+
* interaction: vessels.approval({ prompt: 'Approve the $7,400 payment?' }),
|
|
404
|
+
* });
|
|
405
|
+
*/
|
|
406
|
+
async surface(options) {
|
|
407
|
+
const { body, ...rest } = options;
|
|
408
|
+
return this.push({ ...rest, message: body, kind: "surface" });
|
|
409
|
+
}
|
|
371
410
|
async pushMany(payload) {
|
|
372
411
|
const { idempotencyKey, ...body } = payload;
|
|
373
412
|
const check = PushManyPayloadSchema.safeParse(body);
|
|
@@ -456,6 +495,79 @@ var Vessels = class {
|
|
|
456
495
|
}
|
|
457
496
|
};
|
|
458
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Stream live tokens into a message: the human watches a monospace block fill
|
|
500
|
+
* in real time. Sugar over {@link editMessage} — it keeps the buffer locally
|
|
501
|
+
* and PATCHes a throttled, tail-trimmed window (replace-semantics, so a lost
|
|
502
|
+
* flush self-heals on the next one). Seal with `done()` to clear the block.
|
|
503
|
+
*
|
|
504
|
+
* @param messageId the message to stream into (create it first via `push`).
|
|
505
|
+
* @param opts.throttleMs minimum gap between server flushes (default 120ms).
|
|
506
|
+
* @param opts.maxChars longest window kept; older text scrolls off (default 8000).
|
|
507
|
+
*/
|
|
508
|
+
stream(messageId, opts) {
|
|
509
|
+
const throttleMs = opts?.throttleMs ?? 120;
|
|
510
|
+
const maxChars = opts?.maxChars ?? 8e3;
|
|
511
|
+
let buffer = "";
|
|
512
|
+
let lastSent = null;
|
|
513
|
+
let timer = null;
|
|
514
|
+
let pending = Promise.resolve();
|
|
515
|
+
const windowed = () => buffer.length > maxChars ? buffer.slice(-maxChars) : buffer;
|
|
516
|
+
const flushNow = () => {
|
|
517
|
+
const text = windowed();
|
|
518
|
+
if (text === lastSent) return pending;
|
|
519
|
+
lastSent = text;
|
|
520
|
+
pending = this.editMessage(messageId, { tokenStream: text }).catch(() => {
|
|
521
|
+
lastSent = null;
|
|
522
|
+
});
|
|
523
|
+
return pending;
|
|
524
|
+
};
|
|
525
|
+
const cancelTimer = () => {
|
|
526
|
+
if (timer) {
|
|
527
|
+
clearTimeout(timer);
|
|
528
|
+
timer = null;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
const schedule = () => {
|
|
532
|
+
if (timer) return;
|
|
533
|
+
timer = setTimeout(() => {
|
|
534
|
+
timer = null;
|
|
535
|
+
void flushNow();
|
|
536
|
+
}, throttleMs);
|
|
537
|
+
timer?.unref?.();
|
|
538
|
+
};
|
|
539
|
+
const settle = async () => {
|
|
540
|
+
cancelTimer();
|
|
541
|
+
await pending.catch(() => {
|
|
542
|
+
});
|
|
543
|
+
};
|
|
544
|
+
return {
|
|
545
|
+
write: (text) => {
|
|
546
|
+
buffer += text;
|
|
547
|
+
schedule();
|
|
548
|
+
},
|
|
549
|
+
set: async (text) => {
|
|
550
|
+
buffer = text;
|
|
551
|
+
cancelTimer();
|
|
552
|
+
await flushNow();
|
|
553
|
+
},
|
|
554
|
+
// Clear the stream AND seal any working card (agentActivity: null is a no-op
|
|
555
|
+
// when there's none), so the final content actually renders.
|
|
556
|
+
done: async (finalContent) => {
|
|
557
|
+
await settle();
|
|
558
|
+
await this.editMessage(messageId, {
|
|
559
|
+
tokenStream: null,
|
|
560
|
+
agentActivity: null,
|
|
561
|
+
...finalContent != null ? { content: finalContent } : {}
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
// Remove only the stream; leave the working card alone.
|
|
565
|
+
clear: async () => {
|
|
566
|
+
await settle();
|
|
567
|
+
await this.editMessage(messageId, { tokenStream: null });
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
459
571
|
/**
|
|
460
572
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
461
573
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -501,9 +613,6 @@ var Vessels = class {
|
|
|
501
613
|
textInput(opts) {
|
|
502
614
|
return { type: "text_input", ...opts };
|
|
503
615
|
}
|
|
504
|
-
confirmPreview(opts) {
|
|
505
|
-
return { type: "confirm_preview", ...opts };
|
|
506
|
-
}
|
|
507
616
|
async poll(options = {}) {
|
|
508
617
|
const { since, limit = 50, ack = true } = options;
|
|
509
618
|
const params = new URLSearchParams();
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _vessels_types from '@vessels/types';
|
|
2
|
-
export { AgentActivity, AgentActivityType, AgentTodoInput, AgentTodoStatus, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction,
|
|
2
|
+
export { AgentActivity, AgentActivityType, AgentTodoInput, AgentTodoStatus, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, 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";
|
|
@@ -70,6 +70,34 @@ interface ActivityHandle {
|
|
|
70
70
|
/** Seal the activity — finishes the open step and any in-progress task. */
|
|
71
71
|
done(): Promise<void>;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* A handle for streaming live tokens into a message — the human watches a
|
|
75
|
+
* monospace block fill in real time, and it vanishes when you seal it. Obtain
|
|
76
|
+
* one with {@link Vessels.stream}; the message must already exist (get its id
|
|
77
|
+
* from {@link Vessels.push}). It is a live WINDOW, not a transcript: write all
|
|
78
|
+
* you like, only the last `maxChars` (default 8000) are shown.
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* const { messageId } = await vessels.push({ vessel, agentActivity: { type: 'thinking' } });
|
|
82
|
+
* const out = vessels.stream(messageId);
|
|
83
|
+
* for await (const tok of llm) out.write(tok); // throttled PATCHes under the hood
|
|
84
|
+
* await out.done('Booked you in for 2pm Thursday.'); // clears the stream, sets the final reply
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
interface StreamHandle {
|
|
88
|
+
/** Append text to the live buffer; flushed to the server on a throttle. */
|
|
89
|
+
write(text: string): void;
|
|
90
|
+
/** Replace the whole buffer and flush immediately. */
|
|
91
|
+
set(text: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Finish the turn: flush, clear the block, and seal any working agent-activity
|
|
94
|
+
* card (a no-op if there is none) — optionally setting the message's final
|
|
95
|
+
* content. Use this when the stream was the last thing you were doing.
|
|
96
|
+
*/
|
|
97
|
+
done(finalContent?: string): Promise<void>;
|
|
98
|
+
/** Just remove the block, leaving any working card untouched (you're still working). */
|
|
99
|
+
clear(): Promise<void>;
|
|
100
|
+
}
|
|
73
101
|
interface PushResponse {
|
|
74
102
|
ok: true;
|
|
75
103
|
messageId: string;
|
|
@@ -78,6 +106,14 @@ interface PushResponse {
|
|
|
78
106
|
/** True when this response is a replay of an earlier request with the same idempotency key. */
|
|
79
107
|
replayed: boolean;
|
|
80
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Options for {@link Vessels.surface}. Same as a push, but the artifact text is
|
|
111
|
+
* `body` (not `message`) and `kind` is forced to `'surface'`.
|
|
112
|
+
*/
|
|
113
|
+
type SurfaceOptions = Omit<_vessels_types.PushOptions, 'message' | 'kind'> & {
|
|
114
|
+
/** The artifact body — block markdown (tables, lists, blockquotes, bold headings, links). */
|
|
115
|
+
body: string;
|
|
116
|
+
};
|
|
81
117
|
interface PushManyResult {
|
|
82
118
|
ok: true;
|
|
83
119
|
results: Array<{
|
|
@@ -205,6 +241,28 @@ declare class Vessels {
|
|
|
205
241
|
/** Like {@link validatePush}, but for a `pushMany()` broadcast payload. */
|
|
206
242
|
validatePushMany(payload: _vessels_types.PushManyOptions): ValidationResult;
|
|
207
243
|
push(payload: _vessels_types.PushOptions): Promise<PushResponse>;
|
|
244
|
+
/**
|
|
245
|
+
* Create a **surface** — a full-width composed artifact the human reviews and
|
|
246
|
+
* optionally acts on, rendered as one piece: a `title` heading, an optional
|
|
247
|
+
* `card` of glance-facts, a block-markdown `body` (tables, bullet/numbered
|
|
248
|
+
* lists, blockquotes, bold headings, links), and an optional `interaction`
|
|
249
|
+
* (the action bar). Use this for an email/message draft, a quote, an invoice
|
|
250
|
+
* review, a proposal, a report — anything substantial and structured.
|
|
251
|
+
*
|
|
252
|
+
* Sugar over {@link push}: it sets `kind: 'surface'` and maps `body` to the
|
|
253
|
+
* message content. For conversation, status updates and quick questions, use
|
|
254
|
+
* {@link push} instead (a chat **bubble** — the human just replies).
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* await vessels.surface({
|
|
258
|
+
* vessel: 'inv-42',
|
|
259
|
+
* title: 'GreenTurf Invoice #GTL-2025-042',
|
|
260
|
+
* card: { fields: [{ label: 'Amount Due', value: '$7,400' }] },
|
|
261
|
+
* body: 'Invoice is **$1,200 over** the usual rate.\n\n| Item | Amount |\n|---|---:|\n| Overage | +$1,200 |',
|
|
262
|
+
* interaction: vessels.approval({ prompt: 'Approve the $7,400 payment?' }),
|
|
263
|
+
* });
|
|
264
|
+
*/
|
|
265
|
+
surface(options: SurfaceOptions): Promise<PushResponse>;
|
|
208
266
|
pushMany(payload: _vessels_types.PushManyOptions): Promise<PushManyResult>;
|
|
209
267
|
editMessage(messageId: string, patch: _vessels_types.MessagePatch): Promise<{
|
|
210
268
|
ok: true;
|
|
@@ -217,6 +275,20 @@ declare class Vessels {
|
|
|
217
275
|
* its id from {@link push}.
|
|
218
276
|
*/
|
|
219
277
|
activity(messageId: string): ActivityHandle;
|
|
278
|
+
/**
|
|
279
|
+
* Stream live tokens into a message: the human watches a monospace block fill
|
|
280
|
+
* in real time. Sugar over {@link editMessage} — it keeps the buffer locally
|
|
281
|
+
* and PATCHes a throttled, tail-trimmed window (replace-semantics, so a lost
|
|
282
|
+
* flush self-heals on the next one). Seal with `done()` to clear the block.
|
|
283
|
+
*
|
|
284
|
+
* @param messageId the message to stream into (create it first via `push`).
|
|
285
|
+
* @param opts.throttleMs minimum gap between server flushes (default 120ms).
|
|
286
|
+
* @param opts.maxChars longest window kept; older text scrolls off (default 8000).
|
|
287
|
+
*/
|
|
288
|
+
stream(messageId: string, opts?: {
|
|
289
|
+
throttleMs?: number;
|
|
290
|
+
maxChars?: number;
|
|
291
|
+
}): StreamHandle;
|
|
220
292
|
/**
|
|
221
293
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
222
294
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -268,18 +340,9 @@ declare class Vessels {
|
|
|
268
340
|
submitLabel?: string;
|
|
269
341
|
metadata?: Record<string, unknown>;
|
|
270
342
|
}): _vessels_types.TextInputInteraction;
|
|
271
|
-
confirmPreview(opts: {
|
|
272
|
-
prompt: string;
|
|
273
|
-
previewUrl: string;
|
|
274
|
-
previewLabel?: string;
|
|
275
|
-
approveLabel?: string;
|
|
276
|
-
rejectLabel?: string;
|
|
277
|
-
reasonRequiredOnReject?: boolean;
|
|
278
|
-
metadata?: Record<string, unknown>;
|
|
279
|
-
}): _vessels_types.ConfirmPreviewInteraction;
|
|
280
343
|
poll(options?: PollOptions): Promise<PollResponse>;
|
|
281
344
|
verifyWebhook(body: string, signature: string, webhookSecret: string): Promise<boolean>;
|
|
282
345
|
parseWebhookEvent(body: string, signature: string, webhookSecret: string): Promise<InteractionResponseEvent | UserMessageEvent | VesselCreatedEvent | MessageCancelledEvent | null>;
|
|
283
346
|
}
|
|
284
347
|
|
|
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 };
|
|
348
|
+
export { type ActivityHandle, AgentActivityTypes, type InteractionResponseEvent, type Message, type MessageCancelledEvent, type OriginMessage, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type StreamHandle, type SurfaceOptions, 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, AgentTodoInput, AgentTodoStatus, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction,
|
|
2
|
+
export { AgentActivity, AgentActivityType, AgentTodoInput, AgentTodoStatus, ApprovalInteraction, Attachment, Card, ChecklistInteraction, ChoiceInteraction, 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";
|
|
@@ -70,6 +70,34 @@ interface ActivityHandle {
|
|
|
70
70
|
/** Seal the activity — finishes the open step and any in-progress task. */
|
|
71
71
|
done(): Promise<void>;
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* A handle for streaming live tokens into a message — the human watches a
|
|
75
|
+
* monospace block fill in real time, and it vanishes when you seal it. Obtain
|
|
76
|
+
* one with {@link Vessels.stream}; the message must already exist (get its id
|
|
77
|
+
* from {@link Vessels.push}). It is a live WINDOW, not a transcript: write all
|
|
78
|
+
* you like, only the last `maxChars` (default 8000) are shown.
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* const { messageId } = await vessels.push({ vessel, agentActivity: { type: 'thinking' } });
|
|
82
|
+
* const out = vessels.stream(messageId);
|
|
83
|
+
* for await (const tok of llm) out.write(tok); // throttled PATCHes under the hood
|
|
84
|
+
* await out.done('Booked you in for 2pm Thursday.'); // clears the stream, sets the final reply
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
interface StreamHandle {
|
|
88
|
+
/** Append text to the live buffer; flushed to the server on a throttle. */
|
|
89
|
+
write(text: string): void;
|
|
90
|
+
/** Replace the whole buffer and flush immediately. */
|
|
91
|
+
set(text: string): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Finish the turn: flush, clear the block, and seal any working agent-activity
|
|
94
|
+
* card (a no-op if there is none) — optionally setting the message's final
|
|
95
|
+
* content. Use this when the stream was the last thing you were doing.
|
|
96
|
+
*/
|
|
97
|
+
done(finalContent?: string): Promise<void>;
|
|
98
|
+
/** Just remove the block, leaving any working card untouched (you're still working). */
|
|
99
|
+
clear(): Promise<void>;
|
|
100
|
+
}
|
|
73
101
|
interface PushResponse {
|
|
74
102
|
ok: true;
|
|
75
103
|
messageId: string;
|
|
@@ -78,6 +106,14 @@ interface PushResponse {
|
|
|
78
106
|
/** True when this response is a replay of an earlier request with the same idempotency key. */
|
|
79
107
|
replayed: boolean;
|
|
80
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Options for {@link Vessels.surface}. Same as a push, but the artifact text is
|
|
111
|
+
* `body` (not `message`) and `kind` is forced to `'surface'`.
|
|
112
|
+
*/
|
|
113
|
+
type SurfaceOptions = Omit<_vessels_types.PushOptions, 'message' | 'kind'> & {
|
|
114
|
+
/** The artifact body — block markdown (tables, lists, blockquotes, bold headings, links). */
|
|
115
|
+
body: string;
|
|
116
|
+
};
|
|
81
117
|
interface PushManyResult {
|
|
82
118
|
ok: true;
|
|
83
119
|
results: Array<{
|
|
@@ -205,6 +241,28 @@ declare class Vessels {
|
|
|
205
241
|
/** Like {@link validatePush}, but for a `pushMany()` broadcast payload. */
|
|
206
242
|
validatePushMany(payload: _vessels_types.PushManyOptions): ValidationResult;
|
|
207
243
|
push(payload: _vessels_types.PushOptions): Promise<PushResponse>;
|
|
244
|
+
/**
|
|
245
|
+
* Create a **surface** — a full-width composed artifact the human reviews and
|
|
246
|
+
* optionally acts on, rendered as one piece: a `title` heading, an optional
|
|
247
|
+
* `card` of glance-facts, a block-markdown `body` (tables, bullet/numbered
|
|
248
|
+
* lists, blockquotes, bold headings, links), and an optional `interaction`
|
|
249
|
+
* (the action bar). Use this for an email/message draft, a quote, an invoice
|
|
250
|
+
* review, a proposal, a report — anything substantial and structured.
|
|
251
|
+
*
|
|
252
|
+
* Sugar over {@link push}: it sets `kind: 'surface'` and maps `body` to the
|
|
253
|
+
* message content. For conversation, status updates and quick questions, use
|
|
254
|
+
* {@link push} instead (a chat **bubble** — the human just replies).
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* await vessels.surface({
|
|
258
|
+
* vessel: 'inv-42',
|
|
259
|
+
* title: 'GreenTurf Invoice #GTL-2025-042',
|
|
260
|
+
* card: { fields: [{ label: 'Amount Due', value: '$7,400' }] },
|
|
261
|
+
* body: 'Invoice is **$1,200 over** the usual rate.\n\n| Item | Amount |\n|---|---:|\n| Overage | +$1,200 |',
|
|
262
|
+
* interaction: vessels.approval({ prompt: 'Approve the $7,400 payment?' }),
|
|
263
|
+
* });
|
|
264
|
+
*/
|
|
265
|
+
surface(options: SurfaceOptions): Promise<PushResponse>;
|
|
208
266
|
pushMany(payload: _vessels_types.PushManyOptions): Promise<PushManyResult>;
|
|
209
267
|
editMessage(messageId: string, patch: _vessels_types.MessagePatch): Promise<{
|
|
210
268
|
ok: true;
|
|
@@ -217,6 +275,20 @@ declare class Vessels {
|
|
|
217
275
|
* its id from {@link push}.
|
|
218
276
|
*/
|
|
219
277
|
activity(messageId: string): ActivityHandle;
|
|
278
|
+
/**
|
|
279
|
+
* Stream live tokens into a message: the human watches a monospace block fill
|
|
280
|
+
* in real time. Sugar over {@link editMessage} — it keeps the buffer locally
|
|
281
|
+
* and PATCHes a throttled, tail-trimmed window (replace-semantics, so a lost
|
|
282
|
+
* flush self-heals on the next one). Seal with `done()` to clear the block.
|
|
283
|
+
*
|
|
284
|
+
* @param messageId the message to stream into (create it first via `push`).
|
|
285
|
+
* @param opts.throttleMs minimum gap between server flushes (default 120ms).
|
|
286
|
+
* @param opts.maxChars longest window kept; older text scrolls off (default 8000).
|
|
287
|
+
*/
|
|
288
|
+
stream(messageId: string, opts?: {
|
|
289
|
+
throttleMs?: number;
|
|
290
|
+
maxChars?: number;
|
|
291
|
+
}): StreamHandle;
|
|
220
292
|
/**
|
|
221
293
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
222
294
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -268,18 +340,9 @@ declare class Vessels {
|
|
|
268
340
|
submitLabel?: string;
|
|
269
341
|
metadata?: Record<string, unknown>;
|
|
270
342
|
}): _vessels_types.TextInputInteraction;
|
|
271
|
-
confirmPreview(opts: {
|
|
272
|
-
prompt: string;
|
|
273
|
-
previewUrl: string;
|
|
274
|
-
previewLabel?: string;
|
|
275
|
-
approveLabel?: string;
|
|
276
|
-
rejectLabel?: string;
|
|
277
|
-
reasonRequiredOnReject?: boolean;
|
|
278
|
-
metadata?: Record<string, unknown>;
|
|
279
|
-
}): _vessels_types.ConfirmPreviewInteraction;
|
|
280
343
|
poll(options?: PollOptions): Promise<PollResponse>;
|
|
281
344
|
verifyWebhook(body: string, signature: string, webhookSecret: string): Promise<boolean>;
|
|
282
345
|
parseWebhookEvent(body: string, signature: string, webhookSecret: string): Promise<InteractionResponseEvent | UserMessageEvent | VesselCreatedEvent | MessageCancelledEvent | null>;
|
|
283
346
|
}
|
|
284
347
|
|
|
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 };
|
|
348
|
+
export { type ActivityHandle, AgentActivityTypes, type InteractionResponseEvent, type Message, type MessageCancelledEvent, type OriginMessage, type PollEvent, type PollOptions, type PollResponse, type PushManyResult, type PushResponse, type StreamHandle, type SurfaceOptions, type UserMessageEvent, type ValidationResult, type VesselContext, type VesselCreatedEvent, Vessels, VesselsAuthError, type VesselsConfig, VesselsConflictError, VesselsRateLimitError, VesselsValidationError };
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,7 @@ var InteractionTypeSchema = z.enum([
|
|
|
6
6
|
"approval",
|
|
7
7
|
"choice",
|
|
8
8
|
"checklist",
|
|
9
|
-
"text_input"
|
|
10
|
-
"confirm_preview"
|
|
9
|
+
"text_input"
|
|
11
10
|
]);
|
|
12
11
|
var ApprovalInteractionSchema = z.object({
|
|
13
12
|
type: z.literal("approval"),
|
|
@@ -50,22 +49,11 @@ var TextInputInteractionSchema = z.object({
|
|
|
50
49
|
submitLabel: z.string().optional(),
|
|
51
50
|
metadata: z.record(z.unknown()).optional()
|
|
52
51
|
});
|
|
53
|
-
var ConfirmPreviewInteractionSchema = z.object({
|
|
54
|
-
type: z.literal("confirm_preview"),
|
|
55
|
-
prompt: z.string().min(1),
|
|
56
|
-
previewUrl: z.string().url(),
|
|
57
|
-
previewLabel: z.string().optional(),
|
|
58
|
-
approveLabel: z.string().optional(),
|
|
59
|
-
rejectLabel: z.string().optional(),
|
|
60
|
-
reasonRequiredOnReject: z.boolean().optional(),
|
|
61
|
-
metadata: z.record(z.unknown()).optional()
|
|
62
|
-
});
|
|
63
52
|
var InteractionSchema = z.discriminatedUnion("type", [
|
|
64
53
|
ApprovalInteractionSchema,
|
|
65
54
|
ChoiceInteractionSchema,
|
|
66
55
|
ChecklistInteractionSchema,
|
|
67
|
-
TextInputInteractionSchema
|
|
68
|
-
ConfirmPreviewInteractionSchema
|
|
56
|
+
TextInputInteractionSchema
|
|
69
57
|
]);
|
|
70
58
|
var AgentActivityTypeSchema = z.enum(["thinking", "searching", "tool_use", "browsing", "processing"]);
|
|
71
59
|
var AgentTodoStatusSchema = z.enum(["pending", "in_progress", "done"]);
|
|
@@ -85,7 +73,10 @@ var CardFieldSchema = z.object({
|
|
|
85
73
|
value: z.string()
|
|
86
74
|
});
|
|
87
75
|
var CardSchema = z.object({
|
|
88
|
-
|
|
76
|
+
// Optional: a glance-facts card under a surface takes its heading from the
|
|
77
|
+
// surface `title`, so a card title is redundant there. Still allowed (e.g. a
|
|
78
|
+
// standalone card on a bubble, or a pinned card).
|
|
79
|
+
title: z.string().min(1).optional(),
|
|
89
80
|
fields: z.array(CardFieldSchema)
|
|
90
81
|
});
|
|
91
82
|
var AttachmentSchema = z.discriminatedUnion("type", [
|
|
@@ -93,6 +84,8 @@ var AttachmentSchema = z.discriminatedUnion("type", [
|
|
|
93
84
|
z.object({ type: z.literal("file"), url: z.string().url(), filename: z.string().optional() })
|
|
94
85
|
]);
|
|
95
86
|
var VesselStatusSchema = z.enum(["active", "waiting", "resolved"]);
|
|
87
|
+
var DisplaySchema = z.enum(["bubble", "document"]);
|
|
88
|
+
var KindSchema = z.enum(["bubble", "surface"]);
|
|
96
89
|
var PushPayloadSchema = z.object({
|
|
97
90
|
message: z.string().min(1).max(1e4).optional(),
|
|
98
91
|
vessel: z.string().optional(),
|
|
@@ -109,9 +102,23 @@ var PushPayloadSchema = z.object({
|
|
|
109
102
|
labels: z.array(z.string().min(1).max(50)).max(10).optional(),
|
|
110
103
|
attachments: z.array(AttachmentSchema).max(10).optional(),
|
|
111
104
|
suggestions: z.array(z.string().min(1).max(500)).max(5).optional(),
|
|
112
|
-
agentActivity: AgentActivitySchema.optional()
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
agentActivity: AgentActivitySchema.optional(),
|
|
106
|
+
/**
|
|
107
|
+
* Live token-stream buffer — an ephemeral monospace block the human watches
|
|
108
|
+
* fill in real time (set it on the message you create, then keep replacing it
|
|
109
|
+
* via `PATCH /messages/:id`, and clear it with `null` when done). It is a live
|
|
110
|
+
* window, not a transcript: send the tail you want shown (the SDK trims to the
|
|
111
|
+
* last 8000 chars). Plaintext, like agentActivity. Vanishes when cleared.
|
|
112
|
+
*/
|
|
113
|
+
tokenStream: z.string().max(8e3).optional(),
|
|
114
|
+
/** Bubble (chat) vs surface (composed artifact). Defaults from interaction/card. */
|
|
115
|
+
kind: KindSchema.optional(),
|
|
116
|
+
/** Surface heading. Ignored on bubbles. */
|
|
117
|
+
title: z.string().max(200).optional(),
|
|
118
|
+
/** @deprecated legacy presentation hint — use `kind`. 'document' → surface. */
|
|
119
|
+
display: DisplaySchema.optional()
|
|
120
|
+
}).refine((d) => d.message || d.agentActivity || d.tokenStream, {
|
|
121
|
+
message: "One of message, agentActivity, or tokenStream is required"
|
|
115
122
|
});
|
|
116
123
|
var PushManyPayloadSchema = z.object({
|
|
117
124
|
vessels: z.array(z.string().min(1)).min(1).max(100),
|
|
@@ -126,14 +133,26 @@ var PushManyPayloadSchema = z.object({
|
|
|
126
133
|
metadata: z.record(z.unknown()).refine(
|
|
127
134
|
(v) => JSON.stringify(v).length < 16e3,
|
|
128
135
|
"metadata exceeds 16KB limit"
|
|
129
|
-
).optional()
|
|
136
|
+
).optional(),
|
|
137
|
+
kind: KindSchema.optional(),
|
|
138
|
+
title: z.string().max(200).optional(),
|
|
139
|
+
/** @deprecated use `kind`. */
|
|
140
|
+
display: DisplaySchema.optional()
|
|
130
141
|
});
|
|
131
142
|
z.object({
|
|
132
143
|
content: z.string().min(1).max(1e4).optional(),
|
|
133
144
|
card: CardSchema.nullable().optional(),
|
|
134
145
|
attachments: z.array(AttachmentSchema).max(10).nullable().optional(),
|
|
135
146
|
suggestions: z.array(z.string().min(1).max(500)).max(5).nullable().optional(),
|
|
136
|
-
agentActivity: AgentActivitySchema.nullable().optional()
|
|
147
|
+
agentActivity: AgentActivitySchema.nullable().optional(),
|
|
148
|
+
/** Replace the live token-stream window, or `null` to clear it (block vanishes). */
|
|
149
|
+
tokenStream: z.string().max(8e3).nullable().optional(),
|
|
150
|
+
/** Switch kind: 'surface' = full-width artifact, 'bubble' or null = chat bubble. */
|
|
151
|
+
kind: KindSchema.nullable().optional(),
|
|
152
|
+
/** Update the surface heading, or `null` to clear it. */
|
|
153
|
+
title: z.string().max(200).nullable().optional(),
|
|
154
|
+
/** @deprecated use `kind`. */
|
|
155
|
+
display: DisplaySchema.nullable().optional()
|
|
137
156
|
}).refine((d) => Object.values(d).some((v) => v !== void 0), {
|
|
138
157
|
message: "At least one field required"
|
|
139
158
|
});
|
|
@@ -151,16 +170,11 @@ var ChecklistResponseSchema = z.object({
|
|
|
151
170
|
var TextInputResponseSchema = z.object({
|
|
152
171
|
text: z.string()
|
|
153
172
|
});
|
|
154
|
-
var ConfirmPreviewResponseSchema = z.object({
|
|
155
|
-
action: z.enum(["approved", "rejected"]),
|
|
156
|
-
reason: z.string().optional()
|
|
157
|
-
});
|
|
158
173
|
z.discriminatedUnion("interactionType", [
|
|
159
174
|
z.object({ interactionType: z.literal("approval"), response: ApprovalResponseSchema }),
|
|
160
175
|
z.object({ interactionType: z.literal("choice"), response: ChoiceResponseSchema }),
|
|
161
176
|
z.object({ interactionType: z.literal("checklist"), response: ChecklistResponseSchema }),
|
|
162
|
-
z.object({ interactionType: z.literal("text_input"), response: TextInputResponseSchema })
|
|
163
|
-
z.object({ interactionType: z.literal("confirm_preview"), response: ConfirmPreviewResponseSchema })
|
|
177
|
+
z.object({ interactionType: z.literal("text_input"), response: TextInputResponseSchema })
|
|
164
178
|
]);
|
|
165
179
|
var WebhookVesselSchema = z.object({
|
|
166
180
|
id: z.string(),
|
|
@@ -366,6 +380,31 @@ var Vessels = class {
|
|
|
366
380
|
replayed: res.headers.get("Idempotent-Replayed") === "true"
|
|
367
381
|
};
|
|
368
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Create a **surface** — a full-width composed artifact the human reviews and
|
|
385
|
+
* optionally acts on, rendered as one piece: a `title` heading, an optional
|
|
386
|
+
* `card` of glance-facts, a block-markdown `body` (tables, bullet/numbered
|
|
387
|
+
* lists, blockquotes, bold headings, links), and an optional `interaction`
|
|
388
|
+
* (the action bar). Use this for an email/message draft, a quote, an invoice
|
|
389
|
+
* review, a proposal, a report — anything substantial and structured.
|
|
390
|
+
*
|
|
391
|
+
* Sugar over {@link push}: it sets `kind: 'surface'` and maps `body` to the
|
|
392
|
+
* message content. For conversation, status updates and quick questions, use
|
|
393
|
+
* {@link push} instead (a chat **bubble** — the human just replies).
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* await vessels.surface({
|
|
397
|
+
* vessel: 'inv-42',
|
|
398
|
+
* title: 'GreenTurf Invoice #GTL-2025-042',
|
|
399
|
+
* card: { fields: [{ label: 'Amount Due', value: '$7,400' }] },
|
|
400
|
+
* body: 'Invoice is **$1,200 over** the usual rate.\n\n| Item | Amount |\n|---|---:|\n| Overage | +$1,200 |',
|
|
401
|
+
* interaction: vessels.approval({ prompt: 'Approve the $7,400 payment?' }),
|
|
402
|
+
* });
|
|
403
|
+
*/
|
|
404
|
+
async surface(options) {
|
|
405
|
+
const { body, ...rest } = options;
|
|
406
|
+
return this.push({ ...rest, message: body, kind: "surface" });
|
|
407
|
+
}
|
|
369
408
|
async pushMany(payload) {
|
|
370
409
|
const { idempotencyKey, ...body } = payload;
|
|
371
410
|
const check = PushManyPayloadSchema.safeParse(body);
|
|
@@ -454,6 +493,79 @@ var Vessels = class {
|
|
|
454
493
|
}
|
|
455
494
|
};
|
|
456
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Stream live tokens into a message: the human watches a monospace block fill
|
|
498
|
+
* in real time. Sugar over {@link editMessage} — it keeps the buffer locally
|
|
499
|
+
* and PATCHes a throttled, tail-trimmed window (replace-semantics, so a lost
|
|
500
|
+
* flush self-heals on the next one). Seal with `done()` to clear the block.
|
|
501
|
+
*
|
|
502
|
+
* @param messageId the message to stream into (create it first via `push`).
|
|
503
|
+
* @param opts.throttleMs minimum gap between server flushes (default 120ms).
|
|
504
|
+
* @param opts.maxChars longest window kept; older text scrolls off (default 8000).
|
|
505
|
+
*/
|
|
506
|
+
stream(messageId, opts) {
|
|
507
|
+
const throttleMs = opts?.throttleMs ?? 120;
|
|
508
|
+
const maxChars = opts?.maxChars ?? 8e3;
|
|
509
|
+
let buffer = "";
|
|
510
|
+
let lastSent = null;
|
|
511
|
+
let timer = null;
|
|
512
|
+
let pending = Promise.resolve();
|
|
513
|
+
const windowed = () => buffer.length > maxChars ? buffer.slice(-maxChars) : buffer;
|
|
514
|
+
const flushNow = () => {
|
|
515
|
+
const text = windowed();
|
|
516
|
+
if (text === lastSent) return pending;
|
|
517
|
+
lastSent = text;
|
|
518
|
+
pending = this.editMessage(messageId, { tokenStream: text }).catch(() => {
|
|
519
|
+
lastSent = null;
|
|
520
|
+
});
|
|
521
|
+
return pending;
|
|
522
|
+
};
|
|
523
|
+
const cancelTimer = () => {
|
|
524
|
+
if (timer) {
|
|
525
|
+
clearTimeout(timer);
|
|
526
|
+
timer = null;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
const schedule = () => {
|
|
530
|
+
if (timer) return;
|
|
531
|
+
timer = setTimeout(() => {
|
|
532
|
+
timer = null;
|
|
533
|
+
void flushNow();
|
|
534
|
+
}, throttleMs);
|
|
535
|
+
timer?.unref?.();
|
|
536
|
+
};
|
|
537
|
+
const settle = async () => {
|
|
538
|
+
cancelTimer();
|
|
539
|
+
await pending.catch(() => {
|
|
540
|
+
});
|
|
541
|
+
};
|
|
542
|
+
return {
|
|
543
|
+
write: (text) => {
|
|
544
|
+
buffer += text;
|
|
545
|
+
schedule();
|
|
546
|
+
},
|
|
547
|
+
set: async (text) => {
|
|
548
|
+
buffer = text;
|
|
549
|
+
cancelTimer();
|
|
550
|
+
await flushNow();
|
|
551
|
+
},
|
|
552
|
+
// Clear the stream AND seal any working card (agentActivity: null is a no-op
|
|
553
|
+
// when there's none), so the final content actually renders.
|
|
554
|
+
done: async (finalContent) => {
|
|
555
|
+
await settle();
|
|
556
|
+
await this.editMessage(messageId, {
|
|
557
|
+
tokenStream: null,
|
|
558
|
+
agentActivity: null,
|
|
559
|
+
...finalContent != null ? { content: finalContent } : {}
|
|
560
|
+
});
|
|
561
|
+
},
|
|
562
|
+
// Remove only the stream; leave the working card alone.
|
|
563
|
+
clear: async () => {
|
|
564
|
+
await settle();
|
|
565
|
+
await this.editMessage(messageId, { tokenStream: null });
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
457
569
|
/**
|
|
458
570
|
* Read a vessel's message history — the human-facing record, for re-reading
|
|
459
571
|
* the channel (e.g. a stateless or just-restarted worker reconciling state).
|
|
@@ -499,9 +611,6 @@ var Vessels = class {
|
|
|
499
611
|
textInput(opts) {
|
|
500
612
|
return { type: "text_input", ...opts };
|
|
501
613
|
}
|
|
502
|
-
confirmPreview(opts) {
|
|
503
|
-
return { type: "confirm_preview", ...opts };
|
|
504
|
-
}
|
|
505
614
|
async poll(options = {}) {
|
|
506
615
|
const { since, limit = 50, ack = true } = options;
|
|
507
616
|
const params = new URLSearchParams();
|