@neus/sdk 1.2.0 → 1.2.2

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.
@@ -0,0 +1,522 @@
1
+ /**
2
+ * Runtime Mount — proof-backed agent context bundle (neus.runtime-mount.v1).
3
+ * SSOT for CLI, integrators, and protocol neus_agent_mount response shape.
4
+ */
5
+
6
+ export const RUNTIME_MOUNT_SCHEMA = 'neus.runtime-mount.v1';
7
+
8
+ const PROOF_URL_BASE = 'https://neus.network/proof/';
9
+
10
+ /**
11
+ * @param {string | null | undefined} value
12
+ */
13
+ export function normalizeWallet(value) {
14
+ const wallet = String(value || '').trim().toLowerCase();
15
+ return /^0x[a-f0-9]{40}$/.test(wallet) ? wallet : '';
16
+ }
17
+
18
+ /**
19
+ * @param {unknown} value
20
+ */
21
+ function asString(value) {
22
+ const trimmed = String(value ?? '').trim();
23
+ return trimmed.length > 0 ? trimmed : '';
24
+ }
25
+
26
+ /**
27
+ * @param {unknown} value
28
+ * @returns {string[]}
29
+ */
30
+ function asStringArray(value) {
31
+ if (!Array.isArray(value)) return [];
32
+ return value.map(item => String(item || '').trim()).filter(Boolean);
33
+ }
34
+
35
+ /**
36
+ * @param {Record<string, unknown> | null | undefined} caps
37
+ */
38
+ function capabilitiesToArray(caps) {
39
+ if (Array.isArray(caps)) return asStringArray(caps);
40
+ if (!caps || typeof caps !== 'object') return [];
41
+ return Object.entries(caps)
42
+ .filter(([, enabled]) => enabled === true)
43
+ .map(([key]) => String(key).trim())
44
+ .filter(Boolean);
45
+ }
46
+
47
+ /**
48
+ * @param {number | null | undefined} expiresAt
49
+ */
50
+ export function isDelegationExpired(expiresAt) {
51
+ if (expiresAt === null || expiresAt === 0) return false;
52
+ const ms = Number(expiresAt);
53
+ return Number.isFinite(ms) && ms > 0 && ms <= Date.now();
54
+ }
55
+
56
+ /**
57
+ * @param {Array<Record<string, unknown>>} identities
58
+ * @param {{ agentId?: string, agentWallet?: string, identityQHash?: string }} selector
59
+ */
60
+ export function pickIdentity(identities, selector) {
61
+ const list = Array.isArray(identities) ? identities : [];
62
+ const qHash = asString(selector.identityQHash).toLowerCase();
63
+ if (qHash) {
64
+ return list.find(row => asString(row.qHash).toLowerCase() === qHash) || null;
65
+ }
66
+ const agentId = asString(selector.agentId).toLowerCase();
67
+ const agentWallet = normalizeWallet(selector.agentWallet);
68
+ if (agentId) {
69
+ const byId = list.filter(row => asString(row.agentId).toLowerCase() === agentId);
70
+ if (agentWallet) {
71
+ return byId.find(row => normalizeWallet(row.agentWallet) === agentWallet) || byId[0] || null;
72
+ }
73
+ return byId[0] || null;
74
+ }
75
+ if (agentWallet) {
76
+ return list.find(row => normalizeWallet(row.agentWallet) === agentWallet) || null;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * @param {Array<Record<string, unknown>>} delegations
83
+ * @param {string} controllerWallet
84
+ * @param {string} agentWallet
85
+ * @param {string} agentId
86
+ */
87
+ export function pickActiveDelegation(delegations, controllerWallet, agentWallet, agentId) {
88
+ const list = Array.isArray(delegations) ? delegations : [];
89
+ const controller = normalizeWallet(controllerWallet);
90
+ const agent = normalizeWallet(agentWallet);
91
+ const id = asString(agentId).toLowerCase();
92
+ const candidates = list.filter(row => {
93
+ if (isDelegationExpired(row.expiresAt)) return false;
94
+ const rowAgent = normalizeWallet(row.agentWallet);
95
+ const rowController = normalizeWallet(row.controllerWallet);
96
+ if (agent && rowAgent && rowAgent !== agent) return false;
97
+ if (controller && rowController && rowController !== controller) return false;
98
+ if (id && row.agentId && asString(row.agentId).toLowerCase() !== id) return false;
99
+ return true;
100
+ });
101
+ return candidates[0] || null;
102
+ }
103
+
104
+ /**
105
+ * @param {Record<string, unknown> | null | undefined} identity
106
+ * @param {Record<string, unknown> | null | undefined} delegation
107
+ */
108
+ export function resolveEffectiveRuntime(identity, delegation) {
109
+ const delProvider = asString(delegation?.provider);
110
+ const delModel = asString(delegation?.model);
111
+ if (delProvider || delModel) {
112
+ return {
113
+ provider: delProvider || 'openai',
114
+ model: delModel || ''
115
+ };
116
+ }
117
+ const defaultRuntime =
118
+ identity?.defaultRuntime && typeof identity.defaultRuntime === 'object'
119
+ ? identity.defaultRuntime
120
+ : null;
121
+ const idProvider = asString(defaultRuntime?.provider);
122
+ const idModel = asString(defaultRuntime?.model);
123
+ if (idProvider || idModel) {
124
+ return {
125
+ provider: idProvider || 'openai',
126
+ model: idModel || ''
127
+ };
128
+ }
129
+ return null;
130
+ }
131
+
132
+ /**
133
+ * @param {unknown} proof
134
+ */
135
+ export function extractAgentContextFromProofs(proofs) {
136
+ const identities = [];
137
+ const delegations = [];
138
+ const list = Array.isArray(proofs) ? proofs : [];
139
+
140
+ for (const proof of list) {
141
+ const qHash = asString(proof?.qHash);
142
+ const verifiedVerifiers = Array.isArray(proof?.verifiedVerifiers) ? proof.verifiedVerifiers : [];
143
+ for (const vv of verifiedVerifiers) {
144
+ const verifierId = asString(vv?.verifierId);
145
+ const vvData = vv?.data && typeof vv.data === 'object' ? vv.data : {};
146
+ if (verifierId === 'agent-identity') {
147
+ identities.push({
148
+ qHash,
149
+ agentId: vvData.agentId || null,
150
+ agentWallet: vvData.agentWallet || null,
151
+ agentLabel: vvData.agentLabel || vvData.agentId || 'Agent',
152
+ agentType: vvData.agentType || 'agent',
153
+ description: vvData.description || null,
154
+ capabilities: capabilitiesToArray(vvData.capabilities),
155
+ skills: Array.isArray(vvData.skills) ? vvData.skills : [],
156
+ instructions: vvData.instructions || null,
157
+ services: Array.isArray(vvData.services) ? vvData.services : [],
158
+ defaultRuntime:
159
+ vvData.defaultRuntime && typeof vvData.defaultRuntime === 'object'
160
+ ? vvData.defaultRuntime
161
+ : undefined
162
+ });
163
+ }
164
+ if (verifierId === 'agent-delegation') {
165
+ delegations.push({
166
+ qHash,
167
+ controllerWallet: vvData.controllerWallet || null,
168
+ agentWallet: vvData.agentWallet || null,
169
+ agentId: vvData.agentId || null,
170
+ scope: vvData.scope || 'global',
171
+ allowedActions: asStringArray(vvData.allowedActions),
172
+ deniedActions: asStringArray(vvData.deniedActions),
173
+ runtimePolicy:
174
+ vvData.runtimePolicy && typeof vvData.runtimePolicy === 'object'
175
+ ? vvData.runtimePolicy
176
+ : undefined,
177
+ expiresAt: vvData.expiresAt ?? null,
178
+ isExpired: isDelegationExpired(vvData.expiresAt),
179
+ maxSpend: vvData.maxSpend !== null ? String(vvData.maxSpend) : undefined,
180
+ instructions: vvData.instructions || null,
181
+ skills: Array.isArray(vvData.skills) ? vvData.skills : [],
182
+ provider: vvData.provider || vvData.modelProvider || null,
183
+ model: vvData.model || null
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ return { identities, delegations };
190
+ }
191
+
192
+ /**
193
+ * @param {Record<string, unknown>} input
194
+ */
195
+ export function buildRuntimeBundle(input) {
196
+ const identity = input.identity || {};
197
+ const delegation = input.delegation || null;
198
+ const identityQHash = asString(input.identityQHash || identity.qHash);
199
+ const delegationQHash = delegation ? asString(input.delegationQHash || delegation.qHash) : null;
200
+ const agentId = asString(identity.agentId);
201
+ const agentWallet = normalizeWallet(identity.agentWallet);
202
+
203
+ if (!identityQHash || !agentId || !agentWallet) {
204
+ throw new Error('Runtime mount requires verified agent identity (agentId, agentWallet, identityQHash).');
205
+ }
206
+
207
+ const effectiveRuntime = resolveEffectiveRuntime(identity, delegation);
208
+ const deniedActions = delegation ? asStringArray(delegation.deniedActions) : [];
209
+ const allowedActions = delegation ? asStringArray(delegation.allowedActions) : undefined;
210
+ const requiresHumanApproval =
211
+ delegation?.runtimePolicy &&
212
+ typeof delegation.runtimePolicy === 'object' &&
213
+ delegation.runtimePolicy.requiresHumanApproval === true;
214
+
215
+ const capabilities = asStringArray(identity.capabilities);
216
+ const skills = Array.isArray(identity.skills) ? identity.skills : [];
217
+ const skillIds = skills
218
+ .map(skill =>
219
+ typeof skill === 'string' ? skill : asString(skill?.id || skill?.label)
220
+ )
221
+ .filter(Boolean);
222
+
223
+ const delegations = delegation ? [delegation] : [];
224
+ const activeDelegations = delegations.filter(row => !row.isExpired).length;
225
+
226
+ return {
227
+ schema: RUNTIME_MOUNT_SCHEMA,
228
+ mountedAt: new Date().toISOString(),
229
+ trust: {
230
+ identityQHash,
231
+ delegationQHash: delegationQHash || null,
232
+ identityProofUrl: `${PROOF_URL_BASE}${identityQHash}`,
233
+ delegationProofUrl: delegationQHash ? `${PROOF_URL_BASE}${delegationQHash}` : null
234
+ },
235
+ identity: {
236
+ agentId,
237
+ agentWallet,
238
+ agentLabel: asString(identity.agentLabel) || agentId,
239
+ agentType: asString(identity.agentType) || 'agent',
240
+ description: asString(identity.description) || undefined,
241
+ instructions: asString(identity.instructions) || undefined,
242
+ capabilities,
243
+ skills,
244
+ services: Array.isArray(identity.services) ? identity.services : undefined,
245
+ defaultRuntime:
246
+ identity.defaultRuntime && typeof identity.defaultRuntime === 'object'
247
+ ? identity.defaultRuntime
248
+ : undefined
249
+ },
250
+ delegation: delegation
251
+ ? {
252
+ controllerWallet: normalizeWallet(delegation.controllerWallet) || asString(delegation.controllerWallet),
253
+ scope: asString(delegation.scope) || undefined,
254
+ allowedActions,
255
+ deniedActions,
256
+ runtimePolicy: delegation.runtimePolicy,
257
+ expiresAt: delegation.expiresAt ?? null,
258
+ isExpired: Boolean(delegation.isExpired),
259
+ maxSpend: delegation.maxSpend,
260
+ instructions: asString(delegation.instructions) || undefined,
261
+ skills: Array.isArray(delegation.skills) ? delegation.skills : undefined,
262
+ provider: asString(delegation.provider) || undefined,
263
+ model: asString(delegation.model) || undefined
264
+ }
265
+ : null,
266
+ effectiveRuntime,
267
+ tools: Array.isArray(input.tools) ? input.tools : [],
268
+ secretBindings: Array.isArray(input.secretBindings) ? input.secretBindings : [],
269
+ memoryRefs: Array.isArray(input.memoryRefs) ? input.memoryRefs : undefined,
270
+ enforce: {
271
+ deniedActions,
272
+ ...(allowedActions?.length ? { allowedActions } : {}),
273
+ ...(requiresHumanApproval ? { requiresHumanApproval: true } : {})
274
+ },
275
+ contextPack: {
276
+ identityCount: 1,
277
+ delegationCount: delegations.length,
278
+ activeDelegations,
279
+ capabilitiesSummary: capabilities.slice(0, 32),
280
+ skillsSummary: skillIds.slice(0, 32)
281
+ }
282
+ };
283
+ }
284
+
285
+ /**
286
+ * @param {Record<string, unknown>} profileAgent
287
+ */
288
+ export function profileAgentToIdentitySeed(profileAgent) {
289
+ return {
290
+ agentId: profileAgent.agentId,
291
+ agentWallet: profileAgent.agentWallet,
292
+ agentLabel: profileAgent.agentLabel || profileAgent.name,
293
+ agentType: profileAgent.agentType || profileAgent.typeLabel,
294
+ description: profileAgent.description,
295
+ instructions: profileAgent.instructions,
296
+ capabilities: capabilitiesToArray(profileAgent.capabilities),
297
+ skills: Array.isArray(profileAgent.skills) ? profileAgent.skills : [],
298
+ services: Array.isArray(profileAgent.services) ? profileAgent.services : [],
299
+ identityQHash: profileAgent.identityQHash || profileAgent.qHash
300
+ };
301
+ }
302
+
303
+ /**
304
+ * @param {import('./runtime-mount.js').RuntimeBundle | Record<string, unknown>} value
305
+ */
306
+ export function isRuntimeBundle(value) {
307
+ return Boolean(value && typeof value === 'object' && value.schema === RUNTIME_MOUNT_SCHEMA);
308
+ }
309
+
310
+ /**
311
+ * Resolve mount via MCP (server tool first, then client assembly).
312
+ *
313
+ * @param {{
314
+ * callMcpTool: (args: { name: string, args?: Record<string, unknown>, accessKey?: string, sessionId?: string, signal?: AbortSignal }) => Promise<{ ok: boolean, payload?: unknown, error?: string }>,
315
+ * initializeMcp?: () => Promise<{ sessionId: string }>,
316
+ * accessKey: string,
317
+ * agentId?: string,
318
+ * agentWallet?: string,
319
+ * identityQHash?: string,
320
+ * signal?: AbortSignal
321
+ * }} input
322
+ */
323
+ export async function resolveRuntimeBundleFromMcp(input) {
324
+ const accessKey = asString(input.accessKey);
325
+ if (!accessKey) {
326
+ throw new Error('NEUS access key or authenticated MCP session is required for runtime mount.');
327
+ }
328
+
329
+ const selector = {
330
+ agentId: input.agentId,
331
+ agentWallet: input.agentWallet,
332
+ identityQHash: input.identityQHash
333
+ };
334
+ if (!selector.agentId && !selector.agentWallet && !selector.identityQHash) {
335
+ throw new Error('Provide agentId, agentWallet, or identityQHash.');
336
+ }
337
+
338
+ let sessionId = '';
339
+ if (input.initializeMcp) {
340
+ const init = await input.initializeMcp();
341
+ sessionId = init.sessionId || '';
342
+ }
343
+
344
+ const mountArgs = {
345
+ ...(selector.agentId ? { agentId: selector.agentId } : {}),
346
+ ...(selector.agentWallet ? { agentWallet: selector.agentWallet } : {}),
347
+ ...(selector.identityQHash ? { identityQHash: selector.identityQHash } : {})
348
+ };
349
+
350
+ const serverMount = await input.callMcpTool({
351
+ name: 'neus_agent_mount',
352
+ args: mountArgs,
353
+ accessKey,
354
+ sessionId,
355
+ signal: input.signal
356
+ });
357
+
358
+ if (serverMount.ok) {
359
+ const payload = serverMount.payload;
360
+ if (isRuntimeBundle(payload)) {
361
+ return /** @type {import('./runtime-mount.js').RuntimeBundle} */ (payload);
362
+ }
363
+ if (payload && typeof payload === 'object' && isRuntimeBundle(payload.data)) {
364
+ return /** @type {import('./runtime-mount.js').RuntimeBundle} */ (payload.data);
365
+ }
366
+ }
367
+
368
+ const me = await input.callMcpTool({
369
+ name: 'neus_me',
370
+ args: {},
371
+ accessKey,
372
+ sessionId,
373
+ signal: input.signal
374
+ });
375
+ if (!me.ok) {
376
+ throw new Error(me.error || 'Could not load profile context. Run `neus auth` and retry.');
377
+ }
378
+ const mePayload = /** @type {Record<string, unknown>} */ (me.payload || {});
379
+ if (mePayload.status === 'auth_required') {
380
+ throw new Error('Profile authentication required. Run `neus auth` or set NEUS_ACCESS_KEY.');
381
+ }
382
+
383
+ const principal = /** @type {Record<string, unknown>} */ (mePayload.principal || {});
384
+ const controllerWallet = normalizeWallet(principal.primaryAccount);
385
+ const profileAgents = Array.isArray(mePayload.agents) ? mePayload.agents : [];
386
+
387
+ let agentWallet = normalizeWallet(selector.agentWallet);
388
+ let agentId = asString(selector.agentId);
389
+
390
+ if (!agentWallet && agentId) {
391
+ const row = profileAgents.find(
392
+ row => asString(row.agentId).toLowerCase() === agentId.toLowerCase()
393
+ );
394
+ if (row) {
395
+ agentWallet = normalizeWallet(row.agentWallet);
396
+ }
397
+ }
398
+ if (!agentId && agentWallet) {
399
+ const row = profileAgents.find(row => normalizeWallet(row.agentWallet) === agentWallet);
400
+ if (row) agentId = asString(row.agentId);
401
+ }
402
+ if (!agentWallet && selector.identityQHash) {
403
+ const idProof = await input.callMcpTool({
404
+ name: 'neus_proofs_get',
405
+ args: { qHash: selector.identityQHash, verifierId: 'agent-identity' },
406
+ accessKey,
407
+ sessionId,
408
+ signal: input.signal
409
+ });
410
+ if (idProof.ok) {
411
+ const data = /** @type {Record<string, unknown>} */ (idProof.payload?.data || idProof.payload || {});
412
+ const proofs = Array.isArray(data.proofs) ? data.proofs : [];
413
+ const extracted = extractAgentContextFromProofs(proofs);
414
+ const identity = pickIdentity(extracted.identities, selector);
415
+ if (identity) {
416
+ agentWallet = normalizeWallet(identity.agentWallet);
417
+ agentId = asString(identity.agentId);
418
+ }
419
+ }
420
+ }
421
+
422
+ if (!agentWallet) {
423
+ throw new Error('Could not resolve agent wallet. Check agentId or link the agent on your profile.');
424
+ }
425
+
426
+ const [identityPage, delegationPage] = await Promise.all([
427
+ input.callMcpTool({
428
+ name: 'neus_proofs_get',
429
+ args: { identifier: agentWallet, verifierId: 'agent-identity', limit: 25 },
430
+ accessKey,
431
+ sessionId,
432
+ signal: input.signal
433
+ }),
434
+ controllerWallet
435
+ ? input.callMcpTool({
436
+ name: 'neus_proofs_get',
437
+ args: { identifier: controllerWallet, verifierId: 'agent-delegation', limit: 50 },
438
+ accessKey,
439
+ sessionId,
440
+ signal: input.signal
441
+ })
442
+ : Promise.resolve({ ok: false })
443
+ ]);
444
+
445
+ const identityProofs = identityPage.ok
446
+ ? /** @type {unknown[]} */ (
447
+ identityPage.payload?.data?.proofs || identityPage.payload?.proofs || []
448
+ )
449
+ : [];
450
+ const delegationProofs = delegationPage.ok
451
+ ? /** @type {unknown[]} */ (
452
+ delegationPage.payload?.data?.proofs || delegationPage.payload?.proofs || []
453
+ )
454
+ : [];
455
+
456
+ const idCtx = extractAgentContextFromProofs(identityProofs);
457
+ const delCtx = extractAgentContextFromProofs(delegationProofs);
458
+
459
+ let identity = pickIdentity(idCtx.identities, { ...selector, agentId, agentWallet });
460
+ if (!identity && profileAgents.length > 0) {
461
+ const row = profileAgents.find(
462
+ a =>
463
+ asString(a.agentId).toLowerCase() === agentId.toLowerCase() ||
464
+ normalizeWallet(a.agentWallet) === agentWallet
465
+ );
466
+ if (row) {
467
+ identity = { ...profileAgentToIdentitySeed(row), agentWallet, agentId: agentId || asString(row.agentId) };
468
+ }
469
+ }
470
+ if (!identity) {
471
+ throw new Error('Agent identity proof not found. Complete agent setup on neus.network first.');
472
+ }
473
+
474
+ const delegation = pickActiveDelegation(
475
+ delCtx.delegations,
476
+ controllerWallet,
477
+ agentWallet,
478
+ agentId || asString(identity.agentId)
479
+ );
480
+
481
+ return buildRuntimeBundle({
482
+ identity,
483
+ delegation,
484
+ identityQHash: asString(identity.qHash || selector.identityQHash),
485
+ delegationQHash: delegation ? asString(delegation.qHash) : null,
486
+ tools: [],
487
+ secretBindings: []
488
+ });
489
+ }
490
+
491
+ /**
492
+ * @param {import('./runtime-mount.js').RuntimeBundle | Record<string, unknown> | null | undefined} manifest
493
+ */
494
+ export function evaluateMountFileHealth(manifest) {
495
+ if (!manifest || manifest.schema !== RUNTIME_MOUNT_SCHEMA) {
496
+ return {
497
+ mountFileValid: false,
498
+ missingDelegation: true,
499
+ delegationExpired: false,
500
+ needsRefresh: true,
501
+ reason: 'missing_or_invalid'
502
+ };
503
+ }
504
+
505
+ const delegationQHash = asString(manifest.trust?.delegationQHash);
506
+ const missingDelegation = !delegationQHash;
507
+ const expiresAt = manifest.delegation?.expiresAt;
508
+ const delegationExpired =
509
+ Boolean(manifest.delegation?.isExpired) || isDelegationExpired(expiresAt);
510
+
511
+ return {
512
+ mountFileValid: true,
513
+ missingDelegation,
514
+ delegationExpired,
515
+ needsRefresh: missingDelegation || delegationExpired,
516
+ reason: delegationExpired
517
+ ? 'delegation_expired'
518
+ : missingDelegation
519
+ ? 'delegation_missing'
520
+ : null
521
+ };
522
+ }