@aithos/sdk 0.1.0-alpha.4 → 0.1.0-alpha.40

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 (62) hide show
  1. package/README.md +211 -7
  2. package/dist/src/assets.d.ts +207 -0
  3. package/dist/src/assets.js +533 -0
  4. package/dist/src/auth-api.d.ts +138 -0
  5. package/dist/src/auth-api.js +168 -0
  6. package/dist/src/auth.d.ts +536 -119
  7. package/dist/src/auth.js +1207 -152
  8. package/dist/src/compute.d.ts +221 -9
  9. package/dist/src/compute.js +293 -16
  10. package/dist/src/data-schema-contacts-v1.d.ts +14 -0
  11. package/dist/src/data-schema-contacts-v1.js +28 -0
  12. package/dist/src/data.d.ts +153 -0
  13. package/dist/src/data.js +670 -0
  14. package/dist/src/endpoints.d.ts +9 -0
  15. package/dist/src/endpoints.js +5 -0
  16. package/dist/src/ethos.d.ts +202 -1
  17. package/dist/src/ethos.js +821 -16
  18. package/dist/src/index.d.ts +16 -6
  19. package/dist/src/index.js +33 -6
  20. package/dist/src/internal/delegate-bundle.d.ts +18 -0
  21. package/dist/src/internal/delegate-bundle.js +94 -0
  22. package/dist/src/internal/delegate-state.d.ts +45 -0
  23. package/dist/src/internal/delegate-state.js +120 -0
  24. package/dist/src/internal/envelope.d.ts +77 -0
  25. package/dist/src/internal/envelope.js +154 -0
  26. package/dist/src/internal/owner-signers.d.ts +78 -0
  27. package/dist/src/internal/owner-signers.js +179 -0
  28. package/dist/src/internal/protocol-client-bridge.d.ts +8 -0
  29. package/dist/src/internal/protocol-client-bridge.js +20 -0
  30. package/dist/src/internal/recovery-file.d.ts +29 -0
  31. package/dist/src/internal/recovery-file.js +98 -0
  32. package/dist/src/internal/signer.d.ts +59 -0
  33. package/dist/src/internal/signer.js +86 -0
  34. package/dist/src/key-store.d.ts +128 -0
  35. package/dist/src/key-store.js +244 -0
  36. package/dist/src/mandates.d.ts +163 -1
  37. package/dist/src/mandates.js +286 -8
  38. package/dist/src/react/AithosAsset.d.ts +66 -0
  39. package/dist/src/react/AithosAsset.js +67 -0
  40. package/dist/src/react/context.d.ts +29 -0
  41. package/dist/src/react/context.js +31 -0
  42. package/dist/src/react/index.d.ts +28 -0
  43. package/dist/src/react/index.js +30 -0
  44. package/dist/src/react/use-aithos-asset.d.ts +39 -0
  45. package/dist/src/react/use-aithos-asset.js +118 -0
  46. package/dist/src/sdk.d.ts +39 -3
  47. package/dist/src/sdk.js +36 -23
  48. package/dist/src/wallet.d.ts +4 -6
  49. package/dist/src/wallet.js +18 -8
  50. package/dist/src/web.d.ts +279 -0
  51. package/dist/src/web.js +186 -0
  52. package/package.json +18 -3
  53. package/dist/test/auth.test.d.ts +0 -2
  54. package/dist/test/auth.test.js +0 -175
  55. package/dist/test/compute.test.d.ts +0 -2
  56. package/dist/test/compute.test.js +0 -179
  57. package/dist/test/endpoints.test.d.ts +0 -2
  58. package/dist/test/endpoints.test.js +0 -43
  59. package/dist/test/sdk.test.d.ts +0 -2
  60. package/dist/test/sdk.test.js +0 -86
  61. package/dist/test/wallet.test.d.ts +0 -2
  62. package/dist/test/wallet.test.js +0 -110
