create-panal-agent 0.21.0 → 0.22.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/README.md +36 -5
- package/package.json +1 -1
- package/template/_package.json +1 -1
- package/template/src/server.ts +19 -0
- package/template/src/traduccion.ts +21 -2
package/README.md
CHANGED
|
@@ -115,10 +115,23 @@ por eso el servidor trae **x402**: `POST /x402/ask`, cobro por llamada.
|
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
117
|
X402_PRICE=0.05 # en el .env. Vacío = solo encargos por escrow.
|
|
118
|
+
X402_TOKEN=USDC # PANAL (por defecto), MON, GHO, USDC o AUSD
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
El precio se lee **en la moneda que elijas**, con sus decimales: `0.05` en USDC son cinco
|
|
122
|
+
céntimos, en $PANAL son 0,05 $PANAL. La lista de monedas es fija y viene del SDK
|
|
123
|
+
(`x402Currencies()`): con cualquier otra cosa en `X402_TOKEN` el servidor lo dice al
|
|
124
|
+
arrancar y el cobro por llamada queda apagado, en vez de cobrar en algo que ningún cliente
|
|
125
|
+
de Panal acepta pagar.
|
|
126
|
+
|
|
127
|
+
Cómo se paga depende de la moneda:
|
|
128
|
+
|
|
129
|
+
- **$PANAL, GHO, USDC y AUSD** tienen `permit` (EIP-2612): el cliente firma una
|
|
130
|
+
autorización, sin gas, y tu servidor ejecuta el cobro antes de contestar.
|
|
131
|
+
- **MON** no tiene `permit`, así que el cliente **manda** la transferencia y paga él su gas.
|
|
132
|
+
La cotización pide el precio más unas unidades al azar (menos de una millonésima de
|
|
133
|
+
céntimo), y tu servidor comprueba en la cadena que llegó *esa* transferencia exacta, a tu
|
|
134
|
+
dirección y dentro de plazo. Cada transacción se acepta una sola vez.
|
|
122
135
|
|
|
123
136
|
Estas llamadas **sí tienen memoria**, y las del escrow no. Quién habla lo dice el pago: la
|
|
124
137
|
conversación se guarda por la dirección del pagador, y esa dirección no la afirma nadie —
|
|
@@ -144,9 +157,10 @@ buscando `video`. Un agente de código pagándole a uno de vídeo entrega algo q
|
|
|
144
157
|
correcto —pagó, le contestaron, ancló— y nadie ve un error; solo que el resultado es peor y
|
|
145
158
|
el dinero se fue.
|
|
146
159
|
|
|
147
|
-
El presupuesto va en la moneda de
|
|
148
|
-
|
|
149
|
-
número.
|
|
160
|
+
El presupuesto va en la moneda de `X402_TOKEN`, **no** es un porcentaje de lo que cobras:
|
|
161
|
+
tus encargos pueden ir en MON y tus preguntas en USDC, y convertir una cosa en otra a ojo
|
|
162
|
+
sería inventarse el número. Por eso tu agente solo subcontrata a quien cobre en esa misma
|
|
163
|
+
moneda. Ponlo por debajo de tu `X402_PRICE` —un tercio es un comienzo sano—: igual o por
|
|
150
164
|
encima, cada encargo en el que delegues te deja a cero y encima pagas el gas, que es
|
|
151
165
|
castigar exactamente lo que quieres que tu agente haga.
|
|
152
166
|
|
|
@@ -195,6 +209,21 @@ El escaparate habla diez idiomas; tu descripción, uno. `GET /agent.json?lang=fr
|
|
|
195
209
|
propio modelo. Nadie tiene que aprender un formato nuevo: se siguen leyendo `description` y
|
|
196
210
|
`tiers[].name`. Sin `LLM_API_KEY` no se cae nada — se sirve la ficha original.
|
|
197
211
|
|
|
212
|
+
### Encargos de ejemplo
|
|
213
|
+
|
|
214
|
+
Quien te contrata ve, debajo de la caja del encargo, hasta **tres ejemplos de lo que te
|
|
215
|
+
puede pedir**, escritos por ti. Se ponen desde el panel de [panal.lat](https://panal.lat)
|
|
216
|
+
o desde la app, en «Editar ficha», y se guardan en tu ficha on-chain como
|
|
217
|
+
`ejemplo:<texto>`. No hay que tocar código: tu servidor los relee de la cadena cada cinco
|
|
218
|
+
minutos y los sirve en `/agent.json` (`examples`), traducidos como tu descripción.
|
|
219
|
+
|
|
220
|
+
Escribe lo que de verdad te piden, en una frase. Al pulsarlo, el cliente lo copia en la caja
|
|
221
|
+
y lo completa con lo suyo —el contrato, el texto, los datos—: un ejemplo no es un encargo
|
|
222
|
+
listo para pagar.
|
|
223
|
+
|
|
224
|
+
`npm run register` reescribe la ficha entera desde `PERFIL` y **borra** los niveles y los
|
|
225
|
+
ejemplos que pusiste desde el panel. Si ya estás registrado, edita la ficha desde el panel.
|
|
226
|
+
|
|
198
227
|
### Archivos, en las dos direcciones
|
|
199
228
|
|
|
200
229
|
Tu agente **entrega** archivos devolviendo `{ text, files }` desde `handleTask`. El motor calcula el hash de cada uno y lo cuela en el texto antes de anclarlo, así que el cliente puede demostrar que lo que se baja es exactamente lo que entregaste. Un enlace a secas no daría eso.
|
|
@@ -241,3 +270,5 @@ Y pregúntale a Claude *«¿qué agentes hay en Panal?»*. Si sales, cualquiera
|
|
|
241
270
|
## Licencia
|
|
242
271
|
|
|
243
272
|
MIT — [código en GitHub](https://github.com/AgentHiv/Panal).
|
|
273
|
+
|
|
274
|
+
La licencia cubre el código, no la marca. El nombre **Panal** y su logo son marcas de Panal y Kreno y no entran en la MIT: un fork es bienvenido, pero necesita un nombre y un logo propios.
|
package/package.json
CHANGED
package/template/_package.json
CHANGED
package/template/src/server.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
assertCanServe,
|
|
32
32
|
buildQuote,
|
|
33
33
|
createPanalClient,
|
|
34
|
+
leerEjemplosDeMetadata,
|
|
34
35
|
leerNiveles,
|
|
35
36
|
leerNivelesDeMetadata,
|
|
36
37
|
normalizarIdioma,
|
|
@@ -269,6 +270,15 @@ const NIVELES_DEL_CODIGO = NIVELES_OK;
|
|
|
269
270
|
*/
|
|
270
271
|
let FICHA_TEXTO: { name: string; description: string } = { name: '', description: '' };
|
|
271
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Los encargos de ejemplo que este agente propone al contratarle.
|
|
275
|
+
*
|
|
276
|
+
* Viven en la cadena (`ejemplo:` en la ficha) y se editan desde el panel de la
|
|
277
|
+
* web o desde la app, sin tocar este código. Se releen con los niveles, cada
|
|
278
|
+
* cinco minutos, y se sirven en `/agent.json` traducidos como la descripción.
|
|
279
|
+
*/
|
|
280
|
+
let EJEMPLOS_OK: string[] = [];
|
|
281
|
+
|
|
272
282
|
async function refrescarNiveles(): Promise<void> {
|
|
273
283
|
try {
|
|
274
284
|
const ficha = await panal.getAgent(account.address);
|
|
@@ -276,6 +286,7 @@ async function refrescarNiveles(): Promise<void> {
|
|
|
276
286
|
name: ficha.metadata.name,
|
|
277
287
|
description: ficha.metadata.description,
|
|
278
288
|
};
|
|
289
|
+
EJEMPLOS_OK = leerEjemplosDeMetadata(ficha.metadataURI);
|
|
279
290
|
const enCadena = leerNivelesDeMetadata(ficha.metadataURI);
|
|
280
291
|
const antes = NIVELES_OK.map((n) => `${n.wei}:${n.name ?? ''}`).join('|');
|
|
281
292
|
NIVELES_OK = enCadena.length > 0 ? enCadena : NIVELES_DEL_CODIGO;
|
|
@@ -1318,6 +1329,7 @@ const server = createServer((req, res) => {
|
|
|
1318
1329
|
*/
|
|
1319
1330
|
const idioma = normalizarIdioma(url.searchParams.get('lang'));
|
|
1320
1331
|
const nivelesFicha = NIVELES_OK.map(comoFicha);
|
|
1332
|
+
let ejemplos = EJEMPLOS_OK;
|
|
1321
1333
|
let descripcion = FICHA_TEXTO.description;
|
|
1322
1334
|
/**
|
|
1323
1335
|
* En qué idioma va lo que se sirve, y `null` si va en el original.
|
|
@@ -1335,6 +1347,9 @@ const server = createServer((req, res) => {
|
|
|
1335
1347
|
const frases = {
|
|
1336
1348
|
description: descripcion,
|
|
1337
1349
|
tiers: NIVELES_OK.map((n) => ({ name: n.name ?? '', description: n.description ?? '' })),
|
|
1350
|
+
// Sin campo si no hay: la huella de la ficha no cambia y lo que ya
|
|
1351
|
+
// estaba traducido sigue valiendo.
|
|
1352
|
+
...(EJEMPLOS_OK.length > 0 ? { examples: EJEMPLOS_OK } : {}),
|
|
1338
1353
|
};
|
|
1339
1354
|
const traducido = frasesGuardadas(frases, idioma, DATA_DIR);
|
|
1340
1355
|
if (!traducido) pedirTraduccion(frases, idioma, LLM_FICHA, DATA_DIR);
|
|
@@ -1349,6 +1364,7 @@ const server = createServer((req, res) => {
|
|
|
1349
1364
|
if (destino.name && t.name) destino.name = t.name;
|
|
1350
1365
|
if (destino.description && t.description) destino.description = t.description;
|
|
1351
1366
|
});
|
|
1367
|
+
if (traducido.examples?.length === EJEMPLOS_OK.length) ejemplos = traducido.examples;
|
|
1352
1368
|
}
|
|
1353
1369
|
}
|
|
1354
1370
|
const x402 =
|
|
@@ -1418,6 +1434,9 @@ const server = createServer((req, res) => {
|
|
|
1418
1434
|
// Los niveles, sólo si este agente vende alguno. Ausente significa que
|
|
1419
1435
|
// no los ofrece, y quien lee NO debe inventárselos a partir del precio.
|
|
1420
1436
|
...(nivelesFicha.length > 0 ? { tiers: nivelesFicha } : {}),
|
|
1437
|
+
// Los encargos de ejemplo, en el mismo idioma que la descripción. Los
|
|
1438
|
+
// escribe su dueño en la ficha de la cadena; ausente es que no hay.
|
|
1439
|
+
...(ejemplos.length > 0 ? { examples: ejemplos } : {}),
|
|
1421
1440
|
// ALIAS ANTIGUO, en la raíz. Aquí es donde esta plantilla lo publicaba
|
|
1422
1441
|
// antes, y hay clientes ahí fuera que solo miran este sitio. Se sirve
|
|
1423
1442
|
// por compatibilidad y desaparecerá; lo que se lee es `endpoints`.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Aquí `GET /agent.json?lang=fr` devuelve tu MISMA ficha con las frases en
|
|
14
14
|
* francés. Nadie tiene que aprender un formato nuevo: los lectores siguen
|
|
15
|
-
* mirando `description
|
|
15
|
+
* mirando `description`, `tiers[].name` y `examples`, solo que traducidos.
|
|
16
16
|
*
|
|
17
17
|
* QUÉ CUESTA, QUE ES LA PREGUNTA DE VERDAD
|
|
18
18
|
*
|
|
@@ -65,6 +65,12 @@ export interface Frases {
|
|
|
65
65
|
description: string;
|
|
66
66
|
/** Nombre y descripción de cada nivel, en el orden en que van en la ficha. */
|
|
67
67
|
tiers: { name: string; description: string }[];
|
|
68
|
+
/**
|
|
69
|
+
* Los encargos de ejemplo, en su orden. Sin campo cuando no hay ninguno, y
|
|
70
|
+
* a propósito: así la huella de una ficha sin ejemplos es la de siempre y
|
|
71
|
+
* sus traducciones ya guardadas siguen valiendo.
|
|
72
|
+
*/
|
|
73
|
+
examples?: string[];
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
/** Tope de la respuesta que se acepta del modelo, por frase. */
|
|
@@ -144,7 +150,20 @@ function validar(v: unknown, original: Frases | null): Frases | null {
|
|
|
144
150
|
if (typeof name !== 'string' || typeof d !== 'string') return null;
|
|
145
151
|
salida.push({ name: name.trim().slice(0, MAX_FRASE), description: d.trim().slice(0, MAX_FRASE) });
|
|
146
152
|
}
|
|
147
|
-
|
|
153
|
+
// Los ejemplos, con la misma regla que los niveles: los mismos en número o
|
|
154
|
+
// se descarta todo. Si el original no tiene, no se esperan.
|
|
155
|
+
const crudos = (v as Record<string, unknown>).examples;
|
|
156
|
+
let examples: string[] = [];
|
|
157
|
+
if ((original?.examples?.length ?? 0) > 0 || crudos !== undefined) {
|
|
158
|
+
if (!Array.isArray(crudos) || crudos.some((e) => typeof e !== 'string')) return null;
|
|
159
|
+
if (original && crudos.length !== (original.examples?.length ?? 0)) return null;
|
|
160
|
+
examples = (crudos as string[]).map((e) => e.trim().slice(0, MAX_FRASE));
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
description: description.trim().slice(0, MAX_FRASE),
|
|
164
|
+
tiers: salida,
|
|
165
|
+
...(examples.length > 0 ? { examples } : {}),
|
|
166
|
+
};
|
|
148
167
|
}
|
|
149
168
|
|
|
150
169
|
/** El JSON que venga, aunque llegue envuelto en un bloque de markdown. */
|