nanocodex 0.6.5 → 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 +311 -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/Voice.mjs +11 -4
- package/browser/VoiceSession.mjs +20 -6
- 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 +4 -1
- package/cloudflare/Agent.d.mts +6 -0
- package/cloudflare/Agent.mjs +190 -10
- package/cloudflare/agent-settings.d.mts +1 -1
- package/cloudflare/agent-settings.mjs +12 -3
- package/cloudflare/egress.mjs +9 -6
- package/cloudflare/gateway-binding-responses.mjs +3 -3
- package/cloudflare/gateway-responses.d.mts +1 -0
- package/cloudflare/gateway-responses.mjs +7 -7
- package/cloudflare/managed-auth.d.mts +4 -4
- package/cloudflare/managed-auth.mjs +20 -2
- package/cloudflare/managed-live.d.mts +6 -0
- package/cloudflare/managed-live.mjs +56 -1
- package/cloudflare/tracing.d.mts +16 -0
- package/cloudflare/tracing.mjs +47 -0
- package/cloudflare/workers-ai-responses.d.mts +3 -2
- package/cloudflare/workers-ai-responses.mjs +83 -8
- 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 +33 -3
- package/managed/Agent.d.mts +10 -3
- package/managed/Agent.mjs +60 -10
- package/managed/ManagedError.d.mts +3 -0
- package/managed/Voice.mjs +3 -1
- package/managed/internal.mjs +2 -1
- 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 +45 -9
- package/pkg-node/nanocodex.d.ts +123 -2
- package/pkg-node/nanocodex.js +879 -89
- package/pkg-node/nanocodex_bg.wasm.d.ts +49 -3
- package/pkg-web/.nanocodex-bindgen-stamp +4 -4
- package/pkg-web/nanocodex-build.json +1 -1
- package/pkg-web/nanocodex.d.ts +172 -5
- package/pkg-web/nanocodex.js +872 -88
- package/pkg-web/nanocodex_bg.js +872 -88
- package/pkg-web/nanocodex_bg.wasm +0 -0
- package/pkg-web/nanocodex_bg.wasm.d.ts +49 -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 -55
- package/runtime/mercator.mjs +2 -0
- package/runtime/quickjs-evaluator.mjs +23 -11
- package/runtime/response-controls.mjs +16 -10
- 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 +9 -3
- 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 +17 -0
- package/tools/environment.mjs +24 -1
- package/tools/index.d.mts +6 -3
- package/types.d.mts +107 -6
- 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
package/cloud/Transport.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { HttpError
|
|
2
|
-
import { Hex, PersonalMessage } from "ox";
|
|
1
|
+
import { HttpError } from "./Errors.mjs";
|
|
3
2
|
|
|
4
3
|
export const DEFAULT_API_URL = "https://api.nanocodex.xyz";
|
|
5
|
-
export const MOCK_ACCOUNT_ADDRESS = "0x8ba1f109551bd432803012645ac136ddd64dba72";
|
|
6
|
-
export const MOCK_MACHINE_USD_ADDRESS = "0x20c0000000000000000000000000000000000001";
|
|
7
4
|
|
|
8
5
|
export function from(parameters) {
|
|
9
6
|
if (!parameters || typeof parameters !== "object") {
|
|
@@ -70,388 +67,6 @@ export function http(url = DEFAULT_API_URL, options = {}) {
|
|
|
70
67
|
});
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
export function mock(options = {}) {
|
|
74
|
-
const accountAddress = options.accountAddress ?? MOCK_ACCOUNT_ADDRESS;
|
|
75
|
-
const machineUsdAddress = options.machineUsdAddress ?? MOCK_MACHINE_USD_ADDRESS;
|
|
76
|
-
const appName = options.appName ?? "Acme Workspace";
|
|
77
|
-
const grants = new Map();
|
|
78
|
-
let sequence = 0;
|
|
79
|
-
|
|
80
|
-
return from({
|
|
81
|
-
key: options.key ?? "mock",
|
|
82
|
-
name: options.name ?? "Nanocodex mock",
|
|
83
|
-
type: "mock",
|
|
84
|
-
setup({ appId }) {
|
|
85
|
-
return {
|
|
86
|
-
baseUrl: "memory://nanocodex",
|
|
87
|
-
async request(request) {
|
|
88
|
-
if (request.method === "POST" && request.path === "/v1/connections") {
|
|
89
|
-
const authorization = request.body?.key_authorization;
|
|
90
|
-
const keyId = authorization?.keyId ?? authorization?.address;
|
|
91
|
-
if (typeof keyId !== "string") throw new InvalidResponseError("key authorization is missing keyId");
|
|
92
|
-
const expiry = authorization.expiry ?? Math.floor(Date.now() / 1000) + 30 * 86_400;
|
|
93
|
-
const connectors = requestedConnectors(request.body?.requested_connectors);
|
|
94
|
-
const mcpConnections = requestedMcpConnections(request.body?.requested_mcp_connections);
|
|
95
|
-
const grantId = mockHex(`${appId}:${accountAddress}:${keyId}`, 32);
|
|
96
|
-
const connectorConnections = mockConnectorConnections(connectors, grantId);
|
|
97
|
-
const wire = {
|
|
98
|
-
grant_token: `mock-grant-${grantId.slice(2)}`,
|
|
99
|
-
account_address: accountAddress,
|
|
100
|
-
agent_id: `agent_${grantId.slice(2, 14)}`,
|
|
101
|
-
grant: {
|
|
102
|
-
id: grantId,
|
|
103
|
-
permission: request.body?.permission ?? "agent.run",
|
|
104
|
-
status: "active",
|
|
105
|
-
expires_at: expiry,
|
|
106
|
-
capabilities: [
|
|
107
|
-
"nanocodex.agent",
|
|
108
|
-
"mercator.boost",
|
|
109
|
-
"mpp.mach",
|
|
110
|
-
...connectors,
|
|
111
|
-
...mcpConnections.map(({ id }) => `mcp:${id}`),
|
|
112
|
-
],
|
|
113
|
-
connector_connections: connectorConnections,
|
|
114
|
-
mcp_connections: mcpConnections,
|
|
115
|
-
},
|
|
116
|
-
access_key: {
|
|
117
|
-
address: keyId,
|
|
118
|
-
chain_id: String(authorization.chainId ?? 4217),
|
|
119
|
-
key_id: keyId,
|
|
120
|
-
key_type: authorization.keyType ?? "p256",
|
|
121
|
-
limits: authorization.limits ?? [{ token: machineUsdAddress, limit: "10000000", period: 86_400 }],
|
|
122
|
-
scopes: authorization.scopes ?? [],
|
|
123
|
-
witness: authorization.witness ?? mockHex(`${grantId}:witness`, 32),
|
|
124
|
-
expiry,
|
|
125
|
-
authorization: request.body?.signed_key_authorization ?? "0x01",
|
|
126
|
-
},
|
|
127
|
-
mpp: {
|
|
128
|
-
token: machineUsdAddress,
|
|
129
|
-
symbol: "MACH",
|
|
130
|
-
balance_status: "ready",
|
|
131
|
-
settlement_token: "0x20C000000000000000000000b9537d11c60E8b50",
|
|
132
|
-
settlement_symbol: "USDC.e",
|
|
133
|
-
settlement_balance_atomics: "0",
|
|
134
|
-
limit_atomics: "10000000",
|
|
135
|
-
max_per_request_atomics: "250000",
|
|
136
|
-
period: 86_400,
|
|
137
|
-
balance_atomics: "0",
|
|
138
|
-
spent_atomics: "0",
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
grants.set(grantId, wire);
|
|
142
|
-
return wire;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (request.method === "GET" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+$/)) {
|
|
146
|
-
return requiredGrant(grants, request.path.split("/")[3]);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (request.method === "POST" && request.path === "/v1/connections/prepare") {
|
|
150
|
-
const permission = request.body?.permission;
|
|
151
|
-
if (typeof permission !== "string" || permission.length === 0) {
|
|
152
|
-
throw new TypeError("connection permission must be a non-empty string");
|
|
153
|
-
}
|
|
154
|
-
const resources = request.body?.resources ?? [];
|
|
155
|
-
if (!Array.isArray(resources) || resources.some((resource) => typeof resource !== "string" || resource.length === 0)) {
|
|
156
|
-
throw new TypeError("connection resources must be non-empty strings");
|
|
157
|
-
}
|
|
158
|
-
const connectors = connectorResources(resources);
|
|
159
|
-
const id = `req_${++sequence}`;
|
|
160
|
-
const accessPolicy = request.body?.authorize_access_key;
|
|
161
|
-
const keyId = accessPolicy?.address ?? mockHex(`${appId}:${accountAddress}:${permission}:key`, 20);
|
|
162
|
-
const tokenLimit = accessPolicy?.limits?.find((limit) => limit.token === machineUsdAddress);
|
|
163
|
-
const expiry = accessPolicy?.expiry ?? Math.floor(Date.now() / 1000) + 30 * 86_400;
|
|
164
|
-
const dailyLimit = tokenLimit?.limit ?? "10000000";
|
|
165
|
-
const period = tokenLimit?.period ?? 86_400;
|
|
166
|
-
const appOrigin = options.appOrigin ?? "https://app.example";
|
|
167
|
-
const appUrl = new URL(appOrigin);
|
|
168
|
-
const nonce = mockHex(`${id}:siwe-nonce`, 8).slice(2);
|
|
169
|
-
const issuedAt = new Date().toISOString();
|
|
170
|
-
const authMessage = [
|
|
171
|
-
`${appUrl.host} wants you to sign in with your Ethereum account:`,
|
|
172
|
-
"0x0000000000000000000000000000000000000000",
|
|
173
|
-
"",
|
|
174
|
-
`${appName} wants to connect your Nanocodex agent.`,
|
|
175
|
-
"",
|
|
176
|
-
`URI: ${appUrl.origin}`,
|
|
177
|
-
"Version: 1",
|
|
178
|
-
"Chain ID: 4217",
|
|
179
|
-
`Nonce: ${nonce}`,
|
|
180
|
-
`Issued At: ${issuedAt}`,
|
|
181
|
-
...(resources.length > 0
|
|
182
|
-
? ["Resources:", ...resources.map((resource) => `- ${resource}`)]
|
|
183
|
-
: []),
|
|
184
|
-
].join("\n");
|
|
185
|
-
return {
|
|
186
|
-
request_id: id,
|
|
187
|
-
app: { id: appId, name: appName, origin: appOrigin },
|
|
188
|
-
account_address: accountAddress,
|
|
189
|
-
auth: {
|
|
190
|
-
message: authMessage,
|
|
191
|
-
resources,
|
|
192
|
-
},
|
|
193
|
-
permission: {
|
|
194
|
-
id: permission,
|
|
195
|
-
title: "Use your Nanocodex agent",
|
|
196
|
-
description: "Run an app-owned Nanocodex agent with your approved capabilities.",
|
|
197
|
-
connectors: [
|
|
198
|
-
{ id: "agent", name: "Nanocodex", detail: "Instantiate the real Nanocodex runtime in this app" },
|
|
199
|
-
{ id: "mercator", name: "BOOST with Mercator", detail: "Find and compose the right tools, paying per call through MPP" },
|
|
200
|
-
...connectors.map((provider) => ({
|
|
201
|
-
id: provider,
|
|
202
|
-
name: connectorName(provider),
|
|
203
|
-
detail: `Use the connected ${connectorName(provider)} account through the grant`,
|
|
204
|
-
})),
|
|
205
|
-
],
|
|
206
|
-
},
|
|
207
|
-
access_key: {
|
|
208
|
-
address: keyId,
|
|
209
|
-
chain_id: accessPolicy?.chainId ?? "4217",
|
|
210
|
-
key_id: keyId,
|
|
211
|
-
public_key: accessPolicy?.publicKey ?? mockHex(`${id}:public`, 65),
|
|
212
|
-
key_type: accessPolicy?.keyType ?? "p256",
|
|
213
|
-
limits: accessPolicy?.limits ?? [{ token: machineUsdAddress, limit: dailyLimit, period }],
|
|
214
|
-
scopes: accessPolicy?.scopes ?? [],
|
|
215
|
-
witness: PersonalMessage.getSignPayload(Hex.fromString(authMessage)),
|
|
216
|
-
expiry,
|
|
217
|
-
},
|
|
218
|
-
mpp: {
|
|
219
|
-
token: machineUsdAddress,
|
|
220
|
-
symbol: "MACH",
|
|
221
|
-
settlement_token: "0x20C000000000000000000000b9537d11c60E8b50",
|
|
222
|
-
settlement_symbol: "USDC.e",
|
|
223
|
-
settlement_balance_atomics: "0",
|
|
224
|
-
limit_atomics: dailyLimit,
|
|
225
|
-
period,
|
|
226
|
-
max_per_request_atomics: "250000",
|
|
227
|
-
},
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (request.method === "POST" && request.path === "/v1/connections/complete") {
|
|
232
|
-
const prepared = request.body?.prepared;
|
|
233
|
-
const approval = request.body?.approval;
|
|
234
|
-
if (!prepared || !approval || approval.approved !== true) {
|
|
235
|
-
throw new InvalidResponseError("connection approval is malformed");
|
|
236
|
-
}
|
|
237
|
-
const approvedCapabilities = approval.capabilities;
|
|
238
|
-
if (
|
|
239
|
-
approvedCapabilities?.keyAuthorization?.witness !== prepared.access_key.witness
|
|
240
|
-
|| approvedCapabilities?.personalSign?.message !== prepared.auth.message
|
|
241
|
-
|| typeof approvedCapabilities?.personalSign?.keyAuthorization !== "string"
|
|
242
|
-
|| typeof approvedCapabilities?.signature !== "string"
|
|
243
|
-
) {
|
|
244
|
-
throw new InvalidResponseError("connection approval is not bound to the SIWE challenge and key authorization");
|
|
245
|
-
}
|
|
246
|
-
const grantId = mockHex(`${prepared.app.id}:${prepared.account_address}:${prepared.access_key.key_id}`, 32);
|
|
247
|
-
const wire = {
|
|
248
|
-
grant_token: `mock-grant-${grantId.slice(2)}`,
|
|
249
|
-
account_address: prepared.account_address,
|
|
250
|
-
agent_id: `agent_${grantId.slice(2, 14)}`,
|
|
251
|
-
grant: {
|
|
252
|
-
id: grantId,
|
|
253
|
-
permission: prepared.permission.id,
|
|
254
|
-
status: "active",
|
|
255
|
-
expires_at: prepared.access_key.expiry,
|
|
256
|
-
capabilities: prepared.permission.connectors.map(({ id }) => id),
|
|
257
|
-
},
|
|
258
|
-
access_key: prepared.access_key,
|
|
259
|
-
mpp: {
|
|
260
|
-
...prepared.mpp,
|
|
261
|
-
balance_atomics: "0",
|
|
262
|
-
balance_status: "ready",
|
|
263
|
-
spent_atomics: "0",
|
|
264
|
-
},
|
|
265
|
-
};
|
|
266
|
-
grants.set(grantId, wire);
|
|
267
|
-
return wire;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (request.method === "POST" && request.path === "/v1/connections/disconnect") {
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (request.method === "GET" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/mpp\/balance$/)) {
|
|
275
|
-
const wire = requiredGrant(grants, request.path.split("/")[3]);
|
|
276
|
-
wire.mpp = { ...wire.mpp, balance_status: "ready" };
|
|
277
|
-
return wire;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (request.method === "POST" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/revoke$/)) {
|
|
281
|
-
const grantId = request.path.split("/")[3];
|
|
282
|
-
const wire = requiredGrant(grants, grantId);
|
|
283
|
-
wire.grant = { ...wire.grant, status: "revoked" };
|
|
284
|
-
return wire.grant;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
if (request.method === "GET" && request.path === "/v1/machine-usd/config") {
|
|
288
|
-
return {
|
|
289
|
-
chain_id: 4217,
|
|
290
|
-
min_usd_amount_cents: 500,
|
|
291
|
-
max_usd_amount_cents: 10_000,
|
|
292
|
-
stripe_publishable_key: "pk_test_nanocodex",
|
|
293
|
-
token_address: machineUsdAddress,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (request.method === "POST" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/fund$/)) {
|
|
298
|
-
const grantId = request.path.split("/")[3];
|
|
299
|
-
const wire = requiredActiveGrant(grants, grantId);
|
|
300
|
-
const usdAmountCents = request.body?.usd_amount_cents;
|
|
301
|
-
if (!Number.isSafeInteger(usdAmountCents) || usdAmountCents < 500 || usdAmountCents > 10_000) {
|
|
302
|
-
throw new TypeError("MACH amount must be from 500 through 10000 cents");
|
|
303
|
-
}
|
|
304
|
-
const amount = BigInt(usdAmountCents) * 10_000n;
|
|
305
|
-
wire.mpp = {
|
|
306
|
-
...wire.mpp,
|
|
307
|
-
balance_atomics: String(BigInt(wire.mpp.balance_atomics) + amount),
|
|
308
|
-
};
|
|
309
|
-
return {
|
|
310
|
-
order: {
|
|
311
|
-
id: `ord_${++sequence}`,
|
|
312
|
-
status: "complete",
|
|
313
|
-
usd_amount_cents: usdAmountCents,
|
|
314
|
-
machine_usd_amount_atomics: String(amount),
|
|
315
|
-
issuance_transaction_hash: mockHex(`fund:${sequence}`, 32),
|
|
316
|
-
},
|
|
317
|
-
connection: wire,
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (request.method === "POST" && request.path.match(/^\/v1\/grants\/0x[0-9a-f]+\/mpp\/charge$/)) {
|
|
322
|
-
const grantId = request.path.split("/")[3];
|
|
323
|
-
const wire = requiredActiveGrant(grants, grantId);
|
|
324
|
-
const amount = BigInt(request.body?.amount_atomics ?? "-1");
|
|
325
|
-
const max = BigInt(wire.mpp.max_per_request_atomics);
|
|
326
|
-
const spent = BigInt(wire.mpp.spent_atomics);
|
|
327
|
-
const limit = BigInt(wire.mpp.limit_atomics);
|
|
328
|
-
const balance = BigInt(wire.mpp.balance_atomics);
|
|
329
|
-
if (amount <= 0n) throw new TypeError("MPP amount must be positive");
|
|
330
|
-
if (amount > max) throw new HttpError(403, "This payment exceeds the per-request permission.", { code: "mpp_request_limit_exceeded" });
|
|
331
|
-
if (spent + amount > limit) throw new HttpError(403, "This payment exceeds the daily MPP permission.", { code: "mpp_period_limit_exceeded" });
|
|
332
|
-
if (amount > balance) throw new HttpError(402, "Add MACH before paying for this capability.", { code: "machine_usd_required" });
|
|
333
|
-
wire.mpp = {
|
|
334
|
-
...wire.mpp,
|
|
335
|
-
balance_atomics: String(balance - amount),
|
|
336
|
-
spent_atomics: String(spent + amount),
|
|
337
|
-
};
|
|
338
|
-
return {
|
|
339
|
-
receipt: {
|
|
340
|
-
id: `mpp_${++sequence}`,
|
|
341
|
-
amount_atomics: String(amount),
|
|
342
|
-
origin: request.body?.origin,
|
|
343
|
-
transaction_hash: mockHex(`mpp:${sequence}`, 32),
|
|
344
|
-
},
|
|
345
|
-
connection: wire,
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
throw new HttpError(404, `No mock route for ${request.method ?? "GET"} ${request.path}`, {
|
|
350
|
-
code: "mock_route_not_found",
|
|
351
|
-
});
|
|
352
|
-
},
|
|
353
|
-
};
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function requiredGrant(grants, grantId) {
|
|
359
|
-
const grant = grants.get(grantId);
|
|
360
|
-
if (!grant) throw new HttpError(404, "Nanocodex grant not found", { code: "grant_not_found" });
|
|
361
|
-
return grant;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function requestedConnectors(value) {
|
|
365
|
-
if (value === undefined) return [];
|
|
366
|
-
if (!Array.isArray(value)) throw new TypeError("requested_connectors must be an array");
|
|
367
|
-
const supported = [
|
|
368
|
-
"github",
|
|
369
|
-
"gmail",
|
|
370
|
-
"gdrive",
|
|
371
|
-
"gcalendar",
|
|
372
|
-
"gtasks",
|
|
373
|
-
"gdocs",
|
|
374
|
-
"gsheets",
|
|
375
|
-
"gslides",
|
|
376
|
-
"gcontacts",
|
|
377
|
-
"slack",
|
|
378
|
-
"x",
|
|
379
|
-
"spotify",
|
|
380
|
-
"soundcloud",
|
|
381
|
-
"link",
|
|
382
|
-
"chatgpt",
|
|
383
|
-
];
|
|
384
|
-
if (value.some((provider) => !supported.includes(provider))) {
|
|
385
|
-
throw new TypeError("requested_connectors contains an unsupported provider");
|
|
386
|
-
}
|
|
387
|
-
return supported.filter((provider) => value.includes(provider));
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function requestedMcpConnections(value) {
|
|
391
|
-
if (value === undefined) return [];
|
|
392
|
-
if (!Array.isArray(value) || value.length > 16
|
|
393
|
-
|| value.some((id) => typeof id !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(id))
|
|
394
|
-
|| new Set(value).size !== value.length) {
|
|
395
|
-
throw new TypeError("requested_mcp_connections must contain exact hosted connection IDs");
|
|
396
|
-
}
|
|
397
|
-
return value.map((id) => ({ id, name: "MCP connection" }));
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function connectorResources(resources) {
|
|
401
|
-
return requestedConnectors(resources
|
|
402
|
-
.filter((resource) => resource.startsWith("urn:nanocodex:connector:"))
|
|
403
|
-
.map((resource) => resource.slice("urn:nanocodex:connector:".length)));
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function connectorName(provider) {
|
|
407
|
-
return ({
|
|
408
|
-
github: "GitHub",
|
|
409
|
-
gmail: "Gmail",
|
|
410
|
-
gdrive: "Google Drive",
|
|
411
|
-
gcalendar: "Google Calendar",
|
|
412
|
-
gtasks: "Google Tasks",
|
|
413
|
-
gdocs: "Google Docs",
|
|
414
|
-
gsheets: "Google Sheets",
|
|
415
|
-
gslides: "Google Slides",
|
|
416
|
-
gcontacts: "Google Contacts",
|
|
417
|
-
slack: "Slack",
|
|
418
|
-
x: "X",
|
|
419
|
-
spotify: "Spotify",
|
|
420
|
-
soundcloud: "SoundCloud",
|
|
421
|
-
link: "Stripe Link",
|
|
422
|
-
chatgpt: "ChatGPT",
|
|
423
|
-
})[provider];
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function mockConnectorConnections(connectors, grantId) {
|
|
427
|
-
return Object.fromEntries(connectors
|
|
428
|
-
.filter((connector) => connector !== "chatgpt")
|
|
429
|
-
.map((connector) => [
|
|
430
|
-
connector,
|
|
431
|
-
[mockHex(`${grantId}:${connector}:connection`, 32).slice(2, 45)],
|
|
432
|
-
]));
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
function requiredActiveGrant(grants, grantId) {
|
|
436
|
-
const grant = requiredGrant(grants, grantId);
|
|
437
|
-
if (grant.grant.status !== "active") {
|
|
438
|
-
throw new HttpError(403, "This Nanocodex grant has been revoked.", { code: "grant_revoked" });
|
|
439
|
-
}
|
|
440
|
-
return grant;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function mockHex(seed, bytes) {
|
|
444
|
-
let state = 2166136261;
|
|
445
|
-
let output = "";
|
|
446
|
-
for (let index = 0; output.length < bytes * 2; index += 1) {
|
|
447
|
-
const code = seed.charCodeAt(index % seed.length) + index;
|
|
448
|
-
state ^= code;
|
|
449
|
-
state = Math.imul(state, 16777619) >>> 0;
|
|
450
|
-
output += state.toString(16).padStart(8, "0");
|
|
451
|
-
}
|
|
452
|
-
return `0x${output.slice(0, bytes * 2)}`;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
70
|
function requiredString(value, label) {
|
|
456
71
|
if (typeof value !== "string" || value.length === 0) {
|
|
457
72
|
throw new TypeError(`${label} must be a non-empty string`);
|
|
@@ -7,3 +7,15 @@ export declare namespace logout {
|
|
|
7
7
|
|
|
8
8
|
/** Signs out the Nanocodex account without revoking its app grant or access key. */
|
|
9
9
|
export function logout(client: Client): logout.ReturnType;
|
|
10
|
+
|
|
11
|
+
export declare namespace links {
|
|
12
|
+
type Options = Readonly<{
|
|
13
|
+
connect?: "claude" | "chatgpt" | "openai" | "mcp" | "cloudflare" | "github" | "google" | "slack" | "x" | "spotify" | "soundcloud" | "link" | "whatsapp" | undefined;
|
|
14
|
+
add?: "login" | "api_key" | "card" | "address" | "phone" | "totp" | undefined;
|
|
15
|
+
}>;
|
|
16
|
+
type Result = Readonly<{ connections: string; vault: string; wallet: string; access: string }>;
|
|
17
|
+
type ReturnType = Promise<Result>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Public navigation only; these URLs do not carry account authorization. */
|
|
21
|
+
export function links(client: Client, options?: links.Options): links.ReturnType;
|
|
@@ -17,3 +17,14 @@ export async function logout(client) {
|
|
|
17
17
|
const cleanupFailure = cleanup.find((result) => result.status === "rejected");
|
|
18
18
|
if (cleanupFailure?.status === "rejected") throw cleanupFailure.reason;
|
|
19
19
|
}
|
|
20
|
+
|
|
21
|
+
/** Discovers ordinary account navigation URLs; opening them uses browser sign-in. */
|
|
22
|
+
export function links(client, options = {}) {
|
|
23
|
+
const query = new URLSearchParams();
|
|
24
|
+
for (const key of Object.keys(options)) {
|
|
25
|
+
if (key !== "connect" && key !== "add") throw new TypeError(`Unknown account links option: ${key}`);
|
|
26
|
+
if (options[key] !== undefined) query.set(key, options[key]);
|
|
27
|
+
}
|
|
28
|
+
const suffix = query.size ? `?${query}` : "";
|
|
29
|
+
return client.request({ method: "GET", path: `/v1/account/links${suffix}` });
|
|
30
|
+
}
|
package/cloud/actions/agent.mjs
CHANGED
|
@@ -17,6 +17,9 @@ export async function create(client, options) {
|
|
|
17
17
|
if (!connection || typeof connection !== "object") {
|
|
18
18
|
throw new TypeError("agent.create requires an active connection");
|
|
19
19
|
}
|
|
20
|
+
if (connection.grant?.permission === "services.use" || !connection.agentId) {
|
|
21
|
+
throw new Error("This Connect grant has no agent authority.");
|
|
22
|
+
}
|
|
20
23
|
if (connection.grant?.status !== "active") {
|
|
21
24
|
throw new Error("The Connect authorization is not active.");
|
|
22
25
|
}
|
|
@@ -47,6 +47,8 @@ export type AuthorizeAccessKey = Readonly<{
|
|
|
47
47
|
}>;
|
|
48
48
|
|
|
49
49
|
export type Capabilities = Readonly<{
|
|
50
|
+
/** Exact standalone Vault and phone authority. */
|
|
51
|
+
services?: import("../../services/index.mjs").ServiceCapabilities | undefined;
|
|
50
52
|
/** SIWE authentication folded into the same passkey ceremony. */
|
|
51
53
|
auth?: Auth | undefined;
|
|
52
54
|
/** Access key authorized by the same passkey ceremony. */
|
|
@@ -111,7 +113,7 @@ export function disconnect(client: Client, options?: disconnect.Options | undefi
|
|
|
111
113
|
export declare namespace reconnect {
|
|
112
114
|
type Options = Readonly<{
|
|
113
115
|
/** Reject a retained grant outside these app capability boundaries. */
|
|
114
|
-
capabilities?: Pick<Capabilities, "agent" | "cloudAccounts"> | undefined;
|
|
116
|
+
capabilities?: Pick<Capabilities, "agent" | "cloudAccounts" | "services"> | undefined;
|
|
115
117
|
/** Defaults to the retained connection mode, then to hosted. */
|
|
116
118
|
authorization?: "access_key" | "hosted" | undefined;
|
|
117
119
|
tools?: readonly NamedTool[] | undefined;
|
|
@@ -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">;
|