@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,18 @@
|
|
|
1
|
+
import type { DispatchOutcome } from "./agent-loop.js";
|
|
2
|
+
import type { EthosClient } from "./ethos.js";
|
|
3
|
+
/** Optional structured-data reader (gamma). Wire `sdk.data` here if available. */
|
|
4
|
+
export type DataProvider = (collection: string, limit: number) => Promise<readonly Record<string, unknown>[]>;
|
|
5
|
+
export interface AgentDispatchContext {
|
|
6
|
+
/** Ethos client for the conversation's subject (resolved via ethos.of(did)). */
|
|
7
|
+
readonly ethos: EthosClient;
|
|
8
|
+
/** Scopes carried by the active delegate mandate. Empty/owner → see `mode`. */
|
|
9
|
+
readonly delegateScopes: readonly string[];
|
|
10
|
+
/** Optional gamma reader for `data_query`. Absent → tool returns is_error. */
|
|
11
|
+
readonly dataProvider?: DataProvider;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Execute one tool LOCALLY. Never throws on a tool-level problem — converts it
|
|
15
|
+
* into an `is_error` outcome the model can read and recover from.
|
|
16
|
+
*/
|
|
17
|
+
export declare function dispatchAgentToolLocal(ctx: AgentDispatchContext, name: string, input: Record<string, unknown>): Promise<DispatchOutcome>;
|
|
18
|
+
//# sourceMappingURL=agent-dispatch.d.ts.map
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
import { ZONE_NAMES } from "./ethos.js";
|
|
4
|
+
import { AithosSDKError } from "./types.js";
|
|
5
|
+
import { TOOL_ETHOS_LIST_SECTIONS, TOOL_ETHOS_READ_SECTION, TOOL_DATA_QUERY, TOOL_ETHOS_ADD_SECTION, TOOL_ETHOS_UPDATE_SECTION, TOOL_ETHOS_DELETE_SECTION, } from "./agent-tools.js";
|
|
6
|
+
function ok(payload) {
|
|
7
|
+
return { payload: typeof payload === "string" ? payload : JSON.stringify(payload), isError: false };
|
|
8
|
+
}
|
|
9
|
+
function err(message) {
|
|
10
|
+
return { payload: JSON.stringify({ error: message }), isError: true };
|
|
11
|
+
}
|
|
12
|
+
/** Whether the caller may WRITE the given zone. */
|
|
13
|
+
function canWriteZone(ctx, zone) {
|
|
14
|
+
if (ctx.ethos.mode === "owner")
|
|
15
|
+
return true;
|
|
16
|
+
if (ctx.ethos.mode === "anonymous")
|
|
17
|
+
return false;
|
|
18
|
+
return ctx.delegateScopes.includes(`ethos.write.${zone}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read every zone the caller can see, returning the zone + its sections.
|
|
22
|
+
* Zones the caller cannot read (ungranted scope / undecryptable) are skipped
|
|
23
|
+
* silently — same bounding rule as `AithosSDK.buildWorkingSet`.
|
|
24
|
+
*/
|
|
25
|
+
async function readableZones(ctx) {
|
|
26
|
+
const out = [];
|
|
27
|
+
for (const zone of ZONE_NAMES) {
|
|
28
|
+
try {
|
|
29
|
+
const sections = await ctx.ethos.zone(zone).sections();
|
|
30
|
+
out.push({
|
|
31
|
+
zone,
|
|
32
|
+
sections: sections.map((s) => ({ id: s.id, title: s.title, body: s.body })),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
if (e instanceof AithosSDKError)
|
|
37
|
+
continue; // not granted / not decryptable
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Execute one tool LOCALLY. Never throws on a tool-level problem — converts it
|
|
45
|
+
* into an `is_error` outcome the model can read and recover from.
|
|
46
|
+
*/
|
|
47
|
+
export async function dispatchAgentToolLocal(ctx, name, input) {
|
|
48
|
+
try {
|
|
49
|
+
switch (name) {
|
|
50
|
+
/* ------------------------------- reads ------------------------------ */
|
|
51
|
+
case TOOL_ETHOS_LIST_SECTIONS: {
|
|
52
|
+
const zones = await readableZones(ctx);
|
|
53
|
+
const sections = zones.flatMap(({ zone, sections }) => sections.map((s) => ({ zone, id: s.id, title: s.title })));
|
|
54
|
+
return ok({ sections });
|
|
55
|
+
}
|
|
56
|
+
case TOOL_ETHOS_READ_SECTION: {
|
|
57
|
+
const sectionId = input.section_id;
|
|
58
|
+
if (typeof sectionId !== "string" || !sectionId) {
|
|
59
|
+
return err("section_id (string) is required");
|
|
60
|
+
}
|
|
61
|
+
const zones = await readableZones(ctx);
|
|
62
|
+
for (const { zone, sections } of zones) {
|
|
63
|
+
const found = sections.find((s) => s.id === sectionId);
|
|
64
|
+
if (found) {
|
|
65
|
+
return ok({ zone, title: found.title, body: found.body });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return err(`no readable section with id '${sectionId}'`);
|
|
69
|
+
}
|
|
70
|
+
case TOOL_DATA_QUERY: {
|
|
71
|
+
const collection = input.collection;
|
|
72
|
+
if (typeof collection !== "string" || !collection) {
|
|
73
|
+
return err("collection (string) is required");
|
|
74
|
+
}
|
|
75
|
+
if (!ctx.dataProvider) {
|
|
76
|
+
return err("structured data (gamma) is not available in this session");
|
|
77
|
+
}
|
|
78
|
+
const limit = typeof input.limit === "number" && Number.isInteger(input.limit)
|
|
79
|
+
? Math.max(1, Math.min(100, input.limit))
|
|
80
|
+
: 20;
|
|
81
|
+
const records = await ctx.dataProvider(collection, limit);
|
|
82
|
+
return ok({ collection, records });
|
|
83
|
+
}
|
|
84
|
+
/* ------------------------------ writes ------------------------------ */
|
|
85
|
+
case TOOL_ETHOS_ADD_SECTION: {
|
|
86
|
+
const zone = input.zone;
|
|
87
|
+
const title = input.title;
|
|
88
|
+
const body = input.body;
|
|
89
|
+
if (!isZone(zone))
|
|
90
|
+
return err("zone must be one of public|circle|self");
|
|
91
|
+
if (typeof title !== "string" || !title)
|
|
92
|
+
return err("title (string) is required");
|
|
93
|
+
if (typeof body !== "string")
|
|
94
|
+
return err("body (string) is required");
|
|
95
|
+
if (!canWriteZone(ctx, zone)) {
|
|
96
|
+
return err(`not authorized to write the '${zone}' zone (missing ethos.write.${zone})`);
|
|
97
|
+
}
|
|
98
|
+
ctx.ethos.zone(zone).addSection({ title, body });
|
|
99
|
+
const result = await ctx.ethos.publish();
|
|
100
|
+
return ok({ published: true, zone, editionHeight: result.editionHeight });
|
|
101
|
+
}
|
|
102
|
+
case TOOL_ETHOS_UPDATE_SECTION: {
|
|
103
|
+
const sectionId = input.section_id;
|
|
104
|
+
if (typeof sectionId !== "string" || !sectionId) {
|
|
105
|
+
return err("section_id (string) is required");
|
|
106
|
+
}
|
|
107
|
+
const patch = {};
|
|
108
|
+
if (input.title !== undefined) {
|
|
109
|
+
if (typeof input.title !== "string")
|
|
110
|
+
return err("title must be a string");
|
|
111
|
+
patch.title = input.title;
|
|
112
|
+
}
|
|
113
|
+
if (input.body !== undefined) {
|
|
114
|
+
if (typeof input.body !== "string")
|
|
115
|
+
return err("body must be a string");
|
|
116
|
+
patch.body = input.body;
|
|
117
|
+
}
|
|
118
|
+
if (patch.title === undefined && patch.body === undefined) {
|
|
119
|
+
return err("nothing to update: provide title and/or body");
|
|
120
|
+
}
|
|
121
|
+
const located = await locateSection(ctx, sectionId);
|
|
122
|
+
if (!located)
|
|
123
|
+
return err(`no readable section with id '${sectionId}'`);
|
|
124
|
+
if (!canWriteZone(ctx, located)) {
|
|
125
|
+
return err(`not authorized to write the '${located}' zone (missing ethos.write.${located})`);
|
|
126
|
+
}
|
|
127
|
+
ctx.ethos.zone(located).updateSection(sectionId, patch);
|
|
128
|
+
const result = await ctx.ethos.publish();
|
|
129
|
+
return ok({ published: true, zone: located, editionHeight: result.editionHeight });
|
|
130
|
+
}
|
|
131
|
+
case TOOL_ETHOS_DELETE_SECTION: {
|
|
132
|
+
const sectionId = input.section_id;
|
|
133
|
+
if (typeof sectionId !== "string" || !sectionId) {
|
|
134
|
+
return err("section_id (string) is required");
|
|
135
|
+
}
|
|
136
|
+
const located = await locateSection(ctx, sectionId);
|
|
137
|
+
if (!located)
|
|
138
|
+
return err(`no readable section with id '${sectionId}'`);
|
|
139
|
+
if (!canWriteZone(ctx, located)) {
|
|
140
|
+
return err(`not authorized to write the '${located}' zone (missing ethos.write.${located})`);
|
|
141
|
+
}
|
|
142
|
+
ctx.ethos.zone(located).deleteSection(sectionId);
|
|
143
|
+
const result = await ctx.ethos.publish();
|
|
144
|
+
return ok({ published: true, zone: located, editionHeight: result.editionHeight });
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
return err(`unknown tool '${name}'`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
// Any failure (scope refusal inside publish(), network, decrypt, …) →
|
|
152
|
+
// is_error so the model can recover; we never let it break the loop.
|
|
153
|
+
const message = e instanceof AithosSDKError
|
|
154
|
+
? `${e.code}: ${e.message}`
|
|
155
|
+
: e?.message ?? String(e);
|
|
156
|
+
return err(message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function isZone(v) {
|
|
160
|
+
return v === "public" || v === "circle" || v === "self";
|
|
161
|
+
}
|
|
162
|
+
/** Find which readable zone holds the section id, or null. */
|
|
163
|
+
async function locateSection(ctx, sectionId) {
|
|
164
|
+
for (const zone of ZONE_NAMES) {
|
|
165
|
+
try {
|
|
166
|
+
const sections = await ctx.ethos.zone(zone).sections();
|
|
167
|
+
if (sections.some((s) => s.id === sectionId))
|
|
168
|
+
return zone;
|
|
169
|
+
}
|
|
170
|
+
catch (e) {
|
|
171
|
+
if (e instanceof AithosSDKError)
|
|
172
|
+
continue;
|
|
173
|
+
throw e;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=agent-dispatch.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface TextBlock {
|
|
2
|
+
readonly type: "text";
|
|
3
|
+
readonly text: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ToolUseBlock {
|
|
6
|
+
readonly type: "tool_use";
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly input: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export interface ToolResultBlock {
|
|
12
|
+
readonly type: "tool_result";
|
|
13
|
+
readonly tool_use_id: string;
|
|
14
|
+
/** Stringified result payload (JSON or plain text). */
|
|
15
|
+
readonly content: string;
|
|
16
|
+
/** Set when the tool failed — lets the model recover instead of us failing. */
|
|
17
|
+
readonly is_error?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export type ContentBlock = TextBlock | ToolUseBlock | ToolResultBlock | Record<string, unknown>;
|
|
20
|
+
/** A message in the running conversation. `content` is a string or blocks. */
|
|
21
|
+
export interface AgentMessage {
|
|
22
|
+
readonly role: "user" | "assistant";
|
|
23
|
+
readonly content: string | readonly ContentBlock[];
|
|
24
|
+
}
|
|
25
|
+
/** Anthropic tool definition (forwarded to the proxy in `tools`). */
|
|
26
|
+
export interface AgentToolSpec {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly description: string;
|
|
29
|
+
readonly input_schema: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
export type AgentTurnStopReason = "end_turn" | "max_tokens" | "stop_sequence" | "tool_use";
|
|
32
|
+
/** One per-turn result from the proxy (`aithos.compute_invoke_turn`). */
|
|
33
|
+
export interface AgentTurnResult {
|
|
34
|
+
readonly content: readonly ContentBlock[];
|
|
35
|
+
readonly stopReason: AgentTurnStopReason;
|
|
36
|
+
readonly usage: {
|
|
37
|
+
readonly inputTokens: number;
|
|
38
|
+
readonly outputTokens: number;
|
|
39
|
+
};
|
|
40
|
+
/** Microcredits charged for THIS turn (cumulative billing — HANDOFF §1). */
|
|
41
|
+
readonly creditsCharged?: number;
|
|
42
|
+
readonly walletBalance?: number;
|
|
43
|
+
}
|
|
44
|
+
/** Extract the tool_use blocks from a turn's content (in order). */
|
|
45
|
+
export declare function extractToolUseBlocks(content: readonly ContentBlock[]): readonly ToolUseBlock[];
|
|
46
|
+
/** Concatenate the text blocks of a turn into a single string. */
|
|
47
|
+
export declare function extractText(content: readonly ContentBlock[]): string;
|
|
48
|
+
/** Build a single tool_result block. */
|
|
49
|
+
export declare function toolResult(toolUseId: string, payload: string, isError?: boolean): ToolResultBlock;
|
|
50
|
+
/** Build the user message carrying one or more tool_result blocks. */
|
|
51
|
+
export declare function buildToolResultMessage(results: readonly ToolResultBlock[]): AgentMessage;
|
|
52
|
+
export type LoopStopReason = "end_turn" | "max_tokens" | "stop_sequence" | "max_iterations";
|
|
53
|
+
export interface ToolCallTrace {
|
|
54
|
+
readonly name: string;
|
|
55
|
+
readonly ok: boolean;
|
|
56
|
+
readonly turn: number;
|
|
57
|
+
}
|
|
58
|
+
export interface AggregateUsage {
|
|
59
|
+
readonly inputTokens: number;
|
|
60
|
+
readonly outputTokens: number;
|
|
61
|
+
}
|
|
62
|
+
export interface DispatchOutcome {
|
|
63
|
+
readonly payload: string;
|
|
64
|
+
readonly isError: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface LocalAgenticLoopResult {
|
|
67
|
+
readonly finalContent: string;
|
|
68
|
+
readonly stopReason: LoopStopReason;
|
|
69
|
+
/** Number of proxy turns performed (each is a billed call). */
|
|
70
|
+
readonly iterations: number;
|
|
71
|
+
readonly usage: AggregateUsage;
|
|
72
|
+
readonly toolCalls: readonly ToolCallTrace[];
|
|
73
|
+
/** Sum of per-turn `creditsCharged` (HANDOFF §1: per-turn cumulative billing). */
|
|
74
|
+
readonly creditsCharged: number;
|
|
75
|
+
/** Wallet balance reported by the LAST turn (0 if none reported). */
|
|
76
|
+
readonly walletBalance: number;
|
|
77
|
+
}
|
|
78
|
+
export interface LocalAgenticLoopArgs {
|
|
79
|
+
/** Initial conversation (copied internally; not mutated by the caller). */
|
|
80
|
+
readonly messages: readonly AgentMessage[];
|
|
81
|
+
/** Hard cap on proxy turns. */
|
|
82
|
+
readonly maxIterations: number;
|
|
83
|
+
/** One signed proxy turn. Receives the running message list. */
|
|
84
|
+
readonly invokeTurn: (messages: readonly AgentMessage[]) => Promise<AgentTurnResult>;
|
|
85
|
+
/** Execute one tool call LOCALLY (read/write the user's ethos). Async. */
|
|
86
|
+
readonly dispatch: (name: string, input: Record<string, unknown>) => Promise<DispatchOutcome>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Run the client-side agentic loop. Never throws on tool errors (they become
|
|
90
|
+
* `tool_result.is_error` so the model can recover); only `invokeTurn`
|
|
91
|
+
* rejections propagate to the caller (the proxy already refunded that turn).
|
|
92
|
+
*/
|
|
93
|
+
export declare function runAgenticLoopLocal(args: LocalAgenticLoopArgs): Promise<LocalAgenticLoopResult>;
|
|
94
|
+
//# sourceMappingURL=agent-loop.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
/* -------------------------------------------------------------------------- */
|
|
4
|
+
/* Pure helpers */
|
|
5
|
+
/* -------------------------------------------------------------------------- */
|
|
6
|
+
/** Extract the tool_use blocks from a turn's content (in order). */
|
|
7
|
+
export function extractToolUseBlocks(content) {
|
|
8
|
+
return content.filter((b) => typeof b === "object" &&
|
|
9
|
+
b !== null &&
|
|
10
|
+
b.type === "tool_use");
|
|
11
|
+
}
|
|
12
|
+
/** Concatenate the text blocks of a turn into a single string. */
|
|
13
|
+
export function extractText(content) {
|
|
14
|
+
return content
|
|
15
|
+
.filter((b) => typeof b === "object" &&
|
|
16
|
+
b !== null &&
|
|
17
|
+
b.type === "text" &&
|
|
18
|
+
typeof b.text === "string")
|
|
19
|
+
.map((b) => b.text)
|
|
20
|
+
.join("");
|
|
21
|
+
}
|
|
22
|
+
/** Build a single tool_result block. */
|
|
23
|
+
export function toolResult(toolUseId, payload, isError = false) {
|
|
24
|
+
return {
|
|
25
|
+
type: "tool_result",
|
|
26
|
+
tool_use_id: toolUseId,
|
|
27
|
+
content: payload,
|
|
28
|
+
...(isError ? { is_error: true } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** Build the user message carrying one or more tool_result blocks. */
|
|
32
|
+
export function buildToolResultMessage(results) {
|
|
33
|
+
return { role: "user", content: results };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Run the client-side agentic loop. Never throws on tool errors (they become
|
|
37
|
+
* `tool_result.is_error` so the model can recover); only `invokeTurn`
|
|
38
|
+
* rejections propagate to the caller (the proxy already refunded that turn).
|
|
39
|
+
*/
|
|
40
|
+
export async function runAgenticLoopLocal(args) {
|
|
41
|
+
const messages = [...args.messages];
|
|
42
|
+
const trace = [];
|
|
43
|
+
let inputTokens = 0;
|
|
44
|
+
let outputTokens = 0;
|
|
45
|
+
let creditsCharged = 0;
|
|
46
|
+
let walletBalance = 0;
|
|
47
|
+
let lastText = "";
|
|
48
|
+
const usage = () => ({ inputTokens, outputTokens });
|
|
49
|
+
for (let turn = 1; turn <= args.maxIterations; turn++) {
|
|
50
|
+
const r = await args.invokeTurn(messages);
|
|
51
|
+
inputTokens += r.usage.inputTokens;
|
|
52
|
+
outputTokens += r.usage.outputTokens;
|
|
53
|
+
if (typeof r.creditsCharged === "number")
|
|
54
|
+
creditsCharged += r.creditsCharged;
|
|
55
|
+
if (typeof r.walletBalance === "number")
|
|
56
|
+
walletBalance = r.walletBalance;
|
|
57
|
+
const text = extractText(r.content);
|
|
58
|
+
if (text)
|
|
59
|
+
lastText = text;
|
|
60
|
+
const toolUses = extractToolUseBlocks(r.content);
|
|
61
|
+
// Terminal: the model stopped without (valid) tool calls.
|
|
62
|
+
if (r.stopReason !== "tool_use" || toolUses.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
finalContent: text || lastText,
|
|
65
|
+
stopReason: r.stopReason === "tool_use" ? "end_turn" : r.stopReason,
|
|
66
|
+
iterations: turn,
|
|
67
|
+
usage: usage(),
|
|
68
|
+
toolCalls: trace,
|
|
69
|
+
creditsCharged,
|
|
70
|
+
walletBalance,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Append the assistant turn (carries the tool_use blocks), then dispatch
|
|
74
|
+
// each tool LOCALLY and feed the results back as a single user turn.
|
|
75
|
+
messages.push({ role: "assistant", content: r.content });
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const tu of toolUses) {
|
|
78
|
+
const out = await args.dispatch(tu.name, tu.input);
|
|
79
|
+
trace.push({ name: tu.name, ok: !out.isError, turn });
|
|
80
|
+
results.push(toolResult(tu.id, out.payload, out.isError));
|
|
81
|
+
}
|
|
82
|
+
messages.push(buildToolResultMessage(results));
|
|
83
|
+
}
|
|
84
|
+
// Cap reached while still mid-tool-loop: return the last text we saw.
|
|
85
|
+
return {
|
|
86
|
+
finalContent: lastText,
|
|
87
|
+
stopReason: "max_iterations",
|
|
88
|
+
iterations: args.maxIterations,
|
|
89
|
+
usage: usage(),
|
|
90
|
+
toolCalls: trace,
|
|
91
|
+
creditsCharged,
|
|
92
|
+
walletBalance,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=agent-loop.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AgentToolSpec } from "./agent-loop.js";
|
|
2
|
+
export declare const TOOL_ETHOS_LIST_SECTIONS = "ethos_list_sections";
|
|
3
|
+
export declare const TOOL_ETHOS_READ_SECTION = "ethos_read_section";
|
|
4
|
+
export declare const TOOL_DATA_QUERY = "data_query";
|
|
5
|
+
export declare const TOOL_ETHOS_ADD_SECTION = "ethos_add_section";
|
|
6
|
+
export declare const TOOL_ETHOS_UPDATE_SECTION = "ethos_update_section";
|
|
7
|
+
export declare const TOOL_ETHOS_DELETE_SECTION = "ethos_delete_section";
|
|
8
|
+
export declare const AITHOS_AGENT_READ_TOOLS: readonly AgentToolSpec[];
|
|
9
|
+
export declare const AITHOS_AGENT_WRITE_TOOLS: readonly AgentToolSpec[];
|
|
10
|
+
export declare const AITHOS_AGENT_TOOLS: readonly AgentToolSpec[];
|
|
11
|
+
export declare function isWriteTool(name: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the tool specs to forward to the proxy.
|
|
14
|
+
*
|
|
15
|
+
* - `undefined` / empty → the full catalogue (read + write).
|
|
16
|
+
* - a list of names → exactly those (unknown names ignored).
|
|
17
|
+
* - `{ readOnly: true }` → only the read family (a caller that wants a
|
|
18
|
+
* strictly non-mutating agent).
|
|
19
|
+
*/
|
|
20
|
+
export declare function selectAgentTools(opts?: {
|
|
21
|
+
readonly tools?: readonly string[];
|
|
22
|
+
readonly readOnly?: boolean;
|
|
23
|
+
}): readonly AgentToolSpec[];
|
|
24
|
+
//# sourceMappingURL=agent-tools.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright 2026 Mathieu Colla
|
|
3
|
+
/* ----------------------------------- read ---------------------------------- */
|
|
4
|
+
export const TOOL_ETHOS_LIST_SECTIONS = "ethos_list_sections";
|
|
5
|
+
export const TOOL_ETHOS_READ_SECTION = "ethos_read_section";
|
|
6
|
+
export const TOOL_DATA_QUERY = "data_query";
|
|
7
|
+
/* ---------------------------------- write ---------------------------------- */
|
|
8
|
+
export const TOOL_ETHOS_ADD_SECTION = "ethos_add_section";
|
|
9
|
+
export const TOOL_ETHOS_UPDATE_SECTION = "ethos_update_section";
|
|
10
|
+
export const TOOL_ETHOS_DELETE_SECTION = "ethos_delete_section";
|
|
11
|
+
/** The ethos zones a section can live in. */
|
|
12
|
+
const ZONE_ENUM = ["public", "circle", "self"];
|
|
13
|
+
export const AITHOS_AGENT_READ_TOOLS = [
|
|
14
|
+
{
|
|
15
|
+
name: TOOL_ETHOS_LIST_SECTIONS,
|
|
16
|
+
description: "Liste les sections disponibles de l'ethos de l'utilisateur (zone, id, " +
|
|
17
|
+
"titre), SANS leur contenu. À appeler en premier pour découvrir ce qui " +
|
|
18
|
+
"est lisible, puis utiliser ethos_read_section pour obtenir le détail.",
|
|
19
|
+
input_schema: { type: "object", properties: {}, additionalProperties: false },
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: TOOL_ETHOS_READ_SECTION,
|
|
23
|
+
description: "Renvoie le contenu (titre + corps) d'une section de l'ethos, " +
|
|
24
|
+
"identifiée par son id obtenu via ethos_list_sections.",
|
|
25
|
+
input_schema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
section_id: { type: "string", description: "id de la section à lire" },
|
|
29
|
+
},
|
|
30
|
+
required: ["section_id"],
|
|
31
|
+
additionalProperties: false,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: TOOL_DATA_QUERY,
|
|
36
|
+
description: "Renvoie les enregistrements d'une collection de données structurées " +
|
|
37
|
+
"de l'utilisateur (zone gamma).",
|
|
38
|
+
input_schema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
collection: { type: "string", description: "nom de la collection" },
|
|
42
|
+
limit: {
|
|
43
|
+
type: "integer",
|
|
44
|
+
minimum: 1,
|
|
45
|
+
maximum: 100,
|
|
46
|
+
description: "nombre maximum d'enregistrements (défaut 20)",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
required: ["collection"],
|
|
50
|
+
additionalProperties: false,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
export const AITHOS_AGENT_WRITE_TOOLS = [
|
|
55
|
+
{
|
|
56
|
+
name: TOOL_ETHOS_ADD_SECTION,
|
|
57
|
+
description: "Ajoute une NOUVELLE section à l'ethos de l'utilisateur dans la zone " +
|
|
58
|
+
"indiquée. À n'utiliser que si l'information n'existe pas déjà (vérifier " +
|
|
59
|
+
"avec ethos_list_sections d'abord pour éviter les doublons). La " +
|
|
60
|
+
"modification est signée et publiée localement avec les clés de " +
|
|
61
|
+
"l'utilisateur.",
|
|
62
|
+
input_schema: {
|
|
63
|
+
type: "object",
|
|
64
|
+
properties: {
|
|
65
|
+
zone: {
|
|
66
|
+
type: "string",
|
|
67
|
+
enum: [...ZONE_ENUM],
|
|
68
|
+
description: "zone de l'ethos : 'public' (visible de tous), 'circle' (cercle " +
|
|
69
|
+
"de confiance), 'self' (privé). Choisir selon la sensibilité.",
|
|
70
|
+
},
|
|
71
|
+
title: { type: "string", description: "titre de la section" },
|
|
72
|
+
body: { type: "string", description: "contenu (texte) de la section" },
|
|
73
|
+
},
|
|
74
|
+
required: ["zone", "title", "body"],
|
|
75
|
+
additionalProperties: false,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: TOOL_ETHOS_UPDATE_SECTION,
|
|
80
|
+
description: "Met à jour une section existante de l'ethos (titre et/ou corps), " +
|
|
81
|
+
"identifiée par son id (obtenu via ethos_list_sections). Ne modifier " +
|
|
82
|
+
"que ce qui doit changer.",
|
|
83
|
+
input_schema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
section_id: {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "id de la section à modifier",
|
|
89
|
+
},
|
|
90
|
+
title: { type: "string", description: "nouveau titre (optionnel)" },
|
|
91
|
+
body: { type: "string", description: "nouveau corps (optionnel)" },
|
|
92
|
+
},
|
|
93
|
+
required: ["section_id"],
|
|
94
|
+
additionalProperties: false,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: TOOL_ETHOS_DELETE_SECTION,
|
|
99
|
+
description: "Supprime une section de l'ethos, identifiée par son id. Action " +
|
|
100
|
+
"irréversible une fois publiée — n'utiliser que si la suppression est " +
|
|
101
|
+
"explicitement justifiée.",
|
|
102
|
+
input_schema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
section_id: {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: "id de la section à supprimer",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
required: ["section_id"],
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
export const AITHOS_AGENT_TOOLS = [
|
|
116
|
+
...AITHOS_AGENT_READ_TOOLS,
|
|
117
|
+
...AITHOS_AGENT_WRITE_TOOLS,
|
|
118
|
+
];
|
|
119
|
+
const TOOL_BY_NAME = new Map(AITHOS_AGENT_TOOLS.map((t) => [t.name, t]));
|
|
120
|
+
export function isWriteTool(name) {
|
|
121
|
+
return (name === TOOL_ETHOS_ADD_SECTION ||
|
|
122
|
+
name === TOOL_ETHOS_UPDATE_SECTION ||
|
|
123
|
+
name === TOOL_ETHOS_DELETE_SECTION);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve the tool specs to forward to the proxy.
|
|
127
|
+
*
|
|
128
|
+
* - `undefined` / empty → the full catalogue (read + write).
|
|
129
|
+
* - a list of names → exactly those (unknown names ignored).
|
|
130
|
+
* - `{ readOnly: true }` → only the read family (a caller that wants a
|
|
131
|
+
* strictly non-mutating agent).
|
|
132
|
+
*/
|
|
133
|
+
export function selectAgentTools(opts) {
|
|
134
|
+
if (opts?.readOnly)
|
|
135
|
+
return AITHOS_AGENT_READ_TOOLS;
|
|
136
|
+
const requested = opts?.tools;
|
|
137
|
+
if (!requested || requested.length === 0)
|
|
138
|
+
return AITHOS_AGENT_TOOLS;
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const name of requested) {
|
|
141
|
+
const spec = TOOL_BY_NAME.get(name);
|
|
142
|
+
if (spec)
|
|
143
|
+
out.push(spec);
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=agent-tools.js.map
|
package/dist/src/auth.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type AithosKeyStore } from "./key-store.js";
|
|
|
3
3
|
import { DelegateActor } from "./internal/delegate-state.js";
|
|
4
4
|
import { type SignedEnvelope } from "./internal/envelope.js";
|
|
5
5
|
import { OwnerSigners } from "./internal/owner-signers.js";
|
|
6
|
+
import { type DataClient, type AithosSchemaLite } from "./data.js";
|
|
6
7
|
/** Default URL of the Aithos auth backend. */
|
|
7
8
|
export declare const DEFAULT_AUTH_BASE_URL = "https://auth.aithos.be";
|
|
8
9
|
/** Default URL of the Aithos primitives API (publish_identity, publish_ethos_edition, etc.). */
|
|
@@ -436,6 +437,64 @@ export declare class AithosAuth {
|
|
|
436
437
|
* @internal
|
|
437
438
|
*/
|
|
438
439
|
_getOwnerSigners(): OwnerSigners | null;
|
|
440
|
+
/**
|
|
441
|
+
* Ready-made owner data client bound to the signed-in account, signing +
|
|
442
|
+
* sealing under the dedicated **`#data`** sphere (the protocol-intended owner
|
|
443
|
+
* data key). This is the one-liner apps should use instead of hand-rolling
|
|
444
|
+
* `createDataClient` with a raw seed — hand-rolling with `#root` is exactly
|
|
445
|
+
* what left legacy collections sealed to the wrong key.
|
|
446
|
+
*
|
|
447
|
+
* const data = auth.ownerDataClient({ schemas: [myVendorLite] });
|
|
448
|
+
* await data.collection("notes").insert({ ... }); // owned under #data
|
|
449
|
+
*
|
|
450
|
+
* Throws when no owner is signed in, or when the account has no `#data`
|
|
451
|
+
* sphere (legacy accounts created before #data, or imported from a 4-seed
|
|
452
|
+
* recovery). Add one first with `rotateEthos` / the migration scripts, then
|
|
453
|
+
* re-import the resulting recovery — the error message says so.
|
|
454
|
+
*
|
|
455
|
+
* @param args.pdsUrl PDS base URL. Defaults to the SDK default (pds.aithos.be).
|
|
456
|
+
* @param args.schemas Vendor `AithosSchemaLite` definitions to register for
|
|
457
|
+
* WRITES (reads auto-resolve published schemas from the PDS).
|
|
458
|
+
*/
|
|
459
|
+
ownerDataClient(args?: {
|
|
460
|
+
readonly pdsUrl?: string;
|
|
461
|
+
readonly schemas?: readonly AithosSchemaLite[];
|
|
462
|
+
}): DataClient;
|
|
463
|
+
/**
|
|
464
|
+
* Ready-made DELEGATE data client, bound to a mandate held in this session
|
|
465
|
+
* (imported via `importMandate` / an accepted invite). Same record-CRUD
|
|
466
|
+
* surface as the owner client, bounded by the mandate's scope — you never
|
|
467
|
+
* pass a key, a sphere, or the mandate itself to the data calls.
|
|
468
|
+
*
|
|
469
|
+
* const db = auth.delegateDataClient(); // single active mandate
|
|
470
|
+
* await db.collection("prospects").insert({ ... }); // needs data.prospects.write
|
|
471
|
+
*
|
|
472
|
+
* With several active mandates, pass `{ subjectDid }` or `{ mandateId }`.
|
|
473
|
+
* Owner-only ops (createCollection, authorizeDelegate, …) throw -32042 — the
|
|
474
|
+
* owner does those once, at onboarding.
|
|
475
|
+
*/
|
|
476
|
+
delegateDataClient(args?: {
|
|
477
|
+
readonly subjectDid?: string;
|
|
478
|
+
readonly mandateId?: string;
|
|
479
|
+
readonly pdsUrl?: string;
|
|
480
|
+
readonly schemas?: readonly AithosSchemaLite[];
|
|
481
|
+
}): DataClient;
|
|
482
|
+
/**
|
|
483
|
+
* Unified data accessor — the database for "however you connected":
|
|
484
|
+
* - signed in as owner → your own collections under `#data`;
|
|
485
|
+
* - acting under an imported mandate → the subject's collections (per scope).
|
|
486
|
+
*
|
|
487
|
+
* Identical CRUD surface either way; the developer never sees a sphere, a
|
|
488
|
+
* key, or the mandate. The mode follows how you authenticated, not a flag on
|
|
489
|
+
* the data calls.
|
|
490
|
+
*
|
|
491
|
+
* const db = auth.data;
|
|
492
|
+
* await db.collection("prospects").insert({ ... });
|
|
493
|
+
*
|
|
494
|
+
* Only ambiguous when you are BOTH signed in as owner AND holding mandates;
|
|
495
|
+
* then call `ownerDataClient()` / `delegateDataClient({ … })` explicitly.
|
|
496
|
+
*/
|
|
497
|
+
get data(): DataClient;
|
|
439
498
|
/**
|
|
440
499
|
* Internal accessor — looks up an active delegate by mandate id.
|
|
441
500
|
* @internal
|