panal-mcp 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/niveles.d.ts +1 -1
- package/dist/niveles.js +2 -2
- package/dist/server.js +43 -13
- package/package.json +2 -2
package/dist/niveles.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export declare function precioDeNivel(nivel: Nivel, symbol: string): string;
|
|
|
41
41
|
* agente que vende tres tamaños se veía con un precio suelto —el más barato— y
|
|
42
42
|
* el encargo grande se contrataba al precio del pequeño.
|
|
43
43
|
*/
|
|
44
|
-
export declare function renderNiveles(niveles: Nivel[], symbol: string): string[];
|
|
44
|
+
export declare function renderNiveles(niveles: Nivel[], symbol: string, enUsd?: (wei: bigint) => string): string[];
|
|
45
45
|
/**
|
|
46
46
|
* Los niveles en UNA línea, para las listas.
|
|
47
47
|
*
|
package/dist/niveles.js
CHANGED
|
@@ -47,13 +47,13 @@ export function precioDeNivel(nivel, symbol) {
|
|
|
47
47
|
* agente que vende tres tamaños se veía con un precio suelto —el más barato— y
|
|
48
48
|
* el encargo grande se contrataba al precio del pequeño.
|
|
49
49
|
*/
|
|
50
|
-
export function renderNiveles(niveles, symbol) {
|
|
50
|
+
export function renderNiveles(niveles, symbol, enUsd = () => '') {
|
|
51
51
|
if (niveles.length === 0)
|
|
52
52
|
return [];
|
|
53
53
|
return [
|
|
54
54
|
` Sizes (${niveles.length}) — pass the one the person picks as \`tier\` to panal_quote_hire:`,
|
|
55
55
|
...niveles.map((n) => {
|
|
56
|
-
const partes = [` · ${n.name ?? '(unnamed)'} — ${precioDeNivel(n, symbol)}`];
|
|
56
|
+
const partes = [` · ${n.name ?? '(unnamed)'} — ${precioDeNivel(n, symbol)}${enUsd(n.wei)}`];
|
|
57
57
|
if (n.description)
|
|
58
58
|
partes.push(n.description);
|
|
59
59
|
if (n.maxBriefChars)
|
package/dist/server.js
CHANGED
|
@@ -42,7 +42,7 @@ import { createInterface } from 'node:readline';
|
|
|
42
42
|
import { fileURLToPath } from 'node:url';
|
|
43
43
|
import { formatEther, formatUnits, isAddress, keccak256, parseEther, toBytes } from 'viem';
|
|
44
44
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
45
|
-
import { NATIVE_CURRENCY, TaskStatus, appendAttachmentsManifest, conEjemplosDeLaFicha, createPanalClient, downloadDeliveredFile, erc20Abi, leerEjemplosDeMetadata, matchAttachment, parseAttachmentsManifest, parseFilesManifest, stripFilesManifest, x402Currency, } from '@panal/sdk';
|
|
45
|
+
import { NATIVE_CURRENCY, TaskStatus, appendAttachmentsManifest, conEjemplosDeLaFicha, createPanalClient, downloadDeliveredFile, enDolares, formatearDolares, erc20Abi, leerEjemplosDeMetadata, matchAttachment, parseAttachmentsManifest, parseFilesManifest, stripFilesManifest, x402Currency, } from '@panal/sdk';
|
|
46
46
|
import { MAX_ADJUNTOS, capacidadesDeAgente, guardarDescarga, leerAdjuntoLocal, raizAdjuntos, raizDescargas, subirAdjunto, } from './adjuntos.js';
|
|
47
47
|
import { buscarNivel, lineaDeNiveles, nivelesDeAgente, precioDeNivel, renderNiveles, } from './niveles.js';
|
|
48
48
|
import { QuoteBook, SpendLedger, limitFor, limitsFromEnv } from './limits.js';
|
|
@@ -125,6 +125,31 @@ function symbolOf(currency) {
|
|
|
125
125
|
function fmt(amount, currency) {
|
|
126
126
|
return formatUnits(amount, esNativa(currency) ? 18 : (x402Currency(currency)?.decimals ?? 18));
|
|
127
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Los precios en dólares, leídos como mucho cada 30 s.
|
|
130
|
+
*
|
|
131
|
+
* Solo para enseñar: lo que se firma y se bloquea sigue siendo la cantidad en
|
|
132
|
+
* su moneda. MON sale de Pyth y $PANAL de la curva de nad.fun, los dos en la
|
|
133
|
+
* cadena (ver precios.ts del SDK). Si no se pueden leer, las respuestas salen
|
|
134
|
+
* sin dólares, igual que antes.
|
|
135
|
+
*/
|
|
136
|
+
let preciosCache = { leidos: 0, valor: null };
|
|
137
|
+
async function refrescarPrecios() {
|
|
138
|
+
if (Date.now() - preciosCache.leidos < 30_000)
|
|
139
|
+
return;
|
|
140
|
+
preciosCache = { leidos: Date.now(), valor: await panal.precios().catch(() => null) };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* « (≈ $0.60)» si hay un precio de menos de cinco minutos para esa moneda;
|
|
144
|
+
* nada si no. Las estables ya son dólares y no llevan nada.
|
|
145
|
+
*/
|
|
146
|
+
function usd(amount, currency) {
|
|
147
|
+
const sym = symbolOf(currency);
|
|
148
|
+
if (sym !== 'MON' && sym !== '$PANAL')
|
|
149
|
+
return '';
|
|
150
|
+
const d = enDolares(amount, sym, preciosCache.valor);
|
|
151
|
+
return d ? ` (≈ ${formatearDolares(d.usd)})` : '';
|
|
152
|
+
}
|
|
128
153
|
/** Las monedas que este servidor sabe manejar: las que tienen presupuesto. */
|
|
129
154
|
function monedasConocidas() {
|
|
130
155
|
return [...limits.porMoneda.keys()];
|
|
@@ -234,7 +259,7 @@ function trustLine(agent) {
|
|
|
234
259
|
return ` Trust: ${partes.join('; ')}`;
|
|
235
260
|
}
|
|
236
261
|
function renderAgent(agent, niveles = [], detallado = false) {
|
|
237
|
-
const price = `${formatEther(agent.pricePerTask)} ${symbolOf(agent.currency)}`;
|
|
262
|
+
const price = `${formatEther(agent.pricePerTask)} ${symbolOf(agent.currency)}${usd(agent.pricePerTask, agent.currency)}`;
|
|
238
263
|
const skills = agent.metadata.skills.length ? agent.metadata.skills.join(', ') : '(no skills declared)';
|
|
239
264
|
return [
|
|
240
265
|
`${agent.metadata.name || '(no name)'} — ${agent.address}`,
|
|
@@ -247,7 +272,9 @@ function renderAgent(agent, niveles = [], detallado = false) {
|
|
|
247
272
|
// Los niveles van DEBAJO del precio de tarea y no en su lugar: ese precio
|
|
248
273
|
// sigue siendo el que se bloquea si nadie elige tamaño, y el que cobran
|
|
249
274
|
// los agentes —casi todos— que no venden ninguno.
|
|
250
|
-
...(detallado
|
|
275
|
+
...(detallado
|
|
276
|
+
? renderNiveles(niveles, symbolOf(agent.currency), (wei) => usd(wei, agent.currency))
|
|
277
|
+
: [lineaDeNiveles(niveles, symbolOf(agent.currency))]),
|
|
251
278
|
` Skills: ${skills}`,
|
|
252
279
|
agent.metadata.description ? ` Description: ${agent.metadata.description}` : null,
|
|
253
280
|
agent.metadata.botUrl ? ` Endpoint: ${agent.metadata.botUrl}` : null,
|
|
@@ -262,7 +289,7 @@ function renderTask(task) {
|
|
|
262
289
|
`Task #${task.id} — ${TaskStatus[task.status] ?? task.status}`,
|
|
263
290
|
` Client: ${task.client}`,
|
|
264
291
|
` Agent: ${task.worker}`,
|
|
265
|
-
` Amount: ${formatEther(task.amount)} ${symbolOf(task.currency)}`,
|
|
292
|
+
` Amount: ${formatEther(task.amount)} ${symbolOf(task.currency)}${usd(task.amount, task.currency)}`,
|
|
266
293
|
` Deadline: ${deadline}`,
|
|
267
294
|
` Job hash: ${task.taskHash}`,
|
|
268
295
|
task.resultHash && !/^0x0+$/.test(task.resultHash) ? ` Result hash: ${task.resultHash}` : null,
|
|
@@ -335,6 +362,7 @@ const READ_TOOLS = [
|
|
|
335
362
|
},
|
|
336
363
|
},
|
|
337
364
|
handler: async (args) => {
|
|
365
|
+
await refrescarPrecios();
|
|
338
366
|
const found = await panal.searchAgents(str(args.query), {
|
|
339
367
|
includeInactive: args.include_inactive === true,
|
|
340
368
|
});
|
|
@@ -363,7 +391,7 @@ const READ_TOOLS = [
|
|
|
363
391
|
const address = str(args.address);
|
|
364
392
|
if (!address || !isAddress(address))
|
|
365
393
|
return 'I need a valid 0x address.';
|
|
366
|
-
const agent = await panal.getAgent(address);
|
|
394
|
+
const [agent] = await Promise.all([panal.getAgent(address), refrescarPrecios()]);
|
|
367
395
|
// El perfil es donde alguien mira el precio, así que aquí sí se paga la
|
|
368
396
|
// llamada al manifiesto del agente: enseñar solo el de tarea era la
|
|
369
397
|
// razón de que el precio por consulta fuese invisible desde aquí. En
|
|
@@ -409,7 +437,8 @@ const READ_TOOLS = [
|
|
|
409
437
|
const count = await panal.getTaskCount();
|
|
410
438
|
if (BigInt(id) >= count)
|
|
411
439
|
return `Task #${id} does not exist: there are ${count} so far.`;
|
|
412
|
-
|
|
440
|
+
const [task] = await Promise.all([panal.getTask(BigInt(id)), refrescarPrecios()]);
|
|
441
|
+
return renderTask(task);
|
|
413
442
|
},
|
|
414
443
|
},
|
|
415
444
|
{
|
|
@@ -433,7 +462,7 @@ const READ_TOOLS = [
|
|
|
433
462
|
return 'I need a valid 0x address for the agent.';
|
|
434
463
|
if (!prompt)
|
|
435
464
|
return 'I need the question.';
|
|
436
|
-
const agent = await panal.getAgent(address);
|
|
465
|
+
const [agent] = await Promise.all([panal.getAgent(address), refrescarPrecios()]);
|
|
437
466
|
const name = agent.metadata.name || address;
|
|
438
467
|
let quote;
|
|
439
468
|
try {
|
|
@@ -467,7 +496,7 @@ const READ_TOOLS = [
|
|
|
467
496
|
});
|
|
468
497
|
return [
|
|
469
498
|
`${name} charges per question:`,
|
|
470
|
-
` Price: ${fmt(amount, quote.asset)} ${symbol} for this one question`,
|
|
499
|
+
` Price: ${fmt(amount, quote.asset)} ${symbol}${usd(amount, quote.asset)} for this one question`,
|
|
471
500
|
` Paid to: ${quote.payTo}`,
|
|
472
501
|
quote.description ? ` Covers: ${quote.description}` : null,
|
|
473
502
|
'',
|
|
@@ -515,7 +544,7 @@ const READ_TOOLS = [
|
|
|
515
544
|
inputSchema: { type: 'object', properties: {} },
|
|
516
545
|
handler: async () => {
|
|
517
546
|
try {
|
|
518
|
-
const lista = await panal.listBoard();
|
|
547
|
+
const [lista] = await Promise.all([panal.listBoard(), refrescarPrecios()]);
|
|
519
548
|
if (!lista.length)
|
|
520
549
|
return 'The board is empty: no job is waiting to be taken right now.';
|
|
521
550
|
const ahora = Math.floor(Date.now() / 1000);
|
|
@@ -523,7 +552,7 @@ const READ_TOOLS = [
|
|
|
523
552
|
`${lista.length} job(s) on the board:`,
|
|
524
553
|
...lista.map((e) => {
|
|
525
554
|
const horas = Math.max(0, Math.floor((Number(e.deadline) - ahora) / 3600));
|
|
526
|
-
return (`\n#${e.taskId} — ${formatEther(e.amount)} ${symbolOf(e.currency)}, ${horas} h left\n` +
|
|
555
|
+
return (`\n#${e.taskId} — ${formatEther(e.amount)} ${symbolOf(e.currency)}${usd(e.amount, e.currency)}, ${horas} h left\n` +
|
|
527
556
|
` "${e.anuncio}"\n posted by ${e.cliente}`);
|
|
528
557
|
}),
|
|
529
558
|
'\nTo take one, use panal_claim_task with its number. Only an active registered agent can claim.',
|
|
@@ -561,7 +590,8 @@ const WRITE_TOOLS = [
|
|
|
561
590
|
// perdido — y también hay que preguntarlo moneda a moneda.
|
|
562
591
|
Promise.all(monedas.map((m) => panal.getPendingWithdrawal(address, m).catch(() => null))),
|
|
563
592
|
]);
|
|
564
|
-
|
|
593
|
+
await refrescarPrecios();
|
|
594
|
+
const importe = (v, m) => v === null ? 'could not be read' : `${fmt(v, m)}${v > 0n ? usd(v, m) : ''}`;
|
|
565
595
|
const balances = monedas.map((m, i) => ` ${symbolOf(m)}: ${importe(saldos[i] ?? null, m)}`);
|
|
566
596
|
// Un presupuesto por moneda: MON y $PANAL no valen lo mismo y no hay
|
|
567
597
|
// tipo de cambio, así que se enseñan por separado o no se entienden.
|
|
@@ -630,7 +660,7 @@ const WRITE_TOOLS = [
|
|
|
630
660
|
if (rutas.length > MAX_ADJUNTOS) {
|
|
631
661
|
return `That is ${rutas.length} attachments and the limit is ${MAX_ADJUNTOS} per job.`;
|
|
632
662
|
}
|
|
633
|
-
const agent = await panal.getAgent(address);
|
|
663
|
+
const [agent] = await Promise.all([panal.getAgent(address), refrescarPrecios()]);
|
|
634
664
|
if (!agent.active)
|
|
635
665
|
return `${agent.metadata.name || address} is delisted and does not take jobs.`;
|
|
636
666
|
// ---- Adjuntos -------------------------------------------------------
|
|
@@ -775,7 +805,7 @@ const WRITE_TOOLS = [
|
|
|
775
805
|
});
|
|
776
806
|
return [
|
|
777
807
|
`Quote for ${quote.agentName}:`,
|
|
778
|
-
` Price: ${formatEther(amount)} ${symbol}${elegido ? ` — size "${elegido.name}"` : ''}`,
|
|
808
|
+
` Price: ${formatEther(amount)} ${symbol}${usd(amount, agent.currency)}${elegido ? ` — size "${elegido.name}"` : ''}`,
|
|
779
809
|
// Los otros tamaños se enseñan SIEMPRE que existan, también cuando ya
|
|
780
810
|
// se eligió uno: quien está leyendo esto es quien paga, y enterarse de
|
|
781
811
|
// que había uno más grande después de contratar no sirve de nada.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "panal-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Servidor MCP de Panal: busca y contrata agentes de IA on-chain desde Claude",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"node": ">=20"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@panal/sdk": "^0.
|
|
33
|
+
"@panal/sdk": "^0.21.0",
|
|
34
34
|
"viem": "^2.21.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|