@aithos/sdk 0.1.0-alpha.6 → 0.1.0-alpha.60
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 +202 -7
- package/dist/src/agent-dispatch.d.ts +18 -0
- package/dist/src/agent-dispatch.js +178 -0
- package/dist/src/agent-loop.d.ts +94 -0
- package/dist/src/agent-loop.js +95 -0
- package/dist/src/agent-tools.d.ts +24 -0
- package/dist/src/agent-tools.js +147 -0
- package/dist/src/apps.d.ts +224 -0
- package/dist/src/apps.js +432 -0
- package/dist/src/assets.d.ts +225 -0
- package/dist/src/assets.js +534 -0
- package/dist/src/auth-api.d.ts +219 -0
- package/dist/src/auth-api.js +248 -0
- package/dist/src/auth.d.ts +591 -0
- package/dist/src/auth.js +947 -31
- package/dist/src/compute.d.ts +674 -6
- package/dist/src/compute.js +968 -20
- package/dist/src/data-schema-contacts-v1.d.ts +14 -0
- package/dist/src/data-schema-contacts-v1.js +28 -0
- package/dist/src/data.d.ts +368 -0
- package/dist/src/data.js +1124 -0
- package/dist/src/endpoints.d.ts +43 -0
- package/dist/src/endpoints.js +23 -0
- package/dist/src/ethos.d.ts +85 -0
- package/dist/src/ethos.js +463 -7
- package/dist/src/index.d.ts +22 -4
- package/dist/src/index.js +47 -2
- package/dist/src/internal/cmk-wrap.d.ts +41 -0
- package/dist/src/internal/cmk-wrap.js +132 -0
- package/dist/src/internal/delegate-bundle.js +7 -2
- package/dist/src/internal/envelope.d.ts +93 -0
- package/dist/src/internal/envelope.js +59 -0
- package/dist/src/internal/owner-signers.d.ts +5 -2
- package/dist/src/internal/owner-signers.js +22 -1
- package/dist/src/internal/recovery-file.d.ts +2 -0
- package/dist/src/internal/recovery-file.js +7 -0
- package/dist/src/key-store.d.ts +10 -0
- package/dist/src/key-store.js +6 -0
- package/dist/src/mandates.d.ts +58 -1
- package/dist/src/mandates.js +46 -3
- package/dist/src/migrate.d.ts +105 -0
- package/dist/src/migrate.js +367 -0
- package/dist/src/react/AithosAsset.d.ts +66 -0
- package/dist/src/react/AithosAsset.js +67 -0
- package/dist/src/react/context.d.ts +29 -0
- package/dist/src/react/context.js +31 -0
- package/dist/src/react/index.d.ts +29 -0
- package/dist/src/react/index.js +31 -0
- package/dist/src/react/use-aithos-asset.d.ts +39 -0
- package/dist/src/react/use-aithos-asset.js +118 -0
- package/dist/src/react/use-transcribe-pending.d.ts +21 -0
- package/dist/src/react/use-transcribe-pending.js +47 -0
- package/dist/src/rotate.d.ts +94 -0
- package/dist/src/rotate.js +298 -0
- package/dist/src/sdk.d.ts +36 -2
- package/dist/src/sdk.js +72 -1
- package/dist/src/transcribe-resilience.d.ts +57 -0
- package/dist/src/transcribe-resilience.js +203 -0
- package/dist/src/web.d.ts +279 -0
- package/dist/src/web.js +186 -0
- package/dist/test/agent-dispatch.test.d.ts +2 -0
- package/dist/test/agent-dispatch.test.js +222 -0
- package/dist/test/agent-loop.test.d.ts +2 -0
- package/dist/test/agent-loop.test.js +117 -0
- package/dist/test/agent-tools.test.d.ts +2 -0
- package/dist/test/agent-tools.test.js +50 -0
- package/dist/test/auth-j3.test.js +32 -1
- package/dist/test/canonical-conformance.test.d.ts +2 -0
- package/dist/test/canonical-conformance.test.js +86 -0
- package/dist/test/compute-delegate-path.test.d.ts +2 -0
- package/dist/test/compute-delegate-path.test.js +183 -0
- package/dist/test/compute.test.js +4 -0
- package/dist/test/converse.test.d.ts +2 -0
- package/dist/test/converse.test.js +162 -0
- package/dist/test/data-sphere.test.d.ts +2 -0
- package/dist/test/data-sphere.test.js +57 -0
- package/dist/test/endpoints.test.js +40 -1
- package/dist/test/envelope-core-conformance.test.d.ts +2 -0
- package/dist/test/envelope-core-conformance.test.js +75 -0
- package/dist/test/envelope.test.d.ts +2 -0
- package/dist/test/envelope.test.js +318 -0
- package/dist/test/ethos-first-edition.test.d.ts +2 -0
- package/dist/test/ethos-first-edition.test.js +371 -0
- package/dist/test/invoke-turn-sdk.test.d.ts +2 -0
- package/dist/test/invoke-turn-sdk.test.js +177 -0
- package/dist/test/migrate.test.d.ts +2 -0
- package/dist/test/migrate.test.js +340 -0
- package/dist/test/owner-data-client.test.d.ts +2 -0
- package/dist/test/owner-data-client.test.js +88 -0
- package/dist/test/rotate-ethos.test.d.ts +2 -0
- package/dist/test/rotate-ethos.test.js +151 -0
- package/dist/test/rotate.test.d.ts +2 -0
- package/dist/test/rotate.test.js +63 -0
- package/dist/test/schema-autoresolve.test.d.ts +2 -0
- package/dist/test/schema-autoresolve.test.js +146 -0
- package/dist/test/sdk.test.js +11 -2
- package/dist/test/signup-bootstrap.test.d.ts +2 -0
- package/dist/test/signup-bootstrap.test.js +311 -0
- package/dist/test/transcribe-invoke.test.d.ts +2 -0
- package/dist/test/transcribe-invoke.test.js +204 -0
- package/dist/test/transcribe.test.d.ts +2 -0
- package/dist/test/transcribe.test.js +186 -0
- package/dist/test/web.test.d.ts +2 -0
- package/dist/test/web.test.js +270 -0
- package/package.json +20 -3
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
/**
|
|
4
|
+
* Ethos rotation — building blocks.
|
|
5
|
+
*
|
|
6
|
+
* This module currently ships the foundational, fully-tested piece of the
|
|
7
|
+
* "rotate an Ethos completely" feature: {@link buildSignedRotatedDidDocument},
|
|
8
|
+
* which produces a root-signed did.json that rotates one or more sphere keys
|
|
9
|
+
* (and carries / adds the optional `#data` sphere), extending `aithos.rotated[]`
|
|
10
|
+
* so it is accepted by the existing `aithos.rotate_sphere_key` primitive.
|
|
11
|
+
*
|
|
12
|
+
* Root is NEVER rotated — it IS the DID (`did:aithos:<root-mb>`). Rotating it
|
|
13
|
+
* would change the DID (= a new identity).
|
|
14
|
+
*
|
|
15
|
+
* Browser-safe: only depends on `@aithos/protocol-core/{did,canonical}`
|
|
16
|
+
* (node-free) + the SDK's own X25519 derivation + @noble. So it runs both in
|
|
17
|
+
* the migration script (node) and in a future in-browser app.aithos.be/rotate
|
|
18
|
+
* page.
|
|
19
|
+
*
|
|
20
|
+
* NOTE (scope): the *full* `rotateEthos` orchestration also re-encrypts the
|
|
21
|
+
* Ethos circle/self ZONES under the new sphere keys (publish a fresh edition)
|
|
22
|
+
* before the rotated sphere keys take effect — otherwise existing encrypted
|
|
23
|
+
* zone content sealed to the old sphere kex becomes unreadable. That zone
|
|
24
|
+
* recopy goes through `@aithos/protocol-client`'s editor (loadEditSnapshot /
|
|
25
|
+
* publishZoneEdit) and is built/tested separately. This module deliberately
|
|
26
|
+
* exposes only the did.json builder so callers can't accidentally rotate the
|
|
27
|
+
* Ethos sphere keys without the matching zone recopy.
|
|
28
|
+
*/
|
|
29
|
+
import * as ed from "@noble/ed25519";
|
|
30
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
31
|
+
import { canonicalize } from "@aithos/protocol-core/canonical";
|
|
32
|
+
import { ed25519PublicKeyToMultibase, x25519PublicKeyToMultibase, } from "@aithos/protocol-core/did";
|
|
33
|
+
import { ed25519SeedToX25519PublicKey } from "./internal/cmk-wrap.js";
|
|
34
|
+
// noble/ed25519 v2 needs a sync sha512 for sync sign.
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
ed.etc.sha512Sync = (...m) =>
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
sha512(ed.etc.concatBytes(...m));
|
|
39
|
+
const SPHERES = ["public", "circle", "self"];
|
|
40
|
+
/**
|
|
41
|
+
* Build + sign a rotated did.json. The result rotates every Ethos sphere whose
|
|
42
|
+
* seed differs from the previously-published key, appends one `rotated[]` entry
|
|
43
|
+
* per changed sphere (preserving prior history), carries / adds `#data`, and is
|
|
44
|
+
* signed by `#root`. Ready to POST via `aithos.rotate_sphere_key`.
|
|
45
|
+
*
|
|
46
|
+
* @param seeds the post-rotation seeds (same root, new sphere seeds).
|
|
47
|
+
* @param previousDoc the currently-published did.json (old pubkeys + history).
|
|
48
|
+
* @param displayName display name to carry on the doc.
|
|
49
|
+
* @param reason audit reason recorded on each new rotated entry.
|
|
50
|
+
*/
|
|
51
|
+
export function buildSignedRotatedDidDocument(seeds, previousDoc, displayName, reason = "rotate") {
|
|
52
|
+
const rootPub = ed.getPublicKey(seeds.root);
|
|
53
|
+
const did = "did:aithos:" + ed25519PublicKeyToMultibase(rootPub);
|
|
54
|
+
if (previousDoc.id !== did) {
|
|
55
|
+
throw new Error("buildSignedRotatedDidDocument: root seed does not match previousDoc.id — root (and the DID) is never rotated");
|
|
56
|
+
}
|
|
57
|
+
const now = new Date().toISOString();
|
|
58
|
+
const verificationMethod = SPHERES.map((sphere) => ({
|
|
59
|
+
id: `${did}#${sphere}`,
|
|
60
|
+
type: "Ed25519VerificationKey2020",
|
|
61
|
+
controller: did,
|
|
62
|
+
publicKeyMultibase: ed25519PublicKeyToMultibase(ed.getPublicKey(seeds[sphere])),
|
|
63
|
+
}));
|
|
64
|
+
const keyAgreement = SPHERES.map((sphere) => ({
|
|
65
|
+
id: `${did}#${sphere}-kex`,
|
|
66
|
+
type: "X25519KeyAgreementKey2020",
|
|
67
|
+
controller: did,
|
|
68
|
+
publicKeyMultibase: x25519PublicKeyToMultibase(ed25519SeedToX25519PublicKey(seeds[sphere])),
|
|
69
|
+
}));
|
|
70
|
+
if (seeds.data) {
|
|
71
|
+
verificationMethod.push({
|
|
72
|
+
id: `${did}#data`,
|
|
73
|
+
type: "Ed25519VerificationKey2020",
|
|
74
|
+
controller: did,
|
|
75
|
+
publicKeyMultibase: ed25519PublicKeyToMultibase(ed.getPublicKey(seeds.data)),
|
|
76
|
+
});
|
|
77
|
+
keyAgreement.push({
|
|
78
|
+
id: `${did}#data-kex`,
|
|
79
|
+
type: "X25519KeyAgreementKey2020",
|
|
80
|
+
controller: did,
|
|
81
|
+
publicKeyMultibase: x25519PublicKeyToMultibase(ed25519SeedToX25519PublicKey(seeds.data)),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Carry prior history + append one entry per Ethos sphere whose key changed.
|
|
85
|
+
const prevKeyOf = (sphere) => previousDoc.verificationMethod.find((vm) => vm.id === `${did}#${sphere}`)?.publicKeyMultibase;
|
|
86
|
+
const rotated = [...(previousDoc.aithos.rotated ?? [])];
|
|
87
|
+
for (const sphere of SPHERES) {
|
|
88
|
+
const oldKey = prevKeyOf(sphere);
|
|
89
|
+
const newKey = ed25519PublicKeyToMultibase(ed.getPublicKey(seeds[sphere]));
|
|
90
|
+
if (oldKey && oldKey !== newKey) {
|
|
91
|
+
rotated.push({ sphere, previous_key: oldKey, rotated_at: now, reason });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const unsigned = {
|
|
95
|
+
"@context": ["https://www.w3.org/ns/did/v1", "https://aithos.dev/spec/v0.1"],
|
|
96
|
+
id: did,
|
|
97
|
+
verificationMethod,
|
|
98
|
+
keyAgreement,
|
|
99
|
+
aithos: { version: "0.1.0", display_name: displayName, created_at: now, rotated },
|
|
100
|
+
proof: {
|
|
101
|
+
type: "Ed25519Signature2020",
|
|
102
|
+
created: now,
|
|
103
|
+
verificationMethod: `${did}#root`,
|
|
104
|
+
proofPurpose: "assertionMethod",
|
|
105
|
+
proofValue: "",
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
const bytes = new TextEncoder().encode(canonicalize(unsigned));
|
|
109
|
+
const sig = ed.sign(bytes, seeds.root);
|
|
110
|
+
return { ...unsigned, proof: { ...unsigned.proof, proofValue: base64url(sig) } };
|
|
111
|
+
}
|
|
112
|
+
function base64url(bytes) {
|
|
113
|
+
let bin = "";
|
|
114
|
+
for (let i = 0; i < bytes.length; i++)
|
|
115
|
+
bin += String.fromCharCode(bytes[i]);
|
|
116
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
117
|
+
}
|
|
118
|
+
/* ========================================================================== */
|
|
119
|
+
/* rotateEthos — full rotation orchestration */
|
|
120
|
+
/* ========================================================================== */
|
|
121
|
+
import { loadEditSnapshot, publishZoneEdit, } from "@aithos/protocol-client";
|
|
122
|
+
import { addDataSphereWrap } from "./migrate.js";
|
|
123
|
+
import { DEFAULT_API_BASE_URL } from "./auth.js";
|
|
124
|
+
import { signOwnerEnvelope } from "./internal/envelope.js";
|
|
125
|
+
import { parseRecoveryFile, } from "./internal/recovery-file.js";
|
|
126
|
+
/**
|
|
127
|
+
* Fully rotate an Ethos: new public/circle/self/#data keys (root unchanged),
|
|
128
|
+
* re-encrypt the Ethos zones under the new keys (fresh edition), and dual-wrap
|
|
129
|
+
* every data collection toward the new #data. Entirely client-side; uses only
|
|
130
|
+
* existing API primitives (rotate_sphere_key, publish_ethos_edition via the
|
|
131
|
+
* protocol-client editor, rotate_cmk). Returns a new recovery file — persist it.
|
|
132
|
+
*
|
|
133
|
+
* Option (a) semantics: the new head edition (carrying all current content) is
|
|
134
|
+
* fully verifiable under the new keys; pre-rotation edition snapshots are not
|
|
135
|
+
* re-verifiable against the rotated did.json (rotated[] retains the old keys so
|
|
136
|
+
* a rotated[]-aware verifier can be added later without re-rotating).
|
|
137
|
+
*/
|
|
138
|
+
export async function rotateEthos(recovery, opts = {}) {
|
|
139
|
+
const rec = typeof recovery === "string" ? parseRecoveryFile(recovery) : recovery;
|
|
140
|
+
const fetchImpl = opts.fetch ?? globalThis.fetch.bind(globalThis);
|
|
141
|
+
const apiBaseUrl = (opts.apiBaseUrl ?? DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
142
|
+
const reason = opts.reason ?? "full-rotation";
|
|
143
|
+
const did = rec.did;
|
|
144
|
+
const rootSeed = hexToBytes(rec.seedsHex.root);
|
|
145
|
+
const log = opts.onProgress ?? (() => { });
|
|
146
|
+
// 1. Fetch the currently-published did.json.
|
|
147
|
+
log("fetching current did.json");
|
|
148
|
+
const idRes = await apiRpc(fetchImpl, apiBaseUrl, "aithos.get_identity", { did }, did, rootSeed);
|
|
149
|
+
const previousDoc = (idRes.object ?? idRes);
|
|
150
|
+
// 2. Generate fresh seeds (root kept).
|
|
151
|
+
const newSeeds = {
|
|
152
|
+
root: rootSeed,
|
|
153
|
+
public: randomSeed32(),
|
|
154
|
+
circle: randomSeed32(),
|
|
155
|
+
self: randomSeed32(),
|
|
156
|
+
data: randomSeed32(),
|
|
157
|
+
};
|
|
158
|
+
const newDataHex = bytesToHex(newSeeds.data);
|
|
159
|
+
// 3. Probe whether an Ethos edition exists (mockable apiRpc — NOT the
|
|
160
|
+
// protocol-client editor, so this stays testable). Absent → -32020.
|
|
161
|
+
let hadEdition = true;
|
|
162
|
+
try {
|
|
163
|
+
log("probing for an existing edition");
|
|
164
|
+
await apiRpc(fetchImpl, apiBaseUrl, "aithos.get_ethos_manifest", { did }, did, rootSeed);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
if (e.code === -32020 || /not found/i.test(String(e.message))) {
|
|
168
|
+
hadEdition = false;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
throw e;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (hadEdition && opts.skipZones && !opts.force) {
|
|
175
|
+
throw new Error("rotateEthos: this Ethos has an edition with (possibly encrypted) zone content, but skipZones was set. " +
|
|
176
|
+
"Rotating the sphere keys without recopying the zones would orphan that content. " +
|
|
177
|
+
"Pass force: true only if you are certain the zones hold nothing to preserve.");
|
|
178
|
+
}
|
|
179
|
+
// 4. Publish the rotated did.json (root-signed) via rotate_sphere_key.
|
|
180
|
+
log("publishing rotated did.json");
|
|
181
|
+
const rotatedDoc = buildSignedRotatedDidDocument(newSeeds, previousDoc, rec.displayName, reason);
|
|
182
|
+
await apiRpc(fetchImpl, apiBaseUrl, "aithos.rotate_sphere_key", { new_did_document: rotatedDoc }, did, rootSeed);
|
|
183
|
+
// 5. Republish a fresh edition under the NEW identity → zones re-sealed under
|
|
184
|
+
// the new sphere keys (LIVE: protocol-client editor, ambient api endpoint).
|
|
185
|
+
// loadEditSnapshot decrypts circle/self with the OLD keys; publishZoneEdit
|
|
186
|
+
// re-seals them under the NEW identity.
|
|
187
|
+
let editionRepublished = false;
|
|
188
|
+
if (hadEdition && !opts.skipZones) {
|
|
189
|
+
log("loading current edition (old keys) + republishing (new keys)");
|
|
190
|
+
const snap = await loadEditSnapshot(did, toStoredIdentity(rec));
|
|
191
|
+
const newStored = toStoredIdentity({
|
|
192
|
+
handle: rec.handle,
|
|
193
|
+
displayName: rec.displayName,
|
|
194
|
+
did,
|
|
195
|
+
seedsHex: {
|
|
196
|
+
root: rec.seedsHex.root,
|
|
197
|
+
public: bytesToHex(newSeeds.public),
|
|
198
|
+
circle: bytesToHex(newSeeds.circle),
|
|
199
|
+
self: bytesToHex(newSeeds.self),
|
|
200
|
+
data: newDataHex,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
await publishZoneEdit({
|
|
204
|
+
identity: newStored,
|
|
205
|
+
snapshot: snap,
|
|
206
|
+
newPublicSections: snap.publicSections ?? [],
|
|
207
|
+
...(snap.circleSections ? { newCircleSections: snap.circleSections } : {}),
|
|
208
|
+
...(snap.selfSections ? { newSelfSections: snap.selfSections } : {}),
|
|
209
|
+
});
|
|
210
|
+
editionRepublished = true;
|
|
211
|
+
}
|
|
212
|
+
// 6. Dual-wrap every collection toward the NEW #data (unwrap with old seeds).
|
|
213
|
+
log("re-keying collections toward the new #data");
|
|
214
|
+
const collections = await addDataSphereWrap(rec, {
|
|
215
|
+
...(opts.pdsUrl ? { pdsUrl: opts.pdsUrl } : {}),
|
|
216
|
+
fetch: fetchImpl,
|
|
217
|
+
targetDataSeedHex: newDataHex,
|
|
218
|
+
});
|
|
219
|
+
// 7. New recovery (all new seeds; persist!).
|
|
220
|
+
const updatedRecoveryFile = JSON.stringify({
|
|
221
|
+
aithos_recovery_version: "0.1.0-plaintext",
|
|
222
|
+
handle: rec.handle,
|
|
223
|
+
display_name: rec.displayName,
|
|
224
|
+
did,
|
|
225
|
+
seeds_hex: {
|
|
226
|
+
root: rec.seedsHex.root,
|
|
227
|
+
public: bytesToHex(newSeeds.public),
|
|
228
|
+
circle: bytesToHex(newSeeds.circle),
|
|
229
|
+
self: bytesToHex(newSeeds.self),
|
|
230
|
+
data: newDataHex,
|
|
231
|
+
},
|
|
232
|
+
saved_at: new Date().toISOString(),
|
|
233
|
+
}, null, 2);
|
|
234
|
+
return {
|
|
235
|
+
did,
|
|
236
|
+
rotatedSpheres: ["public", "circle", "self"],
|
|
237
|
+
editionRepublished,
|
|
238
|
+
hadEdition,
|
|
239
|
+
collections,
|
|
240
|
+
updatedRecoveryFile,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/* -------------------------------------------------------------------------- */
|
|
244
|
+
/* helpers */
|
|
245
|
+
/* -------------------------------------------------------------------------- */
|
|
246
|
+
function toStoredIdentity(rec) {
|
|
247
|
+
return {
|
|
248
|
+
handle: rec.handle,
|
|
249
|
+
displayName: rec.displayName,
|
|
250
|
+
did: rec.did,
|
|
251
|
+
seeds: {
|
|
252
|
+
root: rec.seedsHex.root,
|
|
253
|
+
public: rec.seedsHex.public,
|
|
254
|
+
circle: rec.seedsHex.circle,
|
|
255
|
+
self: rec.seedsHex.self,
|
|
256
|
+
...(rec.seedsHex.data ? { data: rec.seedsHex.data } : {}),
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
async function apiRpc(fetchImpl, apiBaseUrl, method, params, did, rootSeed) {
|
|
261
|
+
const url = `${apiBaseUrl}/mcp/primitives/${method.includes("get_") ? "read" : "write"}`;
|
|
262
|
+
const envelope = await signOwnerEnvelope({
|
|
263
|
+
iss: did,
|
|
264
|
+
aud: url,
|
|
265
|
+
method,
|
|
266
|
+
params,
|
|
267
|
+
verificationMethod: `${did}#root`,
|
|
268
|
+
signer: { sign: async (m) => ed.sign(m, rootSeed) },
|
|
269
|
+
});
|
|
270
|
+
const r = await fetchImpl(url, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { "content-type": "application/json" },
|
|
273
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: `rotate_${Date.now()}`, method, params: { ...params, _envelope: envelope } }),
|
|
274
|
+
});
|
|
275
|
+
const j = (await r.json());
|
|
276
|
+
if (j.error) {
|
|
277
|
+
throw Object.assign(new Error(`${method} failed: ${j.error.message}`), { code: j.error.code });
|
|
278
|
+
}
|
|
279
|
+
return j.result ?? {};
|
|
280
|
+
}
|
|
281
|
+
function randomSeed32() {
|
|
282
|
+
const buf = new Uint8Array(32);
|
|
283
|
+
globalThis.crypto?.getRandomValues(buf);
|
|
284
|
+
return buf;
|
|
285
|
+
}
|
|
286
|
+
function hexToBytes(hex) {
|
|
287
|
+
const out = new Uint8Array(hex.length / 2);
|
|
288
|
+
for (let i = 0; i < out.length; i++)
|
|
289
|
+
out[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
function bytesToHex(b) {
|
|
293
|
+
let out = "";
|
|
294
|
+
for (let i = 0; i < b.length; i++)
|
|
295
|
+
out += b[i].toString(16).padStart(2, "0");
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
//# sourceMappingURL=rotate.js.map
|
package/dist/src/sdk.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AithosAuth } from "./auth.js";
|
|
2
|
-
import {
|
|
2
|
+
import { AppsNamespace } from "./apps.js";
|
|
3
|
+
import { ComputeNamespace, type ComputeWorkingSet } from "./compute.js";
|
|
3
4
|
import { type AithosSdkEndpoints } from "./endpoints.js";
|
|
4
|
-
import { EthosNamespace } from "./ethos.js";
|
|
5
|
+
import { EthosNamespace, type ZoneName } from "./ethos.js";
|
|
5
6
|
import { MandatesNamespace } from "./mandates.js";
|
|
6
7
|
import { WalletNamespace } from "./wallet.js";
|
|
8
|
+
import { WebNamespace } from "./web.js";
|
|
7
9
|
export interface AithosSDKConfig {
|
|
8
10
|
/**
|
|
9
11
|
* The {@link AithosAuth} instance the SDK reads sign-in state from.
|
|
@@ -44,8 +46,40 @@ export declare class AithosSDK {
|
|
|
44
46
|
readonly ethos: EthosNamespace;
|
|
45
47
|
/** Mandate lifecycle namespace — create / list / revoke. */
|
|
46
48
|
readonly mandates: MandatesNamespace;
|
|
49
|
+
/** Web extraction namespace — aithos.web_extract through the web extractor proxy. */
|
|
50
|
+
readonly web: WebNamespace;
|
|
51
|
+
/**
|
|
52
|
+
* App lifecycle namespace — sponsorship mandates + app-credit top-ups.
|
|
53
|
+
* V0.1 (2026-05-27 — draft §13). Lets a developer pre-pay a pool that
|
|
54
|
+
* funds compute calls from their app's users within explicit caps.
|
|
55
|
+
*/
|
|
56
|
+
readonly apps: AppsNamespace;
|
|
47
57
|
constructor(config: AithosSDKConfig);
|
|
48
58
|
/** DID of the currently signed-in owner, or null if no owner is loaded. */
|
|
49
59
|
get userDid(): string | null;
|
|
60
|
+
/**
|
|
61
|
+
* Build the client-decrypted working-set for an agentic conversation.
|
|
62
|
+
*
|
|
63
|
+
* Reads the requested ethos zones for `did` through {@link EthosNamespace}
|
|
64
|
+
* (which decrypts client-side using the active owner/delegate keys) and
|
|
65
|
+
* packages them into the {@link ComputeWorkingSet} shape that
|
|
66
|
+
* `sdk.compute.runConversation({ workingSet })` consumes.
|
|
67
|
+
*
|
|
68
|
+
* Only zones the session can actually read are included: a zone the
|
|
69
|
+
* mandate does not grant (or whose delegate wrap is missing) throws
|
|
70
|
+
* `ethos_zone_unreadable` / `ethos_anonymous_private_zone` on read and is
|
|
71
|
+
* silently skipped — so the working-set is naturally bounded by what the
|
|
72
|
+
* caller is authorized to see. The proxy never holds a decryption key;
|
|
73
|
+
* this is where the plaintext is produced (see PLATFORM-COMPUTE-AGENTIC-MCP.md §4).
|
|
74
|
+
*
|
|
75
|
+
* Structured (gamma) data collections are not loaded here in v1 — attach
|
|
76
|
+
* them to the returned object's `data` field yourself if needed.
|
|
77
|
+
*
|
|
78
|
+
* @param did Subject DID whose ethos to read (usually the signed-in owner).
|
|
79
|
+
* @param opts.zones Zones to attempt. Defaults to all three.
|
|
80
|
+
*/
|
|
81
|
+
buildWorkingSet(did: string, opts?: {
|
|
82
|
+
readonly zones?: readonly ZoneName[];
|
|
83
|
+
}): Promise<ComputeWorkingSet>;
|
|
50
84
|
}
|
|
51
85
|
//# sourceMappingURL=sdk.d.ts.map
|
package/dist/src/sdk.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 Mathieu Colla
|
|
3
|
-
import {
|
|
3
|
+
import { AppsNamespace } from "./apps.js";
|
|
4
|
+
import { ComputeNamespace, } from "./compute.js";
|
|
4
5
|
import { resolveEndpoints } from "./endpoints.js";
|
|
6
|
+
import { configureEndpoints as configureProtocolClientEndpoints } from "@aithos/protocol-client";
|
|
5
7
|
import { EthosNamespace } from "./ethos.js";
|
|
6
8
|
import { MandatesNamespace } from "./mandates.js";
|
|
9
|
+
import { AithosSDKError } from "./types.js";
|
|
7
10
|
import { WalletNamespace } from "./wallet.js";
|
|
11
|
+
import { WebNamespace } from "./web.js";
|
|
8
12
|
export class AithosSDK {
|
|
9
13
|
/** Resolved endpoint configuration (defaults + caller overrides). */
|
|
10
14
|
endpoints;
|
|
@@ -20,6 +24,14 @@ export class AithosSDK {
|
|
|
20
24
|
ethos;
|
|
21
25
|
/** Mandate lifecycle namespace — create / list / revoke. */
|
|
22
26
|
mandates;
|
|
27
|
+
/** Web extraction namespace — aithos.web_extract through the web extractor proxy. */
|
|
28
|
+
web;
|
|
29
|
+
/**
|
|
30
|
+
* App lifecycle namespace — sponsorship mandates + app-credit top-ups.
|
|
31
|
+
* V0.1 (2026-05-27 — draft §13). Lets a developer pre-pay a pool that
|
|
32
|
+
* funds compute calls from their app's users within explicit caps.
|
|
33
|
+
*/
|
|
34
|
+
apps;
|
|
23
35
|
constructor(config) {
|
|
24
36
|
if (!config.auth) {
|
|
25
37
|
throw new TypeError("AithosSDK: config.auth is required");
|
|
@@ -30,6 +42,10 @@ export class AithosSDK {
|
|
|
30
42
|
this.endpoints = resolveEndpoints(config.endpoints);
|
|
31
43
|
this.appDid = config.appDid;
|
|
32
44
|
this.auth = config.auth;
|
|
45
|
+
// Ethos reads/writes flow through @aithos/protocol-client, which keeps its
|
|
46
|
+
// own (module-scoped) api/cdn config. Propagate ours so that pointing the
|
|
47
|
+
// SDK at a dev account (endpoints.api/cdn) also redirects the ethos calls.
|
|
48
|
+
configureProtocolClientEndpoints({ api: this.endpoints.api, cdn: this.endpoints.cdn });
|
|
33
49
|
const fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
34
50
|
this.compute = new ComputeNamespace({
|
|
35
51
|
auth: config.auth,
|
|
@@ -53,10 +69,65 @@ export class AithosSDK {
|
|
|
53
69
|
endpoints: this.endpoints,
|
|
54
70
|
fetch: fetchImpl,
|
|
55
71
|
});
|
|
72
|
+
this.web = new WebNamespace({
|
|
73
|
+
auth: config.auth,
|
|
74
|
+
appDid: config.appDid,
|
|
75
|
+
endpoints: this.endpoints,
|
|
76
|
+
fetch: fetchImpl,
|
|
77
|
+
});
|
|
78
|
+
this.apps = new AppsNamespace({
|
|
79
|
+
auth: config.auth,
|
|
80
|
+
appDid: config.appDid,
|
|
81
|
+
endpoints: this.endpoints,
|
|
82
|
+
fetch: fetchImpl,
|
|
83
|
+
});
|
|
56
84
|
}
|
|
57
85
|
/** DID of the currently signed-in owner, or null if no owner is loaded. */
|
|
58
86
|
get userDid() {
|
|
59
87
|
return this.auth.getOwnerInfo()?.did ?? null;
|
|
60
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the client-decrypted working-set for an agentic conversation.
|
|
91
|
+
*
|
|
92
|
+
* Reads the requested ethos zones for `did` through {@link EthosNamespace}
|
|
93
|
+
* (which decrypts client-side using the active owner/delegate keys) and
|
|
94
|
+
* packages them into the {@link ComputeWorkingSet} shape that
|
|
95
|
+
* `sdk.compute.runConversation({ workingSet })` consumes.
|
|
96
|
+
*
|
|
97
|
+
* Only zones the session can actually read are included: a zone the
|
|
98
|
+
* mandate does not grant (or whose delegate wrap is missing) throws
|
|
99
|
+
* `ethos_zone_unreadable` / `ethos_anonymous_private_zone` on read and is
|
|
100
|
+
* silently skipped — so the working-set is naturally bounded by what the
|
|
101
|
+
* caller is authorized to see. The proxy never holds a decryption key;
|
|
102
|
+
* this is where the plaintext is produced (see PLATFORM-COMPUTE-AGENTIC-MCP.md §4).
|
|
103
|
+
*
|
|
104
|
+
* Structured (gamma) data collections are not loaded here in v1 — attach
|
|
105
|
+
* them to the returned object's `data` field yourself if needed.
|
|
106
|
+
*
|
|
107
|
+
* @param did Subject DID whose ethos to read (usually the signed-in owner).
|
|
108
|
+
* @param opts.zones Zones to attempt. Defaults to all three.
|
|
109
|
+
*/
|
|
110
|
+
async buildWorkingSet(did, opts) {
|
|
111
|
+
const zones = opts?.zones ?? ["public", "circle", "self"];
|
|
112
|
+
const client = await this.ethos.of(did);
|
|
113
|
+
const ethos = {};
|
|
114
|
+
for (const zone of zones) {
|
|
115
|
+
try {
|
|
116
|
+
const sections = await client.zone(zone).sections();
|
|
117
|
+
ethos[zone] = sections.map((s) => ({
|
|
118
|
+
id: s.id,
|
|
119
|
+
title: s.title,
|
|
120
|
+
body: s.body,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
// Zone not granted / not decryptable for this session → skip it.
|
|
125
|
+
if (e instanceof AithosSDKError)
|
|
126
|
+
continue;
|
|
127
|
+
throw e;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { ethos };
|
|
131
|
+
}
|
|
61
132
|
}
|
|
62
133
|
//# sourceMappingURL=sdk.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type LocalPendingStatus = "uploading" | "running" | "completed" | "failed";
|
|
2
|
+
export interface LocalPendingEntry {
|
|
3
|
+
readonly jobId: string;
|
|
4
|
+
readonly status: LocalPendingStatus;
|
|
5
|
+
readonly createdAt: number;
|
|
6
|
+
readonly updatedAt: number;
|
|
7
|
+
readonly meta?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Framework-agnostic observable registry of in-flight transcription jobs.
|
|
11
|
+
* Persisted to localStorage when available (so it survives reloads), with
|
|
12
|
+
* an in-memory fallback otherwise. Subscribe with `subscribe(listener)`;
|
|
13
|
+
* read with `getSnapshot()` (stable reference between mutations, so it
|
|
14
|
+
* plugs directly into React's `useSyncExternalStore`).
|
|
15
|
+
*/
|
|
16
|
+
export declare class LocalPendingTranscribeTracker {
|
|
17
|
+
#private;
|
|
18
|
+
constructor();
|
|
19
|
+
/** Current entries. Stable reference until the next mutation. */
|
|
20
|
+
getSnapshot(): readonly LocalPendingEntry[];
|
|
21
|
+
list(): readonly LocalPendingEntry[];
|
|
22
|
+
/** Subscribe to changes. Returns an unsubscribe function. */
|
|
23
|
+
subscribe(listener: () => void): () => void;
|
|
24
|
+
upsert(jobId: string, status: LocalPendingStatus, meta?: Record<string, unknown>): void;
|
|
25
|
+
remove(jobId: string): void;
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
28
|
+
export interface TranscribeDraftMeta {
|
|
29
|
+
readonly title?: string;
|
|
30
|
+
readonly tag?: string;
|
|
31
|
+
readonly contentType?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface TranscribeDraftRecord {
|
|
34
|
+
readonly draftId: string;
|
|
35
|
+
readonly blob: Blob;
|
|
36
|
+
readonly metadata: TranscribeDraftMeta;
|
|
37
|
+
readonly createdAt: number;
|
|
38
|
+
}
|
|
39
|
+
export declare class TranscribeDraftUnavailableError extends Error {
|
|
40
|
+
constructor();
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* IndexedDB-backed queue of recorded audio Blobs. Save a recording the
|
|
44
|
+
* instant it finishes (before any network), then `upload` it when the
|
|
45
|
+
* user confirms — so a flaky network or a closed tab never loses audio.
|
|
46
|
+
* Browser-only: methods reject with {@link TranscribeDraftUnavailableError}
|
|
47
|
+
* when IndexedDB is absent.
|
|
48
|
+
*/
|
|
49
|
+
export declare class TranscribeDraftStore {
|
|
50
|
+
save(blob: Blob, meta?: TranscribeDraftMeta): Promise<{
|
|
51
|
+
readonly draftId: string;
|
|
52
|
+
}>;
|
|
53
|
+
list(): Promise<readonly TranscribeDraftRecord[]>;
|
|
54
|
+
get(draftId: string): Promise<TranscribeDraftRecord | null>;
|
|
55
|
+
delete(draftId: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=transcribe-resilience.d.ts.map
|