@aithos/sdk 0.1.0-alpha.21 → 0.1.0-alpha.24
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 +36 -0
- package/dist/src/compute.d.ts +0 -149
- package/dist/src/compute.js +0 -76
- package/dist/src/endpoints.d.ts +9 -0
- package/dist/src/endpoints.js +5 -0
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +2 -1
- package/dist/src/sdk.d.ts +3 -0
- package/dist/src/sdk.js +9 -0
- package/dist/src/web.d.ts +279 -0
- package/dist/src/web.js +186 -0
- package/dist/test/compute.test.js +4 -162
- package/dist/test/endpoints.test.js +20 -1
- package/dist/test/web.test.d.ts +2 -0
- package/dist/test/web.test.js +270 -0
- package/package.json +2 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
// Unit tests for sdk.web.extract with a mock fetch.
|
|
4
|
+
//
|
|
5
|
+
// Mirrors test/compute.test.ts: real BrowserIdentity (synchronous Ed25519)
|
|
6
|
+
// so the envelope-signing path runs for real and any
|
|
7
|
+
// signature/canonicalization regression surfaces here.
|
|
8
|
+
import { strict as assert } from "node:assert";
|
|
9
|
+
import { describe, it } from "node:test";
|
|
10
|
+
import { createBrowserIdentity } from "@aithos/protocol-client";
|
|
11
|
+
import { AithosAuth, AithosSDK, AithosSDKError, memoryKeyStore, noopStore, } from "../src/index.js";
|
|
12
|
+
import { serializeRecoveryFile } from "../src/internal/recovery-file.js";
|
|
13
|
+
const APP_DID = "did:aithos:app:test";
|
|
14
|
+
async function makeSdk(fetchImpl) {
|
|
15
|
+
const id = createBrowserIdentity("test-handle", "Test User");
|
|
16
|
+
const auth = new AithosAuth({
|
|
17
|
+
authBaseUrl: "https://auth.test",
|
|
18
|
+
fetch: (() => {
|
|
19
|
+
throw new Error("auth not used in web tests");
|
|
20
|
+
}),
|
|
21
|
+
sessionStore: noopStore(),
|
|
22
|
+
keyStore: memoryKeyStore(),
|
|
23
|
+
});
|
|
24
|
+
const { text } = serializeRecoveryFile(id);
|
|
25
|
+
await auth.signInWithRecovery({ file: text });
|
|
26
|
+
return new AithosSDK({
|
|
27
|
+
auth,
|
|
28
|
+
appDid: APP_DID,
|
|
29
|
+
endpoints: { web: "https://extract.example.test" },
|
|
30
|
+
fetch: fetchImpl,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const HAPPY_RESULT = {
|
|
34
|
+
data: {
|
|
35
|
+
url: "https://example.com",
|
|
36
|
+
final_url: "https://example.com/",
|
|
37
|
+
fetched_at: "2026-05-13T05:30:00.000Z",
|
|
38
|
+
render_ms: 1234,
|
|
39
|
+
meta: {
|
|
40
|
+
title: "Example",
|
|
41
|
+
description: null,
|
|
42
|
+
lang: "en",
|
|
43
|
+
charset: "UTF-8",
|
|
44
|
+
viewport: null,
|
|
45
|
+
canonical: null,
|
|
46
|
+
og: {},
|
|
47
|
+
},
|
|
48
|
+
structure: { headings: [], sections: [], nav_links: [], forms: [] },
|
|
49
|
+
content: {
|
|
50
|
+
main_html: "<main>hi</main>",
|
|
51
|
+
main_text: "hi",
|
|
52
|
+
images: [],
|
|
53
|
+
links: { internal: [], external: [] },
|
|
54
|
+
},
|
|
55
|
+
styles: { css: "", inline_styles_count: 0 },
|
|
56
|
+
visual_signature: {
|
|
57
|
+
colors: {
|
|
58
|
+
palette: [],
|
|
59
|
+
background: "#ffffff",
|
|
60
|
+
text: "#000000",
|
|
61
|
+
primary: null,
|
|
62
|
+
link: null,
|
|
63
|
+
},
|
|
64
|
+
typography: {
|
|
65
|
+
heading_font: null,
|
|
66
|
+
body_font: "sans-serif",
|
|
67
|
+
size_scale: [],
|
|
68
|
+
base_size_px: 16,
|
|
69
|
+
base_line_height: 1.5,
|
|
70
|
+
},
|
|
71
|
+
radii: { button: null, input: null, card: null },
|
|
72
|
+
spacing: { base_unit_px: null, common_gaps_px: [] },
|
|
73
|
+
layout: { max_content_width_px: null, mode: "block" },
|
|
74
|
+
components: { buttons: [], inputs: [], cards: [] },
|
|
75
|
+
},
|
|
76
|
+
logo: null,
|
|
77
|
+
},
|
|
78
|
+
creditsCharged: 1,
|
|
79
|
+
walletBalance: 99_999,
|
|
80
|
+
auditId: "audit-web-1",
|
|
81
|
+
};
|
|
82
|
+
describe("web.extract — happy path", () => {
|
|
83
|
+
it("posts to ${web}/v1/invoke with a JSON-RPC envelope and parses the result", async () => {
|
|
84
|
+
let capturedUrl;
|
|
85
|
+
let capturedInit;
|
|
86
|
+
const fakeFetch = async (input, init) => {
|
|
87
|
+
capturedUrl = typeof input === "string" ? input : input.toString();
|
|
88
|
+
capturedInit = init;
|
|
89
|
+
return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
|
|
90
|
+
status: 200,
|
|
91
|
+
headers: { "content-type": "application/json" },
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
const sdk = await makeSdk(fakeFetch);
|
|
95
|
+
const out = await sdk.web.extract({ url: "https://example.com" });
|
|
96
|
+
assert.deepEqual(out, HAPPY_RESULT);
|
|
97
|
+
assert.equal(capturedUrl, "https://extract.example.test/v1/invoke");
|
|
98
|
+
assert.equal(capturedInit?.method, "POST");
|
|
99
|
+
const headers = capturedInit?.headers;
|
|
100
|
+
assert.equal(headers["content-type"], "application/json");
|
|
101
|
+
const body = JSON.parse(capturedInit?.body);
|
|
102
|
+
assert.equal(body.jsonrpc, "2.0");
|
|
103
|
+
assert.equal(body.method, "aithos.web_extract");
|
|
104
|
+
assert.equal(body.params.url, "https://example.com");
|
|
105
|
+
assert.equal(body.params.app_did, APP_DID);
|
|
106
|
+
assert.ok(typeof body.params.mandate_id === "string");
|
|
107
|
+
assert.ok(body.params.mandate_id.length > 0);
|
|
108
|
+
assert.ok(body.params._envelope, "request must carry a signed envelope");
|
|
109
|
+
});
|
|
110
|
+
it("forwards waitUntil and timeoutMs as snake_case-free params", async () => {
|
|
111
|
+
let capturedBody;
|
|
112
|
+
const fakeFetch = async (_input, init) => {
|
|
113
|
+
capturedBody = JSON.parse(init.body);
|
|
114
|
+
return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
|
|
115
|
+
status: 200,
|
|
116
|
+
headers: { "content-type": "application/json" },
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
const sdk = await makeSdk(fakeFetch);
|
|
120
|
+
await sdk.web.extract({
|
|
121
|
+
url: "https://example.com",
|
|
122
|
+
waitUntil: "domcontentloaded",
|
|
123
|
+
timeoutMs: 5000,
|
|
124
|
+
});
|
|
125
|
+
assert.equal(capturedBody?.params.waitUntil, "domcontentloaded");
|
|
126
|
+
assert.equal(capturedBody?.params.timeoutMs, 5000);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe("web.extract — error mapping", () => {
|
|
130
|
+
it("propagates -32071 insufficient_balance as AithosSDKError with data", async () => {
|
|
131
|
+
const fakeFetch = async () => new Response(JSON.stringify({
|
|
132
|
+
error: {
|
|
133
|
+
code: -32071,
|
|
134
|
+
message: "Insufficient balance",
|
|
135
|
+
data: { required: 1, available: 0 },
|
|
136
|
+
},
|
|
137
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
138
|
+
const sdk = await makeSdk(fakeFetch);
|
|
139
|
+
await assert.rejects(() => sdk.web.extract({ url: "https://example.com" }), (err) => {
|
|
140
|
+
assert.ok(err instanceof AithosSDKError);
|
|
141
|
+
assert.equal(err.code, "-32071");
|
|
142
|
+
const data = err.data;
|
|
143
|
+
assert.equal(data?.required, 1);
|
|
144
|
+
assert.equal(data?.available, 0);
|
|
145
|
+
return true;
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
it("propagates -32042 scope mismatch as AithosSDKError", async () => {
|
|
149
|
+
const fakeFetch = async () => new Response(JSON.stringify({
|
|
150
|
+
error: {
|
|
151
|
+
code: -32042,
|
|
152
|
+
message: "mandate does not carry the web.extract scope",
|
|
153
|
+
data: {
|
|
154
|
+
mandate_id: "mandate:abc",
|
|
155
|
+
required_scope: "web.extract",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
159
|
+
const sdk = await makeSdk(fakeFetch);
|
|
160
|
+
await assert.rejects(() => sdk.web.extract({ url: "https://example.com" }), (err) => {
|
|
161
|
+
assert.ok(err instanceof AithosSDKError);
|
|
162
|
+
assert.equal(err.code, "-32042");
|
|
163
|
+
return true;
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
it("rejects HTTP transport errors as AithosSDKError code=http", async () => {
|
|
167
|
+
const fakeFetch = async () => new Response("upstream is down", { status: 502 });
|
|
168
|
+
const sdk = await makeSdk(fakeFetch);
|
|
169
|
+
await assert.rejects(() => sdk.web.extract({ url: "https://example.com" }), (err) => {
|
|
170
|
+
assert.ok(err instanceof AithosSDKError);
|
|
171
|
+
assert.equal(err.code, "http");
|
|
172
|
+
return true;
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe("web.extract — endpoint default", () => {
|
|
177
|
+
it("defaults to https://extract.aithos.be when no override is given", async () => {
|
|
178
|
+
let capturedUrl;
|
|
179
|
+
const fakeFetch = async (input) => {
|
|
180
|
+
capturedUrl = typeof input === "string" ? input : input.toString();
|
|
181
|
+
return new Response(JSON.stringify({ result: HAPPY_RESULT }), {
|
|
182
|
+
status: 200,
|
|
183
|
+
headers: { "content-type": "application/json" },
|
|
184
|
+
});
|
|
185
|
+
};
|
|
186
|
+
const id = createBrowserIdentity("test-handle", "Test User");
|
|
187
|
+
const auth = new AithosAuth({
|
|
188
|
+
authBaseUrl: "https://auth.test",
|
|
189
|
+
fetch: (() => {
|
|
190
|
+
throw new Error("auth not used");
|
|
191
|
+
}),
|
|
192
|
+
sessionStore: noopStore(),
|
|
193
|
+
keyStore: memoryKeyStore(),
|
|
194
|
+
});
|
|
195
|
+
const { text } = serializeRecoveryFile(id);
|
|
196
|
+
await auth.signInWithRecovery({ file: text });
|
|
197
|
+
const sdk = new AithosSDK({
|
|
198
|
+
auth,
|
|
199
|
+
appDid: APP_DID,
|
|
200
|
+
fetch: fakeFetch,
|
|
201
|
+
});
|
|
202
|
+
await sdk.web.extract({ url: "https://example.com" });
|
|
203
|
+
assert.equal(capturedUrl, "https://extract.aithos.be/v1/invoke");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe("web.fetchAsset — happy path", () => {
|
|
207
|
+
it("posts to ${web}/v1/invoke with method aithos.web_fetch_asset", async () => {
|
|
208
|
+
let capturedUrl;
|
|
209
|
+
let capturedInit;
|
|
210
|
+
const fakeResult = {
|
|
211
|
+
data: {
|
|
212
|
+
url: "https://example.com/logo.png",
|
|
213
|
+
final_url: "https://example.com/logo.png",
|
|
214
|
+
content_type: "image/png",
|
|
215
|
+
size_bytes: 4096,
|
|
216
|
+
base64: "aGVsbG8K",
|
|
217
|
+
},
|
|
218
|
+
creditsCharged: 1,
|
|
219
|
+
walletBalance: 99_998,
|
|
220
|
+
auditId: "audit-asset-1",
|
|
221
|
+
};
|
|
222
|
+
const fakeFetch = async (input, init) => {
|
|
223
|
+
capturedUrl = typeof input === "string" ? input : input.toString();
|
|
224
|
+
capturedInit = init;
|
|
225
|
+
return new Response(JSON.stringify({ result: fakeResult }), {
|
|
226
|
+
status: 200,
|
|
227
|
+
headers: { "content-type": "application/json" },
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
const sdk = await makeSdk(fakeFetch);
|
|
231
|
+
const out = await sdk.web.fetchAsset({ url: "https://example.com/logo.png" });
|
|
232
|
+
assert.deepEqual(out, fakeResult);
|
|
233
|
+
assert.equal(capturedUrl, "https://extract.example.test/v1/invoke");
|
|
234
|
+
const body = JSON.parse(capturedInit?.body);
|
|
235
|
+
assert.equal(body.method, "aithos.web_fetch_asset");
|
|
236
|
+
assert.equal(body.params.url, "https://example.com/logo.png");
|
|
237
|
+
assert.equal(body.params.app_did, APP_DID);
|
|
238
|
+
assert.ok(typeof body.params.mandate_id === "string");
|
|
239
|
+
assert.ok(body.params._envelope, "request must carry a signed envelope");
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe("web.fetchAsset — error propagation", () => {
|
|
243
|
+
it("propagates -32071 insufficient_balance as AithosSDKError", async () => {
|
|
244
|
+
const fakeFetch = async () => new Response(JSON.stringify({
|
|
245
|
+
error: {
|
|
246
|
+
code: -32071,
|
|
247
|
+
message: "Insufficient balance",
|
|
248
|
+
data: { required: 1, available: 0 },
|
|
249
|
+
},
|
|
250
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
251
|
+
const sdk = await makeSdk(fakeFetch);
|
|
252
|
+
await assert.rejects(() => sdk.web.fetchAsset({ url: "https://example.com/logo.png" }), (err) => {
|
|
253
|
+
assert.ok(err instanceof AithosSDKError);
|
|
254
|
+
assert.equal(err.code, "-32071");
|
|
255
|
+
return true;
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
it("propagates -32000 upstream fetch failure as AithosSDKError", async () => {
|
|
259
|
+
const fakeFetch = async () => new Response(JSON.stringify({
|
|
260
|
+
error: { code: -32000, message: "Asset fetch failed", data: { reason: "upstream HTTP 404" } },
|
|
261
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
262
|
+
const sdk = await makeSdk(fakeFetch);
|
|
263
|
+
await assert.rejects(() => sdk.web.fetchAsset({ url: "https://example.com/missing.png" }), (err) => {
|
|
264
|
+
assert.ok(err instanceof AithosSDKError);
|
|
265
|
+
assert.equal(err.code, "-32000");
|
|
266
|
+
return true;
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
//# sourceMappingURL=web.test.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aithos/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "Aithos SDK
|
|
3
|
+
"version": "0.1.0-alpha.24",
|
|
4
|
+
"description": "Aithos SDK \u2014 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",
|
|
7
7
|
"sdk",
|