@@ -0,0 +1,186 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright 2026 Mathieu Colla
3
+ // Web namespace — `aithos.web_extract` through the web-extractor proxy.
4
+ //
5
+ // Same JSON-RPC + signed-envelope protocol as the compute namespace, but
6
+ // against a separate Aithos service (`extract.aithos.be`). Pricing is a
7
+ // flat 1 microcredit per successful extraction (refunded on failure).
8
+ //
9
+ // The mandate scope is `web.extract` (exported as {@link WEB_EXTRACT_SCOPE}
10
+ // for owner mint-time use). A delegate that holds only this scope can read
11
+ // pages on the owner's behalf without gaining LLM-spend authority.
12
+ //
13
+ // Signing follows the same owner-vs-delegate logic as the compute namespace
14
+ // (see ComputeNamespace.#resolveSigner). The duplication is bounded — both
15
+ // namespaces' `#signAndPost` helpers can later move into a shared internal
16
+ // once a third primitive arrives.
17
+ import { buildSignedEnvelope, } from "@aithos/protocol-client";
18
+ import { webInvokeUrl, } from "./endpoints.js";
19
+ import { delegateKeyPair, ownerKeyPair, } from "./internal/protocol-client-bridge.js";
20
+ import { AithosSDKError } from "./types.js";
21
+ /** Opt-in scope a mandate must carry to invoke `aithos.web_extract`. */
22
+ export const WEB_EXTRACT_SCOPE = "web.extract";
23
+ /**
24
+ * `sdk.web` namespace — Aithos's web extraction primitive.
25
+ *
26
+ * Designed so a downstream agent can read the static content of any
27
+ * public page (HTML, purged CSS, computed visual signature) without
28
+ * involving an LLM — saving ~30× over a Bedrock-based extraction in
29
+ * both latency and cost.
30
+ *
31
+ * @throws {AithosSDKError} — same error taxonomy as `sdk.compute`,
32
+ * including `-32071` (insufficient balance with `{required, available}`
33
+ * in `data`) and `-32042` (mandate scope mismatch).
34
+ */
35
+ export class WebNamespace {
36
+ #deps;
37
+ constructor(deps) {
38
+ this.#deps = deps;
39
+ }
40
+ /**
41
+ * Extract a public webpage. Returns the cleaned HTML, purged CSS and
42
+ * a deterministic visual signature (palette, typography, dominant
43
+ * radii, spacing, layout mode, component digests).
44
+ */
45
+ async extract(args) {
46
+ const { endpoints, fetch: fetchImpl } = this.#deps;
47
+ const choice = this.#resolveSigner(args.mandateId);
48
+ const url = webInvokeUrl(endpoints);
49
+ const params = {
50
+ app_did: this.#deps.appDid,
51
+ mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
52
+ url: args.url,
53
+ };
54
+ if (args.waitUntil !== undefined)
55
+ params.waitUntil = args.waitUntil;
56
+ if (args.timeoutMs !== undefined)
57
+ params.timeoutMs = args.timeoutMs;
58
+ if (args.idempotencyKey !== undefined) {
59
+ params.idempotencyKey = args.idempotencyKey;
60
+ }
61
+ return await this.#signAndPost({
62
+ url,
63
+ method: "aithos.web_extract",
64
+ params,
65
+ choice,
66
+ fetchImpl,
67
+ signal: args.signal,
68
+ });
69
+ }
70
+ /**
71
+ * Fetch a single asset (image / font / css / json …) server-side,
72
+ * bypassing browser CORS. Returns the bytes as base64 + content-type.
73
+ *
74
+ * Use when `fetch(url, {mode: "cors"})` and `<img crossOrigin>`
75
+ * canvas readback both fail because the asset server doesn't return
76
+ * Access-Control-Allow-Origin headers — typical for production
77
+ * sites' logos hosted on the main domain.
78
+ *
79
+ * For the common "logo of a webpage" case the lambda already
80
+ * resolves and embeds the best symbol-only logo in
81
+ * {@link extract}'s `data.logo` field; you only need fetchAsset
82
+ * when extract's logo doesn't fit, when picking up secondary
83
+ * assets (og:image, hero image, document download), or when
84
+ * fetching an asset on a page you haven't extracted.
85
+ *
86
+ * Costs 1 mc per successful fetch, full refund on failure. Server
87
+ * caps: 15 s timeout, 10 MB body, http/https only.
88
+ */
89
+ async fetchAsset(args) {
90
+ const { endpoints, fetch: fetchImpl } = this.#deps;
91
+ const choice = this.#resolveSigner(args.mandateId);
92
+ const url = webInvokeUrl(endpoints);
93
+ const params = {
94
+ app_did: this.#deps.appDid,
95
+ mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
96
+ url: args.url,
97
+ };
98
+ return await this.#signAndPost({
99
+ url,
100
+ method: "aithos.web_fetch_asset",
101
+ params,
102
+ choice,
103
+ fetchImpl,
104
+ signal: args.signal,
105
+ });
106
+ }
107
+ /* ----------------------------- internals ----------------------------- */
108
+ #resolveSigner(mandateId) {
109
+ const { auth } = this.#deps;
110
+ const owner = auth._getOwnerSigners();
111
+ const ownerLoaded = owner !== null && !owner.destroyed;
112
+ if (ownerLoaded) {
113
+ const publicKp = ownerKeyPair(owner, "public");
114
+ return {
115
+ kind: "owner",
116
+ iss: owner.did,
117
+ verificationMethod: `${owner.did}#public`,
118
+ signer: publicKp,
119
+ mandate: undefined,
120
+ };
121
+ }
122
+ if (mandateId === undefined || mandateId.length === 0) {
123
+ 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.");
124
+ }
125
+ const actor = auth._getDelegateActor(mandateId);
126
+ if (!actor || actor.destroyed) {
127
+ 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.`);
128
+ }
129
+ const kp = delegateKeyPair(actor);
130
+ return {
131
+ kind: "delegate",
132
+ iss: actor.subjectDid,
133
+ verificationMethod: actor.granteePubkeyMultibase,
134
+ signer: kp,
135
+ mandate: actor.mandate,
136
+ };
137
+ }
138
+ #resolveMandateIdForWire(explicit, choice) {
139
+ if (explicit && explicit.length > 0)
140
+ return explicit;
141
+ if (choice.kind === "delegate")
142
+ return choice.mandate.id;
143
+ return `${choice.iss}#self`;
144
+ }
145
+ async #signAndPost(opts) {
146
+ const { url, method, params, choice, fetchImpl, signal } = opts;
147
+ const envelope = buildSignedEnvelope({
148
+ iss: choice.iss,
149
+ aud: url,
150
+ method,
151
+ verificationMethod: choice.verificationMethod,
152
+ params,
153
+ signer: choice.signer,
154
+ ...(choice.kind === "delegate" ? { mandate: choice.mandate } : {}),
155
+ });
156
+ let res;
157
+ try {
158
+ res = await fetchImpl(url, {
159
+ method: "POST",
160
+ headers: { "content-type": "application/json" },
161
+ body: JSON.stringify({
162
+ jsonrpc: "2.0",
163
+ id: method,
164
+ method,
165
+ params: { ...params, _envelope: envelope },
166
+ }),
167
+ ...(signal ? { signal } : {}),
168
+ });
169
+ }
170
+ catch (e) {
171
+ throw new AithosSDKError("network", e.message);
172
+ }
173
+ if (!res.ok) {
174
+ throw new AithosSDKError("http", `HTTP ${res.status} ${res.statusText}`, { status: res.status });
175
+ }
176
+ const body = (await res.json());
177
+ if (body.error) {
178
+ throw new AithosSDKError(String(body.error.code), body.error.message, body.error.data ? { data: body.error.data } : undefined);
179
+ }
180
+ if (!body.result) {
181
+ throw new AithosSDKError("empty", "empty result from web extractor proxy");
182
+ }
183
+ return body.result;
184
+ }
185
+ }
186
+ //# sourceMappingURL=web.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aithos/sdk",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.40",
4
4
  "description": "Aithos SDK — high-level TypeScript developer kit for building agentic apps on the Aithos protocol. Wraps @aithos/protocol-client and exposes the Aithos compute proxy and wallet (Stripe top-up) endpoints.",
