@panal/sdk 0.15.0 → 0.17.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/agent-card.d.ts +30 -0
- package/dist/client.js +4 -4
- package/dist/idiomas.d.ts +69 -0
- package/dist/idiomas.js +88 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +12 -0
- package/dist/net.d.ts +15 -0
- package/dist/net.js +17 -0
- package/dist/niveles.d.ts +133 -0
- package/dist/niveles.js +256 -0
- package/dist/tipo.d.ts +58 -0
- package/dist/tipo.js +77 -0
- package/dist/types.js +11 -0
- package/package.json +2 -2
package/dist/agent-card.d.ts
CHANGED
|
@@ -98,6 +98,14 @@ export interface FichaNivel {
|
|
|
98
98
|
/** Y el de todos los adjuntos juntos. */
|
|
99
99
|
maxAttachCharsTotal?: number;
|
|
100
100
|
}
|
|
101
|
+
/** Dónde deja el cliente los bytes de lo que su encargo anuncia. */
|
|
102
|
+
export interface FichaPostAttachment {
|
|
103
|
+
method?: 'POST';
|
|
104
|
+
/** Ruta relativa, con `:taskId` dentro. */
|
|
105
|
+
path?: string;
|
|
106
|
+
/** Tope por archivo, en bytes. */
|
|
107
|
+
maxAttachmentBytes?: number;
|
|
108
|
+
}
|
|
101
109
|
export interface AgentCard {
|
|
102
110
|
/** La dirección on-chain que este dominio declara suya. Es lo que verifica. */
|
|
103
111
|
agent?: Address;
|
|
@@ -108,6 +116,18 @@ export interface AgentCard {
|
|
|
108
116
|
chainId?: number;
|
|
109
117
|
name?: string;
|
|
110
118
|
description?: string;
|
|
119
|
+
/**
|
|
120
|
+
* El idioma en el que va este `description` y estos `tiers`, si se pidió con
|
|
121
|
+
* `?lang=` y el agente pudo traducirlos.
|
|
122
|
+
*
|
|
123
|
+
* AUSENTE NO ES «está en inglés»: es que va en el idioma en que su dueño lo
|
|
124
|
+
* escribió, aunque hayas pedido otro. Hay que mirarlo antes de guardar una
|
|
125
|
+
* ficha como si fuera una traducción, porque la traducción se encarga por
|
|
126
|
+
* detrás: pedirla antes de que esté lista devuelve la ficha original con un
|
|
127
|
+
* 200 impecable. Un indexador que no lo mirara guardaría el texto original
|
|
128
|
+
* como las diez traducciones y no volvería a por ellas nunca.
|
|
129
|
+
*/
|
|
130
|
+
lang?: string;
|
|
111
131
|
skills?: string[];
|
|
112
132
|
price?: {
|
|
113
133
|
amountWei?: string;
|
|
@@ -135,6 +155,16 @@ export interface AgentCard {
|
|
|
135
155
|
postBrief?: FichaPostBrief;
|
|
136
156
|
getResult?: FichaGetResult;
|
|
137
157
|
x402Ask?: FichaX402;
|
|
158
|
+
/**
|
|
159
|
+
* Dónde subir los archivos que el encargo anuncia, si los acepta.
|
|
160
|
+
*
|
|
161
|
+
* AUSENTE ES «no los acepta». Importa porque el manifiesto de adjuntos
|
|
162
|
+
* viaja DENTRO del brief —dentro de lo que se hashea al pagar—, así que un
|
|
163
|
+
* agente sin esta ruta acepta el encargo, trabaja sin los archivos,
|
|
164
|
+
* entrega y cobra sin que nada dé error. Por eso el cliente pregunta antes
|
|
165
|
+
* de ofrecer el clip; ver `leerCapacidades` en la web.
|
|
166
|
+
*/
|
|
167
|
+
postAttachment?: FichaPostAttachment;
|
|
138
168
|
indexer?: string | null;
|
|
139
169
|
};
|
|
140
170
|
/** Alias ANTIGUO de `endpoints.x402Ask`. Se lee; no se escribe. */
|
package/dist/client.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { createPublicClient, createWalletClient, formatEther, getAddress, http, keccak256, toBytes } from 'viem';
|
|
19
19
|
import { erc20Abi, escrowAbi, namesAbi, registryAbi } from './abis.js';
|
|
20
20
|
import { leerX402 } from './agent-card.js';
|
|
21
|
-
import { assertPublicUrl, fetchLimited } from './net.js';
|
|
21
|
+
import { assertPublicUrl, fetchLimited, rutaDeAgente } from './net.js';
|
|
22
22
|
import { X402Error, payAndAsk, quoteAsk } from './x402.js';
|
|
23
23
|
import { descend, newEnvelope, remainingBudget } from './envelope.js';
|
|
24
24
|
import { NATIVE_CURRENCY, addressesFor, chainFor } from './chains.js';
|
|
@@ -780,7 +780,7 @@ export class PanalClient {
|
|
|
780
780
|
if (!base)
|
|
781
781
|
throw new X402Error(`El agente ${agent} no publica endpoint en su metadata.`);
|
|
782
782
|
try {
|
|
783
|
-
const url = await assertPublicUrl(
|
|
783
|
+
const url = await assertPublicUrl(rutaDeAgente(base, 'agent.json'), options);
|
|
784
784
|
const res = await fetchLimited(url, { timeoutMs: 10_000 });
|
|
785
785
|
if (res.status === 200) {
|
|
786
786
|
// `leerX402` entiende las dos formas. Antes esto solo miraba
|
|
@@ -791,13 +791,13 @@ export class PanalClient {
|
|
|
791
791
|
if (anunciado?.url)
|
|
792
792
|
return anunciado.url;
|
|
793
793
|
if (anunciado?.path)
|
|
794
|
-
return
|
|
794
|
+
return rutaDeAgente(base, anunciado.path);
|
|
795
795
|
}
|
|
796
796
|
}
|
|
797
797
|
catch {
|
|
798
798
|
/* sin tarjeta o ilegible: se cae a la convención */
|
|
799
799
|
}
|
|
800
|
-
return
|
|
800
|
+
return rutaDeAgente(base, 'x402/ask');
|
|
801
801
|
}
|
|
802
802
|
/** Retira lo acreditado en una moneda (patrón pull payment). */
|
|
803
803
|
async withdraw(currency = NATIVE_CURRENCY) {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal — la ficha de un agente en el idioma de quien la lee.
|
|
3
|
+
*
|
|
4
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* EL PROBLEMA
|
|
6
|
+
*
|
|
7
|
+
* El marketplace habla diez idiomas. Los AGENTES no: su nombre, su descripción
|
|
8
|
+
* y sus niveles son texto libre que escribió una persona en el suyo, y ese
|
|
9
|
+
* texto sale igual en las diez versiones del escaparate. Hoy en mainnet hay
|
|
10
|
+
* fichas que enseñan las dos cosas a la vez —la descripción en inglés y los
|
|
11
|
+
* niveles en español, en la misma tarjeta—, porque una la escribió el registro
|
|
12
|
+
* y los otros el código del bot.
|
|
13
|
+
*
|
|
14
|
+
* QUIÉN TRADUCE
|
|
15
|
+
*
|
|
16
|
+
* El propio agente. No hay un traductor de Panal en medio, por dos razones que
|
|
17
|
+
* no son de gusto:
|
|
18
|
+
*
|
|
19
|
+
* - Un servicio central que traduzca todas las fichas es un servidor que hay
|
|
20
|
+
* que pagar, mantener y del que pasa a depender el escaparate. Panal no
|
|
21
|
+
* tiene ninguno: el catálogo se puede reconstruir leyendo la cadena.
|
|
22
|
+
* - El agente YA es un modelo. Traducir cuatro frases suyas es la llamada más
|
|
23
|
+
* barata que va a hacer en su vida, y la paga quien se beneficia.
|
|
24
|
+
*
|
|
25
|
+
* Se pide con `?lang=`, y lo que vuelve es la MISMA ficha con los campos de
|
|
26
|
+
* texto traducidos. Así ningún lector cambia: `leerNiveles`, `leerX402` y todo
|
|
27
|
+
* lo demás siguen leyendo los mismos campos.
|
|
28
|
+
*
|
|
29
|
+
* LO QUE NO SE TRADUCE
|
|
30
|
+
*
|
|
31
|
+
* El nombre del agente. «LexPanal» no significa nada en francés y traducirlo
|
|
32
|
+
* sería inventarle otro nombre a alguien, además de romper toda referencia
|
|
33
|
+
* escrita a él. Se traduce lo que es una frase: la descripción y el nombre y la
|
|
34
|
+
* descripción de cada nivel, que sí describen —«Un archivo», «El repositorio»—
|
|
35
|
+
* y que hoy son lo único que un francés no puede leer.
|
|
36
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Los idiomas del marketplace, en códigos ISO 639-1.
|
|
40
|
+
*
|
|
41
|
+
* Es la MISMA lista que `src/i18n/locales` de la web. Si allí entra uno nuevo,
|
|
42
|
+
* entra aquí, o los agentes no sabrán que se lo pueden pedir.
|
|
43
|
+
*/
|
|
44
|
+
export declare const IDIOMAS: readonly ["ar", "bn", "en", "es", "fr", "hi", "pt", "ru", "ur", "zh"];
|
|
45
|
+
export type Idioma = (typeof IDIOMAS)[number];
|
|
46
|
+
/**
|
|
47
|
+
* `'fr-CA'` → `'fr'`, `'klingon'` → `null`.
|
|
48
|
+
*
|
|
49
|
+
* El navegador dice `es-419` y `zh-Hans`, no `es` y `zh`. Quedarse con la
|
|
50
|
+
* primera parte es lo que hace que un mexicano y un argentino vean lo mismo en
|
|
51
|
+
* vez de caer los dos al inglés.
|
|
52
|
+
*/
|
|
53
|
+
export declare function normalizarIdioma(v: unknown): Idioma | null;
|
|
54
|
+
/**
|
|
55
|
+
* La URL de la ficha de un agente en un idioma.
|
|
56
|
+
*
|
|
57
|
+
* Sin idioma reconocible se pide la ficha de siempre, SIN parámetro: un agente
|
|
58
|
+
* viejo que no sepa de esto contesta igual, y uno nuevo no gasta una traducción
|
|
59
|
+
* en un código que no existe.
|
|
60
|
+
*/
|
|
61
|
+
export declare function fichaEnIdioma(botUrl: string, idioma: unknown): string;
|
|
62
|
+
/**
|
|
63
|
+
* Cómo se llama cada idioma EN ese idioma.
|
|
64
|
+
*
|
|
65
|
+
* Para pedirle una traducción a un modelo, que entiende mucho mejor «français»
|
|
66
|
+
* que «fr». Y de paso es la lista que enseñaría un selector, si algún día hace
|
|
67
|
+
* falta uno aquí.
|
|
68
|
+
*/
|
|
69
|
+
export declare const NOMBRE_IDIOMA: Record<Idioma, string>;
|
package/dist/idiomas.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal — la ficha de un agente en el idioma de quien la lee.
|
|
3
|
+
*
|
|
4
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* EL PROBLEMA
|
|
6
|
+
*
|
|
7
|
+
* El marketplace habla diez idiomas. Los AGENTES no: su nombre, su descripción
|
|
8
|
+
* y sus niveles son texto libre que escribió una persona en el suyo, y ese
|
|
9
|
+
* texto sale igual en las diez versiones del escaparate. Hoy en mainnet hay
|
|
10
|
+
* fichas que enseñan las dos cosas a la vez —la descripción en inglés y los
|
|
11
|
+
* niveles en español, en la misma tarjeta—, porque una la escribió el registro
|
|
12
|
+
* y los otros el código del bot.
|
|
13
|
+
*
|
|
14
|
+
* QUIÉN TRADUCE
|
|
15
|
+
*
|
|
16
|
+
* El propio agente. No hay un traductor de Panal en medio, por dos razones que
|
|
17
|
+
* no son de gusto:
|
|
18
|
+
*
|
|
19
|
+
* - Un servicio central que traduzca todas las fichas es un servidor que hay
|
|
20
|
+
* que pagar, mantener y del que pasa a depender el escaparate. Panal no
|
|
21
|
+
* tiene ninguno: el catálogo se puede reconstruir leyendo la cadena.
|
|
22
|
+
* - El agente YA es un modelo. Traducir cuatro frases suyas es la llamada más
|
|
23
|
+
* barata que va a hacer en su vida, y la paga quien se beneficia.
|
|
24
|
+
*
|
|
25
|
+
* Se pide con `?lang=`, y lo que vuelve es la MISMA ficha con los campos de
|
|
26
|
+
* texto traducidos. Así ningún lector cambia: `leerNiveles`, `leerX402` y todo
|
|
27
|
+
* lo demás siguen leyendo los mismos campos.
|
|
28
|
+
*
|
|
29
|
+
* LO QUE NO SE TRADUCE
|
|
30
|
+
*
|
|
31
|
+
* El nombre del agente. «LexPanal» no significa nada en francés y traducirlo
|
|
32
|
+
* sería inventarle otro nombre a alguien, además de romper toda referencia
|
|
33
|
+
* escrita a él. Se traduce lo que es una frase: la descripción y el nombre y la
|
|
34
|
+
* descripción de cada nivel, que sí describen —«Un archivo», «El repositorio»—
|
|
35
|
+
* y que hoy son lo único que un francés no puede leer.
|
|
36
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
37
|
+
*/
|
|
38
|
+
/**
|
|
39
|
+
* Los idiomas del marketplace, en códigos ISO 639-1.
|
|
40
|
+
*
|
|
41
|
+
* Es la MISMA lista que `src/i18n/locales` de la web. Si allí entra uno nuevo,
|
|
42
|
+
* entra aquí, o los agentes no sabrán que se lo pueden pedir.
|
|
43
|
+
*/
|
|
44
|
+
export const IDIOMAS = ['ar', 'bn', 'en', 'es', 'fr', 'hi', 'pt', 'ru', 'ur', 'zh'];
|
|
45
|
+
/**
|
|
46
|
+
* `'fr-CA'` → `'fr'`, `'klingon'` → `null`.
|
|
47
|
+
*
|
|
48
|
+
* El navegador dice `es-419` y `zh-Hans`, no `es` y `zh`. Quedarse con la
|
|
49
|
+
* primera parte es lo que hace que un mexicano y un argentino vean lo mismo en
|
|
50
|
+
* vez de caer los dos al inglés.
|
|
51
|
+
*/
|
|
52
|
+
export function normalizarIdioma(v) {
|
|
53
|
+
if (typeof v !== 'string')
|
|
54
|
+
return null;
|
|
55
|
+
const base = v.trim().toLowerCase().split(/[-_]/)[0];
|
|
56
|
+
return IDIOMAS.includes(base ?? '') ? base : null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* La URL de la ficha de un agente en un idioma.
|
|
60
|
+
*
|
|
61
|
+
* Sin idioma reconocible se pide la ficha de siempre, SIN parámetro: un agente
|
|
62
|
+
* viejo que no sepa de esto contesta igual, y uno nuevo no gasta una traducción
|
|
63
|
+
* en un código que no existe.
|
|
64
|
+
*/
|
|
65
|
+
export function fichaEnIdioma(botUrl, idioma) {
|
|
66
|
+
const base = `${botUrl.replace(/\/+$/, '')}/agent.json`;
|
|
67
|
+
const lang = normalizarIdioma(idioma);
|
|
68
|
+
return lang ? `${base}?lang=${lang}` : base;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Cómo se llama cada idioma EN ese idioma.
|
|
72
|
+
*
|
|
73
|
+
* Para pedirle una traducción a un modelo, que entiende mucho mejor «français»
|
|
74
|
+
* que «fr». Y de paso es la lista que enseñaría un selector, si algún día hace
|
|
75
|
+
* falta uno aquí.
|
|
76
|
+
*/
|
|
77
|
+
export const NOMBRE_IDIOMA = {
|
|
78
|
+
ar: 'العربية (Arabic)',
|
|
79
|
+
bn: 'বাংলা (Bengali)',
|
|
80
|
+
en: 'English',
|
|
81
|
+
es: 'español (Spanish)',
|
|
82
|
+
fr: 'français (French)',
|
|
83
|
+
hi: 'हिन्दी (Hindi)',
|
|
84
|
+
pt: 'português (Portuguese)',
|
|
85
|
+
ru: 'русский (Russian)',
|
|
86
|
+
ur: 'اردو (Urdu)',
|
|
87
|
+
zh: '中文 (Chinese, simplified)',
|
|
88
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -39,4 +39,10 @@ export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhau
|
|
|
39
39
|
export type { CallEnvelope } from './envelope.js';
|
|
40
40
|
export type { UrlGuardOptions } from './net.js';
|
|
41
41
|
export { leerDireccion, leerMaxBriefChars, leerNiveles, leerX402, nivelPara } from './agent-card.js';
|
|
42
|
-
export type { AgentCard, FichaGetResult, FichaNivel, FichaPostBrief, FichaX402, Nivel } from './agent-card.js';
|
|
42
|
+
export type { AgentCard, FichaGetResult, FichaNivel, FichaPostAttachment, FichaPostBrief, FichaX402, Nivel, } from './agent-card.js';
|
|
43
|
+
export { componerNivel, conTextoDeLaFicha, esTokenDeNivel, leerNivelDeSegmento, leerNivelesDeMetadata, NIVELES_EDITABLES, precioAWei, weiAPrecio, } from './niveles.js';
|
|
44
|
+
export { esTokenDeTipo, leerTipo, leerTipoDeSegmento, tokenDeTipo } from './tipo.js';
|
|
45
|
+
export type { TipoDeAgente } from './tipo.js';
|
|
46
|
+
export { rutaDeAgente } from './net.js';
|
|
47
|
+
export { fichaEnIdioma, IDIOMAS, NOMBRE_IDIOMA, normalizarIdioma } from './idiomas.js';
|
|
48
|
+
export type { Idioma } from './idiomas.js';
|
package/dist/index.js
CHANGED
|
@@ -40,3 +40,15 @@ export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhau
|
|
|
40
40
|
// La ficha de GET /agent.json: un solo formato, y lectores que perdonan el
|
|
41
41
|
// antiguo. En agent-card.ts está por qué llegó a haber dos.
|
|
42
42
|
export { leerDireccion, leerMaxBriefChars, leerNiveles, leerX402, nivelPara } from './agent-card.js';
|
|
43
|
+
// Los niveles escritos en el metadataURI on-chain. Mismo tipo `Nivel` que los
|
|
44
|
+
// de la ficha; en niveles.ts está por qué viven en los dos sitios.
|
|
45
|
+
export { componerNivel, conTextoDeLaFicha, esTokenDeNivel, leerNivelDeSegmento, leerNivelesDeMetadata, NIVELES_EDITABLES, precioAWei, weiAPrecio, } from './niveles.js';
|
|
46
|
+
// Quién hay al otro lado: una persona o un programa. Va en la misma cadena que
|
|
47
|
+
// los niveles, y por lo mismo lo tienen que reconocer todos los que la leen.
|
|
48
|
+
export { esTokenDeTipo, leerTipo, leerTipoDeSegmento, tokenDeTipo } from './tipo.js';
|
|
49
|
+
// La ficha en el idioma de quien la lee. En idiomas.ts está por qué traduce el
|
|
50
|
+
// propio agente y no un servicio de Panal.
|
|
51
|
+
// Unir una ruta con la URL de un agente. Ver por qué en `net.ts`: un agente
|
|
52
|
+
// puede vivir en un subcamino, y `new URL('/x', base)` se lo come.
|
|
53
|
+
export { rutaDeAgente } from './net.js';
|
|
54
|
+
export { fichaEnIdioma, IDIOMAS, NOMBRE_IDIOMA, normalizarIdioma } from './idiomas.js';
|
package/dist/net.d.ts
CHANGED
|
@@ -53,3 +53,18 @@ export declare function fetchLimited(url: URL | string, init?: RequestInit & {
|
|
|
53
53
|
headers: Headers;
|
|
54
54
|
text: string;
|
|
55
55
|
}>;
|
|
56
|
+
/**
|
|
57
|
+
* Una ruta del agente, colgando de DONDE está el agente.
|
|
58
|
+
*
|
|
59
|
+
* Parece una tontería y no lo es: `new URL('/brief/12', base)` descarta el
|
|
60
|
+
* camino de la base. Con un agente que vive en la raíz de su dominio
|
|
61
|
+
* —`https://bot.panal.lat`— da lo mismo y funcionó siempre; con uno que vive
|
|
62
|
+
* en un subcamino —`https://api.panal.lat/buzon/0xAAA`, que es donde reciben
|
|
63
|
+
* los que no tienen servidor propio— manda la petición a
|
|
64
|
+
* `https://api.panal.lat/brief/12`, que no es de nadie.
|
|
65
|
+
*
|
|
66
|
+
* Y lo que se rompe así no avisa: el 404 se lee como «ese agente no contesta»
|
|
67
|
+
* con el pago ya bloqueado, y a las 72 h se libera solo. Por eso la unión de
|
|
68
|
+
* rutas se hace en un sitio y no a mano en cada llamada.
|
|
69
|
+
*/
|
|
70
|
+
export declare function rutaDeAgente(base: string, ruta: string): string;
|
package/dist/net.js
CHANGED
|
@@ -148,3 +148,20 @@ export async function fetchLimited(url, init = {}) {
|
|
|
148
148
|
const { status, headers, bytes } = await fetchBytesLimited(url, init);
|
|
149
149
|
return { status, headers, text: new TextDecoder().decode(bytes) };
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Una ruta del agente, colgando de DONDE está el agente.
|
|
153
|
+
*
|
|
154
|
+
* Parece una tontería y no lo es: `new URL('/brief/12', base)` descarta el
|
|
155
|
+
* camino de la base. Con un agente que vive en la raíz de su dominio
|
|
156
|
+
* —`https://bot.panal.lat`— da lo mismo y funcionó siempre; con uno que vive
|
|
157
|
+
* en un subcamino —`https://api.panal.lat/buzon/0xAAA`, que es donde reciben
|
|
158
|
+
* los que no tienen servidor propio— manda la petición a
|
|
159
|
+
* `https://api.panal.lat/brief/12`, que no es de nadie.
|
|
160
|
+
*
|
|
161
|
+
* Y lo que se rompe así no avisa: el 404 se lee como «ese agente no contesta»
|
|
162
|
+
* con el pago ya bloqueado, y a las 72 h se libera solo. Por eso la unión de
|
|
163
|
+
* rutas se hace en un sitio y no a mano en cada llamada.
|
|
164
|
+
*/
|
|
165
|
+
export function rutaDeAgente(base, ruta) {
|
|
166
|
+
return `${base.replace(/\/+$/, '')}/${ruta.replace(/^\/+/, '')}`;
|
|
167
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal — los niveles de un agente dentro del `metadataURI` on-chain.
|
|
3
|
+
*
|
|
4
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* POR QUÉ AQUÍ Y NO SOLO EN LA FICHA
|
|
6
|
+
*
|
|
7
|
+
* Los niveles nacieron en la ficha que sirve el bot (`tiers`, ver
|
|
8
|
+
* `agent-card.ts`), y allí siguen: es donde un agente declara los topes de
|
|
9
|
+
* caracteres que su modelo puede masticar. Pero vivir SOLO ahí tiene dos
|
|
10
|
+
* consecuencias que se notan:
|
|
11
|
+
*
|
|
12
|
+
* - Para cambiar un precio hay que tocar el código del agente y reiniciarlo.
|
|
13
|
+
* Quien no programa no puede tocar lo que cobra.
|
|
14
|
+
* - Si el bot está caído, sus niveles no existen. El escaparate enseña un
|
|
15
|
+
* precio suelto de un agente que en realidad vende tres cosas distintas.
|
|
16
|
+
*
|
|
17
|
+
* En el `metadataURI` los escribe el registro, los guarda la cadena y los lee
|
|
18
|
+
* cualquiera sin preguntarle a nadie. El precio —que es lo único que hay que
|
|
19
|
+
* bloquear de verdad— deja de depender de que un servidor conteste.
|
|
20
|
+
*
|
|
21
|
+
* EL FORMATO
|
|
22
|
+
*
|
|
23
|
+
* nivel:<precio>|<nombre>|<descripción>|<brief>|<adjunto>|<adjuntos>
|
|
24
|
+
*
|
|
25
|
+
* Un segmento por nivel, entre los demás de la ficha:
|
|
26
|
+
*
|
|
27
|
+
* Lint · Revisa código · solidity · bot:https://… · nivel:0.03|Un archivo|…
|
|
28
|
+
*
|
|
29
|
+
* El precio va en unidades enteras («0.03»), no en wei. En wei son diecisiete
|
|
30
|
+
* dígitos por nivel y esta cadena se escribe en la cadena de bloques: lo que
|
|
31
|
+
* ocupa se paga. Los tres topes son opcionales y los vacíos del final no se
|
|
32
|
+
* escriben, así que un nivel sin topes son tres campos y punto.
|
|
33
|
+
*
|
|
34
|
+
* POR QUÉ SE VALIDA TAN DURO
|
|
35
|
+
*
|
|
36
|
+
* La regla la fija `marca.ts` y es la misma: UN TOKEN SOLO CUENTA SI SU VALOR
|
|
37
|
+
* VALE. La descripción de un agente es texto libre y alguien va a escribir
|
|
38
|
+
* «nivel: depende del encargo» dentro de ella; si bastara con ver dos puntos,
|
|
39
|
+
* esa frase se convertiría en un nivel fantasma y además desaparecería de la
|
|
40
|
+
* descripción. Exigiendo un número decimal, una barra y un nombre no vacío, la
|
|
41
|
+
* frase se queda donde estaba.
|
|
42
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
43
|
+
*/
|
|
44
|
+
import type { Nivel } from './agent-card.js';
|
|
45
|
+
/**
|
|
46
|
+
* Cuántos ofrece el formulario.
|
|
47
|
+
*
|
|
48
|
+
* Tres es lo que la gente entiende de un vistazo —pequeño, mediano, grande— y
|
|
49
|
+
* lo que ya publican los agentes que los usan. El LECTOR admite hasta ocho
|
|
50
|
+
* porque una ficha la escribe cualquiera y recortar lo que ya está escrito
|
|
51
|
+
* sería tirar un nivel que su dueño creía publicado.
|
|
52
|
+
*/
|
|
53
|
+
export declare const NIVELES_EDITABLES = 3;
|
|
54
|
+
/**
|
|
55
|
+
* `'0.03'` → `30000000000000000n`.
|
|
56
|
+
*
|
|
57
|
+
* A mano y no con `parseUnits` porque este módulo no importa viem: lo lee el
|
|
58
|
+
* bot de cada agente, que depende de viem a propósito solo en su cliente. La
|
|
59
|
+
* cuenta es rellenar de ceros a la derecha, que es exactamente lo que hace
|
|
60
|
+
* `parseUnits` sin coma flotante por medio.
|
|
61
|
+
*/
|
|
62
|
+
export declare function precioAWei(precio: string): bigint | null;
|
|
63
|
+
/**
|
|
64
|
+
* `30000000000000000n` → `'0.03'`.
|
|
65
|
+
*
|
|
66
|
+
* Sin ceros de relleno al final: `'0.030000000000000000'` es el mismo número
|
|
67
|
+
* y ocupa quince caracteres más en la cadena de bloques.
|
|
68
|
+
*/
|
|
69
|
+
export declare function weiAPrecio(wei: bigint): string;
|
|
70
|
+
/**
|
|
71
|
+
* `nivel:0.03|Un archivo|Un fichero suelto` → el nivel, o `null` si no lo es.
|
|
72
|
+
*
|
|
73
|
+
* Devolver `null` es lo normal: por aquí pasan TODOS los segmentos de la
|
|
74
|
+
* ficha, incluida la descripción libre del agente.
|
|
75
|
+
*/
|
|
76
|
+
export declare function leerNivelDeSegmento(segmento: string): Nivel | null;
|
|
77
|
+
/** true si este segmento es un nivel. Para que los lectores de ficha lo aparten. */
|
|
78
|
+
export declare function esTokenDeNivel(segmento: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Los niveles que un agente publica en la cadena, de menor a mayor precio.
|
|
81
|
+
*
|
|
82
|
+
* `[]` significa que no publica ninguno, y eso hay que tratarlo como se
|
|
83
|
+
* trataba antes de que esto existiera: el agente tiene UN precio, el del
|
|
84
|
+
* registro. Nadie debe fabricarle niveles a partir de él.
|
|
85
|
+
*/
|
|
86
|
+
export declare function leerNivelesDeMetadata(metadataURI: string | null | undefined): Nivel[];
|
|
87
|
+
/**
|
|
88
|
+
* El nivel → su segmento, o `null` si no se puede escribir.
|
|
89
|
+
*
|
|
90
|
+
* `null` en vez de arreglarlo por su cuenta: un «·» dentro del nombre partiría
|
|
91
|
+
* la ficha en dos y un «|» correría los campos, así que la salida silenciosa
|
|
92
|
+
* sería un nivel que dice algo distinto de lo que su dueño escribió. Quien
|
|
93
|
+
* llama tiene que enseñar el error, no firmar una ficha que no reconoce.
|
|
94
|
+
*/
|
|
95
|
+
export declare function componerNivel(nivel: {
|
|
96
|
+
name: string;
|
|
97
|
+
description?: string | null;
|
|
98
|
+
precio: string;
|
|
99
|
+
maxBriefChars?: number | null;
|
|
100
|
+
maxAttachChars?: number | null;
|
|
101
|
+
maxAttachCharsTotal?: number | null;
|
|
102
|
+
}): string | null;
|
|
103
|
+
/**
|
|
104
|
+
* Los niveles de la cadena, con el texto de la ficha del agente.
|
|
105
|
+
*
|
|
106
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
107
|
+
* POR QUÉ HACE FALTA JUNTARLOS, EN VEZ DE ELEGIR UNO
|
|
108
|
+
*
|
|
109
|
+
* Un nivel es dos cosas distintas con dueños distintos:
|
|
110
|
+
*
|
|
111
|
+
* - El PRECIO y los topes. Es lo que se bloquea en el escrow, y tiene que
|
|
112
|
+
* salir de la cadena: son los únicos que siguen ahí con el bot caído, y los
|
|
113
|
+
* únicos que nadie puede cambiarte entre que miras y pagas.
|
|
114
|
+
*
|
|
115
|
+
* - El NOMBRE y la descripción. Son una etiqueta, y la ficha del agente es el
|
|
116
|
+
* único sitio donde pueden estar TRADUCIDAS: `?lang=fr` devuelve la ficha
|
|
117
|
+
* en francés, la cadena guarda una sola versión y traducirla costaría una
|
|
118
|
+
* transacción por idioma.
|
|
119
|
+
*
|
|
120
|
+
* Quedarse solo con los de la cadena —que es lo que se hizo primero— deja el
|
|
121
|
+
* escaparate entero en francés y los tres niveles de cada agente en español.
|
|
122
|
+
* Quedarse solo con los de la ficha devuelve el problema de antes: un agente
|
|
123
|
+
* caído se queda sin niveles y se le encarga el tamaño grande al precio del
|
|
124
|
+
* pequeño.
|
|
125
|
+
*
|
|
126
|
+
* SE EMPAREJAN POR PRECIO, que es la identidad de un nivel: el agente arma su
|
|
127
|
+
* ficha a partir de lo que tiene en la cadena, así que los importes coinciden
|
|
128
|
+
* exactos. Lo que no empareje se queda con su texto de la cadena, que es la
|
|
129
|
+
* respuesta correcta cuando la ficha dice otra cosa: enseñar el nombre de un
|
|
130
|
+
* nivel junto a un precio que no es el suyo sería peor que no traducirlo.
|
|
131
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
132
|
+
*/
|
|
133
|
+
export declare function conTextoDeLaFicha(enCadena: Nivel[], deLaFicha: Nivel[]): Nivel[];
|
package/dist/niveles.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal — los niveles de un agente dentro del `metadataURI` on-chain.
|
|
3
|
+
*
|
|
4
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* POR QUÉ AQUÍ Y NO SOLO EN LA FICHA
|
|
6
|
+
*
|
|
7
|
+
* Los niveles nacieron en la ficha que sirve el bot (`tiers`, ver
|
|
8
|
+
* `agent-card.ts`), y allí siguen: es donde un agente declara los topes de
|
|
9
|
+
* caracteres que su modelo puede masticar. Pero vivir SOLO ahí tiene dos
|
|
10
|
+
* consecuencias que se notan:
|
|
11
|
+
*
|
|
12
|
+
* - Para cambiar un precio hay que tocar el código del agente y reiniciarlo.
|
|
13
|
+
* Quien no programa no puede tocar lo que cobra.
|
|
14
|
+
* - Si el bot está caído, sus niveles no existen. El escaparate enseña un
|
|
15
|
+
* precio suelto de un agente que en realidad vende tres cosas distintas.
|
|
16
|
+
*
|
|
17
|
+
* En el `metadataURI` los escribe el registro, los guarda la cadena y los lee
|
|
18
|
+
* cualquiera sin preguntarle a nadie. El precio —que es lo único que hay que
|
|
19
|
+
* bloquear de verdad— deja de depender de que un servidor conteste.
|
|
20
|
+
*
|
|
21
|
+
* EL FORMATO
|
|
22
|
+
*
|
|
23
|
+
* nivel:<precio>|<nombre>|<descripción>|<brief>|<adjunto>|<adjuntos>
|
|
24
|
+
*
|
|
25
|
+
* Un segmento por nivel, entre los demás de la ficha:
|
|
26
|
+
*
|
|
27
|
+
* Lint · Revisa código · solidity · bot:https://… · nivel:0.03|Un archivo|…
|
|
28
|
+
*
|
|
29
|
+
* El precio va en unidades enteras («0.03»), no en wei. En wei son diecisiete
|
|
30
|
+
* dígitos por nivel y esta cadena se escribe en la cadena de bloques: lo que
|
|
31
|
+
* ocupa se paga. Los tres topes son opcionales y los vacíos del final no se
|
|
32
|
+
* escriben, así que un nivel sin topes son tres campos y punto.
|
|
33
|
+
*
|
|
34
|
+
* POR QUÉ SE VALIDA TAN DURO
|
|
35
|
+
*
|
|
36
|
+
* La regla la fija `marca.ts` y es la misma: UN TOKEN SOLO CUENTA SI SU VALOR
|
|
37
|
+
* VALE. La descripción de un agente es texto libre y alguien va a escribir
|
|
38
|
+
* «nivel: depende del encargo» dentro de ella; si bastara con ver dos puntos,
|
|
39
|
+
* esa frase se convertiría en un nivel fantasma y además desaparecería de la
|
|
40
|
+
* descripción. Exigiendo un número decimal, una barra y un nombre no vacío, la
|
|
41
|
+
* frase se queda donde estaba.
|
|
42
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Decimales de las dos monedas del registro: MON nativo y $PANAL.
|
|
46
|
+
*
|
|
47
|
+
* Está fijo a propósito. `PanalRegistryV2` solo admite esas dos y las dos
|
|
48
|
+
* tienen dieciocho, así que el precio de un nivel se puede escribir en
|
|
49
|
+
* unidades enteras sin arrastrar de qué moneda se trata. El día que el
|
|
50
|
+
* registro admita un token con otros decimales, esto se rompe de la única
|
|
51
|
+
* forma aceptable: aquí, en un sitio, y no repartido por seis lectores.
|
|
52
|
+
*/
|
|
53
|
+
const DECIMALES = 18;
|
|
54
|
+
/** Cuántos niveles se leen de una ficha ajena. El mismo tope que `leerNiveles`. */
|
|
55
|
+
const MAX_NIVELES = 8;
|
|
56
|
+
/**
|
|
57
|
+
* Cuántos ofrece el formulario.
|
|
58
|
+
*
|
|
59
|
+
* Tres es lo que la gente entiende de un vistazo —pequeño, mediano, grande— y
|
|
60
|
+
* lo que ya publican los agentes que los usan. El LECTOR admite hasta ocho
|
|
61
|
+
* porque una ficha la escribe cualquiera y recortar lo que ya está escrito
|
|
62
|
+
* sería tirar un nivel que su dueño creía publicado.
|
|
63
|
+
*/
|
|
64
|
+
export const NIVELES_EDITABLES = 3;
|
|
65
|
+
/** Tope de cada texto. Los mismos que aplica `leerNiveles` a la ficha del bot. */
|
|
66
|
+
const MAX_NOMBRE = 60;
|
|
67
|
+
const MAX_DESCRIPCION = 200;
|
|
68
|
+
/** Un decimal positivo con hasta dieciocho cifras detrás de la coma. */
|
|
69
|
+
const PRECIO = /^\d{1,12}(\.\d{1,18})?$/;
|
|
70
|
+
/**
|
|
71
|
+
* `'0.03'` → `30000000000000000n`.
|
|
72
|
+
*
|
|
73
|
+
* A mano y no con `parseUnits` porque este módulo no importa viem: lo lee el
|
|
74
|
+
* bot de cada agente, que depende de viem a propósito solo en su cliente. La
|
|
75
|
+
* cuenta es rellenar de ceros a la derecha, que es exactamente lo que hace
|
|
76
|
+
* `parseUnits` sin coma flotante por medio.
|
|
77
|
+
*/
|
|
78
|
+
export function precioAWei(precio) {
|
|
79
|
+
const s = precio.trim();
|
|
80
|
+
if (!PRECIO.test(s))
|
|
81
|
+
return null;
|
|
82
|
+
const [entera, decimal = ''] = s.split('.');
|
|
83
|
+
const wei = BigInt(entera + decimal.padEnd(DECIMALES, '0').slice(0, DECIMALES));
|
|
84
|
+
return wei > 0n ? wei : null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `30000000000000000n` → `'0.03'`.
|
|
88
|
+
*
|
|
89
|
+
* Sin ceros de relleno al final: `'0.030000000000000000'` es el mismo número
|
|
90
|
+
* y ocupa quince caracteres más en la cadena de bloques.
|
|
91
|
+
*/
|
|
92
|
+
export function weiAPrecio(wei) {
|
|
93
|
+
const s = wei.toString().padStart(DECIMALES + 1, '0');
|
|
94
|
+
const entera = s.slice(0, -DECIMALES);
|
|
95
|
+
const decimal = s.slice(-DECIMALES).replace(/0+$/, '');
|
|
96
|
+
return decimal ? `${entera}.${decimal}` : entera;
|
|
97
|
+
}
|
|
98
|
+
/** Un entero positivo, o `null`. Misma regla que los topes de la ficha. */
|
|
99
|
+
function tope(v) {
|
|
100
|
+
if (v === undefined)
|
|
101
|
+
return null;
|
|
102
|
+
const s = v.trim();
|
|
103
|
+
if (!/^\d{1,9}$/.test(s))
|
|
104
|
+
return null;
|
|
105
|
+
const n = Number(s);
|
|
106
|
+
return n > 0 ? n : null;
|
|
107
|
+
}
|
|
108
|
+
/** Texto de un desconocido: espacios colapsados y recortado, porque va a un escaparate. */
|
|
109
|
+
function letrero(v, max) {
|
|
110
|
+
if (v === undefined)
|
|
111
|
+
return null;
|
|
112
|
+
const limpio = v.replace(/\s+/g, ' ').trim().slice(0, max);
|
|
113
|
+
return limpio || null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `nivel:0.03|Un archivo|Un fichero suelto` → el nivel, o `null` si no lo es.
|
|
117
|
+
*
|
|
118
|
+
* Devolver `null` es lo normal: por aquí pasan TODOS los segmentos de la
|
|
119
|
+
* ficha, incluida la descripción libre del agente.
|
|
120
|
+
*/
|
|
121
|
+
export function leerNivelDeSegmento(segmento) {
|
|
122
|
+
const i = segmento.indexOf(':');
|
|
123
|
+
if (i <= 0)
|
|
124
|
+
return null;
|
|
125
|
+
if (segmento.slice(0, i).trim().toLowerCase() !== 'nivel')
|
|
126
|
+
return null;
|
|
127
|
+
const campos = segmento.slice(i + 1).split('|');
|
|
128
|
+
// Sin nombre no hay nivel: el cliente elige por el nombre, y un botón vacío
|
|
129
|
+
// con un precio al lado no es una oferta, es un acertijo.
|
|
130
|
+
if (campos.length < 2)
|
|
131
|
+
return null;
|
|
132
|
+
const wei = precioAWei(campos[0] ?? '');
|
|
133
|
+
if (wei === null)
|
|
134
|
+
return null;
|
|
135
|
+
const name = letrero(campos[1], MAX_NOMBRE);
|
|
136
|
+
if (!name)
|
|
137
|
+
return null;
|
|
138
|
+
return {
|
|
139
|
+
name,
|
|
140
|
+
description: letrero(campos[2], MAX_DESCRIPCION),
|
|
141
|
+
wei,
|
|
142
|
+
maxBriefChars: tope(campos[3]),
|
|
143
|
+
maxAttachChars: tope(campos[4]),
|
|
144
|
+
maxAttachCharsTotal: tope(campos[5]),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** true si este segmento es un nivel. Para que los lectores de ficha lo aparten. */
|
|
148
|
+
export function esTokenDeNivel(segmento) {
|
|
149
|
+
return leerNivelDeSegmento(segmento) !== null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Los niveles que un agente publica en la cadena, de menor a mayor precio.
|
|
153
|
+
*
|
|
154
|
+
* `[]` significa que no publica ninguno, y eso hay que tratarlo como se
|
|
155
|
+
* trataba antes de que esto existiera: el agente tiene UN precio, el del
|
|
156
|
+
* registro. Nadie debe fabricarle niveles a partir de él.
|
|
157
|
+
*/
|
|
158
|
+
export function leerNivelesDeMetadata(metadataURI) {
|
|
159
|
+
if (!metadataURI)
|
|
160
|
+
return [];
|
|
161
|
+
const out = [];
|
|
162
|
+
for (const segmento of metadataURI.split('·')) {
|
|
163
|
+
if (out.length >= MAX_NIVELES)
|
|
164
|
+
break;
|
|
165
|
+
const nivel = leerNivelDeSegmento(segmento.trim());
|
|
166
|
+
// Un nivel mal escrito se cae de la lista en vez de tumbarla entera: un
|
|
167
|
+
// campo roto no puede dejar sin comprar los niveles buenos de al lado.
|
|
168
|
+
if (nivel)
|
|
169
|
+
out.push(nivel);
|
|
170
|
+
}
|
|
171
|
+
// De menor a mayor, que es como se enseñan y lo que `nivelPara` necesita
|
|
172
|
+
// para quedarse con el último que entra en lo pagado.
|
|
173
|
+
return out.sort((a, b) => (a.wei < b.wei ? -1 : a.wei > b.wei ? 1 : 0));
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* El nivel → su segmento, o `null` si no se puede escribir.
|
|
177
|
+
*
|
|
178
|
+
* `null` en vez de arreglarlo por su cuenta: un «·» dentro del nombre partiría
|
|
179
|
+
* la ficha en dos y un «|» correría los campos, así que la salida silenciosa
|
|
180
|
+
* sería un nivel que dice algo distinto de lo que su dueño escribió. Quien
|
|
181
|
+
* llama tiene que enseñar el error, no firmar una ficha que no reconoce.
|
|
182
|
+
*/
|
|
183
|
+
export function componerNivel(nivel) {
|
|
184
|
+
if (precioAWei(nivel.precio) === null)
|
|
185
|
+
return null;
|
|
186
|
+
const name = nivel.name.replace(/\s+/g, ' ').trim();
|
|
187
|
+
const description = (nivel.description ?? '').replace(/\s+/g, ' ').trim();
|
|
188
|
+
if (!name || name.length > MAX_NOMBRE)
|
|
189
|
+
return null;
|
|
190
|
+
if (description.length > MAX_DESCRIPCION)
|
|
191
|
+
return null;
|
|
192
|
+
if (/[·|]/.test(name) || /[·|]/.test(description))
|
|
193
|
+
return null;
|
|
194
|
+
const campos = [
|
|
195
|
+
nivel.precio.trim(),
|
|
196
|
+
name,
|
|
197
|
+
description,
|
|
198
|
+
entero(nivel.maxBriefChars),
|
|
199
|
+
entero(nivel.maxAttachChars),
|
|
200
|
+
entero(nivel.maxAttachCharsTotal),
|
|
201
|
+
];
|
|
202
|
+
// Los vacíos del FINAL se van; los de en medio no pueden irse o el siguiente
|
|
203
|
+
// campo ocuparía el sitio del que falta.
|
|
204
|
+
while (campos.length > 2 && campos[campos.length - 1] === '')
|
|
205
|
+
campos.pop();
|
|
206
|
+
return `nivel:${campos.join('|')}`;
|
|
207
|
+
}
|
|
208
|
+
function entero(v) {
|
|
209
|
+
return typeof v === 'number' && Number.isInteger(v) && v > 0 ? String(v) : '';
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Los niveles de la cadena, con el texto de la ficha del agente.
|
|
213
|
+
*
|
|
214
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
215
|
+
* POR QUÉ HACE FALTA JUNTARLOS, EN VEZ DE ELEGIR UNO
|
|
216
|
+
*
|
|
217
|
+
* Un nivel es dos cosas distintas con dueños distintos:
|
|
218
|
+
*
|
|
219
|
+
* - El PRECIO y los topes. Es lo que se bloquea en el escrow, y tiene que
|
|
220
|
+
* salir de la cadena: son los únicos que siguen ahí con el bot caído, y los
|
|
221
|
+
* únicos que nadie puede cambiarte entre que miras y pagas.
|
|
222
|
+
*
|
|
223
|
+
* - El NOMBRE y la descripción. Son una etiqueta, y la ficha del agente es el
|
|
224
|
+
* único sitio donde pueden estar TRADUCIDAS: `?lang=fr` devuelve la ficha
|
|
225
|
+
* en francés, la cadena guarda una sola versión y traducirla costaría una
|
|
226
|
+
* transacción por idioma.
|
|
227
|
+
*
|
|
228
|
+
* Quedarse solo con los de la cadena —que es lo que se hizo primero— deja el
|
|
229
|
+
* escaparate entero en francés y los tres niveles de cada agente en español.
|
|
230
|
+
* Quedarse solo con los de la ficha devuelve el problema de antes: un agente
|
|
231
|
+
* caído se queda sin niveles y se le encarga el tamaño grande al precio del
|
|
232
|
+
* pequeño.
|
|
233
|
+
*
|
|
234
|
+
* SE EMPAREJAN POR PRECIO, que es la identidad de un nivel: el agente arma su
|
|
235
|
+
* ficha a partir de lo que tiene en la cadena, así que los importes coinciden
|
|
236
|
+
* exactos. Lo que no empareje se queda con su texto de la cadena, que es la
|
|
237
|
+
* respuesta correcta cuando la ficha dice otra cosa: enseñar el nombre de un
|
|
238
|
+
* nivel junto a un precio que no es el suyo sería peor que no traducirlo.
|
|
239
|
+
* ───────────────────────────────────────────────────────────────────────────
|
|
240
|
+
*/
|
|
241
|
+
export function conTextoDeLaFicha(enCadena, deLaFicha) {
|
|
242
|
+
if (enCadena.length === 0 || deLaFicha.length === 0)
|
|
243
|
+
return enCadena;
|
|
244
|
+
return enCadena.map((n) => {
|
|
245
|
+
const igual = deLaFicha.find((f) => f.wei === n.wei);
|
|
246
|
+
if (!igual)
|
|
247
|
+
return n;
|
|
248
|
+
return {
|
|
249
|
+
...n,
|
|
250
|
+
// Solo se pisa lo que la ficha REALMENTE trae: un nivel con nombre no
|
|
251
|
+
// puede perderlo porque la ficha venga a medias.
|
|
252
|
+
name: igual.name ?? n.name,
|
|
253
|
+
description: igual.description ?? n.description,
|
|
254
|
+
};
|
|
255
|
+
});
|
|
256
|
+
}
|
package/dist/tipo.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal — quién hay al otro lado: una persona o un programa.
|
|
3
|
+
*
|
|
4
|
+
* Un token más de los que viven en el `metadataURI` on-chain, junto a `bot:`,
|
|
5
|
+
* los de marca y los de nivel:
|
|
6
|
+
*
|
|
7
|
+
* Marta · Traduce contratos ES⇄FR · traducción · tipo:persona · bot:…
|
|
8
|
+
*
|
|
9
|
+
* POR QUÉ ESTÁ EN LA CADENA Y NO EN LA TARJETA
|
|
10
|
+
*
|
|
11
|
+
* Porque es lo que decide en qué mercado sale y con quién se le compara, y eso
|
|
12
|
+
* no puede depender de que un servidor conteste. Una persona que apaga el
|
|
13
|
+
* ordenador el viernes no se convierte en un bot el sábado.
|
|
14
|
+
*
|
|
15
|
+
* POR QUÉ NO SE ADIVINA
|
|
16
|
+
*
|
|
17
|
+
* Se podría intentar: quien usa el buzón de Panal parece una persona, quien
|
|
18
|
+
* tiene servidor propio parece un bot. Las dos cosas son falsas —un bot puede
|
|
19
|
+
* usar el buzón y una persona puede montarse un servidor—, y equivocarse aquí
|
|
20
|
+
* es etiquetar a alguien de lo que no es. Así que se declara, y quien no
|
|
21
|
+
* declara nada es lo que han sido todos los agentes registrados hasta hoy: un
|
|
22
|
+
* programa.
|
|
23
|
+
*
|
|
24
|
+
* LO QUE ESTE TOKEN NO ES
|
|
25
|
+
*
|
|
26
|
+
* No es una verificación. Nadie comprueba que detrás de `tipo:persona` haya
|
|
27
|
+
* una persona, igual que nadie comprueba que detrás de un nombre haya quien
|
|
28
|
+
* dice. Lo que hace es que la respuesta sea SUYA y esté firmada, en vez de que
|
|
29
|
+
* la deduzca el mercado por su cuenta.
|
|
30
|
+
*/
|
|
31
|
+
/** Quién trabaja: una persona, o un programa. */
|
|
32
|
+
export type TipoDeAgente = 'persona' | 'bot';
|
|
33
|
+
/**
|
|
34
|
+
* Qué dice un segmento, o `null` si no es uno de estos.
|
|
35
|
+
*
|
|
36
|
+
* Solo se reconocen los dos valores. `tipo:` con cualquier otra cosa detrás no
|
|
37
|
+
* es un tipo desconocido que haya que respetar: es texto que alguien escribió
|
|
38
|
+
* mal, y tratarlo como token lo borraría de su descripción sin decírselo.
|
|
39
|
+
*/
|
|
40
|
+
export declare function leerTipoDeSegmento(segmento: string): TipoDeAgente | null;
|
|
41
|
+
/** true si este segmento es un tipo. Para que los lectores de ficha lo aparten. */
|
|
42
|
+
export declare function esTokenDeTipo(segmento: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Quién hay detrás de esta ficha. Sin token, un programa.
|
|
45
|
+
*
|
|
46
|
+
* Ausente NO es «no se sabe» a efectos de enseñarlo: hay que ponerlo en algún
|
|
47
|
+
* mercado, y el sitio honrado para los diez agentes que ya estaban registrados
|
|
48
|
+
* antes de que esto existiera es el de programas, que es lo que son.
|
|
49
|
+
*/
|
|
50
|
+
export declare function leerTipo(metadataURI: string | null | undefined): TipoDeAgente;
|
|
51
|
+
/**
|
|
52
|
+
* El token que hay que escribir, o `null` si no hay que escribir ninguno.
|
|
53
|
+
*
|
|
54
|
+
* `bot` no escribe nada a propósito. Es lo que se supone sin token, así que
|
|
55
|
+
* escribirlo alarga la ficha de todos los agentes —y el gas de registrarlos—
|
|
56
|
+
* para no decir nada que no se supiera.
|
|
57
|
+
*/
|
|
58
|
+
export declare function tokenDeTipo(tipo: TipoDeAgente): string | null;
|
package/dist/tipo.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panal — quién hay al otro lado: una persona o un programa.
|
|
3
|
+
*
|
|
4
|
+
* Un token más de los que viven en el `metadataURI` on-chain, junto a `bot:`,
|
|
5
|
+
* los de marca y los de nivel:
|
|
6
|
+
*
|
|
7
|
+
* Marta · Traduce contratos ES⇄FR · traducción · tipo:persona · bot:…
|
|
8
|
+
*
|
|
9
|
+
* POR QUÉ ESTÁ EN LA CADENA Y NO EN LA TARJETA
|
|
10
|
+
*
|
|
11
|
+
* Porque es lo que decide en qué mercado sale y con quién se le compara, y eso
|
|
12
|
+
* no puede depender de que un servidor conteste. Una persona que apaga el
|
|
13
|
+
* ordenador el viernes no se convierte en un bot el sábado.
|
|
14
|
+
*
|
|
15
|
+
* POR QUÉ NO SE ADIVINA
|
|
16
|
+
*
|
|
17
|
+
* Se podría intentar: quien usa el buzón de Panal parece una persona, quien
|
|
18
|
+
* tiene servidor propio parece un bot. Las dos cosas son falsas —un bot puede
|
|
19
|
+
* usar el buzón y una persona puede montarse un servidor—, y equivocarse aquí
|
|
20
|
+
* es etiquetar a alguien de lo que no es. Así que se declara, y quien no
|
|
21
|
+
* declara nada es lo que han sido todos los agentes registrados hasta hoy: un
|
|
22
|
+
* programa.
|
|
23
|
+
*
|
|
24
|
+
* LO QUE ESTE TOKEN NO ES
|
|
25
|
+
*
|
|
26
|
+
* No es una verificación. Nadie comprueba que detrás de `tipo:persona` haya
|
|
27
|
+
* una persona, igual que nadie comprueba que detrás de un nombre haya quien
|
|
28
|
+
* dice. Lo que hace es que la respuesta sea SUYA y esté firmada, en vez de que
|
|
29
|
+
* la deduzca el mercado por su cuenta.
|
|
30
|
+
*/
|
|
31
|
+
/** El prefijo del token, en minúsculas. */
|
|
32
|
+
const PREFIJO = 'tipo:';
|
|
33
|
+
/**
|
|
34
|
+
* Qué dice un segmento, o `null` si no es uno de estos.
|
|
35
|
+
*
|
|
36
|
+
* Solo se reconocen los dos valores. `tipo:` con cualquier otra cosa detrás no
|
|
37
|
+
* es un tipo desconocido que haya que respetar: es texto que alguien escribió
|
|
38
|
+
* mal, y tratarlo como token lo borraría de su descripción sin decírselo.
|
|
39
|
+
*/
|
|
40
|
+
export function leerTipoDeSegmento(segmento) {
|
|
41
|
+
const s = segmento.trim();
|
|
42
|
+
if (!s.toLowerCase().startsWith(PREFIJO))
|
|
43
|
+
return null;
|
|
44
|
+
const valor = s.slice(PREFIJO.length).trim().toLowerCase();
|
|
45
|
+
return valor === 'persona' || valor === 'bot' ? valor : null;
|
|
46
|
+
}
|
|
47
|
+
/** true si este segmento es un tipo. Para que los lectores de ficha lo aparten. */
|
|
48
|
+
export function esTokenDeTipo(segmento) {
|
|
49
|
+
return leerTipoDeSegmento(segmento) !== null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Quién hay detrás de esta ficha. Sin token, un programa.
|
|
53
|
+
*
|
|
54
|
+
* Ausente NO es «no se sabe» a efectos de enseñarlo: hay que ponerlo en algún
|
|
55
|
+
* mercado, y el sitio honrado para los diez agentes que ya estaban registrados
|
|
56
|
+
* antes de que esto existiera es el de programas, que es lo que son.
|
|
57
|
+
*/
|
|
58
|
+
export function leerTipo(metadataURI) {
|
|
59
|
+
if (!metadataURI)
|
|
60
|
+
return 'bot';
|
|
61
|
+
for (const seg of metadataURI.split('·')) {
|
|
62
|
+
const tipo = leerTipoDeSegmento(seg);
|
|
63
|
+
if (tipo)
|
|
64
|
+
return tipo;
|
|
65
|
+
}
|
|
66
|
+
return 'bot';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* El token que hay que escribir, o `null` si no hay que escribir ninguno.
|
|
70
|
+
*
|
|
71
|
+
* `bot` no escribe nada a propósito. Es lo que se supone sin token, así que
|
|
72
|
+
* escribirlo alarga la ficha de todos los agentes —y el gas de registrarlos—
|
|
73
|
+
* para no decir nada que no se supiera.
|
|
74
|
+
*/
|
|
75
|
+
export function tokenDeTipo(tipo) {
|
|
76
|
+
return tipo === 'persona' ? `${PREFIJO}persona` : null;
|
|
77
|
+
}
|
package/dist/types.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* así que registrar un agente con la forma equivocada era fácil y el error solo
|
|
8
8
|
* se veía al mirar la ficha en el marketplace.
|
|
9
9
|
*/
|
|
10
|
+
import { esTokenDeNivel } from './niveles.js';
|
|
11
|
+
import { esTokenDeTipo } from './tipo.js';
|
|
10
12
|
/** Estados de una tarea en el escrow, en el mismo orden que el enum de Solidity. */
|
|
11
13
|
export var TaskStatus;
|
|
12
14
|
(function (TaskStatus) {
|
|
@@ -164,6 +166,15 @@ export function parseAgentMetadata(metadataURI) {
|
|
|
164
166
|
links[link[0]] = link[1];
|
|
165
167
|
continue;
|
|
166
168
|
}
|
|
169
|
+
// Los niveles tampoco son texto de la ficha: sin apartarlos, los tres
|
|
170
|
+
// `nivel:…` de un agente saldrían anunciados como skills suyas. Los lee
|
|
171
|
+
// `leerNivelesDeMetadata`; aquí solo hace falta reconocerlos.
|
|
172
|
+
if (esTokenDeNivel(seg))
|
|
173
|
+
continue;
|
|
174
|
+
// Y quién hay detrás, por lo mismo: sin apartarlo, `tipo:persona` saldría
|
|
175
|
+
// anunciado como una skill de esa persona.
|
|
176
|
+
if (esTokenDeTipo(seg))
|
|
177
|
+
continue;
|
|
167
178
|
rest.push(seg);
|
|
168
179
|
}
|
|
169
180
|
// Los campos que falten quedan vacíos en vez de desplazar a los siguientes:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panal/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "SDK de Panal: contrata agentes de IA autonomos on-chain en Monad",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "tsc -p tsconfig.json",
|
|
47
47
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
48
|
-
"test": "tsx test/sdk.test.ts && tsx test/x402.test.ts && tsx test/x402-server.test.ts && tsx test/envelope.test.ts && tsx test/files.test.ts && tsx test/attachments.test.ts && tsx test/llm.test.ts && tsx test/skill.test.ts && tsx test/agent-card.test.ts",
|
|
48
|
+
"test": "tsx test/sdk.test.ts && tsx test/x402.test.ts && tsx test/x402-server.test.ts && tsx test/envelope.test.ts && tsx test/files.test.ts && tsx test/attachments.test.ts && tsx test/llm.test.ts && tsx test/skill.test.ts && tsx test/agent-card.test.ts && tsx test/niveles.test.ts",
|
|
49
49
|
"test:nombres": "tsx test/nombres.test.ts"
|
|
50
50
|
}
|
|
51
51
|
}
|