nanocodex 0.6.6 → 0.6.7
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/README.md +297 -6
- package/THIRD-PARTY-LICENSES +23 -0
- package/browser/Agent.d.mts +5 -4
- package/browser/Agent.mjs +4 -0
- package/browser/Claude.d.mts +4 -0
- package/browser/Claude.mjs +11 -0
- package/browser/InlineAgent.mjs +19 -1
- package/browser/host.d.mts +54 -1
- package/browser/host.mjs +65 -16
- package/browser/hostManagedWebSocket.mjs +11 -2
- package/browser/index.d.mts +3 -10
- package/browser/index.mjs +2 -5
- package/browser/socket-observations.mjs +203 -0
- package/cloud/Decorator.d.mts +2 -2
- package/cloud/Decorator.mjs +1 -0
- package/cloud/Dialog.d.mts +13 -0
- package/cloud/Dialog.mjs +52 -6
- package/cloud/Transport.d.mts +0 -11
- package/cloud/Transport.mjs +1 -386
- package/cloud/actions/account.d.mts +12 -0
- package/cloud/actions/account.mjs +11 -0
- package/cloud/actions/agent.mjs +3 -0
- package/cloud/actions/connection.d.mts +3 -1
- package/cloud/actions/connection.mjs +19 -4
- package/cloud/index.d.mts +1 -1
- package/cloud/index.mjs +1 -1
- package/cloud/internal.mjs +19 -4
- package/cloud/mercator.mjs +1 -1
- package/cloud/mock.d.mts +17 -0
- package/cloud/mock.mjs +392 -0
- package/cloud/types.d.mts +3 -1
- package/cloudflare/Agent.d.mts +2 -0
- package/cloudflare/Agent.mjs +88 -2
- package/cloudflare/agent-settings.d.mts +1 -1
- package/cloudflare/agent-settings.mjs +9 -0
- package/cloudflare/egress.mjs +9 -6
- package/cloudflare/gateway-responses.d.mts +1 -0
- package/cloudflare/gateway-responses.mjs +2 -2
- package/cloudflare/managed-auth.d.mts +4 -4
- package/cloudflare/managed-auth.mjs +14 -2
- package/cloudflare/managed-live.d.mts +6 -0
- package/cloudflare/managed-live.mjs +56 -1
- package/cloudflare/workers-ai-responses.d.mts +2 -1
- package/cloudflare/workers-ai-responses.mjs +80 -6
- package/host/Agent.d.mts +6 -2
- package/host/Claude.d.mts +3 -0
- package/host/Claude.mjs +2 -0
- package/host/index.d.mts +6 -10
- package/host/index.mjs +2 -5
- package/index.d.mts +11 -10
- package/index.mjs +0 -5
- package/internal.mjs +28 -3
- package/managed/Agent.d.mts +6 -2
- package/managed/Agent.mjs +39 -5
- package/managed/ManagedError.d.mts +3 -0
- package/node/Agent.d.mts +6 -2
- package/node/Agent.mjs +14 -1
- package/node/Claude.d.mts +4 -0
- package/node/Claude.mjs +24 -0
- package/node/host.mjs +18 -9
- package/node/index.d.mts +6 -10
- package/node/index.mjs +2 -5
- package/package.json +41 -9
- package/pkg-node/nanocodex.d.ts +109 -0
- package/pkg-node/nanocodex.js +818 -51
- package/pkg-node/nanocodex_bg.wasm.d.ts +47 -3
- package/pkg-web/.nanocodex-bindgen-stamp +3 -3
- package/pkg-web/nanocodex-build.json +1 -1
- package/pkg-web/nanocodex.d.ts +156 -3
- package/pkg-web/nanocodex.js +813 -51
- package/pkg-web/nanocodex_bg.js +813 -51
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +47 -3
- package/pkg-web/nanocodex_worker.js +1 -1
- package/runtime/claude-host.mjs +308 -0
- package/runtime/claude-subscription.mjs +79 -0
- package/runtime/claude.d.mts +112 -0
- package/runtime/claude.mjs +221 -0
- package/runtime/code-effect-identity.mjs +123 -0
- package/runtime/harnesses.mjs +51 -0
- package/runtime/mcp-payment.mjs +8 -0
- package/runtime/mcp-runtime.d.mts +10 -0
- package/runtime/mcp-runtime.mjs +242 -57
- package/runtime/mercator.mjs +2 -0
- package/runtime/quickjs-evaluator.mjs +23 -11
- package/runtime/response-controls.mjs +1 -6
- package/runtime/responses-http.mjs +4 -2
- package/runtime/retry-after.d.mts +10 -0
- package/runtime/retry-after.mjs +86 -0
- package/runtime/steer-receipt.mjs +8 -1
- package/runtime/subagent-routing.d.mts +4 -2
- package/runtime/subagent-routing.mjs +7 -2
- package/runtime/subagents.d.mts +8 -3
- package/services/account.d.mts +76 -0
- package/services/account.mjs +151 -0
- package/services/hosted.mjs +72 -0
- package/services/index.d.mts +95 -0
- package/services/index.mjs +192 -0
- package/services/scope.mjs +36 -0
- package/{runtime/tempo-provider.d.mts → tempo/index.d.mts} +20 -1
- package/{runtime/tempo-provider.mjs → tempo/index.mjs} +39 -3
- package/tools/browser/browserShell.mjs +18 -8
- package/tools/browser/python.worker.mjs +17 -1
- package/tools/browser/threadGit.mjs +12 -2
- package/tools/environment.d.mts +8 -0
- package/tools/environment.mjs +10 -0
- package/tools/index.d.mts +6 -3
- package/types.d.mts +104 -4
- package/worker/Claude.d.mts +4 -0
- package/worker/Claude.mjs +11 -0
- package/worker/ClaudeSubscription.d.mts +24 -0
- package/worker/ClaudeSubscription.mjs +9 -0
- package/worker/index.d.mts +9 -0
- package/worker/index.mjs +4 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeServices, serviceResource } from '../../services/scope.mjs';
|
|
1
2
|
import {
|
|
2
3
|
connectionRequestFromGrant,
|
|
3
4
|
connectionFromWire,
|
|
@@ -57,11 +58,14 @@ const AGENT_VISIBILITY_NAMES = Object.freeze({
|
|
|
57
58
|
|
|
58
59
|
export async function connect(client, options) {
|
|
59
60
|
options ??= {};
|
|
60
|
-
const
|
|
61
|
+
const services = normalizeServices(options.capabilities?.services);
|
|
62
|
+
const permission = options.permission ?? (services ? "services.use" : "agent.run");
|
|
63
|
+
const standalone = permission === "services.use";
|
|
64
|
+
if (standalone && !services) throw new TypeError("services.use requires exact service capabilities");
|
|
61
65
|
if (typeof permission !== "string" || permission.length === 0) throw new TypeError("connect permission must be a non-empty string");
|
|
62
66
|
const requestedConnectors = normalizeCloudAccounts(options.capabilities?.cloudAccounts);
|
|
63
|
-
const agentVisibility = normalizeAgentVisibility(options.capabilities?.agent);
|
|
64
|
-
const authorization = options.authorization ?? (client.principal ? "hosted" : "access_key");
|
|
67
|
+
const agentVisibility = normalizeAgentVisibility(options.capabilities?.agent ?? (standalone ? { finalMessages: false, actionSummaries: false } : undefined));
|
|
68
|
+
const authorization = options.authorization ?? (client.principal || standalone ? "hosted" : "access_key");
|
|
65
69
|
if (authorization !== "access_key" && authorization !== "hosted") {
|
|
66
70
|
throw new TypeError("connect authorization must be access_key or hosted");
|
|
67
71
|
}
|
|
@@ -71,6 +75,9 @@ export async function connect(client, options) {
|
|
|
71
75
|
if (client.principal && options.capabilities?.authorizeAccessKey !== undefined) {
|
|
72
76
|
throw new TypeError("host principal connections cannot request access-key or MPP authority");
|
|
73
77
|
}
|
|
78
|
+
if (standalone && (authorization !== "hosted" || options.capabilities?.authorizeAccessKey || options.capabilities?.agent || requestedConnectors.includes("chatgpt") || options.tools?.length || options.mcpConnections?.length || options.conversationId)) {
|
|
79
|
+
throw new TypeError("Standalone services require hosted authorization without agent or payment capabilities");
|
|
80
|
+
}
|
|
74
81
|
const appToolCatalog = hostedAppToolCatalog(options.tools ?? []);
|
|
75
82
|
const appToolCatalogDigest = appToolCatalog.length === 0
|
|
76
83
|
? undefined
|
|
@@ -79,6 +86,7 @@ export async function connect(client, options) {
|
|
|
79
86
|
const mcpConnections = normalizeMcpConnections(options.mcpConnections ?? []);
|
|
80
87
|
const focusMcpConnectionId = normalizeMcpFocus(options.focusMcpConnectionId, mcpConnections);
|
|
81
88
|
const exactRequest = connectionRequestFromGrant({
|
|
89
|
+
services,
|
|
82
90
|
connectors: requestedConnectors,
|
|
83
91
|
mcpConnections,
|
|
84
92
|
permission,
|
|
@@ -98,6 +106,8 @@ export async function connect(client, options) {
|
|
|
98
106
|
conversationId,
|
|
99
107
|
authorization,
|
|
100
108
|
appToolCatalogDigest,
|
|
109
|
+
services,
|
|
110
|
+
standalone,
|
|
101
111
|
);
|
|
102
112
|
if (client.principal
|
|
103
113
|
&& baseAuth.resources.some((resource) => resource.startsWith(MPP_RESOURCE_PREFIX))) {
|
|
@@ -311,10 +321,14 @@ function withConnectionResources(
|
|
|
311
321
|
conversationId,
|
|
312
322
|
authorization,
|
|
313
323
|
appToolCatalogDigest,
|
|
324
|
+
services,
|
|
325
|
+
standalone,
|
|
314
326
|
) {
|
|
315
327
|
const configured = typeof auth === "object" && auth !== null
|
|
316
328
|
? (auth.resources ?? []).filter((resource) =>
|
|
317
|
-
!
|
|
329
|
+
!(standalone && (resource.startsWith("urn:nanocodex:agent:") || resource.startsWith(MPP_RESOURCE_PREFIX)))
|
|
330
|
+
&& !resource.startsWith("urn:nanocodex:services:")
|
|
331
|
+
&& !Object.values(AGENT_VISIBILITY_RESOURCES).includes(resource)
|
|
318
332
|
&& !resource.startsWith(AGENT_VISIBILITY_RESOURCE_PREFIX)
|
|
319
333
|
&& !resource.startsWith(AGENT_CONVERSATION_RESOURCE_PREFIX)
|
|
320
334
|
&& !resource.startsWith(CONNECTOR_RESOURCE_PREFIX)
|
|
@@ -332,6 +346,7 @@ function withConnectionResources(
|
|
|
332
346
|
.map(([, value]) => value);
|
|
333
347
|
const resources = [...new Set([
|
|
334
348
|
...configured,
|
|
349
|
+
...(services ? [serviceResource(services)] : []),
|
|
335
350
|
`${APP_RESOURCE_PREFIX}${encodeURIComponent(appId)}`,
|
|
336
351
|
...(appOrigin ? [`${APP_ORIGIN_RESOURCE_PREFIX}${encodeURIComponent(appOrigin)}`] : []),
|
|
337
352
|
...(requestedConnectors.length === 0
|
package/cloud/index.d.mts
CHANGED
|
@@ -6,7 +6,7 @@ export * as Principal from "./Principal.mjs";
|
|
|
6
6
|
export * as Transport from "./Transport.mjs";
|
|
7
7
|
export { connectActions } from "./Decorator.mjs";
|
|
8
8
|
export { iframe, popup } from "./Dialog.mjs";
|
|
9
|
-
export { http
|
|
9
|
+
export { http } from "./Transport.mjs";
|
|
10
10
|
export type {
|
|
11
11
|
AccessKey,
|
|
12
12
|
AgentTurn,
|
package/cloud/index.mjs
CHANGED
|
@@ -6,4 +6,4 @@ export * as Principal from "./Principal.mjs";
|
|
|
6
6
|
export * as Transport from "./Transport.mjs";
|
|
7
7
|
export { connectActions } from "./Decorator.mjs";
|
|
8
8
|
export { iframe, popup } from "./Dialog.mjs";
|
|
9
|
-
export { http
|
|
9
|
+
export { http } from "./Transport.mjs";
|
package/cloud/internal.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeServices } from '../services/scope.mjs';
|
|
1
2
|
import { InvalidResponseError } from "./Errors.mjs";
|
|
2
3
|
|
|
3
4
|
const CLOUD_ACCOUNT_PROVIDERS = Object.freeze([
|
|
@@ -50,6 +51,11 @@ export function connectionFromWire(value) {
|
|
|
50
51
|
throw new InvalidResponseError("access-key connections require access-key and MPP authority");
|
|
51
52
|
}
|
|
52
53
|
const capabilities = strings(grant.capabilities, "connection.grant.capabilities");
|
|
54
|
+
if (grant.permission === "services.use" && (wire.agent_id !== undefined || authorization !== "hosted"
|
|
55
|
+
|| grant.services === undefined || grant.conversation_id !== undefined || grant.app_tool_catalog_digest !== undefined
|
|
56
|
+
|| capabilities.some(value => value === "nanocodex.agent" || value === "chatgpt" || value.startsWith("agent.") || value.startsWith("mcp:")))) {
|
|
57
|
+
throw new InvalidResponseError("Standalone service grants cannot contain agent or payment authority");
|
|
58
|
+
}
|
|
53
59
|
const grantMcpConnections = mcpConnections(
|
|
54
60
|
grant.mcp_connections,
|
|
55
61
|
"connection.grant.mcp_connections",
|
|
@@ -67,7 +73,7 @@ export function connectionFromWire(value) {
|
|
|
67
73
|
);
|
|
68
74
|
return Object.freeze({
|
|
69
75
|
...owner,
|
|
70
|
-
agentId: string(wire.agent_id, "connection.agent_id"),
|
|
76
|
+
...(grant.permission === "services.use" && wire.agent_id === undefined ? {} : { agentId: string(wire.agent_id, "connection.agent_id") }),
|
|
71
77
|
grant: Object.freeze({
|
|
72
78
|
id: hex(grant.id, "connection.grant.id"),
|
|
73
79
|
permission: string(grant.permission, "connection.grant.permission"),
|
|
@@ -76,13 +82,14 @@ export function connectionFromWire(value) {
|
|
|
76
82
|
...(grant.conversation_id === undefined ? {} : {
|
|
77
83
|
conversationId: agentConversationId(grant.conversation_id, "connection.grant.conversation_id"),
|
|
78
84
|
}),
|
|
85
|
+
...(grant.services === undefined ? {} : { services: normalizeServices(grant.services) }),
|
|
79
86
|
capabilities,
|
|
80
87
|
connectors: grantConnectors,
|
|
81
88
|
...(grantConnectorConnections === undefined ? {} : {
|
|
82
89
|
connectorConnections: grantConnectorConnections,
|
|
83
90
|
}),
|
|
84
91
|
mcpConnections: grantMcpConnections,
|
|
85
|
-
visibility: agentVisibility(capabilities),
|
|
92
|
+
visibility: agentVisibility(capabilities, grant.permission === "services.use"),
|
|
86
93
|
...(grant.app_tool_catalog_digest === undefined ? {} : {
|
|
87
94
|
appToolCatalogDigest: catalogDigest(
|
|
88
95
|
grant.app_tool_catalog_digest,
|
|
@@ -135,6 +142,11 @@ export function connectionMatchesRequest(connection, options = {}) {
|
|
|
135
142
|
return false;
|
|
136
143
|
}
|
|
137
144
|
}
|
|
145
|
+
if (Object.hasOwn(options.capabilities ?? {}, "services")) {
|
|
146
|
+
try {
|
|
147
|
+
if (JSON.stringify(normalizeServices(options.capabilities.services)) !== JSON.stringify(normalizeServices(connection.grant.services))) return false;
|
|
148
|
+
} catch { return false; }
|
|
149
|
+
}
|
|
138
150
|
const requestedCloudAccounts = options.capabilities?.cloudAccounts;
|
|
139
151
|
if (requestedCloudAccounts !== undefined) {
|
|
140
152
|
const requested = CLOUD_ACCOUNT_PROVIDERS.filter(
|
|
@@ -190,6 +202,7 @@ export function reconnectRequestFromConnection(connection) {
|
|
|
190
202
|
export function connectionRequestFromGrant(grant) {
|
|
191
203
|
return Object.freeze({
|
|
192
204
|
capabilities: Object.freeze({
|
|
205
|
+
services: grant.services,
|
|
193
206
|
agent: grant.visibility,
|
|
194
207
|
cloudAccounts: Object.freeze(Object.fromEntries(
|
|
195
208
|
grant.connectors.map((provider) => [provider, true]),
|
|
@@ -297,13 +310,14 @@ export function grantFromWire(value) {
|
|
|
297
310
|
...(grant.conversation_id === undefined ? {} : {
|
|
298
311
|
conversationId: agentConversationId(grant.conversation_id, "grant.conversation_id"),
|
|
299
312
|
}),
|
|
313
|
+
...(grant.services === undefined ? {} : { services: normalizeServices(grant.services) }),
|
|
300
314
|
capabilities,
|
|
301
315
|
connectors: grantConnectors,
|
|
302
316
|
...(grantConnectorConnections === undefined ? {} : {
|
|
303
317
|
connectorConnections: grantConnectorConnections,
|
|
304
318
|
}),
|
|
305
319
|
mcpConnections: grantMcpConnections,
|
|
306
|
-
visibility: agentVisibility(capabilities),
|
|
320
|
+
visibility: agentVisibility(capabilities, grant.permission === "services.use"),
|
|
307
321
|
...(grant.app_tool_catalog_digest === undefined ? {} : {
|
|
308
322
|
appToolCatalogDigest: catalogDigest(grant.app_tool_catalog_digest, "grant.app_tool_catalog_digest"),
|
|
309
323
|
}),
|
|
@@ -329,7 +343,8 @@ const AGENT_VISIBILITY_CAPABILITIES = Object.freeze({
|
|
|
329
343
|
rawTraces: "agent.trace.read",
|
|
330
344
|
});
|
|
331
345
|
|
|
332
|
-
function agentVisibility(capabilities) {
|
|
346
|
+
function agentVisibility(capabilities, standalone = false) {
|
|
347
|
+
if (standalone) return Object.freeze({ finalMessages: false, actionSummaries: false, conversationHistory: false, rawTraces: false });
|
|
333
348
|
const recognized = Object.values(AGENT_VISIBILITY_CAPABILITIES)
|
|
334
349
|
.some((capability) => capabilities.includes(capability));
|
|
335
350
|
const rawTraces = capabilities.includes(AGENT_VISIBILITY_CAPABILITIES.rawTraces);
|
package/cloud/mercator.mjs
CHANGED
package/cloud/mock.d.mts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Transport } from "./Transport.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-memory Nanocodex Connect API for tests and demos. Requires the optional
|
|
5
|
+
* `ox` peer dependency, which computes the EIP-191 access-key witness.
|
|
6
|
+
*/
|
|
7
|
+
export const MOCK_ACCOUNT_ADDRESS: `0x${string}`;
|
|
8
|
+
export const MOCK_MACHINE_USD_ADDRESS: `0x${string}`;
|
|
9
|
+
|
|
10
|
+
export function mock(options?: Readonly<{
|
|
11
|
+
accountAddress?: `0x${string}` | undefined;
|
|
12
|
+
appName?: string | undefined;
|
|
13
|
+
appOrigin?: string | undefined;
|
|
14
|
+
key?: string | undefined;
|
|
15
|
+
machineUsdAddress?: `0x${string}` | undefined;
|
|
16
|
+
name?: string | undefined;
|
|
17
|
+
}>): Transport<"mock">;
|
package/cloud/mock.mjs
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
// In-memory Nanocodex Connect API for tests and demos. Its prepared access-key
|
|
2
|
+
// witness is the real EIP-191 digest, so this module needs the optional `ox`
|
|
3
|
+
// peer dependency; the core `nanocodex/connect` entry point never imports it.
|
|
4
|
+
import { Hex, PersonalMessage } from "ox";
|
|
5
|
+
|
|
6
|
+
import { HttpError, InvalidResponseError } from "./Errors.mjs";
|
|
7
|
+
import { from } from "./Transport.mjs";
|
|
8
|
+
|
|
9
|
+
export const MOCK_ACCOUNT_ADDRESS = "0x8ba1f109551bd432803012645ac136ddd64dba72";
|
|
10
|
+
export const MOCK_MACHINE_USD_ADDRESS = "0x20c0000000000000000000000000000000000001";
|
|
11
|
+
|
|
12
|
+
export function mock(options = {}) {
|
|
13
|
+
const accountAddress = options.accountAddress ?? MOCK_ACCOUNT_ADDRESS;
|
|
14
|
+
const machineUsdAddress = options.machineUsdAddress ?? MOCK_MACHINE_USD_ADDRESS;
|
|
15
|
+
const appName = options.appName ?? "Acme Workspace";
|
|
16
|
+
const grants = new Map();
|
|
17
|
+
let sequence = 0;
|
|
18
|
+
|
|
19
|
+
return from({
|
|
20
|
+
key: options.key ?? "mock",
|
|
21
|
+
name: options.name ?? "Nanocodex mock",
|
|
22
|
+
type: "mock",
|
|
23
|
+
setup({ appId }) {
|
|
24
|
+
return {
|
|
25
|
+
baseUrl: "memory://nanocodex",
|
|
26
|
+
async request(request) {
|
|
27
|
+
if (request.method === "POST" && request.path === "/v1/connections") {
|
|
28
|
+
const authorization = request.body?.key_authorization;
|
|
29
|
+
const keyId = authorization?.keyId ?? authorization?.address;
|
|
30
|
+
if (typeof keyId !== "string") throw new InvalidResponseError("key authorization is missing keyId");
|
|
31
|
+
const expiry = authorization.expiry ?? Math.floor(Date.now() / 1000) + 30 * 86_400;
|
|
32
|
+
const connectors = requestedConnectors(request.body?.requested_connectors);
|
|
33
|
+
const mcpConnections = requestedMcpConnections(request.body?.requested_mcp_connections);
|
|
34
|
+
const grantId = mockHex(`${appId}:${accountAddress}:${keyId}`, 32);
|
|
35
|
+
const connectorConnections = mockConnectorConnections(connectors, grantId);
|
|
36
|
+
const wire = {
|
|
37
|
+
grant_token: `mock-grant-${grantId.slice(2)}`,
|
|
38
|
+
account_address: accountAddress,
|
|
39
|
+
agent_id: `agent_${grantId.slice(2, 14)}`,
|
|
40
|
+
grant: {
|
|
41
|
+
id: grantId,
|
|
42
|
+
permission: request.body?.permission ?? "agent.run",
|
|
43
|
+
status: "active",
|
|
44
|
+
expires_at: expiry,
|
|
45
|
+
capabilities: [
|
|
46
|
+
"nanocodex.agent",
|
|
47
|
+
"mercator.boost",
|
|
48
|
+
"mpp.mach",
|
|
49
|
+
...connectors,
|
|
50
|
+
...mcpConnections.map(({ id }) => `mcp:${id}`),
|
|
51
|
+
],
|
|
52
|
+
connector_connections: connectorConnections,
|
|
53
|
+
mcp_connections: mcpConnections,
|
|
54
|
+
},
|
|
55
|
+
access_key: {
|
|
56
|
+
address: keyId,
|
|
57
|
+
chain_id: String(authorization.chainId ?? 4217),
|
|
58
|
+
key_id: keyId,
|
|
59
|
+
key_type: authorization.keyType ?? "p256",
|
|
60
|
+
limits: authorization.limits ?? [{ token: machineUsdAddress, limit: "10000000", period: 86_400 }],
|
|
61
|
+
scopes: authorization.scopes ?? [],
|
|
62
|
+
witness: authorization.witness ?? mockHex(`${grantId}:witness`, 32),
|
|
63
|
+
expiry,
|
|
64
|
+
authorization: request.body?.signed_key_authorization ?? "0x01",
|
|
65
|
+
},
|
|
66
|
+
mpp: {
|
|
67
|
+
token: machineUsdAddress,
|
|
68
|
+
symbol: "MACH",
|
|
69
|
+
balance_status: "ready",
|
|
70
|
+
settlement_token: "0x20C000000000000000000000b9537d11c60E8b50",
|
|
71
|
+
settlement_symbol: "USDC.e",
|
|
72
|
+
settlement_balance_atomics: "0",
|
|
73
|
+
limit_atomics: "10000000",
|
|
74
|
+
max_per_request_atomics: "250000",
|
|
75
|
+
period: 86_400,
|
|
76
|
+
balance_atomics: "0",
|
|
77
|
+
spent_atomics: "0",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
grants.set(grantId, wire);
|
|
81
|
+
return wire;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (request.method === "GET" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+$/)) {
|
|
85
|
+
return requiredGrant(grants, request.path.split("/")[3]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (request.method === "POST" && request.path === "/v1/connections/prepare") {
|
|
89
|
+
const permission = request.body?.permission;
|
|
90
|
+
if (typeof permission !== "string" || permission.length === 0) {
|
|
91
|
+
throw new TypeError("connection permission must be a non-empty string");
|
|
92
|
+
}
|
|
93
|
+
const resources = request.body?.resources ?? [];
|
|
94
|
+
if (!Array.isArray(resources) || resources.some((resource) => typeof resource !== "string" || resource.length === 0)) {
|
|
95
|
+
throw new TypeError("connection resources must be non-empty strings");
|
|
96
|
+
}
|
|
97
|
+
const connectors = connectorResources(resources);
|
|
98
|
+
const id = `req_${++sequence}`;
|
|
99
|
+
const accessPolicy = request.body?.authorize_access_key;
|
|
100
|
+
const keyId = accessPolicy?.address ?? mockHex(`${appId}:${accountAddress}:${permission}:key`, 20);
|
|
101
|
+
const tokenLimit = accessPolicy?.limits?.find((limit) => limit.token === machineUsdAddress);
|
|
102
|
+
const expiry = accessPolicy?.expiry ?? Math.floor(Date.now() / 1000) + 30 * 86_400;
|
|
103
|
+
const dailyLimit = tokenLimit?.limit ?? "10000000";
|
|
104
|
+
const period = tokenLimit?.period ?? 86_400;
|
|
105
|
+
const appOrigin = options.appOrigin ?? "https://app.example";
|
|
106
|
+
const appUrl = new URL(appOrigin);
|
|
107
|
+
const nonce = mockHex(`${id}:siwe-nonce`, 8).slice(2);
|
|
108
|
+
const issuedAt = new Date().toISOString();
|
|
109
|
+
const authMessage = [
|
|
110
|
+
`${appUrl.host} wants you to sign in with your Ethereum account:`,
|
|
111
|
+
"0x0000000000000000000000000000000000000000",
|
|
112
|
+
"",
|
|
113
|
+
`${appName} wants to connect your Nanocodex agent.`,
|
|
114
|
+
"",
|
|
115
|
+
`URI: ${appUrl.origin}`,
|
|
116
|
+
"Version: 1",
|
|
117
|
+
"Chain ID: 4217",
|
|
118
|
+
`Nonce: ${nonce}`,
|
|
119
|
+
`Issued At: ${issuedAt}`,
|
|
120
|
+
...(resources.length > 0
|
|
121
|
+
? ["Resources:", ...resources.map((resource) => `- ${resource}`)]
|
|
122
|
+
: []),
|
|
123
|
+
].join("\n");
|
|
124
|
+
return {
|
|
125
|
+
request_id: id,
|
|
126
|
+
app: { id: appId, name: appName, origin: appOrigin },
|
|
127
|
+
account_address: accountAddress,
|
|
128
|
+
auth: {
|
|
129
|
+
message: authMessage,
|
|
130
|
+
resources,
|
|
131
|
+
},
|
|
132
|
+
permission: {
|
|
133
|
+
id: permission,
|
|
134
|
+
title: "Use your Nanocodex agent",
|
|
135
|
+
description: "Run an app-owned Nanocodex agent with your approved capabilities.",
|
|
136
|
+
connectors: [
|
|
137
|
+
{ id: "agent", name: "Nanocodex", detail: "Instantiate the real Nanocodex runtime in this app" },
|
|
138
|
+
{ id: "mercator", name: "BOOST with Mercator", detail: "Find and compose the right tools, paying per call through MPP" },
|
|
139
|
+
...connectors.map((provider) => ({
|
|
140
|
+
id: provider,
|
|
141
|
+
name: connectorName(provider),
|
|
142
|
+
detail: `Use the connected ${connectorName(provider)} account through the grant`,
|
|
143
|
+
})),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
access_key: {
|
|
147
|
+
address: keyId,
|
|
148
|
+
chain_id: accessPolicy?.chainId ?? "4217",
|
|
149
|
+
key_id: keyId,
|
|
150
|
+
public_key: accessPolicy?.publicKey ?? mockHex(`${id}:public`, 65),
|
|
151
|
+
key_type: accessPolicy?.keyType ?? "p256",
|
|
152
|
+
limits: accessPolicy?.limits ?? [{ token: machineUsdAddress, limit: dailyLimit, period }],
|
|
153
|
+
scopes: accessPolicy?.scopes ?? [],
|
|
154
|
+
witness: PersonalMessage.getSignPayload(Hex.fromString(authMessage)),
|
|
155
|
+
expiry,
|
|
156
|
+
},
|
|
157
|
+
mpp: {
|
|
158
|
+
token: machineUsdAddress,
|
|
159
|
+
symbol: "MACH",
|
|
160
|
+
settlement_token: "0x20C000000000000000000000b9537d11c60E8b50",
|
|
161
|
+
settlement_symbol: "USDC.e",
|
|
162
|
+
settlement_balance_atomics: "0",
|
|
163
|
+
limit_atomics: dailyLimit,
|
|
164
|
+
period,
|
|
165
|
+
max_per_request_atomics: "250000",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (request.method === "POST" && request.path === "/v1/connections/complete") {
|
|
171
|
+
const prepared = request.body?.prepared;
|
|
172
|
+
const approval = request.body?.approval;
|
|
173
|
+
if (!prepared || !approval || approval.approved !== true) {
|
|
174
|
+
throw new InvalidResponseError("connection approval is malformed");
|
|
175
|
+
}
|
|
176
|
+
const approvedCapabilities = approval.capabilities;
|
|
177
|
+
if (
|
|
178
|
+
approvedCapabilities?.keyAuthorization?.witness !== prepared.access_key.witness
|
|
179
|
+
|| approvedCapabilities?.personalSign?.message !== prepared.auth.message
|
|
180
|
+
|| typeof approvedCapabilities?.personalSign?.keyAuthorization !== "string"
|
|
181
|
+
|| typeof approvedCapabilities?.signature !== "string"
|
|
182
|
+
) {
|
|
183
|
+
throw new InvalidResponseError("connection approval is not bound to the SIWE challenge and key authorization");
|
|
184
|
+
}
|
|
185
|
+
const grantId = mockHex(`${prepared.app.id}:${prepared.account_address}:${prepared.access_key.key_id}`, 32);
|
|
186
|
+
const wire = {
|
|
187
|
+
grant_token: `mock-grant-${grantId.slice(2)}`,
|
|
188
|
+
account_address: prepared.account_address,
|
|
189
|
+
agent_id: `agent_${grantId.slice(2, 14)}`,
|
|
190
|
+
grant: {
|
|
191
|
+
id: grantId,
|
|
192
|
+
permission: prepared.permission.id,
|
|
193
|
+
status: "active",
|
|
194
|
+
expires_at: prepared.access_key.expiry,
|
|
195
|
+
capabilities: prepared.permission.connectors.map(({ id }) => id),
|
|
196
|
+
},
|
|
197
|
+
access_key: prepared.access_key,
|
|
198
|
+
mpp: {
|
|
199
|
+
...prepared.mpp,
|
|
200
|
+
balance_atomics: "0",
|
|
201
|
+
balance_status: "ready",
|
|
202
|
+
spent_atomics: "0",
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
grants.set(grantId, wire);
|
|
206
|
+
return wire;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (request.method === "POST" && request.path === "/v1/connections/disconnect") {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (request.method === "GET" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/mpp\/balance$/)) {
|
|
214
|
+
const wire = requiredGrant(grants, request.path.split("/")[3]);
|
|
215
|
+
wire.mpp = { ...wire.mpp, balance_status: "ready" };
|
|
216
|
+
return wire;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (request.method === "POST" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/revoke$/)) {
|
|
220
|
+
const grantId = request.path.split("/")[3];
|
|
221
|
+
const wire = requiredGrant(grants, grantId);
|
|
222
|
+
wire.grant = { ...wire.grant, status: "revoked" };
|
|
223
|
+
return wire.grant;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (request.method === "GET" && request.path === "/v1/machine-usd/config") {
|
|
227
|
+
return {
|
|
228
|
+
chain_id: 4217,
|
|
229
|
+
min_usd_amount_cents: 500,
|
|
230
|
+
max_usd_amount_cents: 10_000,
|
|
231
|
+
stripe_publishable_key: "pk_test_nanocodex",
|
|
232
|
+
token_address: machineUsdAddress,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (request.method === "POST" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/fund$/)) {
|
|
237
|
+
const grantId = request.path.split("/")[3];
|
|
238
|
+
const wire = requiredActiveGrant(grants, grantId);
|
|
239
|
+
const usdAmountCents = request.body?.usd_amount_cents;
|
|
240
|
+
if (!Number.isSafeInteger(usdAmountCents) || usdAmountCents < 500 || usdAmountCents > 10_000) {
|
|
241
|
+
throw new TypeError("MACH amount must be from 500 through 10000 cents");
|
|
242
|
+
}
|
|
243
|
+
const amount = BigInt(usdAmountCents) * 10_000n;
|
|
244
|
+
wire.mpp = {
|
|
245
|
+
...wire.mpp,
|
|
246
|
+
balance_atomics: String(BigInt(wire.mpp.balance_atomics) + amount),
|
|
247
|
+
};
|
|
248
|
+
return {
|
|
249
|
+
order: {
|
|
250
|
+
id: `ord_${++sequence}`,
|
|
251
|
+
status: "complete",
|
|
252
|
+
usd_amount_cents: usdAmountCents,
|
|
253
|
+
machine_usd_amount_atomics: String(amount),
|
|
254
|
+
issuance_transaction_hash: mockHex(`fund:${sequence}`, 32),
|
|
255
|
+
},
|
|
256
|
+
connection: wire,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (request.method === "POST" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/mpp\/charge$/)) {
|
|
261
|
+
const grantId = request.path.split("/")[3];
|
|
262
|
+
const wire = requiredActiveGrant(grants, grantId);
|
|
263
|
+
const amount = BigInt(request.body?.amount_atomics ?? "-1");
|
|
264
|
+
const max = BigInt(wire.mpp.max_per_request_atomics);
|
|
265
|
+
const spent = BigInt(wire.mpp.spent_atomics);
|
|
266
|
+
const limit = BigInt(wire.mpp.limit_atomics);
|
|
267
|
+
const balance = BigInt(wire.mpp.balance_atomics);
|
|
268
|
+
if (amount <= 0n) throw new TypeError("MPP amount must be positive");
|
|
269
|
+
if (amount > max) throw new HttpError(403, "This payment exceeds the per-request permission.", { code: "mpp_request_limit_exceeded" });
|
|
270
|
+
if (spent + amount > limit) throw new HttpError(403, "This payment exceeds the daily MPP permission.", { code: "mpp_period_limit_exceeded" });
|
|
271
|
+
if (amount > balance) throw new HttpError(402, "Add MACH before paying for this capability.", { code: "machine_usd_required" });
|
|
272
|
+
wire.mpp = {
|
|
273
|
+
...wire.mpp,
|
|
274
|
+
balance_atomics: String(balance - amount),
|
|
275
|
+
spent_atomics: String(spent + amount),
|
|
276
|
+
};
|
|
277
|
+
return {
|
|
278
|
+
receipt: {
|
|
279
|
+
id: `mpp_${++sequence}`,
|
|
280
|
+
amount_atomics: String(amount),
|
|
281
|
+
origin: request.body?.origin,
|
|
282
|
+
transaction_hash: mockHex(`mpp:${sequence}`, 32),
|
|
283
|
+
},
|
|
284
|
+
connection: wire,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
throw new HttpError(404, `No mock route for ${request.method ?? "GET"} ${request.path}`, {
|
|
289
|
+
code: "mock_route_not_found",
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function requiredGrant(grants, grantId) {
|
|
298
|
+
const grant = grants.get(grantId);
|
|
299
|
+
if (!grant) throw new HttpError(404, "Nanocodex grant not found", { code: "grant_not_found" });
|
|
300
|
+
return grant;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function requestedConnectors(value) {
|
|
304
|
+
if (value === undefined) return [];
|
|
305
|
+
if (!Array.isArray(value)) throw new TypeError("requested_connectors must be an array");
|
|
306
|
+
const supported = [
|
|
307
|
+
"github",
|
|
308
|
+
"gmail",
|
|
309
|
+
"gdrive",
|
|
310
|
+
"gcalendar",
|
|
311
|
+
"gtasks",
|
|
312
|
+
"gdocs",
|
|
313
|
+
"gsheets",
|
|
314
|
+
"gslides",
|
|
315
|
+
"gcontacts",
|
|
316
|
+
"slack",
|
|
317
|
+
"x",
|
|
318
|
+
"spotify",
|
|
319
|
+
"soundcloud",
|
|
320
|
+
"link",
|
|
321
|
+
"chatgpt",
|
|
322
|
+
];
|
|
323
|
+
if (value.some((provider) => !supported.includes(provider))) {
|
|
324
|
+
throw new TypeError("requested_connectors contains an unsupported provider");
|
|
325
|
+
}
|
|
326
|
+
return supported.filter((provider) => value.includes(provider));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function requestedMcpConnections(value) {
|
|
330
|
+
if (value === undefined) return [];
|
|
331
|
+
if (!Array.isArray(value) || value.length > 16
|
|
332
|
+
|| value.some((id) => typeof id !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(id))
|
|
333
|
+
|| new Set(value).size !== value.length) {
|
|
334
|
+
throw new TypeError("requested_mcp_connections must contain exact hosted connection IDs");
|
|
335
|
+
}
|
|
336
|
+
return value.map((id) => ({ id, name: "MCP connection" }));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function connectorResources(resources) {
|
|
340
|
+
return requestedConnectors(resources
|
|
341
|
+
.filter((resource) => resource.startsWith("urn:nanocodex:connector:"))
|
|
342
|
+
.map((resource) => resource.slice("urn:nanocodex:connector:".length)));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function connectorName(provider) {
|
|
346
|
+
return ({
|
|
347
|
+
github: "GitHub",
|
|
348
|
+
gmail: "Gmail",
|
|
349
|
+
gdrive: "Google Drive",
|
|
350
|
+
gcalendar: "Google Calendar",
|
|
351
|
+
gtasks: "Google Tasks",
|
|
352
|
+
gdocs: "Google Docs",
|
|
353
|
+
gsheets: "Google Sheets",
|
|
354
|
+
gslides: "Google Slides",
|
|
355
|
+
gcontacts: "Google Contacts",
|
|
356
|
+
slack: "Slack",
|
|
357
|
+
x: "X",
|
|
358
|
+
spotify: "Spotify",
|
|
359
|
+
soundcloud: "SoundCloud",
|
|
360
|
+
link: "Stripe Link",
|
|
361
|
+
chatgpt: "ChatGPT",
|
|
362
|
+
})[provider];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function mockConnectorConnections(connectors, grantId) {
|
|
366
|
+
return Object.fromEntries(connectors
|
|
367
|
+
.filter((connector) => connector !== "chatgpt")
|
|
368
|
+
.map((connector) => [
|
|
369
|
+
connector,
|
|
370
|
+
[mockHex(`${grantId}:${connector}:connection`, 32).slice(2, 45)],
|
|
371
|
+
]));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function requiredActiveGrant(grants, grantId) {
|
|
375
|
+
const grant = requiredGrant(grants, grantId);
|
|
376
|
+
if (grant.grant.status !== "active") {
|
|
377
|
+
throw new HttpError(403, "This Nanocodex grant has been revoked.", { code: "grant_revoked" });
|
|
378
|
+
}
|
|
379
|
+
return grant;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function mockHex(seed, bytes) {
|
|
383
|
+
let state = 2166136261;
|
|
384
|
+
let output = "";
|
|
385
|
+
for (let index = 0; output.length < bytes * 2; index += 1) {
|
|
386
|
+
const code = seed.charCodeAt(index % seed.length) + index;
|
|
387
|
+
state ^= code;
|
|
388
|
+
state = Math.imul(state, 16777619) >>> 0;
|
|
389
|
+
output += state.toString(16).padStart(8, "0");
|
|
390
|
+
}
|
|
391
|
+
return `0x${output.slice(0, bytes * 2)}`;
|
|
392
|
+
}
|
package/cloud/types.d.mts
CHANGED
|
@@ -120,6 +120,7 @@ export type ConnectAgent = Readonly<{
|
|
|
120
120
|
}>;
|
|
121
121
|
|
|
122
122
|
export type Grant = Readonly<{
|
|
123
|
+
services?: import("../services/index.mjs").ServiceCapabilities | undefined;
|
|
123
124
|
id: Hex;
|
|
124
125
|
permission: string;
|
|
125
126
|
status: "active" | "revoked" | "expired";
|
|
@@ -153,7 +154,8 @@ export type MppPermission = Readonly<{
|
|
|
153
154
|
}>;
|
|
154
155
|
|
|
155
156
|
type ConnectionBase = Readonly<{
|
|
156
|
-
|
|
157
|
+
/** Absent on a standalone services grant. */
|
|
158
|
+
agentId?: string;
|
|
157
159
|
grant: Grant;
|
|
158
160
|
}>;
|
|
159
161
|
|
package/cloudflare/Agent.d.mts
CHANGED
|
@@ -96,6 +96,8 @@ export function create(owner: create.Owner, options?: create.Options): Promise<c
|
|
|
96
96
|
export declare namespace create {
|
|
97
97
|
type Owner = DurableObjectOwner;
|
|
98
98
|
type Options = Readonly<{
|
|
99
|
+
/** Observer-only instant steering; disabled unless explicitly enabled. */
|
|
100
|
+
instantToolSteering?: boolean | undefined;
|
|
99
101
|
/** Awaited host preservation barrier; scoped to this root, never inherited by children. */
|
|
100
102
|
beforeCompaction?: AgentOptions["beforeCompaction"];
|
|
101
103
|
/** Stable portable state identity. It cannot change after first construction or import. */
|