5
5
  "keywords": [
6
6
  "aithos",
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/src/index.d.ts",
30
30
  "import": "./dist/src/index.js"
31
31
  },
32
+ "./react": {
33
+ "types": "./dist/src/react/index.d.ts",
34
+ "import": "./dist/src/react/index.js"
35
+ },
32
36
  "./package.json": "./package.json"
33
37
  },
34
38
  "main": "./dist/src/index.js",
@@ -52,11 +56,22 @@
52
56
  "node": ">=20"
53
57
  },
54
58
  "peerDependencies": {
55
- "@aithos/protocol-client": ">=0.1.0-alpha.11 <0.2.0"
59
+ "@aithos/protocol-client": ">=0.1.0-alpha.13 <0.2.0",
60
+ "@aithos/assets-crypto": ">=0.1.0-alpha.1 <0.2.0",
61
+ "react": "^18.0.0 || ^19.0.0"
62
+ },
63
+ "peerDependenciesMeta": {
64
+ "@aithos/assets-crypto": {
65
+ "optional": true
66
+ },
67
+ "react": {
68
+ "optional": true
69
+ }
56
70
  },
57
71
  "devDependencies": {
58
- "@aithos/protocol-client": "^0.1.0-alpha.11",
72
+ "@aithos/protocol-client": "^0.1.0-alpha.13",
59
73
  "@types/node": "^24.12.2",
74
+ "fake-indexeddb": "^6.2.5",
60
75
  "typescript": "^5.9.2"
61
76
  },
62
77
  "publishConfig": {
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=auth.test.d.ts.map
@@ -1,175 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright 2026 Mathieu Colla
3
- // Unit tests for AithosAuth — Sign in with Google flow.
4
- import { strict as assert } from "node:assert";
5
- import { describe, it } from "node:test";
6
- import { AithosAuth, AithosSDKError } from "../src/index.js";
7
- /** Tiny window-shim that records calls instead of actually navigating. */
8
- function makeFakeWindow(initialHref) {
9
- let href = initialHref;
10
- let assigned = null;
11
- let replacedHref = null;
12
- const win = {
13
- location: {
14
- get href() {
15
- return href;
16
- },
17
- assign(target) {
18
- assigned = target;
19
- },
20
- },
21
- history: {
22
- replaceState(_state, _title, url) {
23
- replacedHref = url;
24
- href = url;
25
- },
26
- },
27
- };
28
- return {
29
- win: win,
30
- get assigned() {
31
- return assigned;
32
- },
33
- get replacedHref() {
34
- return replacedHref;
35
- },
36
- };
37
- }
38
- function fakeSession(overrides = {}) {
39
- return {
40
- session: "jwt-token-here",
41
- exp: Math.floor(Date.now() / 1000) + 3600,
42
- did: "did:aithos:zABC123",
43
- handle: "alice-x9y2",
44
- blob_b64: "",
45
- blob_nonce_b64: "",
46
- blob_version: 0,
47
- enc_key_b64: "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=",
48
- is_first_login: true,
49
- ...overrides,
50
- };
51
- }
52
- /* -------------------------------------------------------------------------- */
53
- /* signInWithGoogle */
54
- /* -------------------------------------------------------------------------- */
55
- describe("AithosAuth.signInWithGoogle", () => {
56
- it("navigates to /auth/sso/google/start with no params by default", () => {
57
- const w = makeFakeWindow("https://app.aithos.be/login");
58
- const auth = new AithosAuth({
59
- authBaseUrl: "https://auth.example.test",
60
- window: w.win,
61
- });
62
- assert.throws(() => auth.signInWithGoogle(), AithosSDKError);
63
- assert.equal(w.assigned, "https://auth.example.test/auth/sso/google/start");
64
- });
65
- it("forwards appState as the app_state query param", () => {
66
- const w = makeFakeWindow("https://app.aithos.be/login");
67
- const auth = new AithosAuth({
68
- authBaseUrl: "https://auth.example.test",
69
- window: w.win,
70
- });
71
- assert.throws(() => auth.signInWithGoogle({ appState: "/dashboard" }), AithosSDKError);
72
- const url = new URL(w.assigned);
73
- assert.equal(url.searchParams.get("app_state"), "/dashboard");
74
- });
75
- it("rejects appState longer than 1024 chars without navigating", () => {
76
- const w = makeFakeWindow("https://app.aithos.be/login");
77
- const auth = new AithosAuth({
78
- authBaseUrl: "https://auth.example.test",
79
- window: w.win,
80
- });
81
- const tooLong = "x".repeat(1025);
82
- assert.throws(() => auth.signInWithGoogle({ appState: tooLong }), (e) => e instanceof AithosSDKError && e.code === "auth_app_state_too_long");
83
- assert.equal(w.assigned, null, "must not have navigated");
84
- });
85
- it("trims a trailing slash from authBaseUrl", () => {
86
- const w = makeFakeWindow("https://app.aithos.be/");
87
- const auth = new AithosAuth({
88
- authBaseUrl: "https://auth.example.test/",
89
- window: w.win,
90
- });
91
- assert.throws(() => auth.signInWithGoogle(), AithosSDKError);
92
- assert.equal(w.assigned, "https://auth.example.test/auth/sso/google/start");
93
- });
94
- });
95
- /* -------------------------------------------------------------------------- */
96
- /* handleCallback */
97
- /* -------------------------------------------------------------------------- */
98
- describe("AithosAuth.handleCallback", () => {
99
- it("returns null when the URL has no aithos_code", async () => {
100
- const w = makeFakeWindow("https://app.aithos.be/auth/callback");
101
- const auth = new AithosAuth({ window: w.win, fetch: undefinedFetch() });
102
- const session = await auth.handleCallback();
103
- assert.equal(session, null);
104
- });
105
- it("exchanges the code, returns the session, and strips query params", async () => {
106
- const w = makeFakeWindow("https://app.aithos.be/auth/callback?aithos_code=abc123XYZ_-456&app_state=/dashboard");
107
- const session = fakeSession({ is_first_login: true });
108
- let capturedBody;
109
- const fakeFetch = async (input, init) => {
110
- assert.equal(typeof input === "string" ? input : input.toString(), "https://auth.example.test/auth/sso/exchange");
111
- capturedBody = JSON.parse(init?.body);
112
- return new Response(JSON.stringify(session), {
113
- status: 200,
114
- headers: { "content-type": "application/json" },
115
- });
116
- };
117
- const auth = new AithosAuth({
118
- authBaseUrl: "https://auth.example.test",
119
- window: w.win,
120
- fetch: fakeFetch,
121
- });
122
- const out = await auth.handleCallback();
123
- assert.deepEqual(out, session);
124
- assert.equal(capturedBody?.aithos_code, "abc123XYZ_-456");
125
- assert.equal(w.replacedHref, "https://app.aithos.be/auth/callback", "callback params must be stripped from the URL");
126
- });
127
- it("throws AithosSDKError with the backend code on aithos_error", async () => {
128
- const w = makeFakeWindow("https://app.aithos.be/auth/callback?aithos_error=google_id_token&app_state=/dashboard");
129
- const auth = new AithosAuth({
130
- authBaseUrl: "https://auth.example.test",
131
- window: w.win,
132
- fetch: undefinedFetch(),
133
- });
134
- await assert.rejects(auth.handleCallback(), (e) => e instanceof AithosSDKError && e.code === "auth_google_id_token");
135
- // URL is cleaned even on error so a refresh doesn't loop the message.
136
- assert.equal(w.replacedHref, "https://app.aithos.be/auth/callback");
137
- });
138
- it("wraps a 410 'code_consumed' as AithosSDKError(code='auth_code_consumed')", async () => {
139
- const w = makeFakeWindow("https://app.aithos.be/auth/callback?aithos_code=abc123XYZ_-456");
140
- const fakeFetch = async () => new Response(JSON.stringify({ error: "aithos_code expired or already used", code: "code_consumed" }), { status: 410, headers: { "content-type": "application/json" } });
141
- const auth = new AithosAuth({
142
- authBaseUrl: "https://auth.example.test",
143
- window: w.win,
144
- fetch: fakeFetch,
145
- });
146
- await assert.rejects(auth.handleCallback(), (e) => e instanceof AithosSDKError &&
147
- e.code === "auth_code_consumed" &&
148
- e.status === 410);
149
- });
150
- it("returns null in non-browser environments (no window)", async () => {
151
- // No `window` injected and `globalThis.window` is undefined under Node test.
152
- const auth = new AithosAuth({ window: undefined, fetch: undefinedFetch() });
153
- const session = await auth.handleCallback();
154
- assert.equal(session, null);
155
- });
156
- });
157
- /* -------------------------------------------------------------------------- */
158
- /* signOut */
159
- /* -------------------------------------------------------------------------- */
160
- describe("AithosAuth.signOut", () => {
161
- it("resolves immediately (sessions are stateless)", async () => {
162
- const auth = new AithosAuth({ window: undefined, fetch: undefinedFetch() });
163
- await auth.signOut();
164
- });
165
- });
166
- /* -------------------------------------------------------------------------- */
167
- /* Helpers */
168
- /* -------------------------------------------------------------------------- */
169
- /** A fetch that fails the test if invoked — for code paths that mustn't fetch. */
170
- function undefinedFetch() {
171
- return async () => {
172
- throw new Error("fetch should not have been called");
173
- };
174
- }
175
- //# sourceMappingURL=auth.test.js.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=compute.test.d.ts.map
@@ -1,179 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright 2026 Mathieu Colla
3
- // Unit tests for sdk.compute.invokeBedrock with a mock fetch.
4
- //
5
- // We construct a real BrowserIdentity (Ed25519 keypairs are synchronous)
6
- // rather than stubbing it out — that way the envelope-signing path runs
7
- // for real and any signature/canonicalization regression in the SDK or
8
- // in protocol-client surfaces here.
9
- import { strict as assert } from "node:assert";
10
- import { describe, it } from "node:test";
11
- import { createBrowserIdentity } from "@aithos/protocol-client";
12
- import { AithosSDK, AithosSDKError } from "../src/index.js";
13
- const APP_DID = "did:aithos:app:test";
14
- function makeSdk(fetchImpl) {
15
- const identity = createBrowserIdentity("test-handle", "Test User");
16
- return new AithosSDK({
17
- identity,
18
- appDid: APP_DID,
19
- endpoints: { compute: "https://compute.example.test" },
20
- fetch: fetchImpl,
21
- });
22
- }
23
- const HAPPY_RESULT = {
24
- content: "Hello, Aithos!",
25
- stopReason: "end_turn",
26
- usage: { inputTokens: 12, outputTokens: 8 },
27
- creditsCharged: 9,
28
- walletBalance: 99_991,
29
- auditId: "audit-1",
30
- };
31
- describe("compute.invokeBedrock — happy path", () => {
32
- it("posts to ${compute}/v1/invoke with a JSON-RPC envelope and parses the result", async () => {
33
- let capturedUrl;
34
- let capturedInit;
35
- const fakeFetch = async (input, init) => {
36
- capturedUrl = typeof input === "string" ? input : input.toString();
37
- capturedInit = init;
38
- return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
39
- status: 200,
40
- headers: { "content-type": "application/json" },
41
- });
42
- };
43
- const sdk = makeSdk(fakeFetch);
44
- const out = await sdk.compute.invokeBedrock({
45
- mandateId: "mandate:abc",
46
- model: "claude-sonnet-4-6",
47
- messages: [{ role: "user", content: "Hi" }],
48
- });
49
- assert.deepEqual(out, HAPPY_RESULT);
50
- assert.equal(capturedUrl, "https://compute.example.test/v1/invoke");
51
- assert.equal(capturedInit?.method, "POST");
52
- const headers = capturedInit?.headers;
53
- assert.equal(headers["content-type"], "application/json");
54
- const body = JSON.parse(capturedInit?.body);
55
- assert.equal(body.jsonrpc, "2.0");
56
- assert.equal(body.method, "aithos.compute_invoke");
57
- assert.equal(body.params.app_did, APP_DID);
58
- assert.equal(body.params.mandate_id, "mandate:abc");
59
- assert.equal(body.params.model, "claude-sonnet-4-6");
60
- // Idempotency key is auto-generated when not provided.
61
- assert.match(body.params.idempotency_key, /^[0-9a-f]{32}$/);
62
- // Envelope is present on the wire.
63
- assert.ok(body.params._envelope, "request must carry a signed envelope");
64
- });
65
- it("forwards optional system / max_tokens / temperature and respects a caller-provided idempotencyKey", async () => {
66
- let capturedBody;
67
- const fakeFetch = async (_input, init) => {
68
- capturedBody = JSON.parse(init?.body).params;
69
- return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
70
- status: 200,
71
- headers: { "content-type": "application/json" },
72
- });
73
- };
74
- const sdk = makeSdk(fakeFetch);
75
- await sdk.compute.invokeBedrock({
76
- mandateId: "mandate:abc",
77
- model: "claude-sonnet-4-6",
78
- messages: [{ role: "user", content: "Hi" }],
79
- system: "You are concise.",
80
- maxTokens: 256,
81
- temperature: 0.2,
82
- idempotencyKey: "idem-1",
83
- });
84
- assert.equal(capturedBody?.system, "You are concise.");
85
- assert.equal(capturedBody?.max_tokens, 256);
86
- assert.equal(capturedBody?.temperature, 0.2);
87
- assert.equal(capturedBody?.idempotency_key, "idem-1");
88
- });
89
- });
90
- describe("compute.invokeBedrock — errors", () => {
91
- it("wraps a network failure as AithosSDKError(code='network')", async () => {
92
- const fakeFetch = async () => {
93
- throw new Error("fetch failed: ECONNREFUSED");
94
- };
95
- const sdk = makeSdk(fakeFetch);
96
- await assert.rejects(sdk.compute.invokeBedrock({
97
- mandateId: "mandate:abc",
98
- model: "claude-sonnet-4-6",
99
- messages: [{ role: "user", content: "Hi" }],
100
- }), (err) => {
101
- assert.ok(err instanceof AithosSDKError);
102
- assert.equal(err.code, "network");
103
- assert.match(err.message, /ECONNREFUSED/);
104
- return true;
105
- });
106
- });
107
- it("wraps an HTTP non-2xx as AithosSDKError(code='http')", async () => {
108
- const fakeFetch = async () => new Response("nope", { status: 503, statusText: "Service Unavailable" });
109
- const sdk = makeSdk(fakeFetch);
110
- await assert.rejects(sdk.compute.invokeBedrock({
111
- mandateId: "mandate:abc",
112
- model: "claude-sonnet-4-6",
113
- messages: [{ role: "user", content: "Hi" }],
114
- }), (err) => {
115
- assert.ok(err instanceof AithosSDKError);
116
- assert.equal(err.code, "http");
117
- assert.equal(err.status, 503);
118
- return true;
119
- });
120
- });
121
- it("wraps a JSON-RPC error response with the proxy's code and message", async () => {
122
- const fakeFetch = async () => new Response(JSON.stringify({
123
- error: {
124
- code: 402,
125
- message: "insufficient_credits",
126
- data: { balance: 0, required: 1 },
127
- },
128
- }), { status: 200, headers: { "content-type": "application/json" } });
129
- const sdk = makeSdk(fakeFetch);
130
- await assert.rejects(sdk.compute.invokeBedrock({
131
- mandateId: "mandate:abc",
132
- model: "claude-sonnet-4-6",
133
- messages: [{ role: "user", content: "Hi" }],
134
- }), (err) => {
135
- assert.ok(err instanceof AithosSDKError);
136
- assert.equal(err.code, "402");
137
- assert.equal(err.message, "insufficient_credits");
138
- return true;
139
- });
140
- });
141
- it("rejects an empty result payload as AithosSDKError(code='empty')", async () => {
142
- const fakeFetch = async () => new Response(JSON.stringify({}), {
143
- status: 200,
144
- headers: { "content-type": "application/json" },
145
- });
146
- const sdk = makeSdk(fakeFetch);
147
- await assert.rejects(sdk.compute.invokeBedrock({
148
- mandateId: "mandate:abc",
149
- model: "claude-sonnet-4-6",
150
- messages: [{ role: "user", content: "Hi" }],
151
- }), (err) => {
152
- assert.ok(err instanceof AithosSDKError);
153
- assert.equal(err.code, "empty");
154
- return true;
155
- });
156
- });
157
- });
158
- describe("compute.invokeBedrock — abort", () => {
159
- it("propagates an AbortSignal to fetch", async () => {
160
- let receivedSignal;
161
- const fakeFetch = async (_input, init) => {
162
- receivedSignal = init?.signal;
163
- return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
164
- status: 200,
165
- headers: { "content-type": "application/json" },
166
- });
167
- };
168
- const sdk = makeSdk(fakeFetch);
169
- const ac = new AbortController();
170
- await sdk.compute.invokeBedrock({
171
- mandateId: "mandate:abc",
172
- model: "claude-sonnet-4-6",
173
- messages: [{ role: "user", content: "Hi" }],
174
- signal: ac.signal,
175
- });
176
- assert.equal(receivedSignal, ac.signal);
177
- });
178
- });
179
- //# sourceMappingURL=compute.test.js.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=endpoints.test.d.ts.map
@@ -1,43 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // Copyright 2026 Mathieu Colla
3
- // Unit tests for endpoint resolution + URL composition.
4
- import { strict as assert } from "node:assert";
5
- import { describe, it } from "node:test";
6
- import { DEFAULT_SDK_ENDPOINTS } from "../src/index.js";
7
- import { computeInvokeUrl, resolveEndpoints, walletTopupCheckoutUrl, } from "../src/endpoints.js";
8
- describe("resolveEndpoints", () => {
9
- it("returns a fresh copy of the defaults when no override is given", () => {
10
- const a = resolveEndpoints();
11
- const b = resolveEndpoints();
12
- assert.deepEqual(a, DEFAULT_SDK_ENDPOINTS);
13
- assert.notEqual(a, b, "resolveEndpoints must not return a shared instance");
14
- });
15
- it("merges partial overrides on top of the defaults", () => {
16
- const e = resolveEndpoints({ compute: "https://staging.aithos.be" });
17
- assert.equal(e.compute, "https://staging.aithos.be");
18
- assert.equal(e.wallet, DEFAULT_SDK_ENDPOINTS.wallet);
19
- });
20
- });
21
- describe("computeInvokeUrl", () => {
22
- it("appends /v1/invoke to the compute base", () => {
23
- assert.equal(computeInvokeUrl({
24
- compute: "https://compute.aithos.be",
25
- wallet: "https://wallet.aithos.be",
26
- }), "https://compute.aithos.be/v1/invoke");
27
- });
28
- it("trims a trailing slash on the compute base", () => {
29
- assert.equal(computeInvokeUrl({
30
- compute: "https://compute.aithos.be/",
31
- wallet: "https://wallet.aithos.be",
32
- }), "https://compute.aithos.be/v1/invoke");
33
- });
34
- });
35
- describe("walletTopupCheckoutUrl", () => {
36
- it("appends /v1/wallet/topup/checkout to the wallet base", () => {
37
- assert.equal(walletTopupCheckoutUrl({
38
- compute: "https://compute.aithos.be",
39
- wallet: "https://wallet.aithos.be",
40
- }), "https://wallet.aithos.be/v1/wallet/topup/checkout");
41
- });
42
- });
43
- //# sourceMappingURL=endpoints.test.js.map
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=sdk.test.d.ts.map