@panal/sdk 0.16.0 → 0.17.1

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 CHANGED
@@ -71,27 +71,56 @@ const panal = createPanalClient({
71
71
 
72
72
  **Escritura** — `hire({ agent, brief, amount?, deadline? })` · `approveTask(id, rating)` · `withdraw(currency?)`
73
73
 
74
- **Utilidades** — `parseAgentMetadata()` · `formatAgentMetadata()` · `MAINNET_ADDRESSES` · `NATIVE_CURRENCY` · `TaskStatus` · los ABIs
74
+ **Utilidades** — `parseAgentMetadata()` · `formatAgentMetadata()` · `leerTipo()` · `leerNivelesDeMetadata()` / `nivelPara()` · `rutaDeAgente()` · `fichaEnIdioma()` · `MAINNET_ADDRESSES` · `NATIVE_CURRENCY` · `TaskStatus` · los ABIs
75
75
 
76
76
  ### El metadata de un agente
77
77
 
78
78
  On-chain es una sola cadena con segmentos separados por `·`, no JSON pese al nombre `metadataURI` del contrato:
79
79
 
80
80
  ```
81
- LexPanal · Resúmenes legales y traducción EN<->ES · legal, traducción · bot:https://bot.panal.lat
81
+ LexPanal · Resúmenes legales y traducción EN<->ES · legal, traducción · bot:https://bot.panal.lat · logo:https://lex.dev/l.png · nivel:0.5|Rápido|Un folio|4000|0|0 · tipo:persona
82
82
  ```
83
83
 
84
- Usa los helpers en vez de componerla a mano: `formatAgentMetadata` neutraliza los `·` que lleve tu texto, que si no desplazarían las skills a otro segmento y dejarían la ficha descuadrada sin ningún error visible.
84
+ | Segmento | Qué es |
85
+ |---|---|
86
+ | 1.º, 2.º, 3.º | nombre, descripción y skills (separadas por comas). Van **por posición** |
87
+ | `bot:<url>` | dónde recibe los encargos y dónde sirve lo que entrega |
88
+ | `logo:` `web:` `github:` `x:` `telegram:` | la marca del creador, toda opcional |
89
+ | `nivel:<precio>\|<nombre>\|<desc>\|<maxBrief>\|<maxAdj>\|<maxAdjTotal>` | uno por cada tamaño del mismo trabajo |
90
+ | `tipo:persona` | quién hay al otro lado. Sin este token se asume un programa |
91
+
92
+ **Quien lee la cadena tiene que reconocer todos los tokens, aunque no los use.** No es una recomendación de estilo: los tres primeros campos van por posición, así que un lector que no conozca `tipo:` lo cuenta como un segmento más — y entonces la descripción aparece donde iba el nombre y `tipo:persona` se anuncia como una skill de esa persona. `parseAgentMetadata` los aparta todos aunque solo devuelva los campos de texto; los niveles se leen con `leerNivelesDeMetadata` y quién hay detrás con `leerTipo`.
85
93
 
86
94
  ```ts
87
- import { formatAgentMetadata } from '@panal/sdk';
95
+ import { formatAgentMetadata, leerNivelesDeMetadata, leerTipo } from '@panal/sdk';
88
96
 
89
97
  const uri = formatAgentMetadata({
90
98
  name: 'MiAgente',
91
99
  description: 'Qué hace',
92
100
  skills: ['skill-a', 'skill-b'],
93
101
  botUrl: 'https://mi-agente.com',
102
+ links: { web: 'https://mi-agente.com', github: 'miusuario' },
94
103
  });
104
+
105
+ leerTipo(uri); // 'bot' | 'persona'
106
+ leerNivelesDeMetadata(uri); // los tamaños que vende, o []
107
+ ```
108
+
109
+ `formatAgentMetadata` neutraliza los `·` que lleve tu texto, que si no desplazarían las skills a otro segmento y dejarían la ficha descuadrada sin ningún error visible.
110
+
111
+ **Cuidado al recomponer una ficha que ya existía.** El formateador escribe nombre, descripción, skills, `bot:` y la marca — y nada más. Si editas el perfil de un agente que tenía niveles o `tipo:persona` y guardas solo lo que devuelve, esos tokens desaparecen sin un error: sus precios vuelven a uno y una persona se muda al mercado de los programas. Vuelve a añadirlos con `componerNivel` y `tokenDeTipo`.
112
+
113
+ ### Dónde recibe un agente, y el buzón
114
+
115
+ `bot:` puede apuntar a un servidor del agente o al buzón de Panal —`https://api.panal.lat/buzon/<dirección>`—, que es donde espera el encargo de quien trabaja sin tener nada encendido. Para el SDK son la misma cosa: el protocolo es idéntico y la URL es un dato.
116
+
117
+ Lo que sí cambia es cómo se le pega una ruta a esa base, y ahí hay una trampa del estándar: `new URL('/brief/12', base)` **descarta el camino** de la base. Contra un agente que vive en `https://api.panal.lat/buzon/0xabc…` pediría `https://api.panal.lat/brief/12` — un 404 que se lee como «el agente no contesta», con el pago ya bloqueado. Por eso el SDK une las rutas él:
118
+
119
+ ```ts
120
+ import { rutaDeAgente } from '@panal/sdk';
121
+
122
+ rutaDeAgente('https://api.panal.lat/buzon/0xabc…', '/brief/12');
123
+ // → 'https://api.panal.lat/buzon/0xabc…/brief/12'
95
124
  ```
96
125
 
97
126
  ### Archivos: en las dos direcciones
@@ -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) {
package/dist/index.d.ts CHANGED
@@ -39,7 +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';
43
- export { componerNivel, esTokenDeNivel, leerNivelDeSegmento, leerNivelesDeMetadata, NIVELES_EDITABLES, precioAWei, weiAPrecio, } from './niveles.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';
44
47
  export { fichaEnIdioma, IDIOMAS, NOMBRE_IDIOMA, normalizarIdioma } from './idiomas.js';
45
48
  export type { Idioma } from './idiomas.js';
package/dist/index.js CHANGED
@@ -42,7 +42,13 @@ export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhau
42
42
  export { leerDireccion, leerMaxBriefChars, leerNiveles, leerX402, nivelPara } from './agent-card.js';
43
43
  // Los niveles escritos en el metadataURI on-chain. Mismo tipo `Nivel` que los
44
44
  // de la ficha; en niveles.ts está por qué viven en los dos sitios.
45
- export { componerNivel, esTokenDeNivel, leerNivelDeSegmento, leerNivelesDeMetadata, NIVELES_EDITABLES, precioAWei, weiAPrecio, } from './niveles.js';
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';
46
49
  // La ficha en el idioma de quien la lee. En idiomas.ts está por qué traduce el
47
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';
48
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
+ }
package/dist/niveles.d.ts CHANGED
@@ -100,3 +100,34 @@ export declare function componerNivel(nivel: {
100
100
  maxAttachChars?: number | null;
101
101
  maxAttachCharsTotal?: number | null;
102
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 CHANGED
@@ -208,3 +208,49 @@ export function componerNivel(nivel) {
208
208
  function entero(v) {
209
209
  return typeof v === 'number' && Number.isInteger(v) && v > 0 ? String(v) : '';
210
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
@@ -8,6 +8,7 @@
8
8
  * se veía al mirar la ficha en el marketplace.
9
9
  */
10
10
  import { esTokenDeNivel } from './niveles.js';
11
+ import { esTokenDeTipo } from './tipo.js';
11
12
  /** Estados de una tarea en el escrow, en el mismo orden que el enum de Solidity. */
12
13
  export var TaskStatus;
13
14
  (function (TaskStatus) {
@@ -170,6 +171,10 @@ export function parseAgentMetadata(metadataURI) {
170
171
  // `leerNivelesDeMetadata`; aquí solo hace falta reconocerlos.
171
172
  if (esTokenDeNivel(seg))
172
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;
173
178
  rest.push(seg);
174
179
  }
175
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.16.0",
3
+ "version": "0.17.1",
4
4
  "description": "SDK de Panal: contrata agentes de IA autonomos on-chain en Monad",
5
5
  "type": "module",
6
6
  "license": "MIT",