@aithos/sdk 0.1.0-alpha.56 → 0.1.0-alpha.58
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/dist/src/agent-dispatch.d.ts +18 -0
- package/dist/src/agent-dispatch.js +178 -0
- package/dist/src/agent-loop.d.ts +94 -0
- package/dist/src/agent-loop.js +95 -0
- package/dist/src/agent-tools.d.ts +24 -0
- package/dist/src/agent-tools.js +147 -0
- package/dist/src/auth.d.ts +59 -0
- package/dist/src/auth.js +121 -0
- package/dist/src/compute.d.ts +112 -0
- package/dist/src/compute.js +175 -0
- package/dist/src/data.d.ts +14 -9
- package/dist/src/data.js +77 -41
- package/dist/src/index.d.ts +8 -3
- package/dist/src/index.js +12 -2
- package/dist/test/agent-dispatch.test.d.ts +2 -0
- package/dist/test/agent-dispatch.test.js +222 -0
- package/dist/test/agent-loop.test.d.ts +2 -0
- package/dist/test/agent-loop.test.js +117 -0
- package/dist/test/agent-tools.test.d.ts +2 -0
- package/dist/test/agent-tools.test.js +50 -0
- package/dist/test/invoke-turn-sdk.test.d.ts +2 -0
- package/dist/test/invoke-turn-sdk.test.js +177 -0
- package/dist/test/owner-data-client.test.d.ts +2 -0
- package/dist/test/owner-data-client.test.js +88 -0
- package/package.json +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
// Wire tests for sdk.compute.invokeTurn + sdk.compute.runConversationLocal,
|
|
4
|
+
// mirroring converse.test.ts: a real BrowserIdentity drives envelope signing
|
|
5
|
+
// and we assert on the JSON-RPC body posted to the proxy.
|
|
6
|
+
import { strict as assert } from "node:assert";
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
import { createBrowserIdentity } from "@aithos/protocol-client";
|
|
9
|
+
import { AithosAuth, AithosSDK, AithosSDKError, memoryKeyStore, noopStore, AITHOS_AGENT_TOOLS, } from "../src/index.js";
|
|
10
|
+
import { serializeRecoveryFile } from "../src/internal/recovery-file.js";
|
|
11
|
+
const APP_DID = "did:aithos:app:test";
|
|
12
|
+
async function makeSdk(fetchImpl) {
|
|
13
|
+
const id = createBrowserIdentity("turn-handle", "Turn User");
|
|
14
|
+
const auth = new AithosAuth({
|
|
15
|
+
authBaseUrl: "https://auth.test",
|
|
16
|
+
fetch: (() => {
|
|
17
|
+
throw new Error("auth not used");
|
|
18
|
+
}),
|
|
19
|
+
sessionStore: noopStore(),
|
|
20
|
+
keyStore: memoryKeyStore(),
|
|
21
|
+
});
|
|
22
|
+
const { text } = serializeRecoveryFile(id);
|
|
23
|
+
await auth.signInWithRecovery({ file: text });
|
|
24
|
+
const sdk = new AithosSDK({
|
|
25
|
+
auth,
|
|
26
|
+
appDid: APP_DID,
|
|
27
|
+
endpoints: { compute: "https://compute.example.test" },
|
|
28
|
+
fetch: fetchImpl,
|
|
29
|
+
});
|
|
30
|
+
return { sdk, did: id.did };
|
|
31
|
+
}
|
|
32
|
+
const TURN_RESULT = {
|
|
33
|
+
content: [{ type: "text", text: "Réponse." }],
|
|
34
|
+
stopReason: "end_turn",
|
|
35
|
+
usage: { inputTokens: 100, outputTokens: 20 },
|
|
36
|
+
creditsCharged: 12,
|
|
37
|
+
walletBalance: 9_988,
|
|
38
|
+
auditId: "audit-turn-1",
|
|
39
|
+
fundedBy: "purchase",
|
|
40
|
+
};
|
|
41
|
+
describe("compute.invokeTurn — wire", () => {
|
|
42
|
+
it("posts aithos.compute_invoke_turn with tools + mapped params", async () => {
|
|
43
|
+
let capturedUrl;
|
|
44
|
+
let capturedInit;
|
|
45
|
+
const fakeFetch = async (input, init) => {
|
|
46
|
+
capturedUrl = typeof input === "string" ? input : input.toString();
|
|
47
|
+
capturedInit = init;
|
|
48
|
+
return new Response(JSON.stringify({ result: TURN_RESULT }), {
|
|
49
|
+
status: 200,
|
|
50
|
+
headers: { "content-type": "application/json" },
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const { sdk } = await makeSdk(fakeFetch);
|
|
54
|
+
const out = await sdk.compute.invokeTurn({
|
|
55
|
+
mandateId: "mandate:abc",
|
|
56
|
+
model: "claude-sonnet-4-6",
|
|
57
|
+
system: "sys",
|
|
58
|
+
messages: [{ role: "user", content: "salut" }],
|
|
59
|
+
tools: [
|
|
60
|
+
{
|
|
61
|
+
name: "ethos_list_sections",
|
|
62
|
+
description: "list",
|
|
63
|
+
input_schema: { type: "object", properties: {} },
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
maxTokens: 512,
|
|
67
|
+
temperature: 0.2,
|
|
68
|
+
});
|
|
69
|
+
assert.deepEqual(out, TURN_RESULT);
|
|
70
|
+
assert.equal(capturedUrl, "https://compute.example.test/v1/invoke");
|
|
71
|
+
const body = JSON.parse(capturedInit?.body);
|
|
72
|
+
assert.equal(body.method, "aithos.compute_invoke_turn");
|
|
73
|
+
assert.equal(body.params.app_did, APP_DID);
|
|
74
|
+
assert.equal(body.params.mandate_id, "mandate:abc");
|
|
75
|
+
assert.equal(body.params.model, "claude-sonnet-4-6");
|
|
76
|
+
assert.equal(body.params.system, "sys");
|
|
77
|
+
assert.equal(body.params.max_tokens, 512);
|
|
78
|
+
assert.equal(body.params.temperature, 0.2);
|
|
79
|
+
assert.ok(Array.isArray(body.params.tools), "tools must be on the wire");
|
|
80
|
+
assert.equal(body.params.tools.length, 1);
|
|
81
|
+
assert.match(body.params.idempotency_key, /^[0-9a-f]{32}$/);
|
|
82
|
+
assert.ok(body.params._envelope, "must carry a signed envelope");
|
|
83
|
+
// SDK-only camelCase keys must not leak.
|
|
84
|
+
assert.equal(body.params.maxTokens, undefined);
|
|
85
|
+
});
|
|
86
|
+
it("maps a JSON-RPC error to AithosSDKError", async () => {
|
|
87
|
+
const fakeFetch = async () => new Response(JSON.stringify({ error: { code: -32071, message: "insufficient credits" } }), {
|
|
88
|
+
status: 200,
|
|
89
|
+
headers: { "content-type": "application/json" },
|
|
90
|
+
});
|
|
91
|
+
const { sdk } = await makeSdk(fakeFetch);
|
|
92
|
+
await assert.rejects(() => sdk.compute.invokeTurn({
|
|
93
|
+
model: "claude-sonnet-4-6",
|
|
94
|
+
messages: [{ role: "user", content: "hi" }],
|
|
95
|
+
tools: [],
|
|
96
|
+
}), (err) => {
|
|
97
|
+
assert.ok(err instanceof AithosSDKError);
|
|
98
|
+
assert.equal(err.code, "-32071");
|
|
99
|
+
return true;
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe("compute.runConversationLocal — wiring", () => {
|
|
104
|
+
it("drives the loop: single end_turn → 1 iteration, full catalogue forwarded", async () => {
|
|
105
|
+
const bodies = [];
|
|
106
|
+
const fakeFetch = async (_input, init) => {
|
|
107
|
+
bodies.push(JSON.parse(init?.body));
|
|
108
|
+
return new Response(JSON.stringify({ result: TURN_RESULT }), {
|
|
109
|
+
status: 200,
|
|
110
|
+
headers: { "content-type": "application/json" },
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
const { sdk } = await makeSdk(fakeFetch);
|
|
114
|
+
const out = await sdk.compute.runConversationLocal({
|
|
115
|
+
model: "claude-sonnet-4-6",
|
|
116
|
+
messages: [{ role: "user", content: "Mets à jour ma bio si besoin." }],
|
|
117
|
+
system: "Écris seulement si nécessaire.",
|
|
118
|
+
});
|
|
119
|
+
assert.equal(out.iterations, 1);
|
|
120
|
+
assert.equal(out.stopReason, "end_turn");
|
|
121
|
+
assert.equal(out.content, "Réponse.");
|
|
122
|
+
assert.equal(out.creditsCharged, 12);
|
|
123
|
+
assert.equal(out.walletBalance, 9_988);
|
|
124
|
+
assert.equal(out.auditId, "audit-turn-1");
|
|
125
|
+
assert.equal(out.fundedBy, "purchase");
|
|
126
|
+
assert.deepEqual(out.toolCalls, []);
|
|
127
|
+
// One proxy turn, the right method, the full tool catalogue forwarded.
|
|
128
|
+
assert.equal(bodies.length, 1);
|
|
129
|
+
assert.equal(bodies[0].method, "aithos.compute_invoke_turn");
|
|
130
|
+
assert.equal(bodies[0].params.tools.length, AITHOS_AGENT_TOOLS.length);
|
|
131
|
+
});
|
|
132
|
+
it("readOnly forwards only the read family", async () => {
|
|
133
|
+
const bodies = [];
|
|
134
|
+
const fakeFetch = async (_input, init) => {
|
|
135
|
+
bodies.push(JSON.parse(init?.body));
|
|
136
|
+
return new Response(JSON.stringify({ result: TURN_RESULT }), {
|
|
137
|
+
status: 200,
|
|
138
|
+
headers: { "content-type": "application/json" },
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
const { sdk } = await makeSdk(fakeFetch);
|
|
142
|
+
await sdk.compute.runConversationLocal({
|
|
143
|
+
model: "claude-sonnet-4-6",
|
|
144
|
+
messages: [{ role: "user", content: "résume" }],
|
|
145
|
+
readOnly: true,
|
|
146
|
+
});
|
|
147
|
+
const toolNames = bodies[0].params.tools.map((t) => t.name).sort();
|
|
148
|
+
assert.deepEqual(toolNames, ["data_query", "ethos_list_sections", "ethos_read_section"]);
|
|
149
|
+
});
|
|
150
|
+
it("throws sdk_no_signer when no owner and no mandate/subject", async () => {
|
|
151
|
+
const auth = new AithosAuth({
|
|
152
|
+
authBaseUrl: "https://auth.test",
|
|
153
|
+
fetch: (() => {
|
|
154
|
+
throw new Error("unused");
|
|
155
|
+
}),
|
|
156
|
+
sessionStore: noopStore(),
|
|
157
|
+
keyStore: memoryKeyStore(),
|
|
158
|
+
});
|
|
159
|
+
const sdk = new AithosSDK({
|
|
160
|
+
auth,
|
|
161
|
+
appDid: APP_DID,
|
|
162
|
+
endpoints: { compute: "https://compute.example.test" },
|
|
163
|
+
fetch: (() => {
|
|
164
|
+
throw new Error("fetch must not be reached");
|
|
165
|
+
}),
|
|
166
|
+
});
|
|
167
|
+
await assert.rejects(() => sdk.compute.runConversationLocal({
|
|
168
|
+
model: "claude-sonnet-4-6",
|
|
169
|
+
messages: [{ role: "user", content: "hi" }],
|
|
170
|
+
}), (err) => {
|
|
171
|
+
assert.ok(err instanceof AithosSDKError);
|
|
172
|
+
assert.equal(err.code, "sdk_no_signer");
|
|
173
|
+
return true;
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
//# sourceMappingURL=invoke-turn-sdk.test.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
// Phase D — auth.ownerDataClient() returns a session-bound data client that
|
|
4
|
+
// signs + seals under the dedicated #data sphere (not #root). This is the
|
|
5
|
+
// ergonomic factory apps use so collections are created under #data by default.
|
|
6
|
+
import { test } from "node:test";
|
|
7
|
+
import { strict as assert } from "node:assert";
|
|
8
|
+
import { createBrowserIdentity } from "@aithos/protocol-client";
|
|
9
|
+
import { AithosAuth, memoryKeyStore, noopStore } from "../src/index.js";
|
|
10
|
+
import { serializeRecoveryFile } from "../src/internal/recovery-file.js";
|
|
11
|
+
import { ed25519SeedToX25519PrivateKey, tryUnwrapCmk, } from "../src/internal/cmk-wrap.js";
|
|
12
|
+
function makePds() {
|
|
13
|
+
const collections = new Map();
|
|
14
|
+
const records = new Map();
|
|
15
|
+
const fetchImpl = (async (_url, init) => {
|
|
16
|
+
const body = JSON.parse(init.body);
|
|
17
|
+
const p = body.params ?? {};
|
|
18
|
+
const ok = (r) => new Response(JSON.stringify({ jsonrpc: "2.0", id: body.id, result: r }), { status: 200 });
|
|
19
|
+
const er = (c, m) => new Response(JSON.stringify({ jsonrpc: "2.0", id: body.id, error: { code: c, message: m } }), { status: 200 });
|
|
20
|
+
const urn = (d, n) => `urn:aithos:collection:${d}:${n}`;
|
|
21
|
+
switch (body.method) {
|
|
22
|
+
case "aithos.data.create_collection": {
|
|
23
|
+
const u = urn(p.subject_did, p.collection_name);
|
|
24
|
+
collections.set(u, { urn: u, name: p.collection_name, schema: p.schema, subject_did: p.subject_did, cmk_envelope: p.cmk_envelope, record_count: 0 });
|
|
25
|
+
records.set(u, new Map());
|
|
26
|
+
return ok({ urn: u, name: p.collection_name, schema: p.schema, cmk_envelope: p.cmk_envelope });
|
|
27
|
+
}
|
|
28
|
+
case "aithos.data.get_collection": {
|
|
29
|
+
const c = collections.get(urn(p.subject_did, p.collection_name));
|
|
30
|
+
return c ? ok({ urn: c.urn, name: c.name, schema: c.schema, cmk_envelope: c.cmk_envelope, record_count: c.record_count }) : er(-32020, "nf");
|
|
31
|
+
}
|
|
32
|
+
case "aithos.data.insert_record": {
|
|
33
|
+
records.get(p.collection_urn).set(p.record_id, { record_id: p.record_id, metadata: p.metadata, payload: p.payload });
|
|
34
|
+
return ok({ record_id: p.record_id });
|
|
35
|
+
}
|
|
36
|
+
case "aithos.data.get_record": {
|
|
37
|
+
const r = records.get(p.collection_urn)?.get(p.record_id);
|
|
38
|
+
return r ? ok(r) : er(-32020, "nf");
|
|
39
|
+
}
|
|
40
|
+
default:
|
|
41
|
+
return er(-32601, body.method);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return { fetchImpl, collections };
|
|
45
|
+
}
|
|
46
|
+
function authWith(fetchImpl) {
|
|
47
|
+
return new AithosAuth({
|
|
48
|
+
authBaseUrl: "https://auth.test",
|
|
49
|
+
apiBaseUrl: "https://api.test",
|
|
50
|
+
fetch: fetchImpl,
|
|
51
|
+
sessionStore: noopStore(),
|
|
52
|
+
keyStore: memoryKeyStore(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
test("ownerDataClient seals collections under #data (not #root)", async () => {
|
|
56
|
+
const id = createBrowserIdentity("phased", "Phase D");
|
|
57
|
+
assert.ok(id.data, "fresh identity has #data");
|
|
58
|
+
const { text } = serializeRecoveryFile(id);
|
|
59
|
+
const pds = makePds();
|
|
60
|
+
const auth = authWith(pds.fetchImpl);
|
|
61
|
+
await auth.signInWithRecovery({ file: text });
|
|
62
|
+
const data = auth.ownerDataClient({ pdsUrl: "https://pds.test" });
|
|
63
|
+
await data.createCollection({ name: "contacts", schema: "aithos.contacts.v1" });
|
|
64
|
+
const recId = await data.collection("contacts").insert({ name: "Iris", phone: "+33-phaseD" });
|
|
65
|
+
// Round-trips under #data.
|
|
66
|
+
const got = await data.collection("contacts").get(recId);
|
|
67
|
+
assert.equal(got.phone, "+33-phaseD");
|
|
68
|
+
// The owner wrap opens with #data and NOT with #root → proves the sealing key.
|
|
69
|
+
const urn = `urn:aithos:collection:${id.did}:contacts`;
|
|
70
|
+
const env = pds.collections.get(urn).cmk_envelope;
|
|
71
|
+
const ownerWrap = env.wraps.find((w) => w.recipient === `${id.did}#data-kex`);
|
|
72
|
+
const opensWithData = tryUnwrapCmk({ wrap: ownerWrap, collectionUrn: urn, privateKey: ed25519SeedToX25519PrivateKey(id.data.seed) });
|
|
73
|
+
const opensWithRoot = tryUnwrapCmk({ wrap: ownerWrap, collectionUrn: urn, privateKey: ed25519SeedToX25519PrivateKey(id.root.seed) });
|
|
74
|
+
assert.ok(opensWithData, "owner wrap opens with #data");
|
|
75
|
+
assert.equal(opensWithRoot, null, "owner wrap does NOT open with #root");
|
|
76
|
+
});
|
|
77
|
+
test("ownerDataClient throws for a legacy account (no #data sphere)", async () => {
|
|
78
|
+
const id = { ...createBrowserIdentity("legacy", "Legacy"), data: undefined };
|
|
79
|
+
const { text } = serializeRecoveryFile(id);
|
|
80
|
+
const auth = authWith(makePds().fetchImpl);
|
|
81
|
+
await auth.signInWithRecovery({ file: text });
|
|
82
|
+
assert.throws(() => auth.ownerDataClient(), /no #data sphere/);
|
|
83
|
+
});
|
|
84
|
+
test("ownerDataClient throws when no owner is signed in", () => {
|
|
85
|
+
const auth = authWith(makePds().fetchImpl);
|
|
86
|
+
assert.throws(() => auth.ownerDataClient(), /no owner is signed in/);
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=owner-data-client.test.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aithos/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.58",
|
|
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",
|