@aithos/sdk 0.1.0-alpha.5 → 0.1.0-alpha.50

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.
Files changed (67) hide show
  1. package/README.md +245 -7
  2. package/dist/src/apps.d.ts +224 -0
  3. package/dist/src/apps.js +432 -0
  4. package/dist/src/assets.d.ts +208 -0
  5. package/dist/src/assets.js +534 -0
  6. package/dist/src/auth-api.d.ts +219 -0
  7. package/dist/src/auth-api.js +248 -0
  8. package/dist/src/auth.d.ts +543 -0
  9. package/dist/src/auth.js +937 -31
  10. package/dist/src/compute.d.ts +464 -6
  11. package/dist/src/compute.js +746 -20
  12. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  13. package/dist/src/data-schema-contacts-v1.js +28 -0
  14. package/dist/src/data.d.ts +342 -0
  15. package/dist/src/data.js +1002 -0
  16. package/dist/src/endpoints.d.ts +18 -0
  17. package/dist/src/endpoints.js +6 -0
  18. package/dist/src/ethos.d.ts +85 -0
  19. package/dist/src/ethos.js +463 -7
  20. package/dist/src/index.d.ts +17 -6
  21. package/dist/src/index.js +25 -3
  22. package/dist/src/internal/delegate-bundle.js +7 -2
  23. package/dist/src/internal/envelope.d.ts +93 -0
  24. package/dist/src/internal/envelope.js +59 -0
  25. package/dist/src/mandates.d.ts +111 -2
  26. package/dist/src/mandates.js +150 -7
  27. package/dist/src/react/AithosAsset.d.ts +66 -0
  28. package/dist/src/react/AithosAsset.js +67 -0
  29. package/dist/src/react/context.d.ts +29 -0
  30. package/dist/src/react/context.js +31 -0
  31. package/dist/src/react/index.d.ts +29 -0
  32. package/dist/src/react/index.js +31 -0
  33. package/dist/src/react/use-aithos-asset.d.ts +39 -0
  34. package/dist/src/react/use-aithos-asset.js +118 -0
  35. package/dist/src/react/use-transcribe-pending.d.ts +21 -0
  36. package/dist/src/react/use-transcribe-pending.js +47 -0
  37. package/dist/src/sdk.d.ts +10 -0
  38. package/dist/src/sdk.js +22 -0
  39. package/dist/src/transcribe-resilience.d.ts +57 -0
  40. package/dist/src/transcribe-resilience.js +203 -0
  41. package/dist/src/web.d.ts +279 -0
  42. package/dist/src/web.js +186 -0
  43. package/dist/test/auth-j3.test.js +32 -1
  44. package/dist/test/canonical-conformance.test.d.ts +2 -0
  45. package/dist/test/canonical-conformance.test.js +86 -0
  46. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  47. package/dist/test/compute-delegate-path.test.js +183 -0
  48. package/dist/test/compute.test.js +4 -0
  49. package/dist/test/endpoints.test.js +25 -1
  50. package/dist/test/envelope-core-conformance.test.d.ts +2 -0
  51. package/dist/test/envelope-core-conformance.test.js +75 -0
  52. package/dist/test/envelope.test.d.ts +2 -0
  53. package/dist/test/envelope.test.js +318 -0
  54. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  55. package/dist/test/ethos-first-edition.test.js +371 -0
  56. package/dist/test/mandates-compute.test.d.ts +2 -0
  57. package/dist/test/mandates-compute.test.js +256 -0
  58. package/dist/test/sdk.test.js +10 -2
  59. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  60. package/dist/test/signup-bootstrap.test.js +311 -0
  61. package/dist/test/transcribe-invoke.test.d.ts +2 -0
  62. package/dist/test/transcribe-invoke.test.js +204 -0
  63. package/dist/test/transcribe.test.d.ts +2 -0
  64. package/dist/test/transcribe.test.js +186 -0
  65. package/dist/test/web.test.d.ts +2 -0
  66. package/dist/test/web.test.js +270 -0
  67. package/package.json +20 -3
@@ -0,0 +1,432 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // `sdk.apps` namespace — sponsorship lifecycle for app developers.
4
+ //
5
+ // V0.1 surface (2026-05-27) — minimal but sufficient to bootstrap a free
6
+ // trial flow on Linkedone and similar apps:
7
+ //
8
+ // createSponsorshipMandate(args)
9
+ // Build + sign a SponsorshipMandate as the calling owner. Returns
10
+ // the signed JSON. The caller is responsible for getting the row
11
+ // into the authority's `aithos-app-sponsorships` table — V0.1 has
12
+ // no server endpoint for it (manual `aws dynamodb put-item` or a
13
+ // Terraform-managed seed). V0.2 will add a server endpoint.
14
+ //
15
+ // revokeSponsorshipMandate(mandate, reason)
16
+ // Build + sign a §4.6 revocation document targeting the mandate.
17
+ // Same upload caveat as create — V0.1 has no server endpoint.
18
+ //
19
+ // createAppTopupSession(args)
20
+ // Stripe Checkout session for an `app-credits-*` pack. Reuses the
21
+ // existing wallet-topup endpoint; the difference is the pack id
22
+ // and the DID being credited (the app's, not the user's).
23
+ //
24
+ // Why no `getSponsorshipStatus*` in V0.1: the read side requires either
25
+ // a CloudFront-fronted read endpoint or direct DDB access. We'll add it
26
+ // in V0.2 once the sponsorship-api Lambda exists; for now, devs check
27
+ // their sponsorship via the AWS Console.
28
+ import { base64url, buildSignedEnvelope, sign } from "@aithos/protocol-client";
29
+ import { canonicalize } from "@aithos/protocol-core/canonical";
30
+ import { computeInvokeUrl, walletTopupCheckoutUrl } from "./endpoints.js";
31
+ import { ownerKeyPair } from "./internal/protocol-client-bridge.js";
32
+ import { AithosSDKError } from "./types.js";
33
+ /* -------------------------------------------------------------------------- */
34
+ /* Class */
35
+ /* -------------------------------------------------------------------------- */
36
+ export class AppsNamespace {
37
+ #deps;
38
+ constructor(deps) {
39
+ this.#deps = deps;
40
+ }
41
+ /**
42
+ * Build and sign a `SponsorshipMandate` as the calling owner. The
43
+ * returned JSON is signed by the owner's `#public` sphere key, ready
44
+ * to be seeded into the authority's `aithos-app-sponsorships` table.
45
+ *
46
+ * V0.1 has no server endpoint — get the row into DDB via:
47
+ * 1. `aws dynamodb put-item --table-name aithos-app-sponsorships
48
+ * --item file://mandate.json` (raw), or
49
+ * 2. The ops bootstrap script in
50
+ * `innoesate/aithos/platform/scripts/seed-sponsorship.mjs`
51
+ * (planned for V0.2).
52
+ *
53
+ * Hash with `sponsorshipMandateHash()` from `@aithos/protocol-core`
54
+ * if you need to embed it in an envelope's `sponsorship.hash` field.
55
+ */
56
+ async createSponsorshipMandate(args) {
57
+ const owner = this.#requireOwner();
58
+ validateBudget(args.budget);
59
+ validateAudience(args.audience);
60
+ if (args.scopes.length === 0) {
61
+ throw new AithosSDKError("invalid_input", "scopes must be non-empty");
62
+ }
63
+ if (args.allowedMethods.length === 0) {
64
+ throw new AithosSDKError("invalid_input", "allowedMethods must be non-empty");
65
+ }
66
+ const nb = args.notBefore ?? new Date();
67
+ const ttl = args.ttlSeconds ?? 365 * 24 * 3600;
68
+ const na = new Date(nb.getTime() + ttl * 1000);
69
+ if (na <= nb) {
70
+ throw new AithosSDKError("invalid_input", "ttlSeconds must produce not_after > not_before");
71
+ }
72
+ const nonce = base64url(randomBytes(9));
73
+ const unsigned = {
74
+ "aithos-sponsorship-mandate": "0.1.0",
75
+ id: `spons_${ulid()}`,
76
+ issuer: owner.did,
77
+ issued_by_key: `${owner.did}#public`,
78
+ audience: {
79
+ app_did: args.audience.appDid,
80
+ audience_set: args.audience.audienceSet,
81
+ ...(args.audience.consumers !== undefined
82
+ ? { consumers: [...args.audience.consumers] }
83
+ : {}),
84
+ },
85
+ scopes: [...args.scopes],
86
+ allowed_methods: [...args.allowedMethods],
87
+ ...(args.allowedModels ? { allowed_models: [...args.allowedModels] } : {}),
88
+ budget: {
89
+ unit: args.budget.unit ?? "aithos.mc",
90
+ per_user_cap: args.budget.perUserCap,
91
+ per_user_window_seconds: args.budget.perUserWindowSeconds === undefined
92
+ ? null
93
+ : args.budget.perUserWindowSeconds,
94
+ per_day_total_cap: args.budget.perDayTotalCap,
95
+ pool_cap_total: args.budget.poolCapTotal === undefined
96
+ ? null
97
+ : args.budget.poolCapTotal,
98
+ },
99
+ accounting_authority: { ...args.accountingAuthority },
100
+ not_before: nb.toISOString(),
101
+ not_after: na.toISOString(),
102
+ issued_at: new Date().toISOString(),
103
+ nonce,
104
+ signature: { alg: "ed25519", key: `${owner.did}#public`, value: "" },
105
+ };
106
+ const bytes = new TextEncoder().encode(jcsCanonicalize(unsigned));
107
+ const ownerPub = ownerKeyPair(owner, "public");
108
+ const sigBytes = sign(bytes, ownerPub.seed);
109
+ return {
110
+ ...unsigned,
111
+ signature: { ...unsigned.signature, value: base64url(sigBytes) },
112
+ };
113
+ }
114
+ /**
115
+ * Build and sign a §4.6 revocation document targeting a sponsorship
116
+ * mandate. Same upload caveat as `createSponsorshipMandate`.
117
+ */
118
+ async revokeSponsorshipMandate(mandate, reason) {
119
+ const owner = this.#requireOwner();
120
+ if (mandate.issuer !== owner.did) {
121
+ throw new AithosSDKError("invalid_input", `mandate.issuer (${mandate.issuer}) does not match signed-in owner (${owner.did}) — only the sponsor can revoke`);
122
+ }
123
+ const unsigned = {
124
+ "aithos-revocation": "0.1.0",
125
+ mandate_id: mandate.id,
126
+ mandate_kind: "sponsorship-mandate",
127
+ issuer: owner.did,
128
+ issued_by_key: `${owner.did}#public`,
129
+ revoked_at: new Date().toISOString(),
130
+ reason,
131
+ signature: { alg: "ed25519", key: `${owner.did}#public`, value: "" },
132
+ };
133
+ const bytes = new TextEncoder().encode(jcsCanonicalize(unsigned));
134
+ const ownerPub = ownerKeyPair(owner, "public");
135
+ const sigBytes = sign(bytes, ownerPub.seed);
136
+ return {
137
+ ...unsigned,
138
+ signature: { ...unsigned.signature, value: base64url(sigBytes) },
139
+ };
140
+ }
141
+ /**
142
+ * Stripe Checkout session for an `app-credits-*` pack. The session is
143
+ * bound to the calling owner's DID (which is treated as the app's
144
+ * funding DID — Option A unified wallet, see plan §3.4).
145
+ *
146
+ * Same endpoint and auth pattern as `sdk.wallet.createTopupSession`,
147
+ * just with the app-credits pack id family.
148
+ */
149
+ async createAppTopupSession(args) {
150
+ const { auth, endpoints, fetch: fetchImpl } = this.#deps;
151
+ const owner = auth._getOwnerSigners();
152
+ if (!owner || owner.destroyed) {
153
+ throw new AithosSDKError("sdk_no_owner", "no owner signed in; sign in first");
154
+ }
155
+ const url = walletTopupCheckoutUrl(endpoints);
156
+ let res;
157
+ try {
158
+ res = await fetchImpl(url, {
159
+ method: "POST",
160
+ headers: { "content-type": "application/json" },
161
+ body: JSON.stringify({
162
+ // Same endpoint as user-pack top-ups. The Lambda's pack-table
163
+ // distinguishes `app-credits-*` from `credits-*` and routes
164
+ // the credit to the same `aithos-wallet-user` row keyed on
165
+ // `user_did`, which for an app is its DID (= sdk.appDid).
166
+ user_did: this.#deps.appDid,
167
+ pack_id: args.packId,
168
+ success_url: args.successUrl,
169
+ cancel_url: args.cancelUrl,
170
+ }),
171
+ ...(args.signal ? { signal: args.signal } : {}),
172
+ });
173
+ }
174
+ catch (e) {
175
+ throw new AithosSDKError("network", e.message);
176
+ }
177
+ let body;
178
+ try {
179
+ body = await res.json();
180
+ }
181
+ catch {
182
+ throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText} — non-JSON response`, { status: res.status });
183
+ }
184
+ if (!res.ok) {
185
+ const err = body;
186
+ throw new AithosSDKError(err.error ?? "http", err.detail ?? `HTTP ${res.status} ${res.statusText}`, { status: res.status });
187
+ }
188
+ const ok = body;
189
+ if (typeof ok.checkout_url !== "string" ||
190
+ typeof ok.session_id !== "string") {
191
+ throw new AithosSDKError("empty", "wallet response missing checkout_url or session_id");
192
+ }
193
+ return { checkoutUrl: ok.checkout_url, sessionId: ok.session_id };
194
+ }
195
+ /**
196
+ * Build, sign, and PUBLISH a SponsorshipMandate to the authority's
197
+ * `aithos-app-sponsorships` table via `aithos.sponsorship_create`.
198
+ *
199
+ * Combines local signing (see {@link createSponsorshipMandate}) with the
200
+ * server upload step. After this resolves, the sponsorship is active —
201
+ * the next `sdk.compute.invokeBedrock` call targeting `args.audience.appDid`
202
+ * may be sponsored (subject to caps).
203
+ *
204
+ * Requires that the calling owner is the registered `owner_did` of
205
+ * `args.audience.appDid` in `aithos-auth-apps`; otherwise the server
206
+ * rejects with `-32042` "not the owner".
207
+ */
208
+ async publishSponsorship(args) {
209
+ const mandate = await this.createSponsorshipMandate(args);
210
+ const result = await this.#signedRpc({
211
+ method: "aithos.sponsorship_create",
212
+ params: { mandate },
213
+ });
214
+ return {
215
+ mandate,
216
+ sponsorshipId: result.sponsorship_id,
217
+ mandateHash: result.mandate_hash,
218
+ status: result.status,
219
+ createdAt: result.created_at,
220
+ };
221
+ }
222
+ /**
223
+ * Read the active sponsorship for an app. Open endpoint (no envelope
224
+ * required): the mandate is publicly signed and resolvable anyway.
225
+ *
226
+ * Returns `{ exists: false }` when no row exists for the app.
227
+ */
228
+ async getSponsorship(args) {
229
+ const { endpoints, fetch: fetchImpl } = this.#deps;
230
+ const url = computeInvokeUrl(endpoints);
231
+ let res;
232
+ try {
233
+ res = await fetchImpl(url, {
234
+ method: "POST",
235
+ headers: { "content-type": "application/json" },
236
+ body: JSON.stringify({
237
+ jsonrpc: "2.0",
238
+ id: "aithos.sponsorship_get",
239
+ method: "aithos.sponsorship_get",
240
+ params: { app_did: args.appDid },
241
+ }),
242
+ ...(args.signal ? { signal: args.signal } : {}),
243
+ });
244
+ }
245
+ catch (e) {
246
+ throw new AithosSDKError("network", e.message);
247
+ }
248
+ const body = (await res.json());
249
+ if (body.error) {
250
+ throw new AithosSDKError(String(body.error.code), body.error.message);
251
+ }
252
+ const r = body.result ?? {};
253
+ if (r.exists === false || !r.exists) {
254
+ return { exists: false };
255
+ }
256
+ return {
257
+ exists: true,
258
+ sponsorshipId: r.sponsorship_id,
259
+ mandate: r.mandate,
260
+ mandateHash: r.mandate_hash,
261
+ status: r.status,
262
+ poolConsumedLifetime: r.pool_consumed_lifetime ?? 0,
263
+ createdAt: r.created_at ?? 0,
264
+ lastUpdatedAt: r.last_updated_at ?? 0,
265
+ };
266
+ }
267
+ /**
268
+ * Revoke a published sponsorship by `app_did` + `sponsorship_id`. The
269
+ * row is marked `status: "revoked"` server-side; the routing cache is
270
+ * invalidated so the next compute call falls back to the user wallet.
271
+ */
272
+ async revokeSponsorship(args) {
273
+ const result = await this.#signedRpc({
274
+ method: "aithos.sponsorship_revoke",
275
+ params: {
276
+ app_did: args.appDid,
277
+ sponsorship_id: args.sponsorshipId,
278
+ reason: args.reason,
279
+ },
280
+ ...(args.signal ? { signal: args.signal } : {}),
281
+ });
282
+ return result;
283
+ }
284
+ /**
285
+ * Read the app's wallet balance (= the sponsor pool). Requires owner
286
+ * signature.
287
+ */
288
+ async getAppWalletBalance(args) {
289
+ const result = await this.#signedRpc({
290
+ method: "aithos.app_wallet_get_balance",
291
+ params: { app_did: args.appDid },
292
+ ...(args.signal ? { signal: args.signal } : {}),
293
+ });
294
+ return {
295
+ appDid: result.app_did,
296
+ exists: result.exists,
297
+ balance: result.balance,
298
+ balancePurchase: result.balance_purchase,
299
+ balanceGrant: result.balance_grant,
300
+ dailySpent: result.daily_spent,
301
+ };
302
+ }
303
+ /**
304
+ * Internal: sign an envelope with the calling owner's `#public` sphere
305
+ * and POST a JSON-RPC request to the compute proxy. Used by the
306
+ * sponsorship CRUD methods above. Mirrors `ComputeNamespace.#signAndPost`.
307
+ */
308
+ async #signedRpc(opts) {
309
+ const { endpoints, fetch: fetchImpl } = this.#deps;
310
+ const owner = this.#requireOwner();
311
+ const publicKp = ownerKeyPair(owner, "public");
312
+ const url = computeInvokeUrl(endpoints);
313
+ const envelope = buildSignedEnvelope({
314
+ iss: owner.did,
315
+ aud: url,
316
+ method: opts.method,
317
+ verificationMethod: `${owner.did}#public`,
318
+ params: opts.params,
319
+ signer: publicKp,
320
+ });
321
+ let res;
322
+ try {
323
+ res = await fetchImpl(url, {
324
+ method: "POST",
325
+ headers: { "content-type": "application/json" },
326
+ body: JSON.stringify({
327
+ jsonrpc: "2.0",
328
+ id: opts.method,
329
+ method: opts.method,
330
+ params: { ...opts.params, _envelope: envelope },
331
+ }),
332
+ ...(opts.signal ? { signal: opts.signal } : {}),
333
+ });
334
+ }
335
+ catch (e) {
336
+ throw new AithosSDKError("network", e.message);
337
+ }
338
+ if (!res.ok) {
339
+ throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText}`, { status: res.status });
340
+ }
341
+ const body = (await res.json());
342
+ if (body.error) {
343
+ throw new AithosSDKError(String(body.error.code), body.error.message, body.error.data ? { data: body.error.data } : undefined);
344
+ }
345
+ if (body.result === undefined) {
346
+ throw new AithosSDKError("empty", `empty result for ${opts.method}`);
347
+ }
348
+ return body.result;
349
+ }
350
+ #requireOwner() {
351
+ const owner = this.#deps.auth._getOwnerSigners();
352
+ if (!owner || owner.destroyed) {
353
+ throw new AithosSDKError("sdk_no_owner", "no owner signed in; sign in first");
354
+ }
355
+ return owner;
356
+ }
357
+ }
358
+ /* -------------------------------------------------------------------------- */
359
+ /* Validation */
360
+ /* -------------------------------------------------------------------------- */
361
+ function validateBudget(b) {
362
+ if (!Number.isInteger(b.perUserCap) || b.perUserCap < 0) {
363
+ throw new AithosSDKError("invalid_input", "budget.perUserCap must be a non-negative integer");
364
+ }
365
+ if (!Number.isInteger(b.perDayTotalCap) || b.perDayTotalCap < 0) {
366
+ throw new AithosSDKError("invalid_input", "budget.perDayTotalCap must be a non-negative integer");
367
+ }
368
+ if (b.perUserWindowSeconds !== undefined &&
369
+ b.perUserWindowSeconds !== null) {
370
+ if (!Number.isInteger(b.perUserWindowSeconds) ||
371
+ b.perUserWindowSeconds <= 0) {
372
+ throw new AithosSDKError("invalid_input", "budget.perUserWindowSeconds must be null or a positive integer");
373
+ }
374
+ }
375
+ if (b.poolCapTotal !== undefined && b.poolCapTotal !== null) {
376
+ if (!Number.isInteger(b.poolCapTotal) || b.poolCapTotal < 0) {
377
+ throw new AithosSDKError("invalid_input", "budget.poolCapTotal must be null or a non-negative integer");
378
+ }
379
+ }
380
+ }
381
+ function validateAudience(a) {
382
+ if (!a.appDid || typeof a.appDid !== "string") {
383
+ throw new AithosSDKError("invalid_input", "audience.appDid must be a DID string");
384
+ }
385
+ if (a.audienceSet === "open") {
386
+ if (a.consumers !== undefined) {
387
+ throw new AithosSDKError("invalid_input", "audience.consumers MUST be absent when audienceSet is 'open'");
388
+ }
389
+ }
390
+ else if (a.audienceSet === "list") {
391
+ if (!Array.isArray(a.consumers) || a.consumers.length === 0) {
392
+ throw new AithosSDKError("invalid_input", "audience.consumers MUST be a non-empty array when audienceSet is 'list'");
393
+ }
394
+ }
395
+ else {
396
+ throw new AithosSDKError("invalid_input", `audience.audienceSet must be 'open' or 'list' (got: ${String(a.audienceSet)})`);
397
+ }
398
+ }
399
+ /* -------------------------------------------------------------------------- */
400
+ /* ULID + JCS — local copies to avoid a runtime dep */
401
+ /* -------------------------------------------------------------------------- */
402
+ const ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
403
+ function ulid(now = Date.now()) {
404
+ // 10 chars timestamp + 16 chars random = 26 chars
405
+ let timePart = "";
406
+ let t = now;
407
+ for (let i = 0; i < 10; i++) {
408
+ const mod = t % 32;
409
+ timePart = ULID_ALPHABET[mod] + timePart;
410
+ t = (t - mod) / 32;
411
+ }
412
+ let randPart = "";
413
+ for (let i = 0; i < 16; i++) {
414
+ randPart += ULID_ALPHABET[Math.floor(Math.random() * 32)];
415
+ }
416
+ return timePart + randPart;
417
+ }
418
+ function randomBytes(n) {
419
+ const out = new Uint8Array(n);
420
+ crypto.getRandomValues(out);
421
+ return out;
422
+ }
423
+ /**
424
+ * Minimal RFC-8785 (JCS) canonicalization. Sorts object keys
425
+ * lexicographically by code point, no whitespace, JSON.stringify for
426
+ * primitives. Matches the protocol-core canonicalize used to compute
427
+ * the hash the signature commits to.
428
+ */
429
+ function jcsCanonicalize(value) {
430
+ return canonicalize(value);
431
+ }
432
+ //# sourceMappingURL=apps.js.map
@@ -0,0 +1,208 @@
1
+ /**
2
+ * `sdk.assets` — high-level API for the Aithos assets sub-protocol PDS.
3
+ *
4
+ * Stores binary content (images, PDFs, audio, video) owned by a
5
+ * subject, encrypted client-side under per-asset AMKs (Asset Master
6
+ * Keys), accessible to authorized apps via signed mandates (v0.2).
7
+ *
8
+ * const assets = sdk.assets;
9
+ * const avatar = await assets.upload({
10
+ * bytes: pngBuffer,
11
+ * mediaType: "image/png",
12
+ * attachTo: { ethos: { zone: "public", sectionId: "sec_identity" } },
13
+ * });
14
+ * // avatar.url is a stable CloudFront URL (public asset)
15
+ *
16
+ * const cv = await assets.upload({
17
+ * bytes: pdfBuffer,
18
+ * mediaType: "application/pdf",
19
+ * attachTo: { ethos: { zone: "circle", sectionId: "sec_career_docs" } },
20
+ * });
21
+ * // cv is private: stored encrypted, fetched via short-lived presigned URL.
22
+ *
23
+ * const bytes = await assets.fetch(cv.urn);
24
+ * // decrypted plaintext returned to the caller
25
+ *
26
+ * The module wires:
27
+ * - AMK generation + wrap (via @aithos/assets-crypto)
28
+ * - Bytes encryption with the canonical nonce-prefix on-disk layout
29
+ * - RecipientResolver (v0.2-ethos: maps {zone} → recipient set)
30
+ * - Direct S3 PUT against the presigned URL returned by init_upload
31
+ * - In-memory AMK cache (per asset URN) for sub-second re-fetches
32
+ * - Signed envelope JSON-RPC dispatch to /mcp/primitives/{read,write}
33
+ *
34
+ * Spec ref: spec/assets/ in the aithos-protocol repo.
35
+ */
36
+ import { type AssetMetadata, type AssetReference } from "@aithos/assets-crypto";
37
+ export interface CreateAssetsClientArgs {
38
+ /** Base URL of the assets PDS. Defaults to `https://pds.aithos.be` (the
39
+ * production vanity domain) when omitted. Override for self-hosting/staging. */
40
+ readonly pdsUrl?: string;
41
+ /** Subject DID. */
42
+ readonly did: string;
43
+ /** Ed25519 sphere seed (32 bytes). */
44
+ readonly sphereSeed: Uint8Array;
45
+ /** Verification method URL within the DID document (e.g. `<did>#<multibase>` for did:key). */
46
+ readonly verificationMethod: string;
47
+ /** Optional fetch implementation. Defaults to globalThis.fetch. */
48
+ readonly fetch?: typeof fetch;
49
+ /**
50
+ * Optional override for the recipient resolver. By default the SDK
51
+ * uses a "self-only" resolver that maps every private upload to the
52
+ * subject's own X25519 sphere key. Apps that already orchestrate
53
+ * grantees explicitly may pass a custom resolver.
54
+ */
55
+ readonly recipientResolver?: RecipientResolver;
56
+ }
57
+ export interface AttachedContext {
58
+ readonly ethos?: {
59
+ readonly zone: "public" | "circle" | "self";
60
+ readonly sectionId?: string;
61
+ };
62
+ readonly data?: {
63
+ readonly collectionUrn: string;
64
+ readonly recordId?: string;
65
+ readonly field?: string;
66
+ };
67
+ }
68
+ export interface AssetUploadInput {
69
+ readonly bytes: Uint8Array;
70
+ readonly mediaType: string;
71
+ readonly attachTo?: AttachedContext;
72
+ /**
73
+ * OPTIONAL — force a regime. Defaults to "auto":
74
+ * - ethos.zone === "public" → public
75
+ * - anything else → private
76
+ */
77
+ readonly regime?: "auto" | "public" | "private";
78
+ /** OPTIONAL — strict forward secrecy at AMK rotation time. */
79
+ readonly forwardSecrecy?: "best_effort" | "strict";
80
+ }
81
+ export interface AssetUploadResult {
82
+ readonly urn: string;
83
+ readonly assetId: string;
84
+ readonly mediaType: string;
85
+ readonly sizeBytes: number;
86
+ readonly sha256OfPlaintext: string;
87
+ readonly encrypted: boolean;
88
+ /**
89
+ * Stable URL for public assets (CloudFront-served). Absent for
90
+ * private assets — fetch them via {@link AssetsClient.fetch}.
91
+ */
92
+ readonly url?: string;
93
+ /** Whether this URN was returned by intra-subject dedup (existing asset). */
94
+ readonly dedupHit: boolean;
95
+ }
96
+ export interface AssetFetchResult {
97
+ readonly urn: string;
98
+ readonly mediaType: string;
99
+ readonly sizeBytes: number;
100
+ readonly bytes: Uint8Array;
101
+ readonly sha256OfPlaintext: string;
102
+ }
103
+ export interface AssetBrief {
104
+ readonly urn: string;
105
+ readonly assetId: string;
106
+ readonly mediaType: string;
107
+ readonly sizeBytes: number;
108
+ readonly sha256OfPlaintext: string;
109
+ readonly encrypted: boolean;
110
+ readonly state: "ACTIVE" | "ORPHANED" | "TOMBSTONED";
111
+ readonly referenceCount: number;
112
+ readonly createdAt: string;
113
+ readonly modifiedAt: string;
114
+ }
115
+ export interface ListAssetsOpts {
116
+ readonly filter?: {
117
+ readonly mediaTypePrefix?: string;
118
+ readonly sizeBytes?: {
119
+ gte?: number;
120
+ lte?: number;
121
+ };
122
+ readonly createdAfter?: string;
123
+ readonly createdBefore?: string;
124
+ };
125
+ readonly limit?: number;
126
+ readonly cursor?: string;
127
+ readonly order?: "newest" | "oldest";
128
+ readonly includeOrphaned?: boolean;
129
+ readonly includeTombstoned?: boolean;
130
+ }
131
+ export interface ThumbnailUploadInput extends AssetUploadInput {
132
+ /** Long-edge sizes to produce (e.g. [64, 256]). */
133
+ readonly sizes: readonly number[];
134
+ /**
135
+ * Downscaler. The SDK does NOT bundle an image library; callers pass
136
+ * a function that takes the original bytes and a target size and
137
+ * returns downscaled bytes. Typical implementations: `pica` in the
138
+ * browser, `sharp` in Node.
139
+ */
140
+ readonly downscale: (bytes: Uint8Array, targetLongEdge: number) => Promise<Uint8Array>;
141
+ }
142
+ export interface ThumbnailUploadResult {
143
+ readonly primary: AssetUploadResult;
144
+ readonly thumbnails: readonly {
145
+ size: number;
146
+ result: AssetUploadResult;
147
+ }[];
148
+ }
149
+ /**
150
+ * Maps an attaching context to the set of recipients whose wraps the
151
+ * AMK must carry. The v0.1 default returns the subject's own X25519
152
+ * sphere key derived from the SDK's seed.
153
+ *
154
+ * v0.2 will introduce an Ethos-aware resolver that inspects the
155
+ * current manifest's `zones.<z>.cipher.wraps[]` (or, in v0.3, the
156
+ * per-section wraps) to mirror grantees on attached assets.
157
+ */
158
+ export interface RecipientResolver {
159
+ resolve(input: {
160
+ subjectDid: string;
161
+ context: AttachedContext | undefined;
162
+ }): Promise<RecipientSet>;
163
+ }
164
+ export interface RecipientSet {
165
+ readonly recipients: ReadonlyArray<{
166
+ readonly didUrl: string;
167
+ readonly x25519PublicKey: Uint8Array;
168
+ }>;
169
+ }
170
+ export declare function createAssetsClient(args: CreateAssetsClientArgs): AssetsClient;
171
+ export declare class AssetsClient {
172
+ #private;
173
+ constructor(args: CreateAssetsClientArgs);
174
+ upload(input: AssetUploadInput): Promise<AssetUploadResult>;
175
+ /**
176
+ * Upload a primary asset plus one or more thumbnails (downscaled
177
+ * client-side). Convenience method for the Deep avatar use case and
178
+ * any UI that displays the same asset at multiple resolutions.
179
+ *
180
+ * The thumbnails are attached to the same context as the primary
181
+ * and uploaded in parallel. They carry the {@link AssetReference}
182
+ * role `"thumbnail"` when referenced from a section (see
183
+ * spec/assets/03-asset-descriptors.md §3.2.3).
184
+ */
185
+ uploadWithThumbnails(input: ThumbnailUploadInput): Promise<ThumbnailUploadResult>;
186
+ fetch(urn: string): Promise<AssetFetchResult>;
187
+ head(urn: string): Promise<AssetMetadata>;
188
+ list(opts?: ListAssetsOpts): Promise<{
189
+ items: AssetBrief[];
190
+ nextCursor?: string;
191
+ }>;
192
+ ref(urn: string, reference: AssetReference): Promise<{
193
+ referenceCount: number;
194
+ gammaRef: string;
195
+ }>;
196
+ unref(urn: string, reference: AssetReference): Promise<{
197
+ referenceCount: number;
198
+ gammaRef: string;
199
+ }>;
200
+ listReferences(urn: string): Promise<AssetReference[]>;
201
+ delete(urn: string): Promise<{
202
+ tombstonedAt: string;
203
+ gammaRef: string;
204
+ }>;
205
+ /** Zero in-memory AMK cache. Useful at user logout. */
206
+ reset(): void;
207
+ }
208
+ //# sourceMappingURL=assets.d.ts.map