@ziggs-ai/api-client 0.42.1 → 0.42.2
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/CHANGELOG.md +4 -0
- package/dist/capabilities/buckets.js +49 -6
- package/dist/http/BucketsClient.d.ts +11 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.42.2
|
|
6
|
+
|
|
7
|
+
- `bucket_send` sends a page: `page` (a full HTML document) and `pageName` save the HTML as a file, and carry that exact version as the message's `page` (with its sha256); whoever reads the message opens the page, so nothing is shared. Without `replyTo` the message starts a thread of its own, which shows as that page; another page sent in the thread (`replyTo`) is its new version, and the tool says a new page names no `replyTo`, even in answer to someone. The result carries `page: {artifactId, address}`, the address being `/pages/<thread>`.
|
|
8
|
+
|
|
5
9
|
## 0.42.1
|
|
6
10
|
|
|
7
11
|
## 0.42.0
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BucketsClient, BUCKET_ITEM_KINDS, } from '../http/BucketsClient.js';
|
|
2
|
+
import { ArtifactsClient } from '../http/ArtifactsClient.js';
|
|
2
3
|
import { fullCreds, rethrowWithContext } from './types.js';
|
|
3
4
|
/**
|
|
4
5
|
* The bucket operations as tools, one per route.
|
|
@@ -236,20 +237,25 @@ export const bucketSendCapability = {
|
|
|
236
237
|
names: { sdk: 'bucket_send', mcp: 'ziggs_bucket_send' },
|
|
237
238
|
title: 'Send in a bucket',
|
|
238
239
|
descriptions: {
|
|
239
|
-
sdk: "Send a message inside a bucket. Its readers are the bucket's people; name recipients among them. To reach anyone else, add them with bucket_add_members first. A request wakes the agents it is delivered to.",
|
|
240
|
+
sdk: "Send a message inside a bucket. Its readers are the bucket's people; name recipients among them. To reach anyone else, add them with bucket_add_members first. A request wakes the agents it is delivered to. With page, the message carries an HTML page: its thread shows as that page.",
|
|
240
241
|
},
|
|
241
242
|
annotation: 'destructive',
|
|
242
243
|
needsAgentId: true,
|
|
243
244
|
params: {
|
|
244
245
|
bucketId: BUCKET_ID,
|
|
245
246
|
text: { type: 'string', required: true },
|
|
247
|
+
page: {
|
|
248
|
+
type: 'string',
|
|
249
|
+
description: "A full HTML document (<!DOCTYPE html>), saved as a file that goes with the message. Without replyTo it starts a thread of its own, shown as the page; with replyTo that page's first message, it replaces that page.",
|
|
250
|
+
},
|
|
251
|
+
pageName: { type: 'string', description: 'Short, stable name of the page.' },
|
|
246
252
|
recipients: {
|
|
247
253
|
type: 'array',
|
|
248
254
|
items: { type: 'string' },
|
|
249
255
|
description: 'Who is told, among the bucket\'s people. Under work, name at least one person the work is with.',
|
|
250
256
|
},
|
|
251
257
|
intent: { type: 'string', enum: ['fyi', 'request'] },
|
|
252
|
-
replyTo: { type: 'string' },
|
|
258
|
+
replyTo: { type: 'string', description: 'The message this answers. Not for a new page.' },
|
|
253
259
|
operationId: { type: 'string', description: 'Reuse on retry.' },
|
|
254
260
|
agreementId: {
|
|
255
261
|
type: 'string',
|
|
@@ -257,7 +263,7 @@ export const bucketSendCapability = {
|
|
|
257
263
|
},
|
|
258
264
|
taskId: { type: 'string', description: 'The task under agreementId this is about.' },
|
|
259
265
|
},
|
|
260
|
-
handler: (args, env) => run('bucket_send', () => {
|
|
266
|
+
handler: (args, env) => run('bucket_send', async () => {
|
|
261
267
|
const text = args['text'];
|
|
262
268
|
if (typeof text !== 'string' || !text.trim())
|
|
263
269
|
throw new Error('text is required');
|
|
@@ -267,17 +273,54 @@ export const bucketSendCapability = {
|
|
|
267
273
|
const replyTo = args['replyTo'];
|
|
268
274
|
if (replyTo !== undefined && (typeof replyTo !== 'string' || !replyTo.trim()))
|
|
269
275
|
throw new Error('replyTo is a message id');
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
276
|
+
const named = args['operationId'];
|
|
277
|
+
const operationId = typeof named === 'string' && named.trim() ? named.trim() : `bucket-send-${globalThis.crypto.randomUUID()}`;
|
|
278
|
+
const page = await pageOf(args, env, operationId);
|
|
279
|
+
const sent = await client(env).sendMessage(bucketId(args), {
|
|
280
|
+
operationId,
|
|
273
281
|
content: { text },
|
|
274
282
|
deliveries: { kind: 'explicit', principalIds: principals(args['recipients'], 'recipients') ?? [] },
|
|
275
283
|
intent,
|
|
276
284
|
...(typeof replyTo === 'string' ? { relationships: { replyToMessageId: replyTo.trim() } } : {}),
|
|
277
285
|
...mandateOf(args, env),
|
|
286
|
+
// The message carries the page. Without replyTo it starts a thread of
|
|
287
|
+
// its own. Whoever reads the message opens the page: no share needed.
|
|
288
|
+
...(page ? { page } : {}),
|
|
278
289
|
});
|
|
290
|
+
return page ? { ...sent, page: { artifactId: page.id, address: `/pages/${sent.message.rootMessageId}` } } : sent;
|
|
279
291
|
}),
|
|
280
292
|
};
|
|
293
|
+
/**
|
|
294
|
+
* The page a message carries: the HTML saved as a file, then linked by its
|
|
295
|
+
* exact version. The file is saved under the same retry identity as the
|
|
296
|
+
* message, so a retry links the same file.
|
|
297
|
+
*/
|
|
298
|
+
async function pageOf(args, env, operationId) {
|
|
299
|
+
const html = args['page'];
|
|
300
|
+
if (html === undefined)
|
|
301
|
+
return null;
|
|
302
|
+
if (typeof html !== 'string' || !/^\s*(?:<!doctype\s+html|<html)(?:\s|>)/i.test(html)) {
|
|
303
|
+
throw new Error('page is a full HTML document starting with <!DOCTYPE html>');
|
|
304
|
+
}
|
|
305
|
+
const name = typeof args['pageName'] === 'string' ? args['pageName'].trim() : '';
|
|
306
|
+
if (!name)
|
|
307
|
+
throw new Error('pageName is required with page');
|
|
308
|
+
const creds = fullCreds(env);
|
|
309
|
+
// The stored text is trimmed; the link names exactly what is stored.
|
|
310
|
+
const text = html.trim();
|
|
311
|
+
const { artifactId } = await new ArtifactsClient(creds.operatorKey, creds.agentId).writeStrict({
|
|
312
|
+
text,
|
|
313
|
+
name,
|
|
314
|
+
contentType: 'document',
|
|
315
|
+
visibility: 'address',
|
|
316
|
+
serviceAgreementId: env.serviceAgreementId,
|
|
317
|
+
idempotencyKey: `page-${operationId}`,
|
|
318
|
+
});
|
|
319
|
+
if (!artifactId)
|
|
320
|
+
throw new Error('The page file was not saved');
|
|
321
|
+
const { createHash } = await import('node:crypto');
|
|
322
|
+
return { kind: 'artifact', id: artifactId, sha256: createHash('sha256').update(text, 'utf8').digest('hex') };
|
|
323
|
+
}
|
|
281
324
|
/**
|
|
282
325
|
* The work a send is under. Work under a hire keeps its mandate: the server
|
|
283
326
|
* refuses a run's send that drops it. Named in the call, else the agreement
|
|
@@ -122,8 +122,17 @@ export interface BucketContents {
|
|
|
122
122
|
/** Items held that the reader cannot read here, as they ask now. */
|
|
123
123
|
outOfReach: number;
|
|
124
124
|
}
|
|
125
|
-
/**
|
|
126
|
-
|
|
125
|
+
/**
|
|
126
|
+
* A message sent inside a bucket: its readers are the bucket's people. `page`
|
|
127
|
+
* is the HTML file it carries, by its exact version (contracts 0.49).
|
|
128
|
+
*/
|
|
129
|
+
export type BucketMessage = Omit<MessageCommand, 'audience'> & {
|
|
130
|
+
page?: {
|
|
131
|
+
kind: 'artifact';
|
|
132
|
+
id: string;
|
|
133
|
+
sha256: string;
|
|
134
|
+
};
|
|
135
|
+
};
|
|
127
136
|
export interface BucketItemPage {
|
|
128
137
|
/** Items everyone it is for can read right now. */
|
|
129
138
|
items: Array<BucketItemRef & {
|