@aithos/sdk 0.1.0-alpha.56 → 0.1.0-alpha.59
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/endpoints.d.ts +18 -0
- package/dist/src/endpoints.js +16 -0
- package/dist/src/index.d.ts +9 -4
- package/dist/src/index.js +13 -3
- package/dist/src/sdk.js +5 -0
- 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/endpoints.test.js +10 -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 +5 -5
package/dist/src/data.js
CHANGED
|
@@ -1,35 +1,42 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright 2026 Mathieu Colla
|
|
3
3
|
/**
|
|
4
|
-
* `sdk.data` —
|
|
4
|
+
* `sdk.data` — the Aithos data sub-protocol PDS as a plain database.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Records (contacts, prospects, messages, …) live under a subject's identity,
|
|
7
|
+
* encrypted client-side, sealed under the subject's dedicated **`#data`**
|
|
8
|
+
* sphere. A developer never chooses a sphere, never handles a key — the SDK
|
|
9
|
+
* derives everything from the session. There are exactly two ways in, both off
|
|
10
|
+
* the auth session:
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
-
* const
|
|
13
|
-
* const id = await contacts.insert({ name: "Jean"
|
|
14
|
-
* const
|
|
12
|
+
* // 1. As the OWNER (signed in) — your own database:
|
|
13
|
+
* const db = auth.data; // === auth.ownerDataClient()
|
|
14
|
+
* const id = await db.collection("contacts").insert({ name: "Jean" });
|
|
15
|
+
* const leads = await db.collection("contacts").list({ filter: { status: "lead" } });
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* encrypted payload (server-blind), JSON-RPC dispatch.
|
|
17
|
+
* // 2. As a DELEGATE (you imported a mandate) — the subject's database:
|
|
18
|
+
* const db = auth.delegateDataClient(); // single active mandate; or { subjectDid }
|
|
19
|
+
* await db.collection("prospects").insert({ ... }); // needs data.prospects.write
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
* `auth.data` returns whichever applies to how you connected, with an identical
|
|
22
|
+
* CRUD surface. Owner-only operations (createCollection, authorizeDelegate,
|
|
23
|
+
* registerSchema) belong to the owner and throw `-32042` on the delegate path:
|
|
24
|
+
* the owner holds the CMK and decides who may access the collection (a one-time
|
|
25
|
+
* onboarding step), then the delegate does record CRUD bounded by its scope.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
27
|
+
* The low-level `createDataClient` / `createDelegateDataClient` (which take a
|
|
28
|
+
* raw seed) are NOT exported from the package — they exist only as internals of
|
|
29
|
+
* the session accessors above. This is deliberate: passing a seed by hand is
|
|
30
|
+
* exactly what let an app seal data under the wrong key.
|
|
31
|
+
*
|
|
32
|
+
* Under the hood the module handles: envelope signing per spec §11, CMK / DEK
|
|
33
|
+
* lifecycle (generate, wrap, unwrap), per-record AEAD encryption, the split
|
|
34
|
+
* between indexable metadata (server-visible) and encrypted payload
|
|
35
|
+
* (server-blind), and JSON-RPC dispatch.
|
|
36
|
+
*
|
|
37
|
+
* Vendor schemas (`aithos.x.<vendor>.<name>.v<N>`) are passed via the
|
|
38
|
+
* `{ schemas: [...] }` option on the session accessor; the SDK uses them to
|
|
39
|
+
* split records into indexable metadata and encrypted payload.
|
|
33
40
|
*
|
|
34
41
|
* Spec ref: spec/data/01..10 of the aithos-protocol repo.
|
|
35
42
|
*/
|
|
@@ -61,15 +68,20 @@ export function createDataClient(args) {
|
|
|
61
68
|
return new DataClientImpl(args);
|
|
62
69
|
}
|
|
63
70
|
/**
|
|
64
|
-
* Build a
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* {@link DataClient.authorizeDelegate}.
|
|
71
|
+
* Build a data client that operates on a subject's collections under a
|
|
72
|
+
* mandate (delegate path). It signs every request as the delegate
|
|
73
|
+
* (bare-multibase verificationMethod + the mandate attached to the
|
|
74
|
+
* envelope) and decrypts/encrypts records using the CMK the owner
|
|
75
|
+
* re-wrapped for this delegate via {@link DataClient.authorizeDelegate}.
|
|
70
76
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
77
|
+
* Record CRUD is bounded by the mandate scope: reads need
|
|
78
|
+
* `data.<col>.read`, writes need `data.<col>.write` (or `.admin` /
|
|
79
|
+
* wildcard) — enforced client-side and by the PDS. Owner-only operations
|
|
80
|
+
* (createCollection, authorizeDelegate, revokeDelegate, registerSchema)
|
|
81
|
+
* always throw `-32042`: the owner holds the CMK and controls access.
|
|
82
|
+
*
|
|
83
|
+
* @internal Prefer the session accessor `auth.data` (owner) / the delegate
|
|
84
|
+
* session over hand-constructing this with a raw seed.
|
|
73
85
|
*/
|
|
74
86
|
export function createDelegateDataClient(args) {
|
|
75
87
|
const granteePubMb = args.granteePubkeyMultibase ??
|
|
@@ -171,14 +183,38 @@ class DataClientImpl {
|
|
|
171
183
|
this.#deposit = args.deposit;
|
|
172
184
|
this.#localSchemas = new Map((args.schemas ?? []).map((s) => [s.schema, s]));
|
|
173
185
|
}
|
|
174
|
-
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
186
|
+
/** Owner-only operations: collection lifecycle (create/ensure), delegate
|
|
187
|
+
* management (authorize/revoke) and schema registration. A delegate — even
|
|
188
|
+
* with a write mandate — cannot perform these: the owner holds the CMK and
|
|
189
|
+
* controls who may access the collection. The PDS enforces the same. */
|
|
177
190
|
#assertOwner(op) {
|
|
178
191
|
if (this.#delegate) {
|
|
179
|
-
const e = new Error(`sdk.data: "${op}" is
|
|
180
|
-
`
|
|
181
|
-
`
|
|
192
|
+
const e = new Error(`sdk.data: "${op}" is owner-only. A delegate (even with a write mandate) cannot ` +
|
|
193
|
+
`create collections, authorize/revoke delegates, or register schemas — the owner ` +
|
|
194
|
+
`holds the CMK and controls access. Record CRUD (insert/get/list/update/delete) ` +
|
|
195
|
+
`is available to a delegate whose mandate carries the matching scope.`);
|
|
196
|
+
e.code = -32042;
|
|
197
|
+
throw e;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/** Record-write guard. Owner: always allowed. Delegate: allowed iff the
|
|
201
|
+
* mandate carries a write/admin scope for this collection (data.<col>.write,
|
|
202
|
+
* data.*.write, data.<col>.admin, data.*.admin). The PDS enforces the same;
|
|
203
|
+
* this is the fast, precise local error. */
|
|
204
|
+
#assertCanWrite(op, collectionName) {
|
|
205
|
+
if (!this.#delegate)
|
|
206
|
+
return; // owner can always write
|
|
207
|
+
const scopes = this.#delegate.mandate.scopes ?? [];
|
|
208
|
+
const needed = [
|
|
209
|
+
`data.${collectionName}.write`,
|
|
210
|
+
`data.*.write`,
|
|
211
|
+
`data.${collectionName}.admin`,
|
|
212
|
+
`data.*.admin`,
|
|
213
|
+
];
|
|
214
|
+
const ok = scopes.some((s) => needed.includes(s.split(".").slice(0, 3).join(".")));
|
|
215
|
+
if (!ok) {
|
|
216
|
+
const e = new Error(`sdk.data: "${op}" on "${collectionName}" requires a data.${collectionName}.write ` +
|
|
217
|
+
`(or .admin / wildcard) scope. This mandate grants: [${scopes.join(", ")}].`);
|
|
182
218
|
e.code = -32042;
|
|
183
219
|
throw e;
|
|
184
220
|
}
|
|
@@ -431,7 +467,7 @@ class DataClientImpl {
|
|
|
431
467
|
return state;
|
|
432
468
|
}
|
|
433
469
|
async _insert(state, record) {
|
|
434
|
-
this.#
|
|
470
|
+
this.#assertCanWrite("insert", state.name);
|
|
435
471
|
const cmk = this.#cmkCache.get(state.name);
|
|
436
472
|
if (!cmk)
|
|
437
473
|
throw new Error("CMK not loaded");
|
|
@@ -500,7 +536,7 @@ class DataClientImpl {
|
|
|
500
536
|
};
|
|
501
537
|
}
|
|
502
538
|
async _update(state, recordId, record) {
|
|
503
|
-
this.#
|
|
539
|
+
this.#assertCanWrite("update", state.name);
|
|
504
540
|
const cmk = this.#cmkCache.get(state.name);
|
|
505
541
|
if (!cmk)
|
|
506
542
|
throw new Error("CMK not loaded");
|
|
@@ -519,7 +555,7 @@ class DataClientImpl {
|
|
|
519
555
|
});
|
|
520
556
|
}
|
|
521
557
|
async _delete(state, recordId) {
|
|
522
|
-
this.#
|
|
558
|
+
this.#assertCanWrite("delete", state.name);
|
|
523
559
|
await this.#call("/mcp/primitives/write", "aithos.data.delete_record", {
|
|
524
560
|
collection_urn: state.urn,
|
|
525
561
|
record_id: recordId,
|
package/dist/src/endpoints.d.ts
CHANGED
|
@@ -33,9 +33,27 @@ export interface AithosSdkEndpoints {
|
|
|
33
33
|
* `createAssetsClient`. Override for self-hosting or staging.
|
|
34
34
|
*/
|
|
35
35
|
readonly assets: string;
|
|
36
|
+
/**
|
|
37
|
+
* Protocol API base URL (ethos reads/writes — `get_ethos_manifest`,
|
|
38
|
+
* `get_ethos_section`, `publish_ethos_edition`). Default `https://api.aithos.be`.
|
|
39
|
+
* Reached through `@aithos/protocol-client`; the SDK propagates this to the
|
|
40
|
+
* client via `configureEndpoints` on construction.
|
|
41
|
+
*/
|
|
42
|
+
readonly api: string;
|
|
43
|
+
/**
|
|
44
|
+
* CDN base URL for immutable signed editions / DID documents. Default
|
|
45
|
+
* `https://cdn.aithos.be`. Also propagated to protocol-client.
|
|
46
|
+
*/
|
|
47
|
+
readonly cdn: string;
|
|
36
48
|
}
|
|
37
49
|
/** Production defaults. */
|
|
38
50
|
export declare const DEFAULT_SDK_ENDPOINTS: AithosSdkEndpoints;
|
|
51
|
+
/**
|
|
52
|
+
* Dev-account preset — every endpoint on the isolated `*.dev.aithos.be` infra.
|
|
53
|
+
* Pass to the SDK as `new AithosSDK({ ..., endpoints: DEV_SDK_ENDPOINTS })` to
|
|
54
|
+
* point an app at the dev account (api/cdn flow to protocol-client too).
|
|
55
|
+
*/
|
|
56
|
+
export declare const DEV_SDK_ENDPOINTS: AithosSdkEndpoints;
|
|
39
57
|
/** Compose the full compute-invoke URL: `${compute}/v1/invoke`. */
|
|
40
58
|
export declare function computeInvokeUrl(endpoints: AithosSdkEndpoints): string;
|
|
41
59
|
/** Compose the full web-extract URL: `${web}/v1/invoke`. */
|
package/dist/src/endpoints.js
CHANGED
|
@@ -7,6 +7,22 @@ export const DEFAULT_SDK_ENDPOINTS = {
|
|
|
7
7
|
web: "https://extract.aithos.be",
|
|
8
8
|
pds: "https://pds.aithos.be",
|
|
9
9
|
assets: "https://assets.aithos.be",
|
|
10
|
+
api: "https://api.aithos.be",
|
|
11
|
+
cdn: "https://cdn.aithos.be",
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Dev-account preset — every endpoint on the isolated `*.dev.aithos.be` infra.
|
|
15
|
+
* Pass to the SDK as `new AithosSDK({ ..., endpoints: DEV_SDK_ENDPOINTS })` to
|
|
16
|
+
* point an app at the dev account (api/cdn flow to protocol-client too).
|
|
17
|
+
*/
|
|
18
|
+
export const DEV_SDK_ENDPOINTS = {
|
|
19
|
+
compute: "https://compute.dev.aithos.be",
|
|
20
|
+
wallet: "https://wallet.dev.aithos.be",
|
|
21
|
+
web: "https://extract.dev.aithos.be",
|
|
22
|
+
pds: "https://pds.dev.aithos.be",
|
|
23
|
+
assets: "https://assets.dev.aithos.be",
|
|
24
|
+
api: "https://api.dev.aithos.be",
|
|
25
|
+
cdn: "https://cdn.dev.aithos.be",
|
|
10
26
|
};
|
|
11
27
|
/** Compose the full compute-invoke URL: `${compute}/v1/invoke`. */
|
|
12
28
|
export function computeInvokeUrl(endpoints) {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.0-alpha.
|
|
1
|
+
export declare const VERSION = "0.1.0-alpha.57";
|
|
2
2
|
export { AithosSDK } from "./sdk.js";
|
|
3
3
|
export type { AithosSDKConfig } from "./types.js";
|
|
4
4
|
export { AithosSDKError } from "./types.js";
|
|
5
5
|
export { AithosRpcError } from "@aithos/protocol-client";
|
|
6
6
|
export type { AithosSdkEndpoints } from "./endpoints.js";
|
|
7
|
-
export { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
|
|
8
|
-
export type { ComputeMessage, ImageAspectRatio, ImageModelId, InvokeBedrockArgs, InvokeBedrockResult, InvokeBedrockVisionArgs, InvokeBedrockVisionResult, InvokeImageArgs, InvokeImageImage, InvokeImageResult, InvokeSegmentationArgs, InvokeSegmentationResult, SegmentPolygon, StopReason, RunConversationArgs, RunConversationResult, ComputeWorkingSet, WorkingSetSection, ConverseToolCall, ConverseStopReason, TranscribeModelId, TranscribeProgressState, TranscribeSegment, TranscribeWord, InvokeTranscribeArgs, InvokeTranscribeResult, PrepareTranscribeArgs, PrepareTranscribeResult, StartTranscribeArgs, StartTranscribeResult, TranscribeStatusResult, TranscribeJobSummary, } from "./compute.js";
|
|
7
|
+
export { DEFAULT_SDK_ENDPOINTS, DEV_SDK_ENDPOINTS } from "./endpoints.js";
|
|
8
|
+
export type { ComputeMessage, ImageAspectRatio, ImageModelId, InvokeBedrockArgs, InvokeBedrockResult, InvokeBedrockVisionArgs, InvokeBedrockVisionResult, InvokeImageArgs, InvokeImageImage, InvokeImageResult, InvokeSegmentationArgs, InvokeSegmentationResult, SegmentPolygon, StopReason, RunConversationArgs, RunConversationResult, ComputeWorkingSet, WorkingSetSection, ConverseToolCall, ConverseStopReason, InvokeTurnArgs, InvokeTurnResult, RunConversationLocalArgs, RunConversationLocalResult, TranscribeModelId, TranscribeProgressState, TranscribeSegment, TranscribeWord, InvokeTranscribeArgs, InvokeTranscribeResult, PrepareTranscribeArgs, PrepareTranscribeResult, StartTranscribeArgs, StartTranscribeResult, TranscribeStatusResult, TranscribeJobSummary, } from "./compute.js";
|
|
9
9
|
export { ComputeNamespace } from "./compute.js";
|
|
10
|
+
export type { AgentMessage, AgentToolSpec, AgentTurnResult, ContentBlock, ToolCallTrace, LoopStopReason, DispatchOutcome, } from "./agent-loop.js";
|
|
11
|
+
export { runAgenticLoopLocal } from "./agent-loop.js";
|
|
12
|
+
export { AITHOS_AGENT_TOOLS, AITHOS_AGENT_READ_TOOLS, AITHOS_AGENT_WRITE_TOOLS, selectAgentTools, isWriteTool, } from "./agent-tools.js";
|
|
13
|
+
export type { AgentDispatchContext, DataProvider } from "./agent-dispatch.js";
|
|
14
|
+
export { dispatchAgentToolLocal } from "./agent-dispatch.js";
|
|
10
15
|
export type { LocalPendingEntry, LocalPendingStatus, TranscribeDraftMeta, TranscribeDraftRecord, } from "./transcribe-resilience.js";
|
|
11
16
|
export { LocalPendingTranscribeTracker, TranscribeDraftStore, TranscribeDraftUnavailableError, } from "./transcribe-resilience.js";
|
|
12
17
|
export type { CreditPackId, CreateTopupSessionArgs, CreateTopupSessionResult, GetBalanceArgs, GetBalanceResult, } from "./wallet.js";
|
|
@@ -27,7 +32,7 @@ export type { AudienceSet, AppCreditPackId, CreateAppTopupSessionArgs, CreateApp
|
|
|
27
32
|
export * as onboarding from "./onboarding.js";
|
|
28
33
|
export { createBrowserIdentity, browserIdentityFromStored, type BrowserIdentity, } from "@aithos/protocol-client";
|
|
29
34
|
export type { Section } from "@aithos/protocol-client";
|
|
30
|
-
export {
|
|
35
|
+
export { createAppendDataClient, type CreateAppendDataClientArgs, type DataClient, type DataCollection, type ReadonlyDataClient, type ReadonlyDataCollection, type AppendOnlyDataClient, type AppendOnlyDataCollection, type ListOpts, type AithosSchemaLite, liteFromPublishedSchema, } from "./data.js";
|
|
31
36
|
export { ensureDataSphere, rekeyLegacyCollections, addDataSphereWrap, migrateLegacyEthosToDataSphere, type MigrateOptions, type MigrateProgress, type EnsureDataSphereResult, type RekeyReport, type CollectionRekeyEntry, type CollectionRekeyStatus, type FullMigrationResult, } from "./migrate.js";
|
|
32
37
|
export { buildSignedRotatedDidDocument, rotateEthos, type RotationSeeds, type DidDocumentLike, type RotateEthosOptions, type RotateEthosResult, } from "./rotate.js";
|
|
33
38
|
export { createAssetsClient, AssetsClient, type CreateAssetsClientArgs, type AttachedContext, type AssetUploadInput, type AssetUploadResult, type AssetFetchResult, type AssetBrief, type ListAssetsOpts, type ThumbnailUploadInput, type ThumbnailUploadResult, type RecipientResolver, type RecipientSet, } from "./assets.js";
|
package/dist/src/index.js
CHANGED
|
@@ -17,15 +17,18 @@
|
|
|
17
17
|
// Public types specific to the SDK (`AithosSDKConfig`, `AithosSDKError`)
|
|
18
18
|
// are exported from here. Endpoint config (`AithosSdkEndpoints`,
|
|
19
19
|
// `DEFAULT_SDK_ENDPOINTS`) likewise.
|
|
20
|
-
export const VERSION = "0.1.0-alpha.
|
|
20
|
+
export const VERSION = "0.1.0-alpha.57";
|
|
21
21
|
export { AithosSDK } from "./sdk.js";
|
|
22
22
|
export { AithosSDKError } from "./types.js";
|
|
23
23
|
// Re-export protocol-client's JSON-RPC error type so consumers can
|
|
24
24
|
// `instanceof`-check server-side errors and inspect the JSON-RPC code
|
|
25
25
|
// without taking a direct dependency on @aithos/protocol-client.
|
|
26
26
|
export { AithosRpcError } from "@aithos/protocol-client";
|
|
27
|
-
export { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
|
|
27
|
+
export { DEFAULT_SDK_ENDPOINTS, DEV_SDK_ENDPOINTS } from "./endpoints.js";
|
|
28
28
|
export { ComputeNamespace } from "./compute.js";
|
|
29
|
+
export { runAgenticLoopLocal } from "./agent-loop.js";
|
|
30
|
+
export { AITHOS_AGENT_TOOLS, AITHOS_AGENT_READ_TOOLS, AITHOS_AGENT_WRITE_TOOLS, selectAgentTools, isWriteTool, } from "./agent-tools.js";
|
|
31
|
+
export { dispatchAgentToolLocal } from "./agent-dispatch.js";
|
|
29
32
|
export { LocalPendingTranscribeTracker, TranscribeDraftStore, TranscribeDraftUnavailableError, } from "./transcribe-resilience.js";
|
|
30
33
|
export { WalletNamespace } from "./wallet.js";
|
|
31
34
|
export { WebNamespace, WEB_EXTRACT_SCOPE } from "./web.js";
|
|
@@ -68,7 +71,14 @@ export { createBrowserIdentity, browserIdentityFromStored, } from "@aithos/proto
|
|
|
68
71
|
// `sdk.data` namespace — Aithos data sub-protocol PDS client. Manages
|
|
69
72
|
// the lifecycle of subject-owned, encrypted, schema-validated records.
|
|
70
73
|
// See spec/data/ in the aithos-protocol repo.
|
|
71
|
-
|
|
74
|
+
// The low-level `createDataClient` (owner) and `createDelegateDataClient`
|
|
75
|
+
// (delegate) factories are intentionally NOT exported: they take a raw sphere
|
|
76
|
+
// seed, which is exactly the footgun that let apps sign data ops with the wrong
|
|
77
|
+
// key. Use the session instead — `auth.data` / `auth.ownerDataClient()` for the
|
|
78
|
+
// owner (always `#data`), `auth.delegateDataClient()` for a mandate-bearing
|
|
79
|
+
// delegate. The key/sphere is derived under the hood; the developer only ever
|
|
80
|
+
// sees a database.
|
|
81
|
+
export { createAppendDataClient,
|
|
72
82
|
// Derive an AithosSchemaLite from a published JSON Schema (vendor schemas the
|
|
73
83
|
// client didn't bundle). The SDK uses this internally to auto-resolve unknown
|
|
74
84
|
// collection schemas from the PDS; exported for apps that want it directly.
|
package/dist/src/sdk.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { AppsNamespace } from "./apps.js";
|
|
4
4
|
import { ComputeNamespace, } from "./compute.js";
|
|
5
5
|
import { resolveEndpoints } from "./endpoints.js";
|
|
6
|
+
import { configureEndpoints as configureProtocolClientEndpoints } from "@aithos/protocol-client";
|
|
6
7
|
import { EthosNamespace } from "./ethos.js";
|
|
7
8
|
import { MandatesNamespace } from "./mandates.js";
|
|
8
9
|
import { AithosSDKError } from "./types.js";
|
|
@@ -41,6 +42,10 @@ export class AithosSDK {
|
|
|
41
42
|
this.endpoints = resolveEndpoints(config.endpoints);
|
|
42
43
|
this.appDid = config.appDid;
|
|
43
44
|
this.auth = config.auth;
|
|
45
|
+
// Ethos reads/writes flow through @aithos/protocol-client, which keeps its
|
|
46
|
+
// own (module-scoped) api/cdn config. Propagate ours so that pointing the
|
|
47
|
+
// SDK at a dev account (endpoints.api/cdn) also redirects the ethos calls.
|
|
48
|
+
configureProtocolClientEndpoints({ api: this.endpoints.api, cdn: this.endpoints.cdn });
|
|
44
49
|
const fetchImpl = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
45
50
|
this.compute = new ComputeNamespace({
|
|
46
51
|
auth: config.auth,
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
// Tests for the CLIENT-SIDE local tool dispatch: read navigation, write
|
|
4
|
+
// staging + publish, and the ethos.write.* authorisation gate. The EthosClient
|
|
5
|
+
// is faked (no network) — we assert on what got staged/published and on the
|
|
6
|
+
// is_error outcomes for out-of-scope or malformed calls.
|
|
7
|
+
import { strict as assert } from "node:assert";
|
|
8
|
+
import { describe, it } from "node:test";
|
|
9
|
+
import { dispatchAgentToolLocal, AithosSDKError, } from "../src/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Build a fake EthosClient. `zones` maps zone→sections (a zone absent from the
|
|
12
|
+
* map is treated as unreadable and throws like an ungranted/undecryptable
|
|
13
|
+
* zone, exercising the dispatch's skip-on-error path).
|
|
14
|
+
*/
|
|
15
|
+
function fakeEthos(mode, zones) {
|
|
16
|
+
const state = { staged: [], publishes: 0 };
|
|
17
|
+
const client = {
|
|
18
|
+
mode,
|
|
19
|
+
subjectDid: "did:aithos:subject",
|
|
20
|
+
zone(name) {
|
|
21
|
+
return {
|
|
22
|
+
async sections() {
|
|
23
|
+
const s = zones[name];
|
|
24
|
+
if (!s) {
|
|
25
|
+
throw new AithosSDKError("ethos_zone_unreadable", `cannot read ${name}`);
|
|
26
|
+
}
|
|
27
|
+
return s;
|
|
28
|
+
},
|
|
29
|
+
addSection(input) {
|
|
30
|
+
state.staged.push({ op: "add", zone: name, arg: input });
|
|
31
|
+
},
|
|
32
|
+
updateSection(id, patch) {
|
|
33
|
+
state.staged.push({ op: "update", zone: name, arg: { id, patch } });
|
|
34
|
+
},
|
|
35
|
+
deleteSection(id) {
|
|
36
|
+
state.staged.push({ op: "delete", zone: name, arg: { id } });
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
async publish() {
|
|
41
|
+
state.publishes++;
|
|
42
|
+
return {
|
|
43
|
+
editionHeight: 2,
|
|
44
|
+
manifestHash: "",
|
|
45
|
+
subjectDid: "did:aithos:subject",
|
|
46
|
+
zonesPublished: [],
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return { client, state };
|
|
51
|
+
}
|
|
52
|
+
function ownerCtx(zones) {
|
|
53
|
+
const { client, state } = fakeEthos("owner", zones);
|
|
54
|
+
return { ctx: { ethos: client, delegateScopes: [] }, state };
|
|
55
|
+
}
|
|
56
|
+
function delegateCtx(zones, scopes) {
|
|
57
|
+
const { client, state } = fakeEthos("delegate", zones);
|
|
58
|
+
return { ctx: { ethos: client, delegateScopes: scopes }, state };
|
|
59
|
+
}
|
|
60
|
+
const parse = (o) => JSON.parse(o.payload);
|
|
61
|
+
describe("dispatch — reads", () => {
|
|
62
|
+
it("ethos_list_sections aggregates readable zones (skips unreadable)", async () => {
|
|
63
|
+
const { ctx } = ownerCtx({
|
|
64
|
+
public: [{ id: "p1", title: "Bio", body: "..." }],
|
|
65
|
+
// circle absent → unreadable → skipped
|
|
66
|
+
self: [{ id: "s1", title: "Secret", body: "..." }],
|
|
67
|
+
});
|
|
68
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_list_sections", {});
|
|
69
|
+
assert.equal(out.isError, false);
|
|
70
|
+
const { sections } = parse(out);
|
|
71
|
+
assert.deepEqual(sections.sort((a, b) => a.id.localeCompare(b.id)), [
|
|
72
|
+
{ zone: "public", id: "p1", title: "Bio" },
|
|
73
|
+
{ zone: "self", id: "s1", title: "Secret" },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
it("ethos_read_section returns body for a readable section", async () => {
|
|
77
|
+
const { ctx } = ownerCtx({ public: [{ id: "p1", title: "Bio", body: "hello" }] });
|
|
78
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_read_section", { section_id: "p1" });
|
|
79
|
+
assert.equal(out.isError, false);
|
|
80
|
+
assert.deepEqual(parse(out), { zone: "public", title: "Bio", body: "hello" });
|
|
81
|
+
});
|
|
82
|
+
it("ethos_read_section is_error for unknown id", async () => {
|
|
83
|
+
const { ctx } = ownerCtx({ public: [] });
|
|
84
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_read_section", { section_id: "nope" });
|
|
85
|
+
assert.equal(out.isError, true);
|
|
86
|
+
});
|
|
87
|
+
it("data_query is_error without a data provider", async () => {
|
|
88
|
+
const { ctx } = ownerCtx({ public: [] });
|
|
89
|
+
const out = await dispatchAgentToolLocal(ctx, "data_query", { collection: "contacts" });
|
|
90
|
+
assert.equal(out.isError, true);
|
|
91
|
+
});
|
|
92
|
+
it("data_query uses the provider when present (limit clamped)", async () => {
|
|
93
|
+
const { client } = fakeEthos("owner", { public: [] });
|
|
94
|
+
let seenLimit = -1;
|
|
95
|
+
const ctx = {
|
|
96
|
+
ethos: client,
|
|
97
|
+
delegateScopes: [],
|
|
98
|
+
dataProvider: async (_c, limit) => {
|
|
99
|
+
seenLimit = limit;
|
|
100
|
+
return [{ a: 1 }];
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const out = await dispatchAgentToolLocal(ctx, "data_query", { collection: "c", limit: 999 });
|
|
104
|
+
assert.equal(out.isError, false);
|
|
105
|
+
assert.equal(seenLimit, 100); // clamped to max
|
|
106
|
+
assert.deepEqual(parse(out).records, [{ a: 1 }]);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe("dispatch — writes (owner)", () => {
|
|
110
|
+
it("ethos_add_section stages + publishes", async () => {
|
|
111
|
+
const { ctx, state } = ownerCtx({ public: [] });
|
|
112
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_add_section", {
|
|
113
|
+
zone: "public",
|
|
114
|
+
title: "New",
|
|
115
|
+
body: "content",
|
|
116
|
+
});
|
|
117
|
+
assert.equal(out.isError, false);
|
|
118
|
+
assert.equal(parse(out).published, true);
|
|
119
|
+
assert.equal(state.publishes, 1);
|
|
120
|
+
assert.deepEqual(state.staged, [
|
|
121
|
+
{ op: "add", zone: "public", arg: { title: "New", body: "content" } },
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
it("ethos_update_section locates the zone then publishes", async () => {
|
|
125
|
+
const { ctx, state } = ownerCtx({
|
|
126
|
+
circle: [{ id: "c1", title: "Old", body: "x" }],
|
|
127
|
+
});
|
|
128
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_update_section", {
|
|
129
|
+
section_id: "c1",
|
|
130
|
+
body: "new body",
|
|
131
|
+
});
|
|
132
|
+
assert.equal(out.isError, false);
|
|
133
|
+
assert.equal(parse(out).zone, "circle");
|
|
134
|
+
assert.equal(state.publishes, 1);
|
|
135
|
+
assert.deepEqual(state.staged[0], {
|
|
136
|
+
op: "update",
|
|
137
|
+
zone: "circle",
|
|
138
|
+
arg: { id: "c1", patch: { body: "new body" } },
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
it("ethos_delete_section locates the zone then publishes", async () => {
|
|
142
|
+
const { ctx, state } = ownerCtx({ self: [{ id: "s9", title: "T", body: "B" }] });
|
|
143
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_delete_section", { section_id: "s9" });
|
|
144
|
+
assert.equal(out.isError, false);
|
|
145
|
+
assert.equal(parse(out).zone, "self");
|
|
146
|
+
assert.equal(state.publishes, 1);
|
|
147
|
+
});
|
|
148
|
+
it("invalid zone / missing fields → is_error, nothing published", async () => {
|
|
149
|
+
const { ctx, state } = ownerCtx({ public: [] });
|
|
150
|
+
const bad = await dispatchAgentToolLocal(ctx, "ethos_add_section", {
|
|
151
|
+
zone: "nope",
|
|
152
|
+
title: "x",
|
|
153
|
+
body: "y",
|
|
154
|
+
});
|
|
155
|
+
assert.equal(bad.isError, true);
|
|
156
|
+
const noTitle = await dispatchAgentToolLocal(ctx, "ethos_add_section", {
|
|
157
|
+
zone: "public",
|
|
158
|
+
body: "y",
|
|
159
|
+
});
|
|
160
|
+
assert.equal(noTitle.isError, true);
|
|
161
|
+
assert.equal(state.publishes, 0);
|
|
162
|
+
});
|
|
163
|
+
it("update with nothing to change → is_error", async () => {
|
|
164
|
+
const { ctx } = ownerCtx({ public: [{ id: "p1", title: "T", body: "B" }] });
|
|
165
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_update_section", { section_id: "p1" });
|
|
166
|
+
assert.equal(out.isError, true);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("dispatch — write authorisation gate (delegate)", () => {
|
|
170
|
+
it("publishes when the mandate grants ethos.write.<zone>", async () => {
|
|
171
|
+
const { ctx, state } = delegateCtx({ public: [] }, ["ethos.write.public"]);
|
|
172
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_add_section", {
|
|
173
|
+
zone: "public",
|
|
174
|
+
title: "T",
|
|
175
|
+
body: "B",
|
|
176
|
+
});
|
|
177
|
+
assert.equal(out.isError, false);
|
|
178
|
+
assert.equal(state.publishes, 1);
|
|
179
|
+
});
|
|
180
|
+
it("refuses (is_error, no publish) when the write scope is missing", async () => {
|
|
181
|
+
const { ctx, state } = delegateCtx({ self: [] }, ["ethos.write.public"]);
|
|
182
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_add_section", {
|
|
183
|
+
zone: "self",
|
|
184
|
+
title: "T",
|
|
185
|
+
body: "B",
|
|
186
|
+
});
|
|
187
|
+
assert.equal(out.isError, true);
|
|
188
|
+
assert.match(parse(out).error, /ethos\.write\.self/);
|
|
189
|
+
assert.equal(state.publishes, 0);
|
|
190
|
+
assert.equal(state.staged.length, 0);
|
|
191
|
+
});
|
|
192
|
+
it("update refuses when write scope for the located zone is missing", async () => {
|
|
193
|
+
const { ctx, state } = delegateCtx({ circle: [{ id: "c1", title: "T", body: "B" }] }, ["ethos.write.public"]);
|
|
194
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_update_section", {
|
|
195
|
+
section_id: "c1",
|
|
196
|
+
body: "new",
|
|
197
|
+
});
|
|
198
|
+
assert.equal(out.isError, true);
|
|
199
|
+
assert.equal(state.publishes, 0);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
describe("dispatch — anonymous cannot write", () => {
|
|
203
|
+
it("ethos_add_section is_error for anonymous", async () => {
|
|
204
|
+
const { client, state } = fakeEthos("anonymous", { public: [] });
|
|
205
|
+
const ctx = { ethos: client, delegateScopes: [] };
|
|
206
|
+
const out = await dispatchAgentToolLocal(ctx, "ethos_add_section", {
|
|
207
|
+
zone: "public",
|
|
208
|
+
title: "T",
|
|
209
|
+
body: "B",
|
|
210
|
+
});
|
|
211
|
+
assert.equal(out.isError, true);
|
|
212
|
+
assert.equal(state.publishes, 0);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe("dispatch — unknown tool", () => {
|
|
216
|
+
it("returns is_error for an unknown tool name", async () => {
|
|
217
|
+
const { ctx } = ownerCtx({ public: [] });
|
|
218
|
+
const out = await dispatchAgentToolLocal(ctx, "bogus_tool", {});
|
|
219
|
+
assert.equal(out.isError, true);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
//# sourceMappingURL=agent-dispatch.test.js.map
|