@aithos/sdk 0.1.0-alpha.7 → 0.1.0-alpha.9
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/compute.d.ts +22 -3
- package/dist/src/compute.js +60 -11
- package/dist/src/internal/delegate-bundle.js +7 -2
- package/dist/src/mandates.d.ts +12 -0
- package/dist/src/mandates.js +1 -0
- package/dist/test/auth-j3.test.js +32 -1
- package/dist/test/compute-delegate-path.test.d.ts +2 -0
- package/dist/test/compute-delegate-path.test.js +183 -0
- package/dist/test/sdk.test.js +8 -2
- package/package.json +3 -3
package/dist/src/compute.d.ts
CHANGED
|
@@ -63,10 +63,29 @@ export declare class ComputeNamespace {
|
|
|
63
63
|
* Invoke a Bedrock model through the compute proxy. See
|
|
64
64
|
* {@link InvokeBedrockArgs} and {@link InvokeBedrockResult}.
|
|
65
65
|
*
|
|
66
|
+
* Two signer paths are supported:
|
|
67
|
+
*
|
|
68
|
+
* - **Owner**: when the caller is signed in as an owner, the
|
|
69
|
+
* envelope is signed with the owner's `#public` sphere key and
|
|
70
|
+
* no mandate is attached (the proxy resolves the mandate
|
|
71
|
+
* server-side from `params.mandate_id`).
|
|
72
|
+
* - **Delegate**: when the caller is delegate-only (mandate
|
|
73
|
+
* imported via `auth.importMandate`), the envelope is signed
|
|
74
|
+
* with the delegate's bound keypair and the full SignedMandate
|
|
75
|
+
* is attached so the proxy can verify both signature and
|
|
76
|
+
* authorisation in one pass. The mandate must carry the
|
|
77
|
+
* `compute.invoke` scope and the proxy enforces its constraints
|
|
78
|
+
* (caps, allowed models, …) at server-side.
|
|
79
|
+
*
|
|
80
|
+
* Owner takes precedence: if a session has BOTH an owner and a
|
|
81
|
+
* matching delegate session, we use the owner key (more flexible —
|
|
82
|
+
* the mandate is dereferenced via `mandate_id` and there's no
|
|
83
|
+
* lifetime cliff if the delegate seed has been wiped).
|
|
84
|
+
*
|
|
66
85
|
* @throws {AithosSDKError} on protocol errors. The `code` field is one of
|
|
67
|
-
* `
|
|
68
|
-
* the proxy (`quota_exceeded`,
|
|
69
|
-
* …).
|
|
86
|
+
* `sdk_no_signer`, `sdk_no_delegate_for_mandate`, `network`, `http`,
|
|
87
|
+
* `empty`, or any code returned by the proxy (`quota_exceeded`,
|
|
88
|
+
* `mandate_revoked`, `insufficient_credits`, …).
|
|
70
89
|
*/
|
|
71
90
|
invokeBedrock(args: InvokeBedrockArgs): Promise<InvokeBedrockResult>;
|
|
72
91
|
}
|
package/dist/src/compute.js
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
//
|
|
17
17
|
// MVP scope: single-shot invocation. Multi-turn agentic loops (with native
|
|
18
18
|
// tool calling on the proxy) will land in a follow-up.
|
|
19
|
-
import { buildSignedEnvelope } from "@aithos/protocol-client";
|
|
19
|
+
import { buildSignedEnvelope, } from "@aithos/protocol-client";
|
|
20
20
|
import { computeInvokeUrl, } from "./endpoints.js";
|
|
21
|
-
import { ownerKeyPair } from "./internal/protocol-client-bridge.js";
|
|
21
|
+
import { delegateKeyPair, ownerKeyPair, } from "./internal/protocol-client-bridge.js";
|
|
22
22
|
import { AithosSDKError } from "./types.js";
|
|
23
23
|
/**
|
|
24
24
|
* `sdk.compute` namespace. Constructed once by the {@link AithosSDK}
|
|
@@ -35,18 +35,66 @@ export class ComputeNamespace {
|
|
|
35
35
|
* Invoke a Bedrock model through the compute proxy. See
|
|
36
36
|
* {@link InvokeBedrockArgs} and {@link InvokeBedrockResult}.
|
|
37
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
|
+
*
|
|
38
57
|
* @throws {AithosSDKError} on protocol errors. The `code` field is one of
|
|
39
|
-
* `
|
|
40
|
-
* the proxy (`quota_exceeded`,
|
|
41
|
-
* …).
|
|
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`, …).
|
|
42
61
|
*/
|
|
43
62
|
async invokeBedrock(args) {
|
|
44
63
|
const { auth, appDid, endpoints, fetch: fetchImpl } = this.#deps;
|
|
45
64
|
const owner = auth._getOwnerSigners();
|
|
46
|
-
|
|
47
|
-
|
|
65
|
+
const ownerLoaded = owner !== null && !owner.destroyed;
|
|
66
|
+
let choice;
|
|
67
|
+
if (ownerLoaded) {
|
|
68
|
+
const publicKp = ownerKeyPair(owner, "public");
|
|
69
|
+
choice = {
|
|
70
|
+
kind: "owner",
|
|
71
|
+
iss: owner.did,
|
|
72
|
+
verificationMethod: `${owner.did}#public`,
|
|
73
|
+
signer: publicKp,
|
|
74
|
+
mandate: undefined,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Delegate-only path. Find a session whose mandate id matches.
|
|
79
|
+
const actor = auth._getDelegateActor(args.mandateId);
|
|
80
|
+
if (!actor || actor.destroyed) {
|
|
81
|
+
throw new AithosSDKError("sdk_no_delegate_for_mandate", `no owner signed in and no imported delegate mandate matches '${args.mandateId}'. Sign in as an owner, or import a delegate bundle for that mandate via auth.importMandate.`);
|
|
82
|
+
}
|
|
83
|
+
const kp = delegateKeyPair(actor);
|
|
84
|
+
choice = {
|
|
85
|
+
kind: "delegate",
|
|
86
|
+
iss: actor.subjectDid,
|
|
87
|
+
verificationMethod: actor.granteePubkeyMultibase,
|
|
88
|
+
signer: kp,
|
|
89
|
+
// The DelegateActor stores the SignedMandate as a structurally
|
|
90
|
+
// opaque object so the SDK doesn't have to import the
|
|
91
|
+
// protocol-client type at the storage boundary. Round-trip
|
|
92
|
+
// through `unknown` for the TS cast — at runtime the bytes are
|
|
93
|
+
// the canonical SignedMandate the bundle parser already
|
|
94
|
+
// validated.
|
|
95
|
+
mandate: actor.mandate,
|
|
96
|
+
};
|
|
48
97
|
}
|
|
49
|
-
const publicKp = ownerKeyPair(owner, "public");
|
|
50
98
|
const url = computeInvokeUrl(endpoints);
|
|
51
99
|
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
52
100
|
const params = {
|
|
@@ -63,12 +111,13 @@ export class ComputeNamespace {
|
|
|
63
111
|
if (args.temperature !== undefined)
|
|
64
112
|
params.temperature = args.temperature;
|
|
65
113
|
const envelope = buildSignedEnvelope({
|
|
66
|
-
iss:
|
|
114
|
+
iss: choice.iss,
|
|
67
115
|
aud: url,
|
|
68
116
|
method: "aithos.compute_invoke",
|
|
69
|
-
verificationMethod:
|
|
117
|
+
verificationMethod: choice.verificationMethod,
|
|
70
118
|
params,
|
|
71
|
-
signer:
|
|
119
|
+
signer: choice.signer,
|
|
120
|
+
...(choice.kind === "delegate" ? { mandate: choice.mandate } : {}),
|
|
72
121
|
});
|
|
73
122
|
let res;
|
|
74
123
|
try {
|
|
@@ -50,13 +50,18 @@ export function parseDelegateBundle(text) {
|
|
|
50
50
|
}
|
|
51
51
|
const m = mandate;
|
|
52
52
|
const mandateId = m["id"];
|
|
53
|
-
|
|
53
|
+
// The mandate subject's DID is carried by `issuer` in the wire format
|
|
54
|
+
// emitted by `mintDelegateBundle()` (cf. SignedMandate.issuer in
|
|
55
|
+
// protocol-client). We accept the legacy `subject_did` / camelCase
|
|
56
|
+
// variants too so older test fixtures and any externally-minted
|
|
57
|
+
// bundles using the older shape keep working.
|
|
58
|
+
const subjectDid = m["issuer"] ?? m["subject_did"] ?? m["subjectDid"];
|
|
54
59
|
const grantee = m["grantee"];
|
|
55
60
|
if (typeof mandateId !== "string" || !mandateId) {
|
|
56
61
|
throw bad("mandate.id missing");
|
|
57
62
|
}
|
|
58
63
|
if (typeof subjectDid !== "string" || !subjectDid.startsWith("did:")) {
|
|
59
|
-
throw bad("mandate.
|
|
64
|
+
throw bad("mandate.issuer missing or malformed (expected a `did:` URL)");
|
|
60
65
|
}
|
|
61
66
|
if (typeof grantee !== "object" || grantee === null) {
|
|
62
67
|
throw bad("mandate.grantee missing");
|
package/dist/src/mandates.d.ts
CHANGED
|
@@ -86,6 +86,18 @@ export interface CreateMandateInput {
|
|
|
86
86
|
* which is what a consent UI can review.
|
|
87
87
|
*/
|
|
88
88
|
readonly compute?: CreateMandateComputeInput;
|
|
89
|
+
/**
|
|
90
|
+
* When the mandate becomes valid. Optional — when omitted, the
|
|
91
|
+
* underlying mint helper signs with `not_before = now - 30s` (see
|
|
92
|
+
* `MANDATE_NOTBEFORE_OFFSET_SECONDS_DEFAULT` in
|
|
93
|
+
* `@aithos/protocol-client`) so a server whose clock runs slightly
|
|
94
|
+
* behind the client doesn't reject the freshly-minted mandate as
|
|
95
|
+
* `not yet valid`.
|
|
96
|
+
*
|
|
97
|
+
* Pass an explicit `Date` only for advanced flows (delayed-activation
|
|
98
|
+
* mandates, deterministic tests).
|
|
99
|
+
*/
|
|
100
|
+
readonly notBefore?: Date;
|
|
89
101
|
}
|
|
90
102
|
export interface MintedMandate {
|
|
91
103
|
/** Unique mandate id (matches `mandate.id` inside the bundle). */
|
package/dist/src/mandates.js
CHANGED
|
@@ -90,7 +90,7 @@ describe("recovery file: parse + serialize", () => {
|
|
|
90
90
|
/* parseDelegateBundle */
|
|
91
91
|
/* -------------------------------------------------------------------------- */
|
|
92
92
|
describe("delegate bundle: parse", () => {
|
|
93
|
-
it("parses a well-formed bundle", () => {
|
|
93
|
+
it("parses a well-formed bundle (legacy subject_did field)", () => {
|
|
94
94
|
const text = delegateBundleText({
|
|
95
95
|
mandateId: "mandate:01H8XYZ",
|
|
96
96
|
subjectDid: "did:aithos:zCarol",
|
|
@@ -103,6 +103,37 @@ describe("delegate bundle: parse", () => {
|
|
|
103
103
|
assert.equal(parsed.granteeId, "urn:aithos:agent:bob1");
|
|
104
104
|
assert.equal(parsed.delegateSeedHex.length, 64);
|
|
105
105
|
});
|
|
106
|
+
it("parses a bundle minted by mintDelegateBundle (issuer field)", () => {
|
|
107
|
+
// Real wire shape emitted by `mintDelegateBundle` in protocol-client:
|
|
108
|
+
// SignedMandate carries the subject's DID under `issuer`, NOT
|
|
109
|
+
// `subject_did`. Regression test for the import flow that broke
|
|
110
|
+
// every freshly-minted mandate before this fix.
|
|
111
|
+
const text = JSON.stringify({
|
|
112
|
+
aithos_delegate_version: "0.1.0",
|
|
113
|
+
mandate: {
|
|
114
|
+
"aithos-mandate": "0.1",
|
|
115
|
+
id: "mandate:01H8ISSUER",
|
|
116
|
+
issuer: "did:aithos:zCarol",
|
|
117
|
+
issued_by_key: "did:aithos:zCarol#root",
|
|
118
|
+
grantee: {
|
|
119
|
+
id: "urn:aithos:agent:bob1",
|
|
120
|
+
pubkey: "z6MkqGenericPubKey",
|
|
121
|
+
},
|
|
122
|
+
actor_sphere: "self",
|
|
123
|
+
scopes: ["ethos.read.public", "ethos.write.public"],
|
|
124
|
+
not_before: "2026-05-10T00:00:00Z",
|
|
125
|
+
not_after: "2026-05-11T00:00:00Z",
|
|
126
|
+
issued_at: "2026-05-10T00:00:00Z",
|
|
127
|
+
nonce: "abc",
|
|
128
|
+
signature: { alg: "ed25519", key: "...", value: "..." },
|
|
129
|
+
},
|
|
130
|
+
delegate_seed_hex: "11".repeat(32),
|
|
131
|
+
});
|
|
132
|
+
const parsed = parseDelegateBundle(text);
|
|
133
|
+
assert.equal(parsed.subjectDid, "did:aithos:zCarol");
|
|
134
|
+
assert.equal(parsed.mandateId, "mandate:01H8ISSUER");
|
|
135
|
+
assert.equal(parsed.granteeId, "urn:aithos:agent:bob1");
|
|
136
|
+
});
|
|
106
137
|
it("readDelegateBundleText accepts string passthrough", async () => {
|
|
107
138
|
const text = delegateBundleText({
|
|
108
139
|
mandateId: "m",
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
// Tests for the delegate signing path on sdk.compute.invokeBedrock.
|
|
4
|
+
// Until alpha.9 the method ALWAYS required an owner — a session that
|
|
5
|
+
// only held a mandate (no owner signers) failed with sdk_no_owner
|
|
6
|
+
// before any network call. From alpha.9 onward, when no owner is
|
|
7
|
+
// loaded but a delegate matches the requested mandate id, the SDK
|
|
8
|
+
// signs the envelope with the delegate's keypair and attaches the
|
|
9
|
+
// SignedMandate.
|
|
10
|
+
import { strict as assert } from "node:assert";
|
|
11
|
+
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
12
|
+
import { createBrowserIdentity } from "@aithos/protocol-client";
|
|
13
|
+
import { AithosAuth, AithosSDKError, ComputeNamespace, memoryKeyStore, noopStore, } from "../src/index.js";
|
|
14
|
+
import { DEFAULT_SDK_ENDPOINTS } from "../src/endpoints.js";
|
|
15
|
+
/* -------------------------------------------------------------------------- */
|
|
16
|
+
/* Test plumbing */
|
|
17
|
+
/* -------------------------------------------------------------------------- */
|
|
18
|
+
let savedFetch;
|
|
19
|
+
let lastRequestBody = null;
|
|
20
|
+
function installFetchMock(response) {
|
|
21
|
+
savedFetch = globalThis.fetch;
|
|
22
|
+
lastRequestBody = null;
|
|
23
|
+
globalThis.fetch = (async (_input, init) => {
|
|
24
|
+
lastRequestBody = JSON.parse(init?.body);
|
|
25
|
+
return new Response(JSON.stringify(response.json), {
|
|
26
|
+
status: response.status ?? 200,
|
|
27
|
+
headers: { "content-type": "application/json" },
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function uninstallFetchMock() {
|
|
32
|
+
if (savedFetch)
|
|
33
|
+
globalThis.fetch = savedFetch;
|
|
34
|
+
savedFetch = undefined;
|
|
35
|
+
lastRequestBody = null;
|
|
36
|
+
}
|
|
37
|
+
const okResponse = {
|
|
38
|
+
json: {
|
|
39
|
+
jsonrpc: "2.0",
|
|
40
|
+
id: "x",
|
|
41
|
+
result: {
|
|
42
|
+
content: "ok",
|
|
43
|
+
stopReason: "end_turn",
|
|
44
|
+
usage: { inputTokens: 1, outputTokens: 1 },
|
|
45
|
+
creditsCharged: 1,
|
|
46
|
+
walletBalance: 999,
|
|
47
|
+
auditId: "audit-test",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
function freshAuth() {
|
|
52
|
+
return new AithosAuth({
|
|
53
|
+
sessionStore: noopStore(),
|
|
54
|
+
keyStore: memoryKeyStore(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function freshCompute(auth) {
|
|
58
|
+
return new ComputeNamespace({
|
|
59
|
+
auth,
|
|
60
|
+
appDid: "did:aithos:app:example-placeholder",
|
|
61
|
+
endpoints: DEFAULT_SDK_ENDPOINTS,
|
|
62
|
+
fetch: globalThis.fetch.bind(globalThis),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Build a delegate bundle JSON that matches the wire shape importMandate
|
|
67
|
+
* accepts. Uses a real SignedMandate-like object minted from a fresh
|
|
68
|
+
* issuer so signature verification on import succeeds.
|
|
69
|
+
*/
|
|
70
|
+
function makeDelegateBundleText(args) {
|
|
71
|
+
const issuer = createBrowserIdentity("alice", "Alice");
|
|
72
|
+
return JSON.stringify({
|
|
73
|
+
aithos_delegate_version: "0.1.0",
|
|
74
|
+
mandate: {
|
|
75
|
+
"aithos-mandate": "0.4.0",
|
|
76
|
+
id: args.mandateId,
|
|
77
|
+
issuer: issuer.did,
|
|
78
|
+
issued_by_key: `${issuer.did}#self`,
|
|
79
|
+
grantee: {
|
|
80
|
+
id: args.granteeId ?? "urn:aithos:agent:test",
|
|
81
|
+
pubkey: "z6MkqGenericPubKey",
|
|
82
|
+
},
|
|
83
|
+
actor_sphere: "self",
|
|
84
|
+
scopes: args.scopes,
|
|
85
|
+
not_before: "2026-05-10T00:00:00Z",
|
|
86
|
+
not_after: "2026-05-11T00:00:00Z",
|
|
87
|
+
issued_at: "2026-05-10T00:00:00Z",
|
|
88
|
+
nonce: "abc",
|
|
89
|
+
signature: { alg: "ed25519", key: `${issuer.did}#self`, value: "..." },
|
|
90
|
+
},
|
|
91
|
+
delegate_seed_hex: "11".repeat(32),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/* -------------------------------------------------------------------------- */
|
|
95
|
+
/* Tests */
|
|
96
|
+
/* -------------------------------------------------------------------------- */
|
|
97
|
+
describe("ComputeNamespace.invokeBedrock — delegate path", () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
installFetchMock(okResponse);
|
|
100
|
+
});
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
uninstallFetchMock();
|
|
103
|
+
});
|
|
104
|
+
it("rejects with sdk_no_delegate_for_mandate when delegate-only and mandateId doesn't match", async () => {
|
|
105
|
+
const auth = freshAuth();
|
|
106
|
+
const compute = freshCompute(auth);
|
|
107
|
+
// Import a delegate with a DIFFERENT mandate id than the one the call
|
|
108
|
+
// requests. Should error with the new code (NOT sdk_no_owner anymore).
|
|
109
|
+
await auth.importMandate({
|
|
110
|
+
bundle: makeDelegateBundleText({
|
|
111
|
+
mandateId: "mandate:held",
|
|
112
|
+
scopes: ["compute.invoke"],
|
|
113
|
+
}),
|
|
114
|
+
});
|
|
115
|
+
await assert.rejects(() => compute.invokeBedrock({
|
|
116
|
+
mandateId: "mandate:other-not-held",
|
|
117
|
+
model: "claude-haiku-4-5",
|
|
118
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
119
|
+
maxTokens: 1,
|
|
120
|
+
}), (e) => e instanceof AithosSDKError &&
|
|
121
|
+
e.code === "sdk_no_delegate_for_mandate");
|
|
122
|
+
});
|
|
123
|
+
it("signs with the delegate keypair + attaches the mandate when delegate-only matches", async () => {
|
|
124
|
+
const auth = freshAuth();
|
|
125
|
+
const compute = freshCompute(auth);
|
|
126
|
+
const mandateId = "mandate:matching";
|
|
127
|
+
await auth.importMandate({
|
|
128
|
+
bundle: makeDelegateBundleText({
|
|
129
|
+
mandateId,
|
|
130
|
+
scopes: ["compute.invoke"],
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
await compute.invokeBedrock({
|
|
134
|
+
mandateId,
|
|
135
|
+
model: "claude-haiku-4-5",
|
|
136
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
137
|
+
maxTokens: 1,
|
|
138
|
+
});
|
|
139
|
+
const env = lastRequestBody?.params?._envelope;
|
|
140
|
+
assert.ok(env, "envelope must be present");
|
|
141
|
+
// verificationMethod must be the delegate's bare multibase pubkey
|
|
142
|
+
// (NOT a `#sphere` DID URL). And the SignedMandate must be inside.
|
|
143
|
+
assert.match(env.proof.verificationMethod, /^z[1-9A-HJ-NP-Za-km-z]+$/, `expected multibase pubkey, got ${env.proof.verificationMethod}`);
|
|
144
|
+
assert.ok(env.mandate, "delegate envelopes must attach the SignedMandate");
|
|
145
|
+
assert.equal(env.mandate.id, mandateId);
|
|
146
|
+
});
|
|
147
|
+
it("still works the owner path when an owner is signed in", async () => {
|
|
148
|
+
const auth = freshAuth();
|
|
149
|
+
const compute = freshCompute(auth);
|
|
150
|
+
// Owner sign-in via recovery — picks up four sphere keys from the
|
|
151
|
+
// recovery file format. Build one inline.
|
|
152
|
+
const issuer = createBrowserIdentity("owner", "Owner");
|
|
153
|
+
const seedHex = (b) => Array.from(b).map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
154
|
+
const recoveryText = JSON.stringify({
|
|
155
|
+
aithos_recovery_version: "0.1.0-plaintext",
|
|
156
|
+
handle: issuer.handle,
|
|
157
|
+
display_name: issuer.displayName,
|
|
158
|
+
did: issuer.did,
|
|
159
|
+
created_at: new Date().toISOString(),
|
|
160
|
+
seeds_hex: {
|
|
161
|
+
root: seedHex(issuer.root.seed),
|
|
162
|
+
public: seedHex(issuer.public.seed),
|
|
163
|
+
circle: seedHex(issuer.circle.seed),
|
|
164
|
+
self: seedHex(issuer.self.seed),
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
await auth.signInWithRecovery({ file: recoveryText });
|
|
168
|
+
await compute.invokeBedrock({
|
|
169
|
+
mandateId: "mandate:owner-managed",
|
|
170
|
+
model: "claude-haiku-4-5",
|
|
171
|
+
messages: [{ role: "user", content: "Hi" }],
|
|
172
|
+
maxTokens: 1,
|
|
173
|
+
});
|
|
174
|
+
const env = lastRequestBody?.params?._envelope;
|
|
175
|
+
assert.ok(env, "envelope must be present");
|
|
176
|
+
// Owner path: verificationMethod is a `#public` DID URL, and the
|
|
177
|
+
// envelope MUST NOT carry a mandate (server resolves from
|
|
178
|
+
// params.mandate_id).
|
|
179
|
+
assert.match(env.proof.verificationMethod, /#public$/);
|
|
180
|
+
assert.equal(env.mandate, undefined, "owner-signed envelopes should not attach a mandate");
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=compute-delegate-path.test.js.map
|
package/dist/test/sdk.test.js
CHANGED
|
@@ -101,7 +101,12 @@ describe("AithosSDK constructor", () => {
|
|
|
101
101
|
assert.equal(typeof sdk.ethos.me, "function");
|
|
102
102
|
assert.equal(typeof sdk.mandates.create, "function");
|
|
103
103
|
});
|
|
104
|
-
it("compute.invokeBedrock rejects when no owner
|
|
104
|
+
it("compute.invokeBedrock rejects when no owner AND no delegate matches the mandate", async () => {
|
|
105
|
+
// Since alpha.9, invokeBedrock supports a delegate signing path. The
|
|
106
|
+
// failure mode here (no owner loaded AND no imported mandate matching
|
|
107
|
+
// the requested id) returns code `sdk_no_delegate_for_mandate`.
|
|
108
|
+
// The previous `sdk_no_owner` code now applies only to other entry
|
|
109
|
+
// points where there's no delegate fallback.
|
|
105
110
|
const auth = makeAuth();
|
|
106
111
|
const sdk = new AithosSDK({
|
|
107
112
|
auth,
|
|
@@ -114,7 +119,8 @@ describe("AithosSDK constructor", () => {
|
|
|
114
119
|
mandateId: "mandate:x",
|
|
115
120
|
model: "claude-sonnet-4-6",
|
|
116
121
|
messages: [{ role: "user", content: "hi" }],
|
|
117
|
-
}), (e) => e instanceof AithosSDKError &&
|
|
122
|
+
}), (e) => e instanceof AithosSDKError &&
|
|
123
|
+
e.code === "sdk_no_delegate_for_mandate");
|
|
118
124
|
});
|
|
119
125
|
});
|
|
120
126
|
//# sourceMappingURL=sdk.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.9",
|
|
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",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"node": ">=20"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@aithos/protocol-client": ">=0.1.0-alpha.
|
|
55
|
+
"@aithos/protocol-client": ">=0.1.0-alpha.13 <0.2.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@aithos/protocol-client": "^0.1.0-alpha.
|
|
58
|
+
"@aithos/protocol-client": "^0.1.0-alpha.13",
|
|
59
59
|
"@types/node": "^24.12.2",
|
|
60
60
|
"fake-indexeddb": "^6.2.5",
|
|
61
61
|
"typescript": "^5.9.2"
|