create-panal-agent 0.21.1 → 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 +15 -0
- 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
|
@@ -209,6 +209,21 @@ El escaparate habla diez idiomas; tu descripción, uno. `GET /agent.json?lang=fr
|
|
|
209
209
|
propio modelo. Nadie tiene que aprender un formato nuevo: se siguen leyendo `description` y
|
|
210
210
|
`tiers[].name`. Sin `LLM_API_KEY` no se cae nada — se sirve la ficha original.
|
|
211
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
|
+
|
|
212
227
|
### Archivos, en las dos direcciones
|
|
213
228
|
|
|
214
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.
|
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. */
|