@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.
@@ -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(new URL('/agent.json', base).toString(), options);
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 new URL(anunciado.path, base).toString();
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 new URL('/x402/ask', base).toString();
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>;
@@ -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[];
@@ -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.15.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
  }