@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/auth.js
CHANGED
|
@@ -28,6 +28,9 @@ import { parseDelegateBundle, readDelegateBundleText, } from "./internal/delegat
|
|
|
28
28
|
import { DelegateActor, DelegateRegistry, } from "./internal/delegate-state.js";
|
|
29
29
|
import { signOwnerEnvelope, } from "./internal/envelope.js";
|
|
30
30
|
import { OwnerSigners } from "./internal/owner-signers.js";
|
|
31
|
+
import { createDataClient, createDelegateDataClient, } from "./data.js";
|
|
32
|
+
import { delegateKeyPair } from "./internal/protocol-client-bridge.js";
|
|
33
|
+
import { DEFAULT_SDK_ENDPOINTS } from "./endpoints.js";
|
|
31
34
|
import { parseRecoveryFile, readRecoveryFileText, serializeRecoveryFile, } from "./internal/recovery-file.js";
|
|
32
35
|
import { AithosSDKError } from "./types.js";
|
|
33
36
|
/** Default URL of the Aithos auth backend. */
|
|
@@ -221,6 +224,118 @@ export class AithosAuth {
|
|
|
221
224
|
_getOwnerSigners() {
|
|
222
225
|
return this.#ownerSigners;
|
|
223
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Ready-made owner data client bound to the signed-in account, signing +
|
|
229
|
+
* sealing under the dedicated **`#data`** sphere (the protocol-intended owner
|
|
230
|
+
* data key). This is the one-liner apps should use instead of hand-rolling
|
|
231
|
+
* `createDataClient` with a raw seed — hand-rolling with `#root` is exactly
|
|
232
|
+
* what left legacy collections sealed to the wrong key.
|
|
233
|
+
*
|
|
234
|
+
* const data = auth.ownerDataClient({ schemas: [myVendorLite] });
|
|
235
|
+
* await data.collection("notes").insert({ ... }); // owned under #data
|
|
236
|
+
*
|
|
237
|
+
* Throws when no owner is signed in, or when the account has no `#data`
|
|
238
|
+
* sphere (legacy accounts created before #data, or imported from a 4-seed
|
|
239
|
+
* recovery). Add one first with `rotateEthos` / the migration scripts, then
|
|
240
|
+
* re-import the resulting recovery — the error message says so.
|
|
241
|
+
*
|
|
242
|
+
* @param args.pdsUrl PDS base URL. Defaults to the SDK default (pds.aithos.be).
|
|
243
|
+
* @param args.schemas Vendor `AithosSchemaLite` definitions to register for
|
|
244
|
+
* WRITES (reads auto-resolve published schemas from the PDS).
|
|
245
|
+
*/
|
|
246
|
+
ownerDataClient(args = {}) {
|
|
247
|
+
if (!this.#ownerSigners || this.#ownerSigners.destroyed) {
|
|
248
|
+
throw new AithosSDKError("auth_not_signed_in", "ownerDataClient: no owner is signed in. Call signIn / signUp / signInCustodial first.");
|
|
249
|
+
}
|
|
250
|
+
const stored = this.#ownerSigners._unsafeStoredIdentity();
|
|
251
|
+
if (!stored.seeds.data) {
|
|
252
|
+
throw new AithosSDKError("auth_no_data_sphere", "ownerDataClient: this account has no #data sphere, so it cannot own collections under " +
|
|
253
|
+
"the #data convention. New accounts get one at sign-up; a legacy account (or a 4-seed " +
|
|
254
|
+
"recovery) must add it first via rotateEthos / the migration scripts, then re-import the " +
|
|
255
|
+
"resulting recovery (which carries the #data seed).");
|
|
256
|
+
}
|
|
257
|
+
return createDataClient({
|
|
258
|
+
pdsUrl: args.pdsUrl ?? DEFAULT_SDK_ENDPOINTS.pds,
|
|
259
|
+
did: stored.did,
|
|
260
|
+
sphereSeed: hexToBytesLocal(stored.seeds.data),
|
|
261
|
+
verificationMethod: `${stored.did}#data`,
|
|
262
|
+
...(args.schemas ? { schemas: args.schemas } : {}),
|
|
263
|
+
fetch: this.#fetchImpl,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Ready-made DELEGATE data client, bound to a mandate held in this session
|
|
268
|
+
* (imported via `importMandate` / an accepted invite). Same record-CRUD
|
|
269
|
+
* surface as the owner client, bounded by the mandate's scope — you never
|
|
270
|
+
* pass a key, a sphere, or the mandate itself to the data calls.
|
|
271
|
+
*
|
|
272
|
+
* const db = auth.delegateDataClient(); // single active mandate
|
|
273
|
+
* await db.collection("prospects").insert({ ... }); // needs data.prospects.write
|
|
274
|
+
*
|
|
275
|
+
* With several active mandates, pass `{ subjectDid }` or `{ mandateId }`.
|
|
276
|
+
* Owner-only ops (createCollection, authorizeDelegate, …) throw -32042 — the
|
|
277
|
+
* owner does those once, at onboarding.
|
|
278
|
+
*/
|
|
279
|
+
delegateDataClient(args = {}) {
|
|
280
|
+
let actor;
|
|
281
|
+
if (args.mandateId) {
|
|
282
|
+
actor = this.#delegates.get(args.mandateId);
|
|
283
|
+
}
|
|
284
|
+
else if (args.subjectDid) {
|
|
285
|
+
actor = this.#delegates.findForSubject(args.subjectDid);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const all = this.#delegates.list();
|
|
289
|
+
if (all.length === 1) {
|
|
290
|
+
actor = all[0];
|
|
291
|
+
}
|
|
292
|
+
else if (all.length === 0) {
|
|
293
|
+
throw new AithosSDKError("auth_no_delegate", "delegateDataClient: no mandate imported in this session. Call importMandate / accept an invite first.");
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
throw new AithosSDKError("auth_ambiguous_delegate", `delegateDataClient: ${all.length} mandates are active — pass { subjectDid } or { mandateId } to choose one.`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (!actor || actor.destroyed) {
|
|
300
|
+
throw new AithosSDKError("auth_no_delegate", "delegateDataClient: the requested mandate is not active in this session.");
|
|
301
|
+
}
|
|
302
|
+
return createDelegateDataClient({
|
|
303
|
+
pdsUrl: args.pdsUrl ?? DEFAULT_SDK_ENDPOINTS.pds,
|
|
304
|
+
subjectDid: actor.subjectDid,
|
|
305
|
+
mandate: actor.mandate,
|
|
306
|
+
delegateSeed: delegateKeyPair(actor).seed,
|
|
307
|
+
granteePubkeyMultibase: actor.granteePubkeyMultibase,
|
|
308
|
+
...(args.schemas ? { schemas: args.schemas } : {}),
|
|
309
|
+
fetch: this.#fetchImpl,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Unified data accessor — the database for "however you connected":
|
|
314
|
+
* - signed in as owner → your own collections under `#data`;
|
|
315
|
+
* - acting under an imported mandate → the subject's collections (per scope).
|
|
316
|
+
*
|
|
317
|
+
* Identical CRUD surface either way; the developer never sees a sphere, a
|
|
318
|
+
* key, or the mandate. The mode follows how you authenticated, not a flag on
|
|
319
|
+
* the data calls.
|
|
320
|
+
*
|
|
321
|
+
* const db = auth.data;
|
|
322
|
+
* await db.collection("prospects").insert({ ... });
|
|
323
|
+
*
|
|
324
|
+
* Only ambiguous when you are BOTH signed in as owner AND holding mandates;
|
|
325
|
+
* then call `ownerDataClient()` / `delegateDataClient({ … })` explicitly.
|
|
326
|
+
*/
|
|
327
|
+
get data() {
|
|
328
|
+
const ownerActive = !!this.#ownerSigners && !this.#ownerSigners.destroyed;
|
|
329
|
+
const delegateCount = this.#delegates.list().length;
|
|
330
|
+
if (ownerActive && delegateCount === 0)
|
|
331
|
+
return this.ownerDataClient();
|
|
332
|
+
if (!ownerActive && delegateCount >= 1)
|
|
333
|
+
return this.delegateDataClient();
|
|
334
|
+
if (ownerActive && delegateCount >= 1) {
|
|
335
|
+
throw new AithosSDKError("auth_data_ambiguous", "auth.data: both an owner and delegate mandate(s) are active. Use ownerDataClient() or delegateDataClient({ … }) explicitly.");
|
|
336
|
+
}
|
|
337
|
+
throw new AithosSDKError("auth_not_signed_in", "auth.data: no owner signed in and no mandate imported. Call signIn / signUp or importMandate first.");
|
|
338
|
+
}
|
|
224
339
|
/**
|
|
225
340
|
* Internal accessor — looks up an active delegate by mandate id.
|
|
226
341
|
* @internal
|
|
@@ -1445,6 +1560,12 @@ function bytesToHex(b) {
|
|
|
1445
1560
|
out += b[i].toString(16).padStart(2, "0");
|
|
1446
1561
|
return out;
|
|
1447
1562
|
}
|
|
1563
|
+
function hexToBytesLocal(hex) {
|
|
1564
|
+
const out = new Uint8Array(hex.length / 2);
|
|
1565
|
+
for (let i = 0; i < out.length; i++)
|
|
1566
|
+
out[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
1567
|
+
return out;
|
|
1568
|
+
}
|
|
1448
1569
|
/**
|
|
1449
1570
|
* Split a custodial seed bundle into the keystore's hex seeds, zeroizing the
|
|
1450
1571
|
* raw bytes (the bundle and every slice) before returning.
|
package/dist/src/compute.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AithosAuth } from "./auth.js";
|
|
2
2
|
import { type AithosSdkEndpoints } from "./endpoints.js";
|
|
3
3
|
import { type LocalPendingEntry, type TranscribeDraftMeta, type TranscribeDraftRecord } from "./transcribe-resilience.js";
|
|
4
|
+
import { type AgentMessage, type AgentToolSpec, type AgentTurnStopReason, type ContentBlock, type LoopStopReason, type ToolCallTrace } from "./agent-loop.js";
|
|
5
|
+
import { type DataProvider } from "./agent-dispatch.js";
|
|
4
6
|
export interface ComputeMessage {
|
|
5
7
|
readonly role: "user" | "assistant";
|
|
6
8
|
readonly content: string;
|
|
@@ -173,6 +175,81 @@ export interface RunConversationResult {
|
|
|
173
175
|
readonly receiptId?: string;
|
|
174
176
|
readonly sponsoredBy?: string;
|
|
175
177
|
}
|
|
178
|
+
export interface InvokeTurnArgs {
|
|
179
|
+
/** Mandate id — optional for owner sessions, required for delegate sessions. */
|
|
180
|
+
readonly mandateId?: string;
|
|
181
|
+
readonly model: string;
|
|
182
|
+
/** Running conversation. `content` may be a string OR Anthropic blocks. */
|
|
183
|
+
readonly messages: readonly AgentMessage[];
|
|
184
|
+
/** Anthropic tool specs to expose this turn. */
|
|
185
|
+
readonly tools: readonly AgentToolSpec[];
|
|
186
|
+
readonly system?: string;
|
|
187
|
+
readonly maxTokens?: number;
|
|
188
|
+
readonly temperature?: number;
|
|
189
|
+
readonly idempotencyKey?: string;
|
|
190
|
+
readonly signal?: AbortSignal;
|
|
191
|
+
}
|
|
192
|
+
export interface InvokeTurnResult {
|
|
193
|
+
/** Raw content blocks (text + tool_use) — the caller detects tool calls. */
|
|
194
|
+
readonly content: readonly ContentBlock[];
|
|
195
|
+
readonly stopReason: AgentTurnStopReason;
|
|
196
|
+
readonly usage: {
|
|
197
|
+
readonly inputTokens: number;
|
|
198
|
+
readonly outputTokens: number;
|
|
199
|
+
};
|
|
200
|
+
/** Microcredits charged for THIS single turn. */
|
|
201
|
+
readonly creditsCharged: number;
|
|
202
|
+
readonly walletBalance: number;
|
|
203
|
+
readonly auditId: string;
|
|
204
|
+
readonly fundedBy?: "sponsored" | "grant" | "purchase";
|
|
205
|
+
readonly receiptId?: string;
|
|
206
|
+
readonly sponsoredBy?: string;
|
|
207
|
+
}
|
|
208
|
+
export interface RunConversationLocalArgs {
|
|
209
|
+
/** Mandate id — optional for owner sessions, required for delegate sessions. */
|
|
210
|
+
readonly mandateId?: string;
|
|
211
|
+
/**
|
|
212
|
+
* Subject DID whose ethos the agent reads/writes. Defaults to the signed-in
|
|
213
|
+
* owner, or (delegate session) the mandate's subject.
|
|
214
|
+
*/
|
|
215
|
+
readonly subjectDid?: string;
|
|
216
|
+
readonly model: string;
|
|
217
|
+
/** Initial conversation (plain string turns). */
|
|
218
|
+
readonly messages: readonly ComputeMessage[];
|
|
219
|
+
readonly system?: string;
|
|
220
|
+
/**
|
|
221
|
+
* Subset of Aithos tool names to expose. Omit for the full catalogue
|
|
222
|
+
* (read + write). Ignored when `readOnly` is set.
|
|
223
|
+
*/
|
|
224
|
+
readonly tools?: readonly string[];
|
|
225
|
+
/** Expose only the read family (no mutations). */
|
|
226
|
+
readonly readOnly?: boolean;
|
|
227
|
+
/** Cap on proxy turns (each is a billed call). Default 6, hard max 12. */
|
|
228
|
+
readonly maxIterations?: number;
|
|
229
|
+
readonly maxTokens?: number;
|
|
230
|
+
readonly temperature?: number;
|
|
231
|
+
/** Optional gamma reader powering `data_query`. Absent → that tool errors. */
|
|
232
|
+
readonly dataProvider?: DataProvider;
|
|
233
|
+
readonly idempotencyKey?: string;
|
|
234
|
+
readonly signal?: AbortSignal;
|
|
235
|
+
}
|
|
236
|
+
export interface RunConversationLocalResult {
|
|
237
|
+
readonly content: string;
|
|
238
|
+
readonly stopReason: LoopStopReason;
|
|
239
|
+
readonly iterations: number;
|
|
240
|
+
readonly usage: {
|
|
241
|
+
readonly inputTokens: number;
|
|
242
|
+
readonly outputTokens: number;
|
|
243
|
+
};
|
|
244
|
+
readonly toolCalls: readonly ToolCallTrace[];
|
|
245
|
+
/** Sum of per-turn charges (HANDOFF §1: per-turn cumulative billing). */
|
|
246
|
+
readonly creditsCharged: number;
|
|
247
|
+
readonly walletBalance: number;
|
|
248
|
+
/** Audit id of the LAST turn. */
|
|
249
|
+
readonly auditId: string;
|
|
250
|
+
readonly fundedBy?: "sponsored" | "grant" | "purchase";
|
|
251
|
+
readonly receiptId?: string;
|
|
252
|
+
}
|
|
176
253
|
/**
|
|
177
254
|
* Stable cross-provider image model ids supported by the Aithos compute
|
|
178
255
|
* proxy. New models can be added on the server side without an SDK
|
|
@@ -497,6 +574,41 @@ export declare class ComputeNamespace {
|
|
|
497
574
|
* `mandate_revoked`, `insufficient_credits`, …).
|
|
498
575
|
*/
|
|
499
576
|
runConversation(args: RunConversationArgs): Promise<RunConversationResult>;
|
|
577
|
+
/**
|
|
578
|
+
* Run ONE Bedrock turn with tool-calling through the proxy
|
|
579
|
+
* (`aithos.compute_invoke_turn`). Returns the raw content blocks — including
|
|
580
|
+
* any `tool_use` — so the caller can dispatch tools and loop. This is the
|
|
581
|
+
* per-turn primitive the CLIENT-SIDE agentic loop is built on; most callers
|
|
582
|
+
* want {@link runConversationLocal} instead, which drives the loop and
|
|
583
|
+
* dispatches Aithos tools locally.
|
|
584
|
+
*
|
|
585
|
+
* Same signer paths and billing as {@link invokeBedrock}; billed once per
|
|
586
|
+
* turn.
|
|
587
|
+
*/
|
|
588
|
+
invokeTurn(args: InvokeTurnArgs): Promise<InvokeTurnResult>;
|
|
589
|
+
/**
|
|
590
|
+
* Run a CLIENT-SIDE agentic conversation with tool-calling: a multi-turn
|
|
591
|
+
* loop where each turn is one signed proxy call ({@link invokeTurn}) and the
|
|
592
|
+
* tools the model requests are dispatched LOCALLY against the user's own
|
|
593
|
+
* ethos (reads decrypt locally; writes stage + sign + publish locally). The
|
|
594
|
+
* proxy does pure per-turn inference and never holds a decryption key — keys,
|
|
595
|
+
* data, and dispatch all stay on the client (HANDOFF-AGENT-WRITE-MODE.md
|
|
596
|
+
* §1/§3).
|
|
597
|
+
*
|
|
598
|
+
* Authorisation: an owner has full authority over their own ethos; a
|
|
599
|
+
* delegate is bounded by the mandate's `ethos.read.*` / `ethos.write.*`
|
|
600
|
+
* scopes (a tool out of scope returns an error to the model — nothing is
|
|
601
|
+
* published). The spend capability (`compute.invoke`) is checked server-side
|
|
602
|
+
* on every turn.
|
|
603
|
+
*
|
|
604
|
+
* Billing is PER-TURN cumulative: each turn is billed once and the result's
|
|
605
|
+
* `creditsCharged` is the sum across turns.
|
|
606
|
+
*
|
|
607
|
+
* @throws {AithosSDKError} `sdk_no_signer`, `sdk_no_delegate_for_mandate`,
|
|
608
|
+
* `network`, `http`, `empty`, or any proxy code. Tool-level failures do
|
|
609
|
+
* NOT throw — they are fed back to the model as errors.
|
|
610
|
+
*/
|
|
611
|
+
runConversationLocal(args: RunConversationLocalArgs): Promise<RunConversationLocalResult>;
|
|
500
612
|
/**
|
|
501
613
|
* Multimodal Bedrock invoke — image + text → text response.
|
|
502
614
|
* Default model: `claude-sonnet-4-6` (vision-capable, reliable JSON).
|
package/dist/src/compute.js
CHANGED
|
@@ -21,6 +21,13 @@ import { computeInvokeUrl, } from "./endpoints.js";
|
|
|
21
21
|
import { delegateKeyPair, ownerKeyPair, } from "./internal/protocol-client-bridge.js";
|
|
22
22
|
import { AithosSDKError } from "./types.js";
|
|
23
23
|
import { LocalPendingTranscribeTracker, TranscribeDraftStore, } from "./transcribe-resilience.js";
|
|
24
|
+
import { runAgenticLoopLocal, } from "./agent-loop.js";
|
|
25
|
+
import { selectAgentTools } from "./agent-tools.js";
|
|
26
|
+
import { dispatchAgentToolLocal, } from "./agent-dispatch.js";
|
|
27
|
+
import { EthosNamespace } from "./ethos.js";
|
|
28
|
+
/** Default / hard cap on client-side loop iterations (mirrors the proxy). */
|
|
29
|
+
const DEFAULT_LOCAL_MAX_ITERATIONS = 6;
|
|
30
|
+
const HARD_LOCAL_MAX_ITERATIONS = 12;
|
|
24
31
|
/**
|
|
25
32
|
* `sdk.compute` namespace. Constructed once by the {@link AithosSDK}
|
|
26
33
|
* constructor; reads the active owner from the supplied
|
|
@@ -148,6 +155,134 @@ export class ComputeNamespace {
|
|
|
148
155
|
signal: args.signal,
|
|
149
156
|
});
|
|
150
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Run ONE Bedrock turn with tool-calling through the proxy
|
|
160
|
+
* (`aithos.compute_invoke_turn`). Returns the raw content blocks — including
|
|
161
|
+
* any `tool_use` — so the caller can dispatch tools and loop. This is the
|
|
162
|
+
* per-turn primitive the CLIENT-SIDE agentic loop is built on; most callers
|
|
163
|
+
* want {@link runConversationLocal} instead, which drives the loop and
|
|
164
|
+
* dispatches Aithos tools locally.
|
|
165
|
+
*
|
|
166
|
+
* Same signer paths and billing as {@link invokeBedrock}; billed once per
|
|
167
|
+
* turn.
|
|
168
|
+
*/
|
|
169
|
+
async invokeTurn(args) {
|
|
170
|
+
const { endpoints, fetch: fetchImpl } = this.#deps;
|
|
171
|
+
const choice = this.#resolveSigner(args.mandateId);
|
|
172
|
+
const url = computeInvokeUrl(endpoints);
|
|
173
|
+
const idempotencyKey = args.idempotencyKey ?? generateIdempotencyKey();
|
|
174
|
+
const params = {
|
|
175
|
+
app_did: this.#deps.appDid,
|
|
176
|
+
mandate_id: this.#resolveMandateIdForWire(args.mandateId, choice),
|
|
177
|
+
model: args.model,
|
|
178
|
+
messages: args.messages,
|
|
179
|
+
tools: args.tools,
|
|
180
|
+
idempotency_key: idempotencyKey,
|
|
181
|
+
};
|
|
182
|
+
if (args.system !== undefined)
|
|
183
|
+
params.system = args.system;
|
|
184
|
+
if (args.maxTokens !== undefined)
|
|
185
|
+
params.max_tokens = args.maxTokens;
|
|
186
|
+
if (args.temperature !== undefined)
|
|
187
|
+
params.temperature = args.temperature;
|
|
188
|
+
return await this.#signAndPost({
|
|
189
|
+
url,
|
|
190
|
+
method: "aithos.compute_invoke_turn",
|
|
191
|
+
params,
|
|
192
|
+
choice,
|
|
193
|
+
fetchImpl,
|
|
194
|
+
signal: args.signal,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Run a CLIENT-SIDE agentic conversation with tool-calling: a multi-turn
|
|
199
|
+
* loop where each turn is one signed proxy call ({@link invokeTurn}) and the
|
|
200
|
+
* tools the model requests are dispatched LOCALLY against the user's own
|
|
201
|
+
* ethos (reads decrypt locally; writes stage + sign + publish locally). The
|
|
202
|
+
* proxy does pure per-turn inference and never holds a decryption key — keys,
|
|
203
|
+
* data, and dispatch all stay on the client (HANDOFF-AGENT-WRITE-MODE.md
|
|
204
|
+
* §1/§3).
|
|
205
|
+
*
|
|
206
|
+
* Authorisation: an owner has full authority over their own ethos; a
|
|
207
|
+
* delegate is bounded by the mandate's `ethos.read.*` / `ethos.write.*`
|
|
208
|
+
* scopes (a tool out of scope returns an error to the model — nothing is
|
|
209
|
+
* published). The spend capability (`compute.invoke`) is checked server-side
|
|
210
|
+
* on every turn.
|
|
211
|
+
*
|
|
212
|
+
* Billing is PER-TURN cumulative: each turn is billed once and the result's
|
|
213
|
+
* `creditsCharged` is the sum across turns.
|
|
214
|
+
*
|
|
215
|
+
* @throws {AithosSDKError} `sdk_no_signer`, `sdk_no_delegate_for_mandate`,
|
|
216
|
+
* `network`, `http`, `empty`, or any proxy code. Tool-level failures do
|
|
217
|
+
* NOT throw — they are fed back to the model as errors.
|
|
218
|
+
*/
|
|
219
|
+
async runConversationLocal(args) {
|
|
220
|
+
// Resolve the subject whose ethos the agent operates on.
|
|
221
|
+
const subjectDid = this.#resolveSubjectDid(args.subjectDid, args.mandateId);
|
|
222
|
+
// Resolve the ethos client + the delegate scopes that bound writes.
|
|
223
|
+
const ethosNs = this.#ethosNamespace();
|
|
224
|
+
const ethosClient = await ethosNs.of(subjectDid);
|
|
225
|
+
const delegateScopes = ethosClient.mode === "delegate"
|
|
226
|
+
? this.#delegateScopesForSubject(subjectDid)
|
|
227
|
+
: [];
|
|
228
|
+
const dispatchCtx = {
|
|
229
|
+
ethos: ethosClient,
|
|
230
|
+
delegateScopes,
|
|
231
|
+
...(args.dataProvider ? { dataProvider: args.dataProvider } : {}),
|
|
232
|
+
};
|
|
233
|
+
const tools = selectAgentTools({
|
|
234
|
+
...(args.tools ? { tools: args.tools } : {}),
|
|
235
|
+
...(args.readOnly ? { readOnly: true } : {}),
|
|
236
|
+
});
|
|
237
|
+
const maxIterations = Math.max(1, Math.min(HARD_LOCAL_MAX_ITERATIONS, args.maxIterations ?? DEFAULT_LOCAL_MAX_ITERATIONS));
|
|
238
|
+
// Carry the last turn's billing metadata out of the loop.
|
|
239
|
+
let lastAuditId = "";
|
|
240
|
+
let lastFundedBy;
|
|
241
|
+
let lastReceiptId;
|
|
242
|
+
const initialMessages = args.messages.map((m) => ({
|
|
243
|
+
role: m.role,
|
|
244
|
+
content: m.content,
|
|
245
|
+
}));
|
|
246
|
+
const loop = await runAgenticLoopLocal({
|
|
247
|
+
messages: initialMessages,
|
|
248
|
+
maxIterations,
|
|
249
|
+
invokeTurn: async (messages) => {
|
|
250
|
+
const r = await this.invokeTurn({
|
|
251
|
+
...(args.mandateId !== undefined ? { mandateId: args.mandateId } : {}),
|
|
252
|
+
model: args.model,
|
|
253
|
+
messages,
|
|
254
|
+
tools,
|
|
255
|
+
...(args.system !== undefined ? { system: args.system } : {}),
|
|
256
|
+
...(args.maxTokens !== undefined ? { maxTokens: args.maxTokens } : {}),
|
|
257
|
+
...(args.temperature !== undefined ? { temperature: args.temperature } : {}),
|
|
258
|
+
...(args.signal ? { signal: args.signal } : {}),
|
|
259
|
+
});
|
|
260
|
+
lastAuditId = r.auditId;
|
|
261
|
+
lastFundedBy = r.fundedBy;
|
|
262
|
+
lastReceiptId = r.receiptId;
|
|
263
|
+
return {
|
|
264
|
+
content: r.content,
|
|
265
|
+
stopReason: r.stopReason,
|
|
266
|
+
usage: r.usage,
|
|
267
|
+
creditsCharged: r.creditsCharged,
|
|
268
|
+
walletBalance: r.walletBalance,
|
|
269
|
+
};
|
|
270
|
+
},
|
|
271
|
+
dispatch: (name, input) => dispatchAgentToolLocal(dispatchCtx, name, input),
|
|
272
|
+
});
|
|
273
|
+
return {
|
|
274
|
+
content: loop.finalContent,
|
|
275
|
+
stopReason: loop.stopReason,
|
|
276
|
+
iterations: loop.iterations,
|
|
277
|
+
usage: loop.usage,
|
|
278
|
+
toolCalls: loop.toolCalls,
|
|
279
|
+
creditsCharged: loop.creditsCharged,
|
|
280
|
+
walletBalance: loop.walletBalance,
|
|
281
|
+
auditId: lastAuditId,
|
|
282
|
+
...(lastFundedBy ? { fundedBy: lastFundedBy } : {}),
|
|
283
|
+
...(lastReceiptId ? { receiptId: lastReceiptId } : {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
151
286
|
/**
|
|
152
287
|
* Multimodal Bedrock invoke — image + text → text response.
|
|
153
288
|
* Default model: `claude-sonnet-4-6` (vision-capable, reliable JSON).
|
|
@@ -597,6 +732,46 @@ export class ComputeNamespace {
|
|
|
597
732
|
},
|
|
598
733
|
};
|
|
599
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* Lazily-built EthosNamespace for the local agent dispatch — reuses the
|
|
737
|
+
* compute deps (auth / endpoints / fetch), so no extra wiring in sdk.ts.
|
|
738
|
+
*/
|
|
739
|
+
#ethosNs = null;
|
|
740
|
+
#ethosNamespace() {
|
|
741
|
+
if (!this.#ethosNs) {
|
|
742
|
+
this.#ethosNs = new EthosNamespace({
|
|
743
|
+
auth: this.#deps.auth,
|
|
744
|
+
endpoints: this.#deps.endpoints,
|
|
745
|
+
fetch: this.#deps.fetch,
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
return this.#ethosNs;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Resolve the subject DID the local agent operates on:
|
|
752
|
+
* 1. explicit `subjectDid` always wins;
|
|
753
|
+
* 2. else the signed-in owner;
|
|
754
|
+
* 3. else (delegate session) the subject of the imported mandate.
|
|
755
|
+
*/
|
|
756
|
+
#resolveSubjectDid(explicit, mandateId) {
|
|
757
|
+
if (explicit && explicit.length > 0)
|
|
758
|
+
return explicit;
|
|
759
|
+
const owner = this.#deps.auth._getOwnerSigners();
|
|
760
|
+
if (owner && !owner.destroyed)
|
|
761
|
+
return owner.did;
|
|
762
|
+
if (mandateId) {
|
|
763
|
+
const actor = this.#deps.auth._getDelegateActor(mandateId);
|
|
764
|
+
if (actor && !actor.destroyed)
|
|
765
|
+
return actor.subjectDid;
|
|
766
|
+
}
|
|
767
|
+
throw new AithosSDKError("sdk_no_signer", "cannot determine the subject DID: sign in as an owner, pass a mandateId for a delegate session, or pass subjectDid explicitly.");
|
|
768
|
+
}
|
|
769
|
+
/** Scopes carried by the delegate mandate for `did` (empty if none). */
|
|
770
|
+
#delegateScopesForSubject(did) {
|
|
771
|
+
const actor = this.#deps.auth._findDelegateForSubject(did);
|
|
772
|
+
const raw = actor?.mandate?.scopes;
|
|
773
|
+
return Array.isArray(raw) ? raw.filter((s) => typeof s === "string") : [];
|
|
774
|
+
}
|
|
600
775
|
/**
|
|
601
776
|
* Resolve the active signer (owner takes precedence over delegate).
|
|
602
777
|
*
|
package/dist/src/data.d.ts
CHANGED
|
@@ -278,17 +278,22 @@ export interface CreateDelegateDataClientArgs {
|
|
|
278
278
|
readonly fetch?: typeof fetch;
|
|
279
279
|
}
|
|
280
280
|
/**
|
|
281
|
-
* Build a
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* {@link DataClient.authorizeDelegate}.
|
|
281
|
+
* Build a data client that operates on a subject's collections under a
|
|
282
|
+
* mandate (delegate path). It signs every request as the delegate
|
|
283
|
+
* (bare-multibase verificationMethod + the mandate attached to the
|
|
284
|
+
* envelope) and decrypts/encrypts records using the CMK the owner
|
|
285
|
+
* re-wrapped for this delegate via {@link DataClient.authorizeDelegate}.
|
|
287
286
|
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
287
|
+
* Record CRUD is bounded by the mandate scope: reads need
|
|
288
|
+
* `data.<col>.read`, writes need `data.<col>.write` (or `.admin` /
|
|
289
|
+
* wildcard) — enforced client-side and by the PDS. Owner-only operations
|
|
290
|
+
* (createCollection, authorizeDelegate, revokeDelegate, registerSchema)
|
|
291
|
+
* always throw `-32042`: the owner holds the CMK and controls access.
|
|
292
|
+
*
|
|
293
|
+
* @internal Prefer the session accessor `auth.data` (owner) / the delegate
|
|
294
|
+
* session over hand-constructing this with a raw seed.
|
|
290
295
|
*/
|
|
291
|
-
export declare function createDelegateDataClient(args: CreateDelegateDataClientArgs):
|
|
296
|
+
export declare function createDelegateDataClient(args: CreateDelegateDataClientArgs): DataClient;
|
|
292
297
|
/** An append-only handle on one collection: `insert` and nothing else. */
|
|
293
298
|
export interface AppendOnlyDataCollection {
|
|
294
299
|
readonly name: string;
|