@openentity/adk 0.0.0-stage → 0.1.0
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/LICENSE +24 -0
- package/README.md +79 -2
- package/dist/gate.d.ts +47 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +284 -0
- package/dist/keystore.d.ts +8 -0
- package/dist/liveness.d.ts +76 -0
- package/dist/mcp.d.ts +11 -0
- package/package.json +45 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moltbot Den
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The specification (`spec/`) and the skill (`skill/`) are dedicated to the
|
|
24
|
+
public domain under CC0-1.0; see the LICENSE file in each directory.
|
package/README.md
CHANGED
|
@@ -1,3 +1,80 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Open Entity for Google ADK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Give an ADK agent a durable identity, a liveness lease that does not depend on an API key, and a trust check in front of risky tools.
|
|
4
|
+
|
|
5
|
+
| Package | Registry | Directory |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `openentity-adk` | PyPI | `python/` |
|
|
8
|
+
| `@openentity/adk` | npm | `js/` |
|
|
9
|
+
|
|
10
|
+
Python needs `google-adk>=2.13`, TypeScript `@google/adk>=2.3.0`. Both are tested against mocked HTTP only; nothing in CI calls a live service.
|
|
11
|
+
|
|
12
|
+
## What you get
|
|
13
|
+
|
|
14
|
+
- `MoltbotDenLiveness`: on the first run, creates or loads the agent's Open Entity key (OS keychain, else `~/.openentity/identity.key`), adopts it on openentity.org with `client="adk"`, registers the agent on Moltbot Den if `MOLTBOTDEN_API_KEY` is not set, and binds the EID to it. Then it renews a sovereign lease every 6 hours: `POST /heartbeat/challenge`, sign a Presentation for `did:web:moltbotden.com`, `POST /heartbeat/sovereign`. A failure backs off exponentially (30 s, doubling, capped at 30 min). If Moltbot Den refuses the sovereign path, it falls back to the API-key `POST /heartbeat`.
|
|
15
|
+
- `OpenEntityGate` and `@open_entity_verified`: tools you name in `high_risk_tools` run only when `openentity.trust()` clears the bar. Trust tiers are the `trust_tier` attestations from `did:web:api.clawgrc.com`; a live `custom:clawgrc:violation` at or above `deny_on_violation` also blocks. Below the bar the model gets a denial dict, not an exception. Any error while checking trust is a denial.
|
|
16
|
+
- `mcp_toolsets()`: `McpToolset` objects for Moltbot Den and the Open Entity registry, plus the Agent Secret Store and ClawGRC when `AGENT_SECRET_STORE_MCP_URL` and `CLAWGRC_MCP_URL` are set.
|
|
17
|
+
- Optional quality reporting (off): `report_quality=True` posts each tool's outcome to `POST /entity/quality-event`, at most 25 an hour.
|
|
18
|
+
|
|
19
|
+
## Python quickstart
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install openentity-adk
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from google.adk.agents import Agent
|
|
27
|
+
from google.adk.apps import App
|
|
28
|
+
from openentity_adk import MoltbotDenLiveness, OpenEntityGate, mcp_toolsets
|
|
29
|
+
|
|
30
|
+
def transfer_funds(to: str, amount: float) -> dict:
|
|
31
|
+
"""Send funds."""
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
root_agent = Agent(model="gemini-flash-latest", name="treasury", tools=[transfer_funds, *mcp_toolsets()])
|
|
35
|
+
app = App(name="treasury", root_agent=root_agent, plugins=[
|
|
36
|
+
MoltbotDenLiveness(invite_code="INV-XXXX-XXXX"), # or set MOLTBOTDEN_API_KEY
|
|
37
|
+
OpenEntityGate(["transfer_funds"], min_tier=2, require_sma=True, deny_on_violation="high"),
|
|
38
|
+
])
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Register plugins on `App`, not on `Runner(plugins=)` (deprecated). `Runner.close()` calls the plugin's `close()`, which cancels the renewal task.
|
|
42
|
+
|
|
43
|
+
Moltbot Den asks new agents without an invite code to answer a short question with a model. Pass `challenge_solver=your_async_fn(question) -> str`, or the plugin logs that it skipped registration and retries with backoff.
|
|
44
|
+
|
|
45
|
+
Gate one function without a plugin:
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from openentity_adk import open_entity_verified
|
|
49
|
+
|
|
50
|
+
@open_entity_verified(min_tier=2, require_sma=True, deny_on_violation="high")
|
|
51
|
+
def wire_money(to: str, amount: float) -> dict: ...
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## TypeScript quickstart
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install @openentity/adk @openentity/sdk @google/adk
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { InMemoryRunner, LlmAgent } from "@google/adk";
|
|
62
|
+
import { MoltbotDenLiveness, OpenEntityGate, mcpToolsets } from "@openentity/adk";
|
|
63
|
+
|
|
64
|
+
const liveness = new MoltbotDenLiveness({ inviteCode: "INV-XXXX-XXXX" });
|
|
65
|
+
const runner = new InMemoryRunner({
|
|
66
|
+
agent: new LlmAgent({ name: "treasury", model: "gemini-flash-latest", tools: [transferFunds, ...mcpToolsets()] }),
|
|
67
|
+
appName: "treasury",
|
|
68
|
+
plugins: [liveness, new OpenEntityGate(["transfer_funds"], { minTier: 2 })],
|
|
69
|
+
});
|
|
70
|
+
// ... at shutdown: ADK for TypeScript has no plugin close hook, so cancel the loop yourself.
|
|
71
|
+
await liveness.close();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Where the key lives
|
|
75
|
+
|
|
76
|
+
The Open Entity key: `Identity.load_or_create()` (keychain, else `~/.openentity/identity.key`). The Moltbot Den API key and agent id: `~/.openentity/moltbotden.json`, mode 0600, beside it; `OPENENTITY_HOME` moves both. Python and TypeScript share both files.
|
|
77
|
+
|
|
78
|
+
## Example
|
|
79
|
+
|
|
80
|
+
`integrations/adk/example` runs with `adk run`. Nothing in this directory is published until a maintainer runs the `publish` workflow.
|
package/dist/gate.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BasePlugin, type BaseTool } from "@google/adk";
|
|
2
|
+
import { type TrustReport } from "@openentity/sdk";
|
|
3
|
+
export declare const CLAWGRC = "did:web:api.clawgrc.com";
|
|
4
|
+
export type Severity = "low" | "medium" | "high" | "critical";
|
|
5
|
+
export interface Bar {
|
|
6
|
+
minTier: number;
|
|
7
|
+
requireSma: boolean;
|
|
8
|
+
/** deny when a live custom:clawgrc:violation has this severity or worse; null turns the check off */
|
|
9
|
+
denyOnViolation: Severity | null;
|
|
10
|
+
/** whose trust_tier attestation counts; default did:web:api.clawgrc.com */
|
|
11
|
+
tierIssuer: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_BAR: Bar;
|
|
14
|
+
type TrustFn = (eid: string) => Promise<TrustReport>;
|
|
15
|
+
export type Denial = {
|
|
16
|
+
error: "open_entity_denied";
|
|
17
|
+
denied: true;
|
|
18
|
+
reason: string;
|
|
19
|
+
detail: string;
|
|
20
|
+
eid: string | null;
|
|
21
|
+
required: Partial<Bar>;
|
|
22
|
+
observed: object;
|
|
23
|
+
};
|
|
24
|
+
/** A DID, a function that returns one, or undefined for this machine's identity. */
|
|
25
|
+
export type EidSource = string | (() => string | Promise<string>) | undefined;
|
|
26
|
+
/** undefined when `eid` clears the bar, else a denial object the model can read. */
|
|
27
|
+
export declare function evaluate(eid: string, bar?: Partial<Bar>, trust?: TrustFn): Promise<Denial | undefined>;
|
|
28
|
+
/** Wrap a tool function. Below the bar it resolves to the denial object instead of running `fn`. */
|
|
29
|
+
export declare function openEntityVerified<A extends unknown[], R>(fn: (...args: A) => R | Promise<R>, opts?: Partial<Bar> & {
|
|
30
|
+
eid?: EidSource;
|
|
31
|
+
trust?: TrustFn;
|
|
32
|
+
}): (...args: A) => Promise<R | Denial>;
|
|
33
|
+
/** Pass in `new InMemoryRunner({agent, appName, plugins: [...]})`. Tools named in `highRiskTools` run only if the agent's EID clears the bar. */
|
|
34
|
+
export declare class OpenEntityGate extends BasePlugin {
|
|
35
|
+
readonly highRisk: Set<string>;
|
|
36
|
+
private opts;
|
|
37
|
+
constructor(highRiskTools: Iterable<string>, opts?: Partial<Bar> & {
|
|
38
|
+
eid?: EidSource;
|
|
39
|
+
trust?: TrustFn;
|
|
40
|
+
});
|
|
41
|
+
beforeToolCallback({ tool }: {
|
|
42
|
+
tool: BaseTool;
|
|
43
|
+
toolArgs: Record<string, unknown>;
|
|
44
|
+
toolContext: unknown;
|
|
45
|
+
}): Promise<Record<string, unknown> | undefined>;
|
|
46
|
+
}
|
|
47
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { OpenEntityGate, openEntityVerified, evaluate, DEFAULT_BAR, CLAWGRC, type Bar, type Denial, type EidSource, type Severity } from "./gate.ts";
|
|
2
|
+
export { MoltbotDenLiveness, MoltbotDenError, type LivenessOptions } from "./liveness.ts";
|
|
3
|
+
export { mcpToolsets, MOLTBOTDEN_MCP, OPENENTITY_MCP } from "./mcp.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// src/gate.ts
|
|
2
|
+
import { BasePlugin } from "@google/adk";
|
|
3
|
+
import { trust as sdkTrust } from "@openentity/sdk";
|
|
4
|
+
import { Identity } from "@openentity/sdk/node";
|
|
5
|
+
var CLAWGRC = "did:web:api.clawgrc.com";
|
|
6
|
+
var SEVERITY = { low: 1, medium: 2, high: 3, critical: 4 };
|
|
7
|
+
var DEFAULT_BAR = { minTier: 2, requireSma: true, denyOnViolation: "high", tierIssuer: CLAWGRC };
|
|
8
|
+
var denial = (eid, required, reason, detail, observed = {}) => ({ error: "open_entity_denied", denied: true, reason, detail, eid, required, observed });
|
|
9
|
+
async function evaluate(eid, bar = {}, trust = (e) => sdkTrust(e)) {
|
|
10
|
+
const b = { ...DEFAULT_BAR, ...bar };
|
|
11
|
+
const deny = (reason, detail, observed) => denial(eid, b, reason, detail, observed);
|
|
12
|
+
try {
|
|
13
|
+
const r = await trust(eid);
|
|
14
|
+
if (!r.ok) return deny("trust_unavailable", `Trust could not be established (${r.error}); denied.`);
|
|
15
|
+
if (r.withdrawn) return deny("withdrawn", "The entity withdrew its Declaration.");
|
|
16
|
+
const tier = r.trustTier[b.tierIssuer] ?? 0;
|
|
17
|
+
if (tier < b.minTier) return deny("tier_too_low", `trust_tier ${tier} from ${b.tierIssuer} is below ${b.minTier}.`, { trust_tier: tier });
|
|
18
|
+
if (b.requireSma && !r.securityPosture) return deny("sma_posture_missing", "No live Secure My Agent security_posture attestation.");
|
|
19
|
+
if (b.denyOnViolation) {
|
|
20
|
+
const bar2 = SEVERITY[b.denyOnViolation];
|
|
21
|
+
const hits = r.violations.filter((v) => (SEVERITY[v.severity] ?? bar2) >= bar2);
|
|
22
|
+
if (hits.length)
|
|
23
|
+
return deny(
|
|
24
|
+
"violation",
|
|
25
|
+
`${hits.length} live ${hits[0].issuer} violation(s) at ${b.denyOnViolation} or worse.`,
|
|
26
|
+
{ violations: hits.map((v) => ({ severity: v.severity, policy: v.policy, sha256: v.sha256 })) }
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return deny("trust_unavailable", `${e?.name ?? "Error"}: ${e?.message}; denied.`);
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
async function check(eid, bar, trust) {
|
|
35
|
+
let who;
|
|
36
|
+
try {
|
|
37
|
+
who = typeof eid === "string" ? eid : eid ? await eid() : (await Identity.loadOrCreate()).did;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return denial(null, {}, "trust_unavailable", `${e?.name ?? "Error"}: ${e?.message}; denied.`);
|
|
40
|
+
}
|
|
41
|
+
return evaluate(who, bar, trust);
|
|
42
|
+
}
|
|
43
|
+
function openEntityVerified(fn, opts = {}) {
|
|
44
|
+
const { eid, trust, ...bar } = opts;
|
|
45
|
+
return async (...args) => await check(eid, bar, trust) ?? fn(...args);
|
|
46
|
+
}
|
|
47
|
+
var OpenEntityGate = class extends BasePlugin {
|
|
48
|
+
highRisk;
|
|
49
|
+
opts;
|
|
50
|
+
constructor(highRiskTools, opts = {}) {
|
|
51
|
+
super("openentity_gate");
|
|
52
|
+
this.highRisk = new Set(highRiskTools);
|
|
53
|
+
this.opts = opts;
|
|
54
|
+
}
|
|
55
|
+
async beforeToolCallback({ tool }) {
|
|
56
|
+
if (!this.highRisk.has(tool.name)) return void 0;
|
|
57
|
+
const { eid, trust, ...bar } = this.opts;
|
|
58
|
+
return await check(eid, bar, trust);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/liveness.ts
|
|
63
|
+
import { BasePlugin as BasePlugin2 } from "@google/adk";
|
|
64
|
+
import { adopt as sdkAdopt, buildPresentation } from "@openentity/sdk";
|
|
65
|
+
import { Identity as Identity2 } from "@openentity/sdk/node";
|
|
66
|
+
import { createHash } from "node:crypto";
|
|
67
|
+
|
|
68
|
+
// src/keystore.ts
|
|
69
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
70
|
+
import { identityFile } from "@openentity/sdk/node";
|
|
71
|
+
import { dirname, join } from "node:path";
|
|
72
|
+
var path = () => join(dirname(identityFile()), "moltbotden.json");
|
|
73
|
+
function loadStore() {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(readFileSync(path(), "utf8"));
|
|
76
|
+
} catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function saveStore(fields) {
|
|
81
|
+
const data = { ...loadStore(), ...fields };
|
|
82
|
+
mkdirSync(dirname(path()), { recursive: true, mode: 448 });
|
|
83
|
+
writeFileSync(path(), JSON.stringify(data), { mode: 384 });
|
|
84
|
+
chmodSync(path(), 384);
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
var apiKey = () => process.env.MOLTBOTDEN_API_KEY || loadStore().api_key;
|
|
88
|
+
|
|
89
|
+
// src/liveness.ts
|
|
90
|
+
var AUDIENCE = "did:web:moltbotden.com";
|
|
91
|
+
var MoltbotDenError = class extends Error {
|
|
92
|
+
};
|
|
93
|
+
var realSleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
94
|
+
if (signal.aborted) return reject(signal.reason);
|
|
95
|
+
const t = setTimeout(resolve, ms);
|
|
96
|
+
t.unref();
|
|
97
|
+
signal.addEventListener("abort", () => (clearTimeout(t), reject(signal.reason)), { once: true });
|
|
98
|
+
});
|
|
99
|
+
var MoltbotDenLiveness = class extends BasePlugin2 {
|
|
100
|
+
lease = null;
|
|
101
|
+
mode = null;
|
|
102
|
+
o;
|
|
103
|
+
abort = null;
|
|
104
|
+
loop = null;
|
|
105
|
+
id;
|
|
106
|
+
sent = [];
|
|
107
|
+
constructor(o = {}) {
|
|
108
|
+
super("openentity_liveness");
|
|
109
|
+
this.o = { apiBase: "https://api.moltbotden.com", registry: "https://openentity.org", renewMs: 6 * 36e5, backoffBaseMs: 3e4, backoffMaxMs: 18e5, ...o };
|
|
110
|
+
this.id = o.identity;
|
|
111
|
+
}
|
|
112
|
+
get f() {
|
|
113
|
+
return this.o.fetch ?? fetch;
|
|
114
|
+
}
|
|
115
|
+
warn(m) {
|
|
116
|
+
(this.o.log ?? ((x) => console.warn(`openentity liveness: ${x}`)))(m);
|
|
117
|
+
}
|
|
118
|
+
async beforeRunCallback({ invocationContext }) {
|
|
119
|
+
if (!this.loop) {
|
|
120
|
+
this.o.appName ??= invocationContext?.appName;
|
|
121
|
+
this.abort = new AbortController();
|
|
122
|
+
this.loop = this.run(this.abort.signal);
|
|
123
|
+
}
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
async close() {
|
|
127
|
+
this.abort?.abort(new Error("closed"));
|
|
128
|
+
await this.loop?.catch(() => {
|
|
129
|
+
});
|
|
130
|
+
this.abort = this.loop = null;
|
|
131
|
+
}
|
|
132
|
+
async afterToolCallback({ tool }) {
|
|
133
|
+
await this.quality(tool.name, 1, "tool returned");
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
async onToolErrorCallback({ tool, error }) {
|
|
137
|
+
await this.quality(tool.name, 0, `tool raised ${error?.name}`);
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
async run(signal) {
|
|
141
|
+
const sleep = this.o.sleep ?? realSleep;
|
|
142
|
+
let failures = 0, delay = 0;
|
|
143
|
+
while (!signal.aborted) {
|
|
144
|
+
try {
|
|
145
|
+
await this.step();
|
|
146
|
+
failures = 0;
|
|
147
|
+
delay = this.o.renewMs;
|
|
148
|
+
} catch (e) {
|
|
149
|
+
failures++;
|
|
150
|
+
delay = Math.min(this.o.backoffBaseMs * 2 ** (failures - 1), this.o.backoffMaxMs);
|
|
151
|
+
this.warn(`${e?.name}: ${e?.message}; retrying in ${delay / 1e3}s`);
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
if (signal.aborted) return;
|
|
155
|
+
await sleep(delay, signal);
|
|
156
|
+
} catch {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** One pass: identity, adoption, Moltbot Den key, EID binding, then a renewal. */
|
|
162
|
+
async step() {
|
|
163
|
+
this.id ??= await Identity2.loadOrCreate();
|
|
164
|
+
await this.adopt();
|
|
165
|
+
const key = await this.ensureKey();
|
|
166
|
+
if (!key) throw new MoltbotDenError("no Moltbot Den API key: set MOLTBOTDEN_API_KEY, pass inviteCode or challengeSolver");
|
|
167
|
+
await this.bind(key);
|
|
168
|
+
await this.renew(key);
|
|
169
|
+
}
|
|
170
|
+
async adopt() {
|
|
171
|
+
const r = await this.f(`${this.o.registry}/v1/entities/${this.id.did}`);
|
|
172
|
+
if (r.ok) return;
|
|
173
|
+
if (r.status !== 404) throw new MoltbotDenError(`registry ${r.status}`);
|
|
174
|
+
const name = this.o.displayName ?? this.o.appName ?? "adk-agent";
|
|
175
|
+
await (this.o.adopt ?? sdkAdopt)(this.id.kp, { name, description: `A Google ADK agent (${name}).`, registry: this.o.registry, client: "adk" });
|
|
176
|
+
}
|
|
177
|
+
call(path2, key, body = {}) {
|
|
178
|
+
return this.f(this.o.apiBase + path2, { method: "POST", headers: { "content-type": "application/json", ...key ? { "x-api-key": key } : {} }, body: JSON.stringify(body) });
|
|
179
|
+
}
|
|
180
|
+
async ensureKey() {
|
|
181
|
+
const have = apiKey();
|
|
182
|
+
if (have) return have;
|
|
183
|
+
const app = this.o.displayName ?? this.o.appName ?? "adk-agent";
|
|
184
|
+
const slug = app.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30) || "adk-agent";
|
|
185
|
+
const agentId = this.o.agentId ?? loadStore().agent_id ?? `${slug}-${createHash("sha256").update(this.id.did).digest("hex").slice(0, 6)}`;
|
|
186
|
+
const body = { agent_id: agentId, profile: {
|
|
187
|
+
display_name: app.slice(0, 50),
|
|
188
|
+
tagline: "Google ADK agent with an Open Entity identity",
|
|
189
|
+
description: `A Google ADK agent (${app}) that proves liveness with its own Open Entity key.`
|
|
190
|
+
} };
|
|
191
|
+
if (this.o.inviteCode) body.invite_code = this.o.inviteCode;
|
|
192
|
+
let r = await this.call("/agents/register", void 0, body);
|
|
193
|
+
if (r.status === 202) {
|
|
194
|
+
if (!this.o.challengeSolver) {
|
|
195
|
+
this.warn("Moltbot Den asks a registration question; pass challengeSolver, an inviteCode, or set MOLTBOTDEN_API_KEY");
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
const c = await r.json();
|
|
199
|
+
r = await this.call("/agents/register/verify", void 0, { challenge_id: c.challenge_id, challenge_response: await this.o.challengeSolver(c.challenge) });
|
|
200
|
+
}
|
|
201
|
+
if (r.status !== 201) throw new MoltbotDenError(`register: ${r.status} ${(await r.text()).slice(0, 200)}`);
|
|
202
|
+
const key = (await r.json()).api_key;
|
|
203
|
+
saveStore({ agent_id: agentId, api_key: key });
|
|
204
|
+
return key;
|
|
205
|
+
}
|
|
206
|
+
async bind(key) {
|
|
207
|
+
if (loadStore().bound_eid === this.id.did) return;
|
|
208
|
+
const c = await this.call("/entity/openentity/bind/challenge", key);
|
|
209
|
+
if (!c.ok) throw new MoltbotDenError(`bind challenge: ${c.status}`);
|
|
210
|
+
const p = await buildPresentation(this.id.kp, { audience: AUDIENCE, nonce: (await c.json()).nonce });
|
|
211
|
+
const v = await this.call("/entity/openentity/bind/verify", key, p);
|
|
212
|
+
if (!v.ok) throw new MoltbotDenError(`bind: ${v.status} ${(await v.text()).slice(0, 200)}`);
|
|
213
|
+
saveStore({ bound_eid: this.id.did });
|
|
214
|
+
}
|
|
215
|
+
async renew(key) {
|
|
216
|
+
let c = await this.call("/heartbeat/challenge", key);
|
|
217
|
+
if (c.ok) {
|
|
218
|
+
const p = await buildPresentation(this.id.kp, { audience: AUDIENCE, nonce: (await c.json()).nonce });
|
|
219
|
+
const r2 = await this.call("/heartbeat/sovereign", key, p);
|
|
220
|
+
if (r2.ok) {
|
|
221
|
+
this.lease = await r2.json();
|
|
222
|
+
this.mode = "sovereign";
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
c = r2;
|
|
226
|
+
}
|
|
227
|
+
if (c.status === 429 || c.status >= 500 && c.status !== 503) throw new MoltbotDenError(`sovereign: ${c.status}`);
|
|
228
|
+
this.warn(`sovereign refused (${c.status}); using the API-key heartbeat`);
|
|
229
|
+
const r = await this.call("/heartbeat", key);
|
|
230
|
+
if (!r.ok) throw new MoltbotDenError(`heartbeat: ${r.status}`);
|
|
231
|
+
this.lease = await r.json();
|
|
232
|
+
this.mode = "heartbeat";
|
|
233
|
+
}
|
|
234
|
+
/** Optional (reportQuality: true): at most 25 an hour (the API allows 30). */
|
|
235
|
+
async quality(tool, score, what) {
|
|
236
|
+
const key = apiKey();
|
|
237
|
+
if (!this.o.reportQuality || !key) return;
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
this.sent = this.sent.filter((t) => now - t < 36e5);
|
|
240
|
+
if (this.sent.length >= 25) return;
|
|
241
|
+
this.sent.push(now);
|
|
242
|
+
await this.call("/entity/quality-event", key, { event_type: "tool_outcome", quality_score: score, context: `${tool}: ${what}`, domain: "adk" }).catch(() => {
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/mcp.ts
|
|
248
|
+
import { MCPToolset } from "@google/adk";
|
|
249
|
+
var MOLTBOTDEN_MCP = "https://api.moltbotden.com/mcp";
|
|
250
|
+
var OPENENTITY_MCP = "https://mcp.openentity.org/mcp";
|
|
251
|
+
var toolset = (url, filter, prefix, fetchWith) => new MCPToolset({
|
|
252
|
+
type: "StreamableHTTPConnectionParams",
|
|
253
|
+
url,
|
|
254
|
+
timeout: 1e4,
|
|
255
|
+
sseReadTimeout: 3e5,
|
|
256
|
+
...fetchWith ? { transportOptions: { fetch: (u, init) => fetch(u, fetchWith(init ?? {})) } } : {}
|
|
257
|
+
}, filter, prefix);
|
|
258
|
+
var withHeader = (name, value) => (init) => {
|
|
259
|
+
const v = value(), h = new Headers(init.headers);
|
|
260
|
+
if (v) h.set(name, v);
|
|
261
|
+
return { ...init, headers: h };
|
|
262
|
+
};
|
|
263
|
+
function mcpToolsets(o = {}) {
|
|
264
|
+
const out = [];
|
|
265
|
+
if (o.moltbotden ?? true) out.push(toolset(MOLTBOTDEN_MCP, o.toolFilter, void 0, withHeader("X-API-Key", apiKey)));
|
|
266
|
+
if (o.openentity ?? true) out.push(toolset(OPENENTITY_MCP, o.toolFilter));
|
|
267
|
+
for (const [urlVar, keyVar, prefix] of [["AGENT_SECRET_STORE_MCP_URL", "AGENT_SECRET_STORE_API_KEY", "ass"], ["CLAWGRC_MCP_URL", "CLAWGRC_API_KEY", "clawgrc"]]) {
|
|
268
|
+
const url = process.env[urlVar];
|
|
269
|
+
if (url) out.push(toolset(url, o.toolFilter, prefix, withHeader("Authorization", () => process.env[keyVar] && `Bearer ${process.env[keyVar]}`)));
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
export {
|
|
274
|
+
CLAWGRC,
|
|
275
|
+
DEFAULT_BAR,
|
|
276
|
+
MOLTBOTDEN_MCP,
|
|
277
|
+
MoltbotDenError,
|
|
278
|
+
MoltbotDenLiveness,
|
|
279
|
+
OPENENTITY_MCP,
|
|
280
|
+
OpenEntityGate,
|
|
281
|
+
evaluate,
|
|
282
|
+
mcpToolsets,
|
|
283
|
+
openEntityVerified
|
|
284
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { BasePlugin, type BaseTool } from "@google/adk";
|
|
2
|
+
import { buildPresentation } from "@openentity/sdk";
|
|
3
|
+
export declare class MoltbotDenError extends Error {
|
|
4
|
+
}
|
|
5
|
+
type Id = {
|
|
6
|
+
did: string;
|
|
7
|
+
kp: Parameters<typeof buildPresentation>[0];
|
|
8
|
+
};
|
|
9
|
+
export interface LivenessOptions {
|
|
10
|
+
agentId?: string;
|
|
11
|
+
displayName?: string;
|
|
12
|
+
/** used for the display name and agent id when none is given */
|
|
13
|
+
appName?: string;
|
|
14
|
+
apiBase?: string;
|
|
15
|
+
registry?: string;
|
|
16
|
+
renewMs?: number;
|
|
17
|
+
backoffBaseMs?: number;
|
|
18
|
+
backoffMaxMs?: number;
|
|
19
|
+
inviteCode?: string;
|
|
20
|
+
/** answers Moltbot Den's registration question with a model; without it (and without an invite code or MOLTBOTDEN_API_KEY) registration is skipped and logged */
|
|
21
|
+
challengeSolver?: (question: string) => string | Promise<string>;
|
|
22
|
+
/** off by default: POST /entity/quality-event after each tool */
|
|
23
|
+
reportQuality?: boolean;
|
|
24
|
+
identity?: Id;
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
adopt?: (kp: Id["kp"], o: {
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
registry: string;
|
|
30
|
+
client: "adk";
|
|
31
|
+
}) => Promise<unknown>;
|
|
32
|
+
/** resolves after `ms`, or rejects if aborted */
|
|
33
|
+
sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
34
|
+
log?: (msg: string) => void;
|
|
35
|
+
}
|
|
36
|
+
/** Pass in `new InMemoryRunner({agent, appName, plugins: [...]})`. The first run starts a background loop.
|
|
37
|
+
* ADK for TypeScript has no plugin close hook: call `await plugin.close()` at shutdown to cancel it (it never holds the process open). */
|
|
38
|
+
export declare class MoltbotDenLiveness extends BasePlugin {
|
|
39
|
+
lease: any;
|
|
40
|
+
mode: "sovereign" | "heartbeat" | null;
|
|
41
|
+
private o;
|
|
42
|
+
private abort;
|
|
43
|
+
private loop;
|
|
44
|
+
private id?;
|
|
45
|
+
private sent;
|
|
46
|
+
constructor(o?: LivenessOptions);
|
|
47
|
+
private get f();
|
|
48
|
+
private warn;
|
|
49
|
+
beforeRunCallback({ invocationContext }: {
|
|
50
|
+
invocationContext: any;
|
|
51
|
+
}): Promise<undefined>;
|
|
52
|
+
close(): Promise<void>;
|
|
53
|
+
afterToolCallback({ tool }: {
|
|
54
|
+
tool: BaseTool;
|
|
55
|
+
toolArgs: unknown;
|
|
56
|
+
toolContext: unknown;
|
|
57
|
+
result: unknown;
|
|
58
|
+
}): Promise<undefined>;
|
|
59
|
+
onToolErrorCallback({ tool, error }: {
|
|
60
|
+
tool: BaseTool;
|
|
61
|
+
toolArgs: unknown;
|
|
62
|
+
toolContext: unknown;
|
|
63
|
+
error: Error;
|
|
64
|
+
}): Promise<undefined>;
|
|
65
|
+
private run;
|
|
66
|
+
/** One pass: identity, adoption, Moltbot Den key, EID binding, then a renewal. */
|
|
67
|
+
step(): Promise<void>;
|
|
68
|
+
private adopt;
|
|
69
|
+
private call;
|
|
70
|
+
private ensureKey;
|
|
71
|
+
private bind;
|
|
72
|
+
private renew;
|
|
73
|
+
/** Optional (reportQuality: true): at most 25 an hour (the API allows 30). */
|
|
74
|
+
private quality;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MCPToolset, type ToolPredicate } from "@google/adk";
|
|
2
|
+
export declare const MOLTBOTDEN_MCP = "https://api.moltbotden.com/mcp";
|
|
3
|
+
export declare const OPENENTITY_MCP = "https://mcp.openentity.org/mcp";
|
|
4
|
+
/** Moltbot Den (X-API-Key from MOLTBOTDEN_API_KEY or the key MoltbotDenLiveness stored, read on every request), Open Entity (public) and,
|
|
5
|
+
* when set in the environment, the Agent Secret Store (AGENT_SECRET_STORE_MCP_URL, AGENT_SECRET_STORE_API_KEY) and
|
|
6
|
+
* ClawGRC (CLAWGRC_MCP_URL, CLAWGRC_API_KEY). `toolFilter` applies to every toolset. */
|
|
7
|
+
export declare function mcpToolsets(o?: {
|
|
8
|
+
moltbotden?: boolean;
|
|
9
|
+
openentity?: boolean;
|
|
10
|
+
toolFilter?: ToolPredicate | string[];
|
|
11
|
+
}): MCPToolset[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openentity/adk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Open Entity for Google ADK (TypeScript): sovereign liveness on Moltbot Den, a trust gate for high-risk tools, and MCP toolset presets.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"@google/adk": ">=2.3.0",
|
|
15
|
+
"@openentity/sdk": ">=0.4.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@google/adk": "^2.3.0",
|
|
19
|
+
"@types/node": "^26.6.5",
|
|
20
|
+
"esbuild": "^0.28.2",
|
|
21
|
+
"typescript": "^7.0.2",
|
|
22
|
+
"vitest": "^5.0.5",
|
|
23
|
+
"zod": "^4.0.0",
|
|
24
|
+
"@openentity/sdk": "0.4.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/moltbot-den/openentity.git",
|
|
33
|
+
"directory": "integrations/adk/js"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://openentity.org",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=22.13"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "esbuild src/index.ts --bundle --packages=external --format=esm --platform=node --target=es2022 --outfile=dist/index.js && tsc -p tsconfig.build.json",
|
|
44
|
+
"lint": "tsc --noEmit",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run"
|
|
47
|
+
}
|
|
6
48
|
}
|