@aithos/sdk 0.1.0-alpha.3 → 0.1.0-alpha.31

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 (66) hide show
  1. package/README.md +159 -0
  2. package/dist/src/auth-api.d.ts +149 -0
  3. package/dist/src/auth-api.js +226 -0
  4. package/dist/src/auth.d.ts +436 -67
  5. package/dist/src/auth.js +1098 -69
  6. package/dist/src/compute.d.ts +221 -9
  7. package/dist/src/compute.js +293 -16
  8. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  9. package/dist/src/data-schema-contacts-v1.js +28 -0
  10. package/dist/src/data.d.ts +97 -0
  11. package/dist/src/data.js +634 -0
  12. package/dist/src/endpoints.d.ts +9 -0
  13. package/dist/src/endpoints.js +5 -0
  14. package/dist/src/ethos.d.ts +202 -1
  15. package/dist/src/ethos.js +821 -16
  16. package/dist/src/index.d.ts +15 -6
  17. package/dist/src/index.js +36 -9
  18. package/dist/src/internal/delegate-bundle.d.ts +18 -0
  19. package/dist/src/internal/delegate-bundle.js +94 -0
  20. package/dist/src/internal/delegate-state.d.ts +45 -0
  21. package/dist/src/internal/delegate-state.js +120 -0
  22. package/dist/src/internal/owner-signers.d.ts +78 -0
  23. package/dist/src/internal/owner-signers.js +179 -0
  24. package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
  25. package/dist/src/internal/protocol-client-bridge.js +20 -0
  26. package/dist/src/internal/recovery-file.d.ts +29 -0
  27. package/dist/src/internal/recovery-file.js +98 -0
  28. package/dist/src/internal/signer.d.ts +59 -0
  29. package/dist/src/internal/signer.js +86 -0
  30. package/dist/src/key-store.d.ts +128 -0
  31. package/dist/src/key-store.js +244 -0
  32. package/dist/src/mandates.d.ts +163 -1
  33. package/dist/src/mandates.js +286 -8
  34. package/dist/src/sdk.d.ts +39 -3
  35. package/dist/src/sdk.js +36 -23
  36. package/dist/src/session-store.d.ts +58 -0
  37. package/dist/src/session-store.js +158 -0
  38. package/dist/src/wallet.d.ts +4 -6
  39. package/dist/src/wallet.js +18 -8
  40. package/dist/src/web.d.ts +279 -0
  41. package/dist/src/web.js +186 -0
  42. package/dist/test/auth-j3.test.d.ts +2 -0
  43. package/dist/test/auth-j3.test.js +391 -0
  44. package/dist/test/compute-delegate-path.test.d.ts +2 -0
  45. package/dist/test/compute-delegate-path.test.js +183 -0
  46. package/dist/test/compute.test.js +26 -11
  47. package/dist/test/endpoints.test.js +20 -1
  48. package/dist/test/ethos-first-edition.test.d.ts +2 -0
  49. package/dist/test/ethos-first-edition.test.js +248 -0
  50. package/dist/test/ethos.test.d.ts +2 -0
  51. package/dist/test/ethos.test.js +219 -0
  52. package/dist/test/key-store.test.d.ts +2 -0
  53. package/dist/test/key-store.test.js +161 -0
  54. package/dist/test/mandates-compute.test.d.ts +2 -0
  55. package/dist/test/mandates-compute.test.js +256 -0
  56. package/dist/test/mandates.test.d.ts +2 -0
  57. package/dist/test/mandates.test.js +93 -0
  58. package/dist/test/sdk.test.js +70 -30
  59. package/dist/test/signer.test.d.ts +2 -0
  60. package/dist/test/signer.test.js +117 -0
  61. package/dist/test/signup-bootstrap.test.d.ts +2 -0
  62. package/dist/test/signup-bootstrap.test.js +222 -0
  63. package/dist/test/wallet.test.js +20 -9
  64. package/dist/test/web.test.d.ts +2 -0
  65. package/dist/test/web.test.js +270 -0
  66. package/package.json +5 -4
@@ -1,2 +1,164 @@
1
- export { mintDelegateBundle, signAndPublishMandate, MintError, type MintArgs, type MintResult, type SignAndPublishMandateArgs, DEFAULT_READ_SCOPES, TTL_PRESETS, } from "@aithos/protocol-client";
1
+ import type { AithosAuth } from "./auth.js";
2
+ import type { AithosSdkEndpoints } from "./endpoints.js";
3
+ /** Capability scope the SDK accepts. Server-side ultimately decides.
4
+ *
5
+ * Note: `compute.invoke` is intentionally NOT in this union. The token-
6
+ * spending capability is opt-in via the dedicated {@link CreateMandateInput.compute}
7
+ * namespace — see {@link MandatesNamespace.create}. Passing `compute.invoke`
8
+ * directly in `scopes` is rejected at runtime; the compiler can't enforce
9
+ * it (callers who up-cast to string[] would slip through), so the runtime
10
+ * check is the real gate. */
11
+ export type Scope = "ethos.read.public" | "ethos.read.circle" | "ethos.read.self" | "ethos.write.public" | "ethos.write.circle" | "ethos.write.self";
12
+ /**
13
+ * The opt-in scope that authorizes a delegate to spend the subject's
14
+ * compute credits via the Aithos compute proxy. Mirror of
15
+ * `COMPUTE_INVOKE_SCOPE` in `@aithos/protocol-core` v0.4.0.
16
+ *
17
+ * The SDK's `mandates.create()` injects this scope automatically when
18
+ * the caller passes a `compute` namespace, and refuses to mint a
19
+ * mandate where the caller put it directly into `scopes` — this is
20
+ * what makes "compute is a separate, conscious decision" hold at the
21
+ * API surface.
22
+ */
23
+ export declare const COMPUTE_INVOKE_SCOPE: "compute.invoke";
24
+ /**
25
+ * Which sphere of the owner signs the mandate. Bounds the upper-most
26
+ * scope set the mandate can carry.
27
+ */
28
+ export type ActorSphere = "public" | "circle" | "self";
29
+ /**
30
+ * Compute-spending capability — opt-in only, never implied by ethos
31
+ * scopes.
32
+ *
33
+ * When `compute` is set on {@link CreateMandateInput}, the SDK:
34
+ * 1. Adds the `compute.invoke` scope to the minted mandate.
35
+ * 2. Maps the caller's caps onto `constraints.compute` in the
36
+ * protocol's snake_case shape (= what the verifier reads).
37
+ * 3. Forbids the caller from passing `compute.invoke` in `scopes`
38
+ * directly — that would let an app slip the scope past a
39
+ * consent UI that only reviews `compute`.
40
+ *
41
+ * At least one of `dailyCapMicrocredits` or `totalCapMicrocredits` MUST
42
+ * be set: an unbounded compute mandate is the kind of bearer-token
43
+ * footgun this whole namespace exists to prevent. Validation happens
44
+ * at the SDK boundary (here) AND at the protocol layer (the
45
+ * server-side verifier rejects capless 0.4.0 mandates), so a bug in
46
+ * either tier still fails closed.
47
+ *
48
+ * `maxCreditsPerCall` is a per-invocation safety net for runaway
49
+ * single requests. `allowedModels`, when set, restricts which Bedrock
50
+ * model ids the delegate may target (the proxy's own allowlist still
51
+ * applies on top).
52
+ */
53
+ export interface CreateMandateComputeInput {
54
+ /** Hard cap on credits debited per UTC day under this mandate. */
55
+ readonly dailyCapMicrocredits?: number;
56
+ /** Hard cap on credits debited over the whole mandate lifetime. */
57
+ readonly totalCapMicrocredits?: number;
58
+ /** Hard cap on credits debited by any single invocation. */
59
+ readonly maxCreditsPerCall?: number;
60
+ /** Allowlist of Bedrock model ids the delegate may invoke. */
61
+ readonly allowedModels?: readonly string[];
62
+ }
63
+ export interface CreateMandateInput {
64
+ /** Grantee URN — usually `urn:aithos:agent:<extension-id>` or similar. */
65
+ readonly granteeId: string;
66
+ /** Optional human-readable label for the grantee. */
67
+ readonly granteeLabel?: string;
68
+ /**
69
+ * Sphere of the owner that issues the mandate. Defaults to the
70
+ * highest-numbered sphere covered by `scopes` (most permissive
71
+ * common ancestor): `"self"` if any scope ends in `.self`, else
72
+ * `"circle"` if any ends in `.circle`, else `"public"`.
73
+ */
74
+ readonly actorSphere?: ActorSphere;
75
+ /** Capability set granted by the mandate. */
76
+ readonly scopes: readonly Scope[];
77
+ /** Lifetime in seconds. */
78
+ readonly ttlSeconds: number;
79
+ /**
80
+ * Opt-in compute (token-spending) capability — adds the
81
+ * `compute.invoke` scope and a bounded `constraints.compute` budget
82
+ * to the mandate. See {@link CreateMandateComputeInput}.
83
+ *
84
+ * NEVER add `compute.invoke` to `scopes` directly — the SDK rejects
85
+ * that path so the caller has to pass through this typed namespace,
86
+ * which is what a consent UI can review.
87
+ */
88
+ readonly compute?: CreateMandateComputeInput;
89
+ /**
90
+ * When the mandate becomes valid. Optional — when omitted, the
91
+ * underlying mint helper signs with `not_before = now - 30s` (see
92
+ * `MANDATE_NOTBEFORE_OFFSET_SECONDS_DEFAULT` in
93
+ * `@aithos/protocol-client`) so a server whose clock runs slightly
94
+ * behind the client doesn't reject the freshly-minted mandate as
95
+ * `not yet valid`.
96
+ *
97
+ * Pass an explicit `Date` only for advanced flows (delayed-activation
98
+ * mandates, deterministic tests).
99
+ */
100
+ readonly notBefore?: Date;
101
+ }
102
+ export interface MintedMandate {
103
+ /** Unique mandate id (matches `mandate.id` inside the bundle). */
104
+ readonly mandateId: string;
105
+ /** Subject DID — the owner who issued it. */
106
+ readonly subjectDid: string;
107
+ /** Grantee URN. */
108
+ readonly granteeId: string;
109
+ readonly scopes: readonly Scope[];
110
+ /** ISO-8601 (UTC) — `null` if the mandate has no `not_after`. */
111
+ readonly expiresAt: string | null;
112
+ /** Shareable `.aithos-delegate.json` Blob. Hand this to the grantee. */
113
+ readonly bundle: Blob;
114
+ /** Suggested filename for the bundle. */
115
+ readonly filename: string;
116
+ }
117
+ export interface OwnedMandate {
118
+ readonly mandateId: string;
119
+ readonly issuerDid: string;
120
+ readonly actorDid: string;
121
+ readonly scopes: readonly Scope[];
122
+ readonly notBefore: number | null;
123
+ readonly notAfter: number | null;
124
+ readonly createdAt: number;
125
+ }
126
+ export interface MandatesNamespaceDeps {
127
+ readonly auth: AithosAuth;
128
+ readonly endpoints: AithosSdkEndpoints;
129
+ readonly fetch: typeof fetch;
130
+ }
131
+ export declare class MandatesNamespace {
132
+ #private;
133
+ constructor(deps: MandatesNamespaceDeps);
134
+ /**
135
+ * Mint, sign, publish, and package a fresh delegate bundle. The
136
+ * grantee's keypair is generated inside this call and never
137
+ * persisted on the owner's machine — the seed flows out via the
138
+ * returned Blob and only via that Blob.
139
+ */
140
+ create(input: CreateMandateInput): Promise<MintedMandate>;
141
+ /**
142
+ * List mandates issued by the signed-in owner. Pages through
143
+ * `aithos.list_mandates` until exhausted (or until 5 pages have
144
+ * been crawled — an owner with more than 1000 active mandates is
145
+ * out of scope today).
146
+ */
147
+ list(): Promise<readonly OwnedMandate[]>;
148
+ /**
149
+ * Publish a §4.2 revocation for `mandateId`. The mandate stops
150
+ * authorizing future actions (artifacts dated before `revoked_at`
151
+ * remain valid — revocation is not retroactive).
152
+ *
153
+ * Server-side: handled by `aithos.publish_revocation`. The envelope
154
+ * is signed by the owner's `#public` sphere — the spec also accepts
155
+ * `#root`, but `#public` is what the existing app does.
156
+ *
157
+ * Throws {@link AithosSDKError} on backend errors. Note that
158
+ * server-side support is in-flight; this method may surface a
159
+ * `mandates_-32601` (method not found) until the auth platform
160
+ * lands the corresponding write handler.
161
+ */
162
+ revoke(mandateId: string): Promise<void>;
163
+ }
2
164
  //# sourceMappingURL=mandates.d.ts.map
@@ -1,13 +1,291 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright 2026 Mathieu Colla
3
- // Mandates namespace — re-exports of mandate mint/sign primitives.
3
+ // `sdk.mandates` namespace — owner-side mandate lifecycle.
4
4
  //
5
- // A mandate is a signed delegation bundle that grants an app DID the right
6
- // to act on the user's behalf within a scoped set of operations (read
7
- // scopes, compute spend cap, TTL). Mandates are required by the compute
8
- // proxy on every Bedrock call.
5
+ // Three verbs:
6
+ // create(input) → mints + publishes + returns the
7
+ // shareable .aithos-delegate.json bundle
8
+ // list() → mandates the owner has issued
9
+ // revoke(mandateId) → publishes a §4.2 revocation
9
10
  //
10
- // See `@aithos/protocol-client/src/mandate-mint.ts` for the canonical
11
- // implementation; this namespace re-exports the public surface verbatim.
12
- export { mintDelegateBundle, signAndPublishMandate, MintError, DEFAULT_READ_SCOPES, TTL_PRESETS, } from "@aithos/protocol-client";
11
+ // Capability boundary: every method requires an owner signed in. We
12
+ // reach the OwnerSigners through `auth._getOwnerSigners()` and project
13
+ // to a `StoredIdentity` for protocol-client interop. When
14
+ // protocol-client gains a Signer-shaped API the projection step
15
+ // disappears.
16
+ import { buildSignedEnvelope, mintDelegateBundle, readRpc, } from "@aithos/protocol-client";
17
+ import { ownerKeyPair } from "./internal/protocol-client-bridge.js";
18
+ import { AithosSDKError } from "./types.js";
19
+ /**
20
+ * The opt-in scope that authorizes a delegate to spend the subject's
21
+ * compute credits via the Aithos compute proxy. Mirror of
22
+ * `COMPUTE_INVOKE_SCOPE` in `@aithos/protocol-core` v0.4.0.
23
+ *
24
+ * The SDK's `mandates.create()` injects this scope automatically when
25
+ * the caller passes a `compute` namespace, and refuses to mint a
26
+ * mandate where the caller put it directly into `scopes` — this is
27
+ * what makes "compute is a separate, conscious decision" hold at the
28
+ * API surface.
29
+ */
30
+ export const COMPUTE_INVOKE_SCOPE = "compute.invoke";
31
+ export class MandatesNamespace {
32
+ #deps;
33
+ constructor(deps) {
34
+ this.#deps = deps;
35
+ }
36
+ /**
37
+ * Mint, sign, publish, and package a fresh delegate bundle. The
38
+ * grantee's keypair is generated inside this call and never
39
+ * persisted on the owner's machine — the seed flows out via the
40
+ * returned Blob and only via that Blob.
41
+ */
42
+ async create(input) {
43
+ const owner = this.#requireOwner();
44
+ // A mandate must carry at least one capability — either ethos
45
+ // scopes, the compute namespace, or both. A "compute-only" mandate
46
+ // (`scopes: []` + `compute: { ... }`) is legitimate: it gives the
47
+ // grantee no access to the subject's ethos data, only the right
48
+ // to spend a bounded amount of compute credits in their name.
49
+ // Useful for creative assistants, brainstorming agents, etc.
50
+ if (input.scopes.length === 0 && input.compute === undefined) {
51
+ throw new AithosSDKError("mandates_invalid_scopes", "scopes must be a non-empty list (or pass `compute` for a compute-only mandate)");
52
+ }
53
+ if (input.ttlSeconds <= 0) {
54
+ throw new AithosSDKError("mandates_invalid_ttl", "ttlSeconds must be > 0");
55
+ }
56
+ // Forbid `compute.invoke` smuggled in via `scopes[]`. The whole
57
+ // point of the dedicated `compute` namespace is that adding token-
58
+ // spending capability requires a typed, named, reviewable input —
59
+ // not a string lost in a generic list. Type-checking can't catch
60
+ // this (the union doesn't include the literal, but callers can
61
+ // up-cast); the runtime check is what holds.
62
+ if (input.scopes.some((s) => s === COMPUTE_INVOKE_SCOPE)) {
63
+ throw new AithosSDKError("mandates_invalid_scopes", `Pass token-spending capability via the dedicated 'compute' namespace, ` +
64
+ `not by adding "${COMPUTE_INVOKE_SCOPE}" to scopes[]. The namespace forces ` +
65
+ `an explicit budget and is what a consent UI reviews.`);
66
+ }
67
+ // Validate + project the compute namespace if present, then derive
68
+ // the final scopes/constraints to send to the protocol layer.
69
+ const computeProjection = projectCompute(input.compute);
70
+ const projectedScopes = [...input.scopes];
71
+ if (computeProjection) {
72
+ projectedScopes.push(COMPUTE_INVOKE_SCOPE);
73
+ }
74
+ const actorSphere = input.actorSphere ?? defaultSphereFromScopes(input.scopes);
75
+ const ownerStored = owner._unsafeStoredIdentity();
76
+ const result = await mintDelegateBundle({
77
+ owner: ownerStored,
78
+ granteeId: input.granteeId,
79
+ ...(input.granteeLabel ? { granteeLabel: input.granteeLabel } : {}),
80
+ actorSphere,
81
+ scopes: projectedScopes,
82
+ ttlSeconds: input.ttlSeconds,
83
+ // protocol-client v0.1.0-alpha.11 ships MandateConstraints without
84
+ // the `compute` field; the wire format accepts it though (the
85
+ // canonicalizer just serializes whatever's in the object). We
86
+ // up-cast through `unknown` to bypass the structural check until
87
+ // protocol-client picks up protocol-core 0.4.0 types.
88
+ ...(computeProjection
89
+ ? {
90
+ constraints: {
91
+ compute: computeProjection,
92
+ },
93
+ }
94
+ : {}),
95
+ ...(input.notBefore ? { notBefore: input.notBefore } : {}),
96
+ });
97
+ const mandate = result.mandate;
98
+ return {
99
+ mandateId: mandate.id,
100
+ subjectDid: mandate.subject_did,
101
+ granteeId: input.granteeId,
102
+ scopes: projectedScopes,
103
+ expiresAt: mandate.not_after ?? null,
104
+ bundle: result.bundleBlob,
105
+ filename: `aithos-delegate-${mandate.id}.json`,
106
+ };
107
+ }
108
+ /**
109
+ * List mandates issued by the signed-in owner. Pages through
110
+ * `aithos.list_mandates` until exhausted (or until 5 pages have
111
+ * been crawled — an owner with more than 1000 active mandates is
112
+ * out of scope today).
113
+ */
114
+ async list() {
115
+ const owner = this.#requireOwner();
116
+ const out = [];
117
+ let cursor;
118
+ for (let i = 0; i < 5; i++) {
119
+ const page = await readRpc("aithos.list_mandates", {
120
+ issuer_did: owner.did,
121
+ limit: 200,
122
+ ...(cursor ? { cursor } : {}),
123
+ });
124
+ for (const it of page.items) {
125
+ out.push(toOwnedMandate(it));
126
+ }
127
+ if (!page.next_cursor)
128
+ break;
129
+ cursor = page.next_cursor;
130
+ }
131
+ return out;
132
+ }
133
+ /**
134
+ * Publish a §4.2 revocation for `mandateId`. The mandate stops
135
+ * authorizing future actions (artifacts dated before `revoked_at`
136
+ * remain valid — revocation is not retroactive).
137
+ *
138
+ * Server-side: handled by `aithos.publish_revocation`. The envelope
139
+ * is signed by the owner's `#public` sphere — the spec also accepts
140
+ * `#root`, but `#public` is what the existing app does.
141
+ *
142
+ * Throws {@link AithosSDKError} on backend errors. Note that
143
+ * server-side support is in-flight; this method may surface a
144
+ * `mandates_-32601` (method not found) until the auth platform
145
+ * lands the corresponding write handler.
146
+ */
147
+ async revoke(mandateId) {
148
+ const owner = this.#requireOwner();
149
+ const ownerStored = owner._unsafeStoredIdentity();
150
+ // TODO(post-alpha): once @aithos/protocol-client exposes a public
151
+ // configuration API for the `api` endpoint, route this through the
152
+ // same channel as `readRpc` / `publishZoneEdit` so self-hosters
153
+ // can override. For now, target the production write surface.
154
+ const url = "https://api.aithos.be/mcp/primitives/write";
155
+ const params = {
156
+ mandate_id: mandateId,
157
+ revoked_at: new Date().toISOString(),
158
+ };
159
+ // Reach into the owner's #public signer for envelope signing via
160
+ // the centralized migration bridge (will go away when
161
+ // protocol-client accepts Signer-shaped objects).
162
+ const publicKp = ownerKeyPair(owner, "public");
163
+ const envelope = buildSignedEnvelope({
164
+ iss: ownerStored.did,
165
+ aud: url,
166
+ method: "aithos.publish_revocation",
167
+ verificationMethod: `${ownerStored.did}#public`,
168
+ params,
169
+ signer: publicKp,
170
+ });
171
+ let res;
172
+ try {
173
+ res = await this.#deps.fetch(url, {
174
+ method: "POST",
175
+ headers: { "content-type": "application/json" },
176
+ body: JSON.stringify({
177
+ jsonrpc: "2.0",
178
+ id: "aithos.publish_revocation",
179
+ method: "aithos.publish_revocation",
180
+ params: { ...params, _envelope: envelope },
181
+ }),
182
+ });
183
+ }
184
+ catch (e) {
185
+ throw new AithosSDKError("network", e.message);
186
+ }
187
+ const body = (await res.json().catch(() => null));
188
+ if (!body) {
189
+ throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText} — non-JSON response`, { status: res.status });
190
+ }
191
+ if (body.error) {
192
+ throw new AithosSDKError(`mandates_${body.error.code}`, body.error.message, body.error.data ? { data: body.error.data } : undefined);
193
+ }
194
+ }
195
+ /* ------------------------------------------------------------------------ */
196
+ /* Internals */
197
+ /* ------------------------------------------------------------------------ */
198
+ #requireOwner() {
199
+ const owner = this.#deps.auth._getOwnerSigners();
200
+ if (!owner || owner.destroyed) {
201
+ throw new AithosSDKError("mandates_no_owner", "no owner signed in; sign in first");
202
+ }
203
+ return owner;
204
+ }
205
+ }
206
+ /* -------------------------------------------------------------------------- */
207
+ /* Helpers */
208
+ /* -------------------------------------------------------------------------- */
209
+ function defaultSphereFromScopes(scopes) {
210
+ if (scopes.some((s) => s.endsWith(".self")))
211
+ return "self";
212
+ if (scopes.some((s) => s.endsWith(".circle")))
213
+ return "circle";
214
+ return "public";
215
+ }
216
+ /**
217
+ * Validate the SDK-side `compute` namespace and project it onto the
218
+ * snake_case shape the protocol layer canonicalizes into the mandate.
219
+ *
220
+ * Returns `null` if the caller passed nothing — that's the "no compute
221
+ * authorization" path. Throws {@link AithosSDKError} on any structural
222
+ * problem (camelCase mirror of the rules `validateComputeAuthorization`
223
+ * enforces server-side; we duplicate them here so a misuse fails at the
224
+ * SDK boundary with a precise error rather than only blowing up at mint).
225
+ */
226
+ function projectCompute(c) {
227
+ if (c === undefined)
228
+ return null;
229
+ const hasDaily = typeof c.dailyCapMicrocredits === "number";
230
+ const hasTotal = typeof c.totalCapMicrocredits === "number";
231
+ if (!hasDaily && !hasTotal) {
232
+ throw new AithosSDKError("mandates_invalid_compute", "compute namespace requires at least one of dailyCapMicrocredits " +
233
+ "or totalCapMicrocredits — an unbounded compute mandate is a bearer " +
234
+ "token to drain the subject's wallet.");
235
+ }
236
+ for (const [field, value] of [
237
+ ["dailyCapMicrocredits", c.dailyCapMicrocredits],
238
+ ["totalCapMicrocredits", c.totalCapMicrocredits],
239
+ ["maxCreditsPerCall", c.maxCreditsPerCall],
240
+ ]) {
241
+ if (value === undefined)
242
+ continue;
243
+ if (!Number.isInteger(value) || value <= 0) {
244
+ throw new AithosSDKError("mandates_invalid_compute", `compute.${field} must be a positive integer (got ${value}).`);
245
+ }
246
+ }
247
+ if (c.allowedModels !== undefined) {
248
+ if (!Array.isArray(c.allowedModels)) {
249
+ throw new AithosSDKError("mandates_invalid_compute", "compute.allowedModels must be an array of strings.");
250
+ }
251
+ for (const m of c.allowedModels) {
252
+ if (typeof m !== "string" || m.length === 0) {
253
+ throw new AithosSDKError("mandates_invalid_compute", `compute.allowedModels entries must be non-empty strings (got ${JSON.stringify(m)}).`);
254
+ }
255
+ }
256
+ }
257
+ const wire = {};
258
+ if (hasDaily)
259
+ wire.daily_cap_microcredits = c.dailyCapMicrocredits;
260
+ if (hasTotal)
261
+ wire.total_cap_microcredits = c.totalCapMicrocredits;
262
+ if (c.maxCreditsPerCall !== undefined)
263
+ wire.max_credits_per_call = c.maxCreditsPerCall;
264
+ if (c.allowedModels !== undefined)
265
+ wire.allowed_models = [...c.allowedModels];
266
+ return wire;
267
+ }
268
+ function toOwnedMandate(it) {
269
+ return {
270
+ mandateId: requireString(it, "mandate_id"),
271
+ issuerDid: requireString(it, "issuer_did"),
272
+ actorDid: requireString(it, "actor_did"),
273
+ scopes: filterScopeArray(it["scopes"]),
274
+ notBefore: typeof it["not_before"] === "number" ? it["not_before"] : null,
275
+ notAfter: typeof it["not_after"] === "number" ? it["not_after"] : null,
276
+ createdAt: typeof it["created_at"] === "number" ? it["created_at"] : 0,
277
+ };
278
+ }
279
+ function requireString(o, key) {
280
+ const v = o[key];
281
+ if (typeof v !== "string") {
282
+ throw new AithosSDKError("mandates_response_malformed", `list_mandates row missing string field ${key}`);
283
+ }
284
+ return v;
285
+ }
286
+ function filterScopeArray(v) {
287
+ if (!Array.isArray(v))
288
+ return [];
289
+ return v.filter((s) => typeof s === "string");
290
+ }
13
291
  //# sourceMappingURL=mandates.js.map
package/dist/src/sdk.d.ts CHANGED
@@ -1,18 +1,54 @@
1
+ import type { AithosAuth } from "./auth.js";
1
2
  import { ComputeNamespace } from "./compute.js";
2
3
  import { type AithosSdkEndpoints } from "./endpoints.js";
4
+ import { EthosNamespace } from "./ethos.js";
5
+ import { MandatesNamespace } from "./mandates.js";
3
6
  import { WalletNamespace } from "./wallet.js";
4
- import type { AithosSDKConfig } from "./types.js";
7
+ import { WebNamespace } from "./web.js";
8
+ export interface AithosSDKConfig {
9
+ /**
10
+ * The {@link AithosAuth} instance the SDK reads sign-in state from.
11
+ * Constructed and managed by the app — typically a singleton at the
12
+ * top of the application tree.
13
+ */
14
+ readonly auth: AithosAuth;
15
+ /**
16
+ * Application DID — identifies the calling app in mandates, audit
17
+ * logs, billing splits. Issued at developer onboarding (will be
18
+ * self-service before 0.1.0 stable).
19
+ */
20
+ readonly appDid: string;
21
+ /**
22
+ * Optional endpoint overrides. Production defaults point at the
23
+ * Aithos infrastructure; pass overrides for staging, self-hosting,
24
+ * tests.
25
+ */
26
+ readonly endpoints?: Partial<AithosSdkEndpoints>;
27
+ /**
28
+ * Optional `fetch` implementation. Defaults to the global `fetch`.
29
+ * Used by tests to inject a mock without monkeypatching globals.
30
+ */
31
+ readonly fetch?: typeof fetch;
32
+ }
5
33
  export declare class AithosSDK {
6
34
  /** Resolved endpoint configuration (defaults + caller overrides). */
7
35
  readonly endpoints: AithosSdkEndpoints;
8
36
  /** Application DID (as declared in mandates). */
9
37
  readonly appDid: string;
10
- /** User DID (derived from `config.identity`). */
11
- readonly userDid: string;
38
+ /** The same auth instance the app constructed and passed in. */
39
+ readonly auth: AithosAuth;
12
40
  /** Compute proxy namespace — Bedrock invocation. */
13
41
  readonly compute: ComputeNamespace;
14
42
  /** Wallet namespace — Stripe top-up. */
15
43
  readonly wallet: WalletNamespace;
44
+ /** Ethos editing namespace — load, mutate (staged), publish. */
45
+ readonly ethos: EthosNamespace;
46
+ /** Mandate lifecycle namespace — create / list / revoke. */
47
+ readonly mandates: MandatesNamespace;
48
+ /** Web extraction namespace — aithos.web_extract through the web extractor proxy. */
49
+ readonly web: WebNamespace;
16
50
  constructor(config: AithosSDKConfig);
51
+ /** DID of the currently signed-in owner, or null if no owner is loaded. */
52
+ get userDid(): string | null;
17
53
  }
18
54
  //# sourceMappingURL=sdk.d.ts.map
package/dist/src/sdk.js CHANGED
@@ -1,58 +1,71 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // Copyright 2026 Mathieu Colla
3
- // AithosSDK — top-level developer surface.
4
- //
5
- // One construction, namespaced methods. The SDK takes the user's identity
6
- // (a `BrowserIdentity` from `@aithos/protocol-client`) and the calling
7
- // app's DID, then exposes:
8
- //
9
- // sdk.compute — Bedrock invocation through the compute proxy.
10
- // sdk.wallet — Stripe Checkout for credit-pack top-ups.
11
- // sdk.ethos — re-exports from protocol-client (zone editor, signers).
12
- // sdk.onboarding / sdk.mandates — likewise.
13
- //
14
- // The class is intentionally thin: the heavy lifting lives in dedicated
15
- // namespace classes (`ComputeNamespace`, `WalletNamespace`) so each can be
16
- // tested in isolation with a mock fetch and so an advanced caller could
17
- // instantiate just one of them if needed.
18
3
  import { ComputeNamespace } from "./compute.js";
19
4
  import { resolveEndpoints } from "./endpoints.js";
5
+ import { EthosNamespace } from "./ethos.js";
6
+ import { MandatesNamespace } from "./mandates.js";
20
7
  import { WalletNamespace } from "./wallet.js";
8
+ import { WebNamespace } from "./web.js";
21
9
  export class AithosSDK {
22
10
  /** Resolved endpoint configuration (defaults + caller overrides). */
23
11
  endpoints;
24
12
  /** Application DID (as declared in mandates). */
25
13
  appDid;
26
- /** User DID (derived from `config.identity`). */
27
- userDid;
14
+ /** The same auth instance the app constructed and passed in. */
15
+ auth;
28
16
  /** Compute proxy namespace — Bedrock invocation. */
29
17
  compute;
30
18
  /** Wallet namespace — Stripe top-up. */
31
19
  wallet;
20
+ /** Ethos editing namespace — load, mutate (staged), publish. */
21
+ ethos;
22
+ /** Mandate lifecycle namespace — create / list / revoke. */
23
+ mandates;
24
+ /** Web extraction namespace — aithos.web_extract through the web extractor proxy. */
25
+ web;
32
26
  constructor(config) {
33
- if (!config.identity) {
34
- throw new TypeError("AithosSDK: config.identity is required");
27
+ if (!config.auth) {
28
+ throw new TypeError("AithosSDK: config.auth is required");
35
29
  }
36
30
  if (!config.appDid || typeof config.appDid !== "string") {
37
31
  throw new TypeError("AithosSDK: config.appDid is required (string)");
38
32
  }
39
33
  this.endpoints = resolveEndpoints(config.endpoints);
40
34
  this.appDid = config.appDid;
41
- this.userDid = config.identity.did;
35
+ this.auth = config.auth;
42
36
  const fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
43
37
  this.compute = new ComputeNamespace({
44
- identity: config.identity,
38
+ auth: config.auth,
45
39
  appDid: config.appDid,
46
40
  endpoints: this.endpoints,
47
41
  fetch: fetchImpl,
48
42
  });
49
43
  this.wallet = new WalletNamespace({
50
- identity: config.identity,
44
+ auth: config.auth,
51
45
  appDid: config.appDid,
52
- userDid: config.identity.did,
53
46
  endpoints: this.endpoints,
54
47
  fetch: fetchImpl,
55
48
  });
49
+ this.ethos = new EthosNamespace({
50
+ auth: config.auth,
51
+ endpoints: this.endpoints,
52
+ fetch: fetchImpl,
53
+ });
54
+ this.mandates = new MandatesNamespace({
55
+ auth: config.auth,
56
+ endpoints: this.endpoints,
57
+ fetch: fetchImpl,
58
+ });
59
+ this.web = new WebNamespace({
60
+ auth: config.auth,
61
+ appDid: config.appDid,
62
+ endpoints: this.endpoints,
63
+ fetch: fetchImpl,
64
+ });
65
+ }
66
+ /** DID of the currently signed-in owner, or null if no owner is loaded. */
67
+ get userDid() {
68
+ return this.auth.getOwnerInfo()?.did ?? null;
56
69
  }
57
70
  }
58
71
  //# sourceMappingURL=sdk.js.map
@@ -0,0 +1,58 @@
1
+ import type { AithosSession } from "./auth.js";
2
+ /**
3
+ * Pluggable storage backend for the active session.
4
+ *
5
+ * Implementations should be **synchronous** : the SDK reads the store on
6
+ * boot to surface `getCurrentSession()` and async storage would force a
7
+ * Promise everywhere it's not warranted. Async backends should pre-warm
8
+ * the cache during the app's bootstrap and then implement the methods
9
+ * over that cache.
10
+ *
11
+ * `set` is called after every successful sign-in / sign-up / Google
12
+ * callback exchange. `clear` is called on `signOut`. `get` is called by
13
+ * `getCurrentSession()` and may also be called by the app on boot.
14
+ */
15
+ export interface AithosSessionStore {
16
+ /** Read the persisted session. Return null if none, or if it's expired. */
17
+ get(): AithosSession | null;
18
+ /** Persist the session. Implementations should not throw — at worst
19
+ * they should warn and silently no-op (e.g. quota exceeded). */
20
+ set(session: AithosSession): void;
21
+ /** Wipe the persisted session. */
22
+ clear(): void;
23
+ }
24
+ /**
25
+ * Storage key used by the bundled stores. Apps that want to coexist with
26
+ * other Aithos-aware libs (or that want to scope sessions per-tenant) can
27
+ * pass a custom key via {@link sessionStorageStore} or
28
+ * {@link localStorageStore}.
29
+ */
30
+ export declare const DEFAULT_SESSION_STORAGE_KEY = "aithos.session.v1";
31
+ interface WebStorageStoreOptions {
32
+ /** Storage key. Defaults to {@link DEFAULT_SESSION_STORAGE_KEY}. */
33
+ readonly key?: string;
34
+ }
35
+ /**
36
+ * Default web store : `sessionStorage`. The session lives until the tab
37
+ * is closed. Cleared on `signOut()`. Use this when reauthenticating each
38
+ * day is acceptable and reduces blast radius after an XSS.
39
+ */
40
+ export declare function sessionStorageStore(opts?: WebStorageStoreOptions): AithosSessionStore;
41
+ /**
42
+ * `localStorage` store. The session persists until the JWT expires or the
43
+ * user explicitly signs out. Higher convenience, larger XSS blast radius.
44
+ */
45
+ export declare function localStorageStore(opts?: WebStorageStoreOptions): AithosSessionStore;
46
+ /**
47
+ * No-op store. `set` and `clear` discard their input ; `get` always
48
+ * returns null. The default in non-browser contexts (Node, edge runtimes)
49
+ * — apps running there should pass their own store explicitly.
50
+ */
51
+ export declare function noopStore(): AithosSessionStore;
52
+ /**
53
+ * Pick a sensible default : `sessionStorage` if the browser environment
54
+ * is available, {@link noopStore} otherwise.
55
+ */
56
+ export declare function defaultSessionStore(): AithosSessionStore;
57
+ export {};
58
+ //# sourceMappingURL=session-store.d.ts.map