@aithos/sdk 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +45 -0
- package/dist/src/auth-api.d.ts +50 -0
- package/dist/src/auth-api.js +102 -0
- package/dist/src/auth.d.ts +253 -0
- package/dist/src/auth.js +940 -0
- package/dist/src/compute.d.ts +370 -9
- package/dist/src/compute.js +369 -16
- package/dist/src/ethos.d.ts +164 -1
- package/dist/src/ethos.js +729 -16
- package/dist/src/index.d.ts +11 -4
- package/dist/src/index.js +31 -5
- package/dist/src/internal/delegate-bundle.d.ts +18 -0
- package/dist/src/internal/delegate-bundle.js +94 -0
- package/dist/src/internal/delegate-state.d.ts +45 -0
- package/dist/src/internal/delegate-state.js +120 -0
- package/dist/src/internal/owner-signers.d.ts +78 -0
- package/dist/src/internal/owner-signers.js +179 -0
- package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
- package/dist/src/internal/protocol-client-bridge.js +20 -0
- package/dist/src/internal/recovery-file.d.ts +29 -0
- package/dist/src/internal/recovery-file.js +98 -0
- package/dist/src/internal/signer.d.ts +59 -0
- package/dist/src/internal/signer.js +86 -0
- package/dist/src/key-store.d.ts +128 -0
- package/dist/src/key-store.js +244 -0
- package/dist/src/mandates.d.ts +163 -1
- package/dist/src/mandates.js +286 -8
- package/dist/src/sdk.d.ts +36 -3
- package/dist/src/sdk.js +27 -23
- package/dist/src/session-store.d.ts +58 -0
- package/dist/src/session-store.js +158 -0
- package/dist/src/wallet.d.ts +4 -6
- package/dist/src/wallet.js +18 -8
- package/dist/test/auth-j3.test.d.ts +2 -0
- package/dist/test/auth-j3.test.js +391 -0
- package/dist/test/auth.test.d.ts +2 -0
- package/dist/test/auth.test.js +175 -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 +184 -11
- package/dist/test/ethos-first-edition.test.d.ts +2 -0
- package/dist/test/ethos-first-edition.test.js +248 -0
- package/dist/test/ethos.test.d.ts +2 -0
- package/dist/test/ethos.test.js +219 -0
- package/dist/test/key-store.test.d.ts +2 -0
- package/dist/test/key-store.test.js +161 -0
- package/dist/test/mandates-compute.test.d.ts +2 -0
- package/dist/test/mandates-compute.test.js +256 -0
- package/dist/test/mandates.test.d.ts +2 -0
- package/dist/test/mandates.test.js +93 -0
- package/dist/test/sdk.test.js +70 -30
- package/dist/test/signer.test.d.ts +2 -0
- package/dist/test/signer.test.js +117 -0
- package/dist/test/signup-bootstrap.test.d.ts +2 -0
- package/dist/test/signup-bootstrap.test.js +222 -0
- package/dist/test/wallet.test.js +20 -9
- package/package.json +4 -3
package/dist/src/compute.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Wraps the JSON-RPC + signed-envelope protocol of `compute.aithos.be`
|
|
6
6
|
// behind an ergonomic method on the SDK. Internally builds a §11 envelope
|
|
7
|
-
// signed with the user's public-sphere key
|
|
8
|
-
// `${compute}/v1/invoke`.
|
|
7
|
+
// signed with the user's public-sphere key (read from
|
|
8
|
+
// {@link AithosAuth} at call time) and posts it to `${compute}/v1/invoke`.
|
|
9
9
|
//
|
|
10
10
|
// Server-side guarantees (enforced by the proxy, not by this lib):
|
|
11
11
|
// - Envelope signature verified against the user's `did.json`.
|
|
@@ -18,11 +18,13 @@
|
|
|
18
18
|
// tool calling on the proxy) will land in a follow-up.
|
|
19
19
|
import { buildSignedEnvelope, } from "@aithos/protocol-client";
|
|
20
20
|
import { computeInvokeUrl, } from "./endpoints.js";
|
|
21
|
+
import { delegateKeyPair, ownerKeyPair, } from "./internal/protocol-client-bridge.js";
|
|
21
22
|
import { AithosSDKError } from "./types.js";
|
|
22
23
|
/**
|
|
23
24
|
* `sdk.compute` namespace. Constructed once by the {@link AithosSDK}
|
|
24
|
-
* constructor;
|
|
25
|
-
*
|
|
25
|
+
* constructor; reads the active owner from the supplied
|
|
26
|
+
* {@link AithosAuth} on every call so signing material follows the
|
|
27
|
+
* latest sign-in/sign-out state.
|
|
26
28
|
*/
|
|
27
29
|
export class ComputeNamespace {
|
|
28
30
|
#deps;
|
|
@@ -33,17 +35,38 @@ export class ComputeNamespace {
|
|
|
33
35
|
* Invoke a Bedrock model through the compute proxy. See
|
|
34
36
|
* {@link InvokeBedrockArgs} and {@link InvokeBedrockResult}.
|
|
35
37
|
*
|
|
38
|
+
* Two signer paths are supported:
|
|
39
|
+
*
|
|
40
|
+
* - **Owner**: when the caller is signed in as an owner, the
|
|
41
|
+
* envelope is signed with the owner's `#public` sphere key and
|
|
42
|
+
* no mandate is attached (the proxy resolves the mandate
|
|
43
|
+
* server-side from `params.mandate_id`).
|
|
44
|
+
* - **Delegate**: when the caller is delegate-only (mandate
|
|
45
|
+
* imported via `auth.importMandate`), the envelope is signed
|
|
46
|
+
* with the delegate's bound keypair and the full SignedMandate
|
|
47
|
+
* is attached so the proxy can verify both signature and
|
|
48
|
+
* authorisation in one pass. The mandate must carry the
|
|
49
|
+
* `compute.invoke` scope and the proxy enforces its constraints
|
|
50
|
+
* (caps, allowed models, …) at server-side.
|
|
51
|
+
*
|
|
52
|
+
* Owner takes precedence: if a session has BOTH an owner and a
|
|
53
|
+
* matching delegate session, we use the owner key (more flexible —
|
|
54
|
+
* the mandate is dereferenced via `mandate_id` and there's no
|
|
55
|
+
* lifetime cliff if the delegate seed has been wiped).
|
|
56
|
+
*
|
|
36
57
|
* @throws {AithosSDKError} on protocol errors. The `code` field is one of
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
58
|
+
* `sdk_no_signer`, `sdk_no_delegate_for_mandate`, `network`, `http`,
|
|
59
|
+
* `empty`, or any code returned by the proxy (`quota_exceeded`,
|
|
60
|
+
* `mandate_revoked`, `insufficient_credits`, …).
|
|
39
61
|
*/
|
|
40
62
|
async invokeBedrock(args) {
|
|
41
|
-
const {
|
|
63
|
+
const { endpoints, fetch: fetchImpl } = this.#deps;
|
|
64
|
+
const choice = this.#resolveSigner(args.mandateId);
|
|
42
65
|
const url = computeInvokeUrl(endpoints);
|
|
43
66
|
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
44
67
|
const params = {
|
|
45
|
-
app_did: appDid,
|
|
46
|
-
mandate_id: args.mandateId,
|
|
68
|
+
app_did: this.#deps.appDid,
|
|
69
|
+
mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
|
|
47
70
|
model: args.model,
|
|
48
71
|
messages: args.messages,
|
|
49
72
|
idempotency_key: idempotencyKey,
|
|
@@ -54,13 +77,328 @@ export class ComputeNamespace {
|
|
|
54
77
|
params.max_tokens = args.maxTokens;
|
|
55
78
|
if (args.temperature !== undefined)
|
|
56
79
|
params.temperature = args.temperature;
|
|
80
|
+
return await this.#signAndPost({
|
|
81
|
+
url,
|
|
82
|
+
method: "aithos.compute_invoke",
|
|
83
|
+
params,
|
|
84
|
+
choice,
|
|
85
|
+
fetchImpl,
|
|
86
|
+
signal: args.signal,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Multimodal Bedrock invoke — image + text → text response.
|
|
91
|
+
* Default model: `claude-sonnet-4-6` (vision-capable, reliable JSON).
|
|
92
|
+
*
|
|
93
|
+
* Use when you need a VLM to reason about an image: locating
|
|
94
|
+
* features, structured extraction, semantic Q&A. Prompt the model
|
|
95
|
+
* to return JSON if you need structured output (the API itself is
|
|
96
|
+
* unstructured).
|
|
97
|
+
*/
|
|
98
|
+
async invokeBedrockVision(args) {
|
|
99
|
+
const { endpoints, fetch: fetchImpl } = this.#deps;
|
|
100
|
+
const choice = this.#resolveSigner(args.mandateId);
|
|
101
|
+
let imageBase64;
|
|
102
|
+
let imageContentType;
|
|
103
|
+
if ("base64" in args.image) {
|
|
104
|
+
imageBase64 = args.image.base64;
|
|
105
|
+
imageContentType = args.image.contentType;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
const buf = await args.image.arrayBuffer();
|
|
109
|
+
imageBase64 = arrayBufferToBase64(buf);
|
|
110
|
+
imageContentType = args.image.type || "image/png";
|
|
111
|
+
}
|
|
112
|
+
const url = computeInvokeUrl(endpoints);
|
|
113
|
+
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
114
|
+
const model = args.model ?? "claude-sonnet-4-6";
|
|
115
|
+
const params = {
|
|
116
|
+
app_did: this.#deps.appDid,
|
|
117
|
+
mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
|
|
118
|
+
model,
|
|
119
|
+
image_base64: imageBase64,
|
|
120
|
+
image_content_type: imageContentType,
|
|
121
|
+
prompt: args.prompt,
|
|
122
|
+
idempotency_key: idempotencyKey,
|
|
123
|
+
};
|
|
124
|
+
if (args.system !== undefined)
|
|
125
|
+
params.system = args.system;
|
|
126
|
+
if (args.maxTokens !== undefined)
|
|
127
|
+
params.max_tokens = args.maxTokens;
|
|
128
|
+
if (args.temperature !== undefined)
|
|
129
|
+
params.temperature = args.temperature;
|
|
130
|
+
return await this.#signAndPost({
|
|
131
|
+
url,
|
|
132
|
+
method: "aithos.compute_invoke_bedrock_vision",
|
|
133
|
+
params,
|
|
134
|
+
choice,
|
|
135
|
+
fetchImpl,
|
|
136
|
+
signal: args.signal,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Generate one or more images through the Aithos compute proxy
|
|
141
|
+
* (currently powered by fal.ai FLUX models). Spec mirror of
|
|
142
|
+
* {@link invokeBedrock}: same envelope, same wallet path, same
|
|
143
|
+
* mandate-scope gate (`compute.invoke` + `allowed_models`). The
|
|
144
|
+
* separation at the JSON-RPC method level (`aithos.compute_invoke_image`
|
|
145
|
+
* vs `aithos.compute_invoke`) commits each envelope to a specific
|
|
146
|
+
* modality so a stolen text-invoke envelope cannot be replayed
|
|
147
|
+
* against the image endpoint.
|
|
148
|
+
*
|
|
149
|
+
* Default model: `"image:flux-pro-1.1"`. Default aspect ratio: 1:1.
|
|
150
|
+
* Default count: 1 image.
|
|
151
|
+
*
|
|
152
|
+
* Pricing is per image and deterministic (no token-based reconcile):
|
|
153
|
+
* - flux-schnell: 3 000 mc + fee per image
|
|
154
|
+
* - flux-dev: 25 000 mc + fee per image
|
|
155
|
+
* - flux-pro-1.1: 40 000 mc + fee per image
|
|
156
|
+
* - flux-pro-1.1-ultra: 60 000 mc + fee per image
|
|
157
|
+
*/
|
|
158
|
+
async invokeImage(args) {
|
|
159
|
+
const { endpoints, fetch: fetchImpl } = this.#deps;
|
|
160
|
+
const choice = this.#resolveSigner(args.mandateId);
|
|
161
|
+
const url = computeInvokeUrl(endpoints);
|
|
162
|
+
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
163
|
+
// Default is Imagen 4 since alpha.17 — flat-illustration style is
|
|
164
|
+
// the right match for brand-mascot use cases. FLUX Pro 1.1 remains
|
|
165
|
+
// available via explicit `model: "image:flux-pro-1.1"`.
|
|
166
|
+
const model = args.model ?? "image:imagen-4";
|
|
167
|
+
const params = {
|
|
168
|
+
app_did: this.#deps.appDid,
|
|
169
|
+
mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
|
|
170
|
+
model,
|
|
171
|
+
prompt: args.prompt,
|
|
172
|
+
idempotency_key: idempotencyKey,
|
|
173
|
+
};
|
|
174
|
+
if (args.negativePrompt !== undefined)
|
|
175
|
+
params.negative_prompt = args.negativePrompt;
|
|
176
|
+
if (args.aspectRatio !== undefined)
|
|
177
|
+
params.aspect_ratio = args.aspectRatio;
|
|
178
|
+
if (args.seed !== undefined)
|
|
179
|
+
params.seed = args.seed;
|
|
180
|
+
if (args.numberOfImages !== undefined)
|
|
181
|
+
params.number_of_images = args.numberOfImages;
|
|
182
|
+
return await this.#signAndPost({
|
|
183
|
+
url,
|
|
184
|
+
method: "aithos.compute_invoke_image",
|
|
185
|
+
params,
|
|
186
|
+
choice,
|
|
187
|
+
fetchImpl,
|
|
188
|
+
signal: args.signal,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Run text-prompted segmentation (Florence-2 referring-expression)
|
|
193
|
+
* on a source image. Returns one or more polygons hugging the
|
|
194
|
+
* region matching the text prompt.
|
|
195
|
+
*
|
|
196
|
+
* Use cases: locate the chest/torso area of a generated mascot
|
|
197
|
+
* for logo compositing, find the face zone for a thumbnail crop,
|
|
198
|
+
* extract a product from a marketing shot — anything that needs
|
|
199
|
+
* a precise mask + bbox from natural-language description.
|
|
200
|
+
*
|
|
201
|
+
* Pricing: flat 5 000 mc per call (~$0.005 — Florence-2 is cheap).
|
|
202
|
+
*/
|
|
203
|
+
async invokeSegmentation(args) {
|
|
204
|
+
const { endpoints, fetch: fetchImpl } = this.#deps;
|
|
205
|
+
const choice = this.#resolveSigner(args.mandateId);
|
|
206
|
+
// Normalize image input to base64 + content type.
|
|
207
|
+
let imageBase64;
|
|
208
|
+
let imageContentType;
|
|
209
|
+
if ("base64" in args.image) {
|
|
210
|
+
imageBase64 = args.image.base64;
|
|
211
|
+
imageContentType = args.image.contentType;
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const buf = await args.image.arrayBuffer();
|
|
215
|
+
imageBase64 = arrayBufferToBase64(buf);
|
|
216
|
+
imageContentType = args.image.type || "image/png";
|
|
217
|
+
}
|
|
218
|
+
const url = computeInvokeUrl(endpoints);
|
|
219
|
+
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
220
|
+
const params = {
|
|
221
|
+
app_did: this.#deps.appDid,
|
|
222
|
+
mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
|
|
223
|
+
image_base64: imageBase64,
|
|
224
|
+
image_content_type: imageContentType,
|
|
225
|
+
text_input: args.textInput,
|
|
226
|
+
idempotency_key: idempotencyKey,
|
|
227
|
+
};
|
|
228
|
+
return await this.#signAndPost({
|
|
229
|
+
url,
|
|
230
|
+
method: "aithos.compute_invoke_segmentation",
|
|
231
|
+
params,
|
|
232
|
+
choice,
|
|
233
|
+
fetchImpl,
|
|
234
|
+
signal: args.signal,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Fetch one or more URLs and have Claude analyze the content. Routes
|
|
239
|
+
* through `api.anthropic.com` with the `web_fetch` server-side tool —
|
|
240
|
+
* NOT through Bedrock — because Bedrock does not expose Anthropic's
|
|
241
|
+
* server-side tools (web_fetch, web_search, etc.). The proxy hides
|
|
242
|
+
* this multi-backend detail from the SDK consumer; the wallet,
|
|
243
|
+
* envelope, and mandate-scope contracts are unchanged.
|
|
244
|
+
*
|
|
245
|
+
* Typical use:
|
|
246
|
+
* ```ts
|
|
247
|
+
* const r = await sdk.compute.invokeUrlFetch({
|
|
248
|
+
* prompt: "Voici l'URL https://tata.com — résume le service, " +
|
|
249
|
+
* "identifie le style et la couleur primaire. JSON.",
|
|
250
|
+
* });
|
|
251
|
+
* console.log(r.content);
|
|
252
|
+
* for (const c of r.citations) console.log(c.url, "→", c.citedText);
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* Mandate scope: requires `compute.url_fetch` (distinct from
|
|
256
|
+
* `compute.invoke` — see {@link InvokeUrlFetchArgs.mandateId}).
|
|
257
|
+
*
|
|
258
|
+
* Pricing: same Claude 4.x rates as Bedrock (Anthropic's direct API
|
|
259
|
+
* and Bedrock list prices are identical). Default Haiku 4.5 ≈
|
|
260
|
+
* $0.001/1k input + $0.005/1k output. The proxy pre-debits a
|
|
261
|
+
* conservative upper bound (`maxFetches × maxContentTokens × input
|
|
262
|
+
* rate + maxTokens × output rate`) and reconciles down to the actual
|
|
263
|
+
* usage Anthropic reports — so the wallet is always charged the
|
|
264
|
+
* exact post-call cost.
|
|
265
|
+
*
|
|
266
|
+
* @throws {AithosSDKError} on protocol errors. Notable codes:
|
|
267
|
+
* `sdk_no_signer`, `sdk_no_delegate_for_mandate`, `network`,
|
|
268
|
+
* `http`, `empty`, plus proxy codes `-32042` (mandate scope
|
|
269
|
+
* missing), `-32070`/`-32071` (wallet), `-32074` (fetch blocked
|
|
270
|
+
* by robots.txt / domain filter), `-32050` (rate limit).
|
|
271
|
+
*/
|
|
272
|
+
async invokeUrlFetch(args) {
|
|
273
|
+
const { endpoints, fetch: fetchImpl } = this.#deps;
|
|
274
|
+
const choice = this.#resolveSigner(args.mandateId);
|
|
275
|
+
const url = computeInvokeUrl(endpoints);
|
|
276
|
+
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
277
|
+
const model = args.model ?? "claude-haiku-4-5";
|
|
278
|
+
const params = {
|
|
279
|
+
app_did: this.#deps.appDid,
|
|
280
|
+
mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
|
|
281
|
+
model,
|
|
282
|
+
prompt: args.prompt,
|
|
283
|
+
idempotency_key: idempotencyKey,
|
|
284
|
+
};
|
|
285
|
+
if (args.system !== undefined)
|
|
286
|
+
params.system = args.system;
|
|
287
|
+
if (args.maxTokens !== undefined)
|
|
288
|
+
params.max_tokens = args.maxTokens;
|
|
289
|
+
if (args.temperature !== undefined)
|
|
290
|
+
params.temperature = args.temperature;
|
|
291
|
+
if (args.maxFetches !== undefined)
|
|
292
|
+
params.max_fetches = args.maxFetches;
|
|
293
|
+
if (args.maxContentTokens !== undefined) {
|
|
294
|
+
params.max_content_tokens = args.maxContentTokens;
|
|
295
|
+
}
|
|
296
|
+
if (args.citations !== undefined)
|
|
297
|
+
params.citations = args.citations;
|
|
298
|
+
if (args.allowedDomains !== undefined && args.allowedDomains.length > 0) {
|
|
299
|
+
params.allowed_domains = args.allowedDomains;
|
|
300
|
+
}
|
|
301
|
+
if (args.blockedDomains !== undefined && args.blockedDomains.length > 0) {
|
|
302
|
+
params.blocked_domains = args.blockedDomains;
|
|
303
|
+
}
|
|
304
|
+
return await this.#signAndPost({
|
|
305
|
+
url,
|
|
306
|
+
method: "aithos.compute_invoke_url_fetch",
|
|
307
|
+
params,
|
|
308
|
+
choice,
|
|
309
|
+
fetchImpl,
|
|
310
|
+
signal: args.signal,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Resolve the active signer (owner takes precedence over delegate).
|
|
315
|
+
*
|
|
316
|
+
* - When an owner is signed in: returns the owner-signer regardless
|
|
317
|
+
* of `mandateId` (the proxy ignores params.mandate_id on
|
|
318
|
+
* owner-signed envelopes, so owners can omit it).
|
|
319
|
+
* - When delegate-only: requires `mandateId` to find the matching
|
|
320
|
+
* imported bundle. Throws `sdk_no_delegate_for_mandate` if absent
|
|
321
|
+
* or no match.
|
|
322
|
+
*/
|
|
323
|
+
#resolveSigner(mandateId) {
|
|
324
|
+
const { auth } = this.#deps;
|
|
325
|
+
const owner = auth._getOwnerSigners();
|
|
326
|
+
const ownerLoaded = owner !== null && !owner.destroyed;
|
|
327
|
+
if (ownerLoaded) {
|
|
328
|
+
const publicKp = ownerKeyPair(owner, "public");
|
|
329
|
+
return {
|
|
330
|
+
kind: "owner",
|
|
331
|
+
iss: owner.did,
|
|
332
|
+
verificationMethod: `${owner.did}#public`,
|
|
333
|
+
signer: publicKp,
|
|
334
|
+
mandate: undefined,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (mandateId === undefined || mandateId.length === 0) {
|
|
338
|
+
throw new AithosSDKError("sdk_no_signer", "no owner signed in and no mandateId provided — pass a mandateId for a delegate session, or sign in as an owner first.");
|
|
339
|
+
}
|
|
340
|
+
const actor = auth._getDelegateActor(mandateId);
|
|
341
|
+
if (!actor || actor.destroyed) {
|
|
342
|
+
throw new AithosSDKError("sdk_no_delegate_for_mandate", `no owner signed in and no imported delegate mandate matches '${mandateId}'. Sign in as an owner, or import a delegate bundle for that mandate via auth.importMandate.`);
|
|
343
|
+
}
|
|
344
|
+
const kp = delegateKeyPair(actor);
|
|
345
|
+
return {
|
|
346
|
+
kind: "delegate",
|
|
347
|
+
iss: actor.subjectDid,
|
|
348
|
+
verificationMethod: actor.granteePubkeyMultibase,
|
|
349
|
+
signer: kp,
|
|
350
|
+
// The DelegateActor stores the SignedMandate as a structurally
|
|
351
|
+
// opaque object so the SDK doesn't have to import the
|
|
352
|
+
// protocol-client type at the storage boundary. Round-trip
|
|
353
|
+
// through `unknown` for the TS cast — at runtime the bytes are
|
|
354
|
+
// the canonical SignedMandate the bundle parser already
|
|
355
|
+
// validated.
|
|
356
|
+
mandate: actor.mandate,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Resolve the `mandate_id` value the SDK writes into the JSON-RPC
|
|
361
|
+
* params for the wire. The proxy requires this field to be a
|
|
362
|
+
* non-empty string but only consults it for the delegate path —
|
|
363
|
+
* for owner-signed envelopes it's audit-log metadata, with no
|
|
364
|
+
* security implication.
|
|
365
|
+
*
|
|
366
|
+
* Strategy:
|
|
367
|
+
* - Caller-provided id always wins (owner who wants to attribute
|
|
368
|
+
* to a specific minted mandate can still pass it).
|
|
369
|
+
* - Delegate path: the choice carries the real mandate.id — use it
|
|
370
|
+
* so a delegate cannot accidentally lie about which mandate
|
|
371
|
+
* it claims to spend under.
|
|
372
|
+
* - Owner path with no explicit id: use the owner DID followed
|
|
373
|
+
* by `#self` — a stable sentinel that's unambiguously not a
|
|
374
|
+
* real mandate id (mandate ids are ULIDs).
|
|
375
|
+
*/
|
|
376
|
+
#resolveMandateIdForWire(explicit, choice) {
|
|
377
|
+
if (explicit && explicit.length > 0)
|
|
378
|
+
return explicit;
|
|
379
|
+
if (choice.kind === "delegate")
|
|
380
|
+
return choice.mandate.id;
|
|
381
|
+
// Owner-direct sentinel. The proxy never inspects this value
|
|
382
|
+
// beyond non-empty-string validation when no mandate is attached.
|
|
383
|
+
return `${choice.iss}#self`;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Sign the params with the resolved signer and POST a JSON-RPC
|
|
387
|
+
* request. Shared between `invokeBedrock` and `invokeImage` — the
|
|
388
|
+
* only difference between those two is the method name and the
|
|
389
|
+
* params shape; the envelope construction + transport + error
|
|
390
|
+
* mapping is identical.
|
|
391
|
+
*/
|
|
392
|
+
async #signAndPost(opts) {
|
|
393
|
+
const { url, method, params, choice, fetchImpl, signal } = opts;
|
|
57
394
|
const envelope = buildSignedEnvelope({
|
|
58
|
-
iss:
|
|
395
|
+
iss: choice.iss,
|
|
59
396
|
aud: url,
|
|
60
|
-
method
|
|
61
|
-
verificationMethod:
|
|
397
|
+
method,
|
|
398
|
+
verificationMethod: choice.verificationMethod,
|
|
62
399
|
params,
|
|
63
|
-
signer:
|
|
400
|
+
signer: choice.signer,
|
|
401
|
+
...(choice.kind === "delegate" ? { mandate: choice.mandate } : {}),
|
|
64
402
|
});
|
|
65
403
|
let res;
|
|
66
404
|
try {
|
|
@@ -69,11 +407,11 @@ export class ComputeNamespace {
|
|
|
69
407
|
headers: { "content-type": "application/json" },
|
|
70
408
|
body: JSON.stringify({
|
|
71
409
|
jsonrpc: "2.0",
|
|
72
|
-
id:
|
|
73
|
-
method
|
|
410
|
+
id: method,
|
|
411
|
+
method,
|
|
74
412
|
params: { ...params, _envelope: envelope },
|
|
75
413
|
}),
|
|
76
|
-
...(
|
|
414
|
+
...(signal ? { signal } : {}),
|
|
77
415
|
});
|
|
78
416
|
}
|
|
79
417
|
catch (e) {
|
|
@@ -101,4 +439,19 @@ function generateIdempotencyKey() {
|
|
|
101
439
|
}
|
|
102
440
|
return hex;
|
|
103
441
|
}
|
|
442
|
+
/**
|
|
443
|
+
* Encode an ArrayBuffer as base64 in environments where `Buffer` is
|
|
444
|
+
* not available (browser). Uses btoa over a binary string — safe for
|
|
445
|
+
* the small payload sizes the SDK deals with (≤ a few MB).
|
|
446
|
+
*/
|
|
447
|
+
function arrayBufferToBase64(buf) {
|
|
448
|
+
const bytes = new Uint8Array(buf);
|
|
449
|
+
let bin = "";
|
|
450
|
+
// Process in chunks to avoid stack overflow on String.fromCharCode.apply
|
|
451
|
+
const CHUNK = 0x8000;
|
|
452
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
453
|
+
bin += String.fromCharCode.apply(null, Array.from(bytes.subarray(i, i + CHUNK)));
|
|
454
|
+
}
|
|
455
|
+
return btoa(bin);
|
|
456
|
+
}
|
|
104
457
|
//# sourceMappingURL=compute.js.map
|
package/dist/src/ethos.d.ts
CHANGED
|
@@ -1,2 +1,165 @@
|
|
|
1
|
-
|
|
1
|
+
import { type Section } from "@aithos/protocol-client";
|
|
2
|
+
import type { AithosAuth } from "./auth.js";
|
|
3
|
+
import type { AithosSdkEndpoints } from "./endpoints.js";
|
|
4
|
+
import type { DelegateActor } from "./internal/delegate-state.js";
|
|
5
|
+
import type { OwnerSigners } from "./internal/owner-signers.js";
|
|
6
|
+
export type ZoneName = "public" | "circle" | "self";
|
|
7
|
+
export declare const ZONE_NAMES: readonly ZoneName[];
|
|
8
|
+
export interface AddSectionInput {
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly body: string;
|
|
11
|
+
readonly tags?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
export interface UpdateSectionPatch {
|
|
14
|
+
readonly title?: string;
|
|
15
|
+
readonly body?: string;
|
|
16
|
+
readonly tags?: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export type StagedChange = {
|
|
19
|
+
readonly kind: "add";
|
|
20
|
+
readonly zone: ZoneName;
|
|
21
|
+
readonly section: Section;
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "update";
|
|
24
|
+
readonly zone: ZoneName;
|
|
25
|
+
readonly sectionId: string;
|
|
26
|
+
readonly patch: UpdateSectionPatch;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "delete";
|
|
29
|
+
readonly zone: ZoneName;
|
|
30
|
+
readonly sectionId: string;
|
|
31
|
+
};
|
|
32
|
+
export interface PublishResult {
|
|
33
|
+
/** New manifest height after publish. */
|
|
34
|
+
readonly editionHeight: number;
|
|
35
|
+
/** SHA-256 hex of the canonical manifest. */
|
|
36
|
+
readonly manifestHash: string;
|
|
37
|
+
/** DID of the subject we published for. */
|
|
38
|
+
readonly subjectDid: string;
|
|
39
|
+
/** Zones whose contents changed in this edition. */
|
|
40
|
+
readonly zonesPublished: readonly ZoneName[];
|
|
41
|
+
}
|
|
42
|
+
type ActorOwner = {
|
|
43
|
+
readonly kind: "owner";
|
|
44
|
+
readonly subjectDid: string;
|
|
45
|
+
readonly signers: OwnerSigners;
|
|
46
|
+
};
|
|
47
|
+
type ActorDelegate = {
|
|
48
|
+
readonly kind: "delegate";
|
|
49
|
+
readonly subjectDid: string;
|
|
50
|
+
readonly actor: DelegateActor;
|
|
51
|
+
};
|
|
52
|
+
type ActorAnonymous = {
|
|
53
|
+
readonly kind: "anonymous";
|
|
54
|
+
readonly subjectDid: string;
|
|
55
|
+
};
|
|
56
|
+
type Actor = ActorOwner | ActorDelegate | ActorAnonymous;
|
|
57
|
+
export declare class EthosClient {
|
|
58
|
+
#private;
|
|
59
|
+
readonly subjectDid: string;
|
|
60
|
+
readonly mode: Actor["kind"];
|
|
61
|
+
constructor(actor: Actor);
|
|
62
|
+
/** Return the per-zone proxy. */
|
|
63
|
+
zone(name: ZoneName): EthosZone;
|
|
64
|
+
hasPendingChanges(): boolean;
|
|
65
|
+
pendingChanges(): readonly StagedChange[];
|
|
66
|
+
discard(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Build and publish a new edition with all staged mutations applied.
|
|
69
|
+
* Throws if there's nothing staged. After a successful publish, the
|
|
70
|
+
* mutation buffer is cleared and any cached snapshot is invalidated
|
|
71
|
+
* so the next read picks up the fresh edition.
|
|
72
|
+
*/
|
|
73
|
+
publish(): Promise<PublishResult>;
|
|
74
|
+
_readZone(zone: ZoneName): Promise<readonly Section[]>;
|
|
75
|
+
_stageAdd(zone: ZoneName, input: AddSectionInput): void;
|
|
76
|
+
_stageUpdate(zone: ZoneName, sectionId: string, patch: UpdateSectionPatch): void;
|
|
77
|
+
_stageDelete(zone: ZoneName, sectionId: string): void;
|
|
78
|
+
}
|
|
79
|
+
export declare class EthosZone {
|
|
80
|
+
#private;
|
|
81
|
+
constructor(parent: EthosClient, name: ZoneName);
|
|
82
|
+
get name(): ZoneName;
|
|
83
|
+
/** Effective sections (persisted + staged mutations applied). */
|
|
84
|
+
sections(): Promise<readonly Section[]>;
|
|
85
|
+
addSection(input: AddSectionInput): void;
|
|
86
|
+
updateSection(sectionId: string, patch: UpdateSectionPatch): void;
|
|
87
|
+
deleteSection(sectionId: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Return every section in this zone whose `title` is exactly `title`.
|
|
90
|
+
*
|
|
91
|
+
* Match is exact and case-sensitive. The result is always an array — it
|
|
92
|
+
* may be empty (no match), have one element (the typical case), or have
|
|
93
|
+
* more than one element when the author has happened to publish two
|
|
94
|
+
* sections with the same title. Section titles are not required by the
|
|
95
|
+
* protocol to be unique within a zone.
|
|
96
|
+
*
|
|
97
|
+
* The order of returned sections is the zone's authored order
|
|
98
|
+
* (`sections()` ordering, spec §2.5.2).
|
|
99
|
+
*
|
|
100
|
+
* @param title Section title to look up — exact, case-sensitive.
|
|
101
|
+
*/
|
|
102
|
+
findSectionsByTitle(title: string): Promise<readonly Section[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Stage an update for **every** section in this zone whose `title`
|
|
105
|
+
* matches `title` exactly. Returns the list of section IDs that were
|
|
106
|
+
* staged — empty when nothing matched.
|
|
107
|
+
*
|
|
108
|
+
* Apply with `client.publish()` like any other staged mutation. The
|
|
109
|
+
* staged entries are identical to what `updateSection(id, patch)` would
|
|
110
|
+
* produce, one per matched section, so `pendingChanges()` /
|
|
111
|
+
* `discard()` behave normally.
|
|
112
|
+
*
|
|
113
|
+
* Note: this method does NOT throw when there is no match — it returns
|
|
114
|
+
* `[]`. That's intentional: callers driven by an LLM frequently want to
|
|
115
|
+
* upsert (try update, then fall back to add) and shouldn't have to
|
|
116
|
+
* catch.
|
|
117
|
+
*
|
|
118
|
+
* @param title Section title to look up — exact, case-sensitive.
|
|
119
|
+
* @param patch Same patch shape accepted by `updateSection`.
|
|
120
|
+
* @returns Array of `section.id` strings whose updates were staged.
|
|
121
|
+
*/
|
|
122
|
+
updateSectionsByTitle(title: string, patch: UpdateSectionPatch): Promise<readonly string[]>;
|
|
123
|
+
/**
|
|
124
|
+
* Stage a delete for **every** section in this zone whose `title`
|
|
125
|
+
* matches `title` exactly. Returns the list of section IDs that were
|
|
126
|
+
* staged — empty when nothing matched.
|
|
127
|
+
*
|
|
128
|
+
* Same semantics as {@link updateSectionsByTitle}: silent on no-match,
|
|
129
|
+
* apply with `client.publish()`.
|
|
130
|
+
*
|
|
131
|
+
* @param title Section title to look up — exact, case-sensitive.
|
|
132
|
+
* @returns Array of `section.id` strings whose deletes were staged.
|
|
133
|
+
*/
|
|
134
|
+
deleteSectionsByTitle(title: string): Promise<readonly string[]>;
|
|
135
|
+
}
|
|
136
|
+
export interface EthosNamespaceDeps {
|
|
137
|
+
readonly auth: AithosAuth;
|
|
138
|
+
readonly endpoints: AithosSdkEndpoints;
|
|
139
|
+
readonly fetch: typeof fetch;
|
|
140
|
+
}
|
|
141
|
+
export declare class EthosNamespace {
|
|
142
|
+
#private;
|
|
143
|
+
constructor(deps: EthosNamespaceDeps);
|
|
144
|
+
/**
|
|
145
|
+
* EthosClient for the currently signed-in owner. Throws if there is
|
|
146
|
+
* no owner — callers should check `auth.canSignAsOwner()` first or
|
|
147
|
+
* surface the error to the user as "please sign in".
|
|
148
|
+
*/
|
|
149
|
+
me(): EthosClient;
|
|
150
|
+
/**
|
|
151
|
+
* EthosClient for an arbitrary subject DID. The mode is resolved at
|
|
152
|
+
* construction time:
|
|
153
|
+
* - if `did` matches the currently signed-in owner → owner mode
|
|
154
|
+
* - else if a mandate held by `auth` covers this subject → delegate mode
|
|
155
|
+
* - else → anonymous read-only mode
|
|
156
|
+
*
|
|
157
|
+
* Async signature so future implementations may do an eager manifest
|
|
158
|
+
* fetch (e.g. to fail fast on unknown DIDs); today resolution is sync
|
|
159
|
+
* and the actual fetch happens on the first `sections()` / `publish()`
|
|
160
|
+
* call.
|
|
161
|
+
*/
|
|
162
|
+
of(did: string): Promise<EthosClient>;
|
|
163
|
+
}
|
|
164
|
+
export {};
|
|
2
165
|
//# sourceMappingURL=ethos.d.ts.map
|