@panal/sdk 0.10.1 → 0.11.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.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * La ficha que un agente sirve en `GET /agent.json`.
3
+ *
4
+ * POR QUÉ ESTE ARCHIVO EXISTE
5
+ *
6
+ * Había dos formatos. El bot colocaba el cobro por llamada en
7
+ * `endpoints.x402Ask`; la plantilla lo ponía en la raíz, como `x402Ask`. Los
8
+ * dos servían la misma información y ningún lector veía las dos, porque cada
9
+ * uno parseaba con un tipo escrito a mano allí donde hacía falta.
10
+ *
11
+ * No fue un descuido de nadie: el tipo `AgentJson` vivía dentro del bot, así
12
+ * que la plantilla —que no depende del bot— no tenía de dónde copiarlo y
13
+ * escribió su propio objeto. Dos implementaciones honestas de una idea que
14
+ * nunca se escribió en un sitio común divergen solas.
15
+ *
16
+ * Aquí está esa idea, en el paquete del que ya dependen la plantilla y el MCP.
17
+ * Quien sirva una ficha, que la sirva con esta forma; quien la lea, que la lea
18
+ * con `leerX402` y `leerMaxBriefChars`, que entienden también la forma vieja.
19
+ *
20
+ * COMPATIBILIDAD
21
+ *
22
+ * Hay agentes desplegados sirviendo el formato antiguo, y no se les puede
23
+ * pedir que se actualicen para seguir siendo contratables. Los lectores
24
+ * aceptan las dos formas y lo seguirán haciendo: quien escribe se moderniza,
25
+ * quien lee perdona. Es la misma regla que ya seguía `agentAddress`.
26
+ */
27
+ import type { Address } from 'viem';
28
+ /** Cobro por llamada (x402), tal y como lo anuncia un agente. */
29
+ export interface FichaX402 {
30
+ method?: 'POST';
31
+ /** Ruta relativa. `url` gana si están las dos. */
32
+ path?: string;
33
+ url?: string;
34
+ scheme?: string;
35
+ /** Token ERC-20 en el que cobra. */
36
+ asset?: Address;
37
+ assetSymbol?: string;
38
+ /** Precio por llamada, en wei del token, como cadena decimal. */
39
+ amount?: string;
40
+ payTo?: Address;
41
+ howTo?: string;
42
+ }
43
+ /** Cómo mandarle el encargo a un agente, y cuánto texto acepta. */
44
+ export interface FichaPostBrief {
45
+ method?: 'POST';
46
+ path?: string;
47
+ signMessage?: string;
48
+ body?: string;
49
+ /**
50
+ * Tope de caracteres del encargo.
51
+ *
52
+ * Publicarlo es lo que evita que un cliente bloquee el pago con un encargo
53
+ * que el agente va a rechazar. Ausente significa NO LO DICE, que no es lo
54
+ * mismo que «no hay tope»: tratarlo como ilimitado es volver a averiguarlo
55
+ * pagando.
56
+ */
57
+ maxBriefChars?: number;
58
+ }
59
+ /** Cómo descargar el resultado ya entregado. */
60
+ export interface FichaGetResult {
61
+ method?: 'GET';
62
+ path?: string;
63
+ signMessage?: string;
64
+ }
65
+ /**
66
+ * La ficha completa. Casi todo es opcional a propósito: los dos motores tienen
67
+ * capacidades distintas —el de la plantilla no lee el registry al servirla, y
68
+ * el del bot sí— y un esquema que obligue a rellenar lo que no se sabe termina
69
+ * rellenándose con mentiras.
70
+ */
71
+ export interface AgentCard {
72
+ /** La dirección on-chain que este dominio declara suya. Es lo que verifica. */
73
+ agent?: Address;
74
+ /** Alias antiguo de `agent`. Se sigue leyendo; no lo escribas en fichas nuevas. */
75
+ agentAddress?: Address;
76
+ protocol?: 'panal';
77
+ network?: string;
78
+ chainId?: number;
79
+ name?: string;
80
+ description?: string;
81
+ skills?: string[];
82
+ price?: {
83
+ amountWei?: string;
84
+ currency?: Address;
85
+ symbol?: string;
86
+ } | null;
87
+ active?: boolean | null;
88
+ contracts?: {
89
+ escrow?: Address;
90
+ registry?: Address;
91
+ token?: Address;
92
+ };
93
+ endpoints?: {
94
+ base?: string | null;
95
+ postBrief?: FichaPostBrief;
96
+ getResult?: FichaGetResult;
97
+ x402Ask?: FichaX402;
98
+ indexer?: string | null;
99
+ };
100
+ /** Alias ANTIGUO de `endpoints.x402Ask`. Se lee; no se escribe. */
101
+ x402Ask?: FichaX402;
102
+ howToHire?: string[];
103
+ }
104
+ /** La dirección que la ficha declara suya, mirando también el alias viejo. */
105
+ export declare function leerDireccion(card: unknown): string | null;
106
+ /**
107
+ * El bloque de cobro por llamada, venga en el sitio nuevo o en el viejo.
108
+ *
109
+ * El sitio canónico gana si están los dos: un agente que sirva ambos está en
110
+ * mitad de una migración, y el nuevo es el que va a seguir manteniendo.
111
+ */
112
+ export declare function leerX402(card: unknown): FichaX402 | null;
113
+ /**
114
+ * El tope de caracteres del encargo, o `null` si la ficha no lo dice.
115
+ *
116
+ * `null` es NO LO SÉ y hay que tratarlo así. La ficha la sirve un desconocido,
117
+ * así que solo cuenta un entero positivo: un 0 o un negativo harían imposible
118
+ * cualquier encargo, y eso lo decide el agente bajando su tope, no mandando
119
+ * basura en un campo.
120
+ */
121
+ export declare function leerMaxBriefChars(card: unknown): number | null;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * La ficha que un agente sirve en `GET /agent.json`.
3
+ *
4
+ * POR QUÉ ESTE ARCHIVO EXISTE
5
+ *
6
+ * Había dos formatos. El bot colocaba el cobro por llamada en
7
+ * `endpoints.x402Ask`; la plantilla lo ponía en la raíz, como `x402Ask`. Los
8
+ * dos servían la misma información y ningún lector veía las dos, porque cada
9
+ * uno parseaba con un tipo escrito a mano allí donde hacía falta.
10
+ *
11
+ * No fue un descuido de nadie: el tipo `AgentJson` vivía dentro del bot, así
12
+ * que la plantilla —que no depende del bot— no tenía de dónde copiarlo y
13
+ * escribió su propio objeto. Dos implementaciones honestas de una idea que
14
+ * nunca se escribió en un sitio común divergen solas.
15
+ *
16
+ * Aquí está esa idea, en el paquete del que ya dependen la plantilla y el MCP.
17
+ * Quien sirva una ficha, que la sirva con esta forma; quien la lea, que la lea
18
+ * con `leerX402` y `leerMaxBriefChars`, que entienden también la forma vieja.
19
+ *
20
+ * COMPATIBILIDAD
21
+ *
22
+ * Hay agentes desplegados sirviendo el formato antiguo, y no se les puede
23
+ * pedir que se actualicen para seguir siendo contratables. Los lectores
24
+ * aceptan las dos formas y lo seguirán haciendo: quien escribe se moderniza,
25
+ * quien lee perdona. Es la misma regla que ya seguía `agentAddress`.
26
+ */
27
+ /** La dirección que la ficha declara suya, mirando también el alias viejo. */
28
+ export function leerDireccion(card) {
29
+ const c = card;
30
+ const dir = typeof c?.agent === 'string' ? c.agent : typeof c?.agentAddress === 'string' ? c.agentAddress : '';
31
+ return dir || null;
32
+ }
33
+ /**
34
+ * El bloque de cobro por llamada, venga en el sitio nuevo o en el viejo.
35
+ *
36
+ * El sitio canónico gana si están los dos: un agente que sirva ambos está en
37
+ * mitad de una migración, y el nuevo es el que va a seguir manteniendo.
38
+ */
39
+ export function leerX402(card) {
40
+ const c = card;
41
+ return c?.endpoints?.x402Ask ?? c?.x402Ask ?? null;
42
+ }
43
+ /**
44
+ * El tope de caracteres del encargo, o `null` si la ficha no lo dice.
45
+ *
46
+ * `null` es NO LO SÉ y hay que tratarlo así. La ficha la sirve un desconocido,
47
+ * así que solo cuenta un entero positivo: un 0 o un negativo harían imposible
48
+ * cualquier encargo, y eso lo decide el agente bajando su tope, no mandando
49
+ * basura en un campo.
50
+ */
51
+ export function leerMaxBriefChars(card) {
52
+ const max = card?.endpoints?.postBrief?.maxBriefChars;
53
+ return typeof max === 'number' && Number.isInteger(max) && max > 0 ? max : null;
54
+ }
package/dist/client.js CHANGED
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import { createPublicClient, createWalletClient, formatEther, getAddress, http, keccak256, toBytes } from 'viem';
19
19
  import { erc20Abi, escrowAbi, namesAbi, registryAbi } from './abis.js';
20
+ import { leerX402 } from './agent-card.js';
20
21
  import { assertPublicUrl, fetchLimited } from './net.js';
21
22
  import { X402Error, payAndAsk, quoteAsk } from './x402.js';
22
23
  import { descend, newEnvelope, remainingBudget } from './envelope.js';
@@ -753,8 +754,11 @@ export class PanalClient {
753
754
  const url = await assertPublicUrl(new URL('/agent.json', base).toString(), options);
754
755
  const res = await fetchLimited(url, { timeoutMs: 10_000 });
755
756
  if (res.status === 200) {
756
- const card = JSON.parse(res.text);
757
- const anunciado = card.endpoints?.x402Ask;
757
+ // `leerX402` entiende las dos formas. Antes esto solo miraba
758
+ // `endpoints.x402Ask`, así que con la ficha de un agente de plantilla
759
+ // no encontraba nada y caía a la convención: funcionaba de casualidad,
760
+ // y solo mientras el agente escuchara justo en /x402/ask.
761
+ const anunciado = leerX402(JSON.parse(res.text));
758
762
  if (anunciado?.url)
759
763
  return anunciado.url;
760
764
  if (anunciado?.path)
package/dist/index.d.ts CHANGED
@@ -36,3 +36,5 @@ export type { SettleDeps, SettleResult, X402Payment, X402ServerAccept, X402Serve
36
36
  export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhausted, LoopDetected, assertCanServe, descend, envelopeHeaders, newEnvelope, parseEnvelope, remainingBudget, } from './envelope.js';
37
37
  export type { CallEnvelope } from './envelope.js';
38
38
  export type { UrlGuardOptions } from './net.js';
39
+ export { leerDireccion, leerMaxBriefChars, leerX402 } from './agent-card.js';
40
+ export type { AgentCard, FichaGetResult, FichaPostBrief, FichaX402 } from './agent-card.js';
package/dist/index.js CHANGED
@@ -33,3 +33,6 @@ export { FILES_BLOCK, MAX_FILE_BYTES, FileVerificationError, appendFilesManifest
33
33
  export { X402_VERSION, X402_SERVER_SCHEME, buildQuote, enqueueByPayer, parsePaymentHeader, permitNonce, permitTypedData, readPermitDomain, resourceId, splitSignature, verifyAndSettle, } from './x402-server.js';
34
34
  // El sobre que viaja entre agentes: profundidad, presupuesto y detección de ciclos.
35
35
  export { ENVELOPE_HEADERS, DEFAULT_DEPTH, MAX_DEPTH, BudgetExhausted, DepthExhausted, LoopDetected, assertCanServe, descend, envelopeHeaders, newEnvelope, parseEnvelope, remainingBudget, } from './envelope.js';
36
+ // La ficha de GET /agent.json: un solo formato, y lectores que perdonan el
37
+ // antiguo. En agent-card.ts está por qué llegó a haber dos.
38
+ export { leerDireccion, leerMaxBriefChars, leerX402 } from './agent-card.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@panal/sdk",
3
- "version": "0.10.1",
3
+ "version": "0.11.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/skill.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/skill.test.ts && tsx test/agent-card.test.ts",
49
49
  "test:nombres": "tsx test/nombres.test.ts"
50
50
  }
51
51
  }