@backendkit-labs/aval-tools 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/__tests__/check-payment.test.d.ts +2 -0
- package/dist/__tests__/check-payment.test.d.ts.map +1 -0
- package/dist/__tests__/check-payment.test.js +69 -0
- package/dist/__tests__/check-payment.test.js.map +1 -0
- package/dist/__tests__/pay.test.d.ts +2 -0
- package/dist/__tests__/pay.test.d.ts.map +1 -0
- package/dist/__tests__/pay.test.js +83 -0
- package/dist/__tests__/pay.test.js.map +1 -0
- package/dist/check-payment.d.ts +11 -0
- package/dist/check-payment.d.ts.map +1 -0
- package/dist/check-payment.js +53 -0
- package/dist/check-payment.js.map +1 -0
- package/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +71 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/pay.d.ts +8 -0
- package/dist/pay.d.ts.map +1 -0
- package/dist/pay.js +88 -0
- package/dist/pay.js.map +1 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @backendkit-labs/aval-tools
|
|
2
|
+
|
|
3
|
+
Herramientas de pago para agentes de `@backendkit-labs/agent-core` que gastan a través de una
|
|
4
|
+
instancia de [AVAL](../../../../workspace-ideas/internet) — credencial por agente, presupuesto
|
|
5
|
+
previo y bitácora encadenada.
|
|
6
|
+
|
|
7
|
+
## Qué trae
|
|
8
|
+
|
|
9
|
+
- **`pay`** — la vía amarrada: el agente pide un techo de gasto, AVAL decide y paga. Devuelve
|
|
10
|
+
el resultado como texto: pagado (con el coste real y la referencia del carril), retenido
|
|
11
|
+
(con el `authorizationId` para consultar después) o denegado (con el motivo).
|
|
12
|
+
- **`check_payment`** — consulta el estado de una autorización propia por `authorizationId`.
|
|
13
|
+
Es la pieza que le faltaba al framework para la vía "retenido": nada bloquea una sesión
|
|
14
|
+
esperando a un humano, así que un gasto retenido se resuelve volviendo a preguntar más
|
|
15
|
+
tarde — en otra invocación, disparada por un trigger o por el propio usuario.
|
|
16
|
+
|
|
17
|
+
Ninguna herramienta guarda dinero ni credenciales del proveedor: eso lo tiene AVAL.
|
|
18
|
+
|
|
19
|
+
## Instalación
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @backendkit-labs/aval-tools
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Uso
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { AgentEngine, ToolRegistry } from '@backendkit-labs/agent-core';
|
|
29
|
+
import { createAvalTools } from '@backendkit-labs/aval-tools';
|
|
30
|
+
|
|
31
|
+
const tools = new ToolRegistry();
|
|
32
|
+
for (const tool of createAvalTools({ baseUrl: 'https://aval.internal' })) {
|
|
33
|
+
tools.register(tool);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const profile = {
|
|
37
|
+
id: 'expedientes-bot',
|
|
38
|
+
// ...
|
|
39
|
+
allowedTools: ['pay', 'check_payment' /* ... */],
|
|
40
|
+
// La clave vive en el perfil de ESTE agente, no en una variable de entorno
|
|
41
|
+
// compartida por todos. Cárgala desde donde guardes secretos (vault,
|
|
42
|
+
// gestor de secretos del proveedor de nube, etc.), nunca en texto plano
|
|
43
|
+
// en un fichero que se vaya a commitear.
|
|
44
|
+
secrets: { AVAL_KEY: process.env.EXPEDIENTES_BOT_AVAL_KEY! },
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Configuración
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
interface AvalToolsConfig {
|
|
52
|
+
baseUrl: string; // dónde está AVAL
|
|
53
|
+
secretName?: string; // nombre de la credencial en ctx.secrets — por defecto "AVAL_KEY"
|
|
54
|
+
timeoutMs?: number; // por defecto 15000
|
|
55
|
+
maxAttempts?: number; // por defecto 3
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## El flujo de un gasto retenido
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
1. El agente llama a pay("tarifas.aduana", 0.70, "Actualización trimestral").
|
|
63
|
+
→ "Retenido para aprobación humana. authorizationId=auth_xyz. [...]"
|
|
64
|
+
El turno del agente termina ahí. No hace falta que la sesión siga viva.
|
|
65
|
+
|
|
66
|
+
2. Horas después, alguien aprueba el gasto desde la consola de AVAL —
|
|
67
|
+
aprobar es pagar: AVAL usa su propia credencial, el agente no interviene.
|
|
68
|
+
|
|
69
|
+
3. Una invocación nueva del agente (un cron, un webhook, o simplemente el
|
|
70
|
+
usuario preguntando "¿ya se aprobó eso?") llama a
|
|
71
|
+
check_payment("auth_xyz").
|
|
72
|
+
→ "auth_xyz → liquidado — el pago se completó. [...]"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Por qué reintentar no duplica el gasto
|
|
76
|
+
|
|
77
|
+
`pay` deriva una clave de idempotencia a partir de la sesión y los argumentos exactos de la
|
|
78
|
+
llamada (`sha256(sessionId:counterparty:ceiling:purpose)`). Un reintento del propio agente
|
|
79
|
+
tras un timeout de red produce la misma clave y AVAL lo trata como una repetición, no como un
|
|
80
|
+
segundo gasto. Una llamada genuinamente distinta —otro importe, otro propósito, otra sesión—
|
|
81
|
+
obtiene una clave distinta por sí sola.
|
|
82
|
+
|
|
83
|
+
## Qué pasa si AVAL no responde
|
|
84
|
+
|
|
85
|
+
`pay` y `check_payment` reintentan automáticamente ante errores de red o 5xx (backoff
|
|
86
|
+
exponencial con `jitter`, tres intentos por defecto). Los 4xx —denegado, credencial revocada,
|
|
87
|
+
límite superado— nunca se reintentan: son respuestas de negocio, no fallos de transporte, y
|
|
88
|
+
se devuelven al agente como texto para que decida qué hacer.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-payment.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/check-payment.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const check_payment_1 = require("../check-payment");
|
|
4
|
+
function makeCtx(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
agentId: 'expedientes-bot',
|
|
7
|
+
sessionId: 'session-1',
|
|
8
|
+
memory: { get: () => undefined, set: () => undefined, getAll: () => ({}) },
|
|
9
|
+
askAgent: async () => '',
|
|
10
|
+
...overrides,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function jsonResponse(status, body) {
|
|
14
|
+
return { status, ok: status >= 200 && status < 300, json: async () => body };
|
|
15
|
+
}
|
|
16
|
+
const config = { baseUrl: 'https://aval.test' };
|
|
17
|
+
const checkPayment = (0, check_payment_1.createCheckPaymentTool)(config);
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
global.fetch = jest.fn();
|
|
20
|
+
});
|
|
21
|
+
describe('check_payment', () => {
|
|
22
|
+
it('requires a credential before calling out', async () => {
|
|
23
|
+
const result = await checkPayment.execute({ authorizationId: 'auth_1' }, makeCtx());
|
|
24
|
+
expect(result).toMatch(/no tiene credencial de AVAL/);
|
|
25
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
it('reports a resolved (settled) authorization', async () => {
|
|
28
|
+
fetch.mockResolvedValueOnce(jsonResponse(200, {
|
|
29
|
+
authorizationId: 'auth_2',
|
|
30
|
+
state: 'settled',
|
|
31
|
+
amount: 589500,
|
|
32
|
+
vendor: 'tarifas.aduana',
|
|
33
|
+
purpose: 'Padrón arancelario completo — trimestre',
|
|
34
|
+
rail: 'proxy',
|
|
35
|
+
reasons: ['Pagado por proxy: $0,5895.'],
|
|
36
|
+
createdAt: '2026-08-07T00:00:00.000Z',
|
|
37
|
+
}));
|
|
38
|
+
const result = await checkPayment.execute({ authorizationId: 'auth_2' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
39
|
+
expect(result).toMatch(/liquidado/);
|
|
40
|
+
expect(result).toMatch(/\$0\.5895/); // 589500 micro-dollars, shown to sub-cent precision
|
|
41
|
+
expect(result).toMatch(/tarifas\.aduana/);
|
|
42
|
+
});
|
|
43
|
+
it('still shows "held" while nobody has decided yet', async () => {
|
|
44
|
+
fetch.mockResolvedValueOnce(jsonResponse(200, {
|
|
45
|
+
authorizationId: 'auth_3',
|
|
46
|
+
state: 'held',
|
|
47
|
+
amount: 700000,
|
|
48
|
+
vendor: 'tarifas.aduana',
|
|
49
|
+
purpose: 'Actualización trimestral',
|
|
50
|
+
rail: 'proxy',
|
|
51
|
+
reasons: [],
|
|
52
|
+
createdAt: '2026-08-07T00:00:00.000Z',
|
|
53
|
+
}));
|
|
54
|
+
const result = await checkPayment.execute({ authorizationId: 'auth_3' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
55
|
+
expect(result).toMatch(/retenido/);
|
|
56
|
+
expect(result).toMatch(/todavía no se gastó nada/);
|
|
57
|
+
});
|
|
58
|
+
it('reports 404 as a plain not-found message, not a raw error', async () => {
|
|
59
|
+
fetch.mockResolvedValueOnce(jsonResponse(404, { error: 'No existe la autorización.' }));
|
|
60
|
+
const result = await checkPayment.execute({ authorizationId: 'auth_ghost' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
61
|
+
expect(result).toMatch(/No existe/);
|
|
62
|
+
});
|
|
63
|
+
it('reports 403 as an ownership message when the authorization is not this agent\'s', async () => {
|
|
64
|
+
fetch.mockResolvedValueOnce(jsonResponse(403, { error: 'no es de este agente' }));
|
|
65
|
+
const result = await checkPayment.execute({ authorizationId: 'auth_other' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
66
|
+
expect(result).toMatch(/no pertenece a este agente/);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=check-payment.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-payment.test.js","sourceRoot":"","sources":["../../src/__tests__/check-payment.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pay.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/pay.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const pay_1 = require("../pay");
|
|
4
|
+
function makeCtx(overrides = {}) {
|
|
5
|
+
return {
|
|
6
|
+
agentId: 'expedientes-bot',
|
|
7
|
+
sessionId: 'session-1',
|
|
8
|
+
memory: { get: () => undefined, set: () => undefined, getAll: () => ({}) },
|
|
9
|
+
askAgent: async () => '',
|
|
10
|
+
...overrides,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function jsonResponse(status, body) {
|
|
14
|
+
return { status, ok: status >= 200 && status < 300, json: async () => body };
|
|
15
|
+
}
|
|
16
|
+
const config = { baseUrl: 'https://aval.test' };
|
|
17
|
+
const pay = (0, pay_1.createPayTool)(config);
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
global.fetch = jest.fn();
|
|
20
|
+
});
|
|
21
|
+
describe('pay', () => {
|
|
22
|
+
it('rejects without touching the network when the agent has no AVAL credential', async () => {
|
|
23
|
+
const result = await pay.execute({ counterparty: 'ocr.docs', ceilingUsd: 0.3, purpose: 'test' }, makeCtx());
|
|
24
|
+
expect(result).toMatch(/no tiene credencial de AVAL/);
|
|
25
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
it('sends the ceiling in micro-dollars and a bearer token from ctx.secrets', async () => {
|
|
28
|
+
fetch.mockResolvedValueOnce(jsonResponse(200, {
|
|
29
|
+
decision: 'authorized',
|
|
30
|
+
authorizationId: 'auth_1',
|
|
31
|
+
rail: 'proxy',
|
|
32
|
+
reasons: [],
|
|
33
|
+
capability: {
|
|
34
|
+
rail: 'proxy',
|
|
35
|
+
kind: 'respuesta',
|
|
36
|
+
actual: 280000,
|
|
37
|
+
payment: { rail: 'proxy', asset: 'USD', nativeAmount: 280000, rate: 1, ref: 'req_abc' },
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
await pay.execute({ counterparty: 'ocr.docs', ceilingUsd: 0.3, purpose: 'Lectura del pedimento' }, makeCtx({ secrets: { AVAL_KEY: 'aval_sk_test_secret' } }));
|
|
41
|
+
const [url, init] = fetch.mock.calls[0];
|
|
42
|
+
expect(url).toBe('https://aval.test/api/v1/spend');
|
|
43
|
+
expect(init.headers.authorization).toBe('Bearer aval_sk_test_secret');
|
|
44
|
+
const body = JSON.parse(init.body);
|
|
45
|
+
expect(body.ceiling).toBe(300000); // $0.30 → 300000 micro-dollars
|
|
46
|
+
expect(body.counterparty).toBe('ocr.docs');
|
|
47
|
+
expect(typeof body.idempotencyKey).toBe('string');
|
|
48
|
+
});
|
|
49
|
+
it('derives the same idempotency key for the same session and args, a different one for different args', async () => {
|
|
50
|
+
fetch.mockResolvedValue(jsonResponse(200, { decision: 'denied', reasons: [] }));
|
|
51
|
+
const ctx = makeCtx({ secrets: { AVAL_KEY: 'k' } });
|
|
52
|
+
await pay.execute({ counterparty: 'ocr.docs', ceilingUsd: 0.3, purpose: 'A' }, ctx);
|
|
53
|
+
await pay.execute({ counterparty: 'ocr.docs', ceilingUsd: 0.3, purpose: 'A' }, ctx);
|
|
54
|
+
await pay.execute({ counterparty: 'ocr.docs', ceilingUsd: 0.31, purpose: 'A' }, ctx);
|
|
55
|
+
const keys = fetch.mock.calls.map(([, init]) => JSON.parse(init.body).idempotencyKey);
|
|
56
|
+
expect(keys[0]).toBe(keys[1]);
|
|
57
|
+
expect(keys[0]).not.toBe(keys[2]);
|
|
58
|
+
});
|
|
59
|
+
it('reports a held spend without claiming money moved', async () => {
|
|
60
|
+
fetch.mockResolvedValueOnce(jsonResponse(200, {
|
|
61
|
+
decision: 'held',
|
|
62
|
+
authorizationId: 'auth_2',
|
|
63
|
+
reasons: ['$0,70 supera el umbral de $0,60: requiere aprobación de María Restrepo.'],
|
|
64
|
+
}));
|
|
65
|
+
const result = await pay.execute({ counterparty: 'tarifas.aduana', ceilingUsd: 0.7, purpose: 'Padrón' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
66
|
+
expect(result).toMatch(/Retenido/);
|
|
67
|
+
expect(result).toMatch(/auth_2/);
|
|
68
|
+
expect(result).toMatch(/check_payment/);
|
|
69
|
+
expect(result).not.toMatch(/Pagado/);
|
|
70
|
+
});
|
|
71
|
+
it('surfaces the denial reason instead of a raw error', async () => {
|
|
72
|
+
fetch.mockResolvedValueOnce(jsonResponse(200, { decision: 'denied', reasons: ['$0,90 supera el tope por operación de $0,80.'] }));
|
|
73
|
+
const result = await pay.execute({ counterparty: 'tarifas.aduana', ceilingUsd: 0.9, purpose: 'Descarga' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
74
|
+
expect(result).toMatch(/Denegado/);
|
|
75
|
+
expect(result).toMatch(/tope por operación/);
|
|
76
|
+
});
|
|
77
|
+
it('surfaces rate limiting with the retry window', async () => {
|
|
78
|
+
fetch.mockResolvedValueOnce(jsonResponse(429, { error: 'Demasiados intentos.', retryAfterSeconds: 57 }));
|
|
79
|
+
const result = await pay.execute({ counterparty: 'ocr.docs', ceilingUsd: 0.1, purpose: 'x' }, makeCtx({ secrets: { AVAL_KEY: 'k' } }));
|
|
80
|
+
expect(result).toMatch(/57 s/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=pay.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pay.test.js","sourceRoot":"","sources":["../../src/__tests__/pay.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type AvalToolsConfig } from './client';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the `check_payment` tool: the other half of the async approval
|
|
4
|
+
* story. `pay` can return "retenido" and end the turn there — nothing in the
|
|
5
|
+
* framework blocks waiting for a human. This tool is how a later invocation
|
|
6
|
+
* (a cron trigger, a follow-up prompt, a human asking "¿ya se aprobó?") finds
|
|
7
|
+
* out what happened without needing the console credential AVAL's dashboard
|
|
8
|
+
* requires.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createCheckPaymentTool(config: AvalToolsConfig): import("@backendkit-labs/agent-core").ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=check-payment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-payment.d.ts","sourceRoot":"","sources":["../src/check-payment.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAyBtE;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,wDAoC7D"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCheckPaymentTool = createCheckPaymentTool;
|
|
4
|
+
const agent_core_1 = require("@backendkit-labs/agent-core");
|
|
5
|
+
const client_1 = require("./client");
|
|
6
|
+
const USD_PER_UNIT = 1_000_000;
|
|
7
|
+
const usdFormat = new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
|
8
|
+
const formatUsd = (units) => `$${usdFormat.format(units / USD_PER_UNIT)}`;
|
|
9
|
+
const STATE_LABEL = {
|
|
10
|
+
held: 'retenido — esperando aprobación humana, todavía no se gastó nada',
|
|
11
|
+
authorized: 'autorizado — AVAL está pagando o ya pagó, aún sin confirmar el coste final',
|
|
12
|
+
settled: 'liquidado — el pago se completó',
|
|
13
|
+
released: 'liberado — se rechazó o falló, no se gastó nada',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Creates the `check_payment` tool: the other half of the async approval
|
|
17
|
+
* story. `pay` can return "retenido" and end the turn there — nothing in the
|
|
18
|
+
* framework blocks waiting for a human. This tool is how a later invocation
|
|
19
|
+
* (a cron trigger, a follow-up prompt, a human asking "¿ya se aprobó?") finds
|
|
20
|
+
* out what happened without needing the console credential AVAL's dashboard
|
|
21
|
+
* requires.
|
|
22
|
+
*/
|
|
23
|
+
function createCheckPaymentTool(config) {
|
|
24
|
+
return (0, agent_core_1.defineTool)({
|
|
25
|
+
name: 'check_payment',
|
|
26
|
+
description: 'Consulta el estado de un gasto pedido antes con pay, usando su authorizationId. ' +
|
|
27
|
+
'Úsalo para saber si un gasto "retenido" ya fue aprobado o rechazado.',
|
|
28
|
+
input: agent_core_1.z.object({
|
|
29
|
+
authorizationId: agent_core_1.z.string().describe('El authorizationId que devolvió pay.'),
|
|
30
|
+
}),
|
|
31
|
+
execute: async ({ authorizationId }, ctx) => {
|
|
32
|
+
const token = (0, client_1.avalKey)(config, ctx.secrets);
|
|
33
|
+
if (!token) {
|
|
34
|
+
return `Error: este agente no tiene credencial de AVAL configurada ` +
|
|
35
|
+
`(falta ctx.secrets.${config.secretName ?? 'AVAL_KEY'} en su perfil).`;
|
|
36
|
+
}
|
|
37
|
+
const { status, body } = await (0, client_1.avalRequest)(config, token, 'GET', `/api/v1/spend/${encodeURIComponent(authorizationId)}`);
|
|
38
|
+
if (status === 404)
|
|
39
|
+
return `No existe ninguna autorización con id "${authorizationId}".`;
|
|
40
|
+
if (status === 403)
|
|
41
|
+
return `Esa autorización no pertenece a este agente.`;
|
|
42
|
+
if (status === 401)
|
|
43
|
+
return `Error de credencial: ${body.error}`;
|
|
44
|
+
if (!body.state)
|
|
45
|
+
return `Error: respuesta inesperada de AVAL (${status}): ${body.error ?? JSON.stringify(body)}`;
|
|
46
|
+
const reasons = body.reasons?.length ? `\nMotivo: ${body.reasons.join(' ')}` : '';
|
|
47
|
+
return (`${authorizationId} → ${STATE_LABEL[body.state]}.\n` +
|
|
48
|
+
`${formatUsd(body.amount)} · ${body.vendor}${body.rail ? ` · vía ${body.rail}` : ''} · ${body.purpose}` +
|
|
49
|
+
reasons);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=check-payment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-payment.js","sourceRoot":"","sources":["../src/check-payment.ts"],"names":[],"mappings":";;AAkCA,wDAoCC;AAtED,4DAA4D;AAC5D,qCAAsE;AAEtE,MAAM,YAAY,GAAG,SAAS,CAAC;AAC/B,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,EAAE,qBAAqB,EAAE,CAAC,EAAE,qBAAqB,EAAE,CAAC,EAAE,CAAC,CAAC;AACzG,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,YAAY,CAAC,EAAE,CAAC;AAclF,MAAM,WAAW,GAAqD;IAClE,IAAI,EAAE,kEAAkE;IACxE,UAAU,EAAE,4EAA4E;IACxF,OAAO,EAAE,iCAAiC;IAC1C,QAAQ,EAAE,iDAAiD;CAC9D,CAAC;AAEF;;;;;;;GAOG;AACH,SAAgB,sBAAsB,CAAC,MAAuB;IAC1D,OAAO,IAAA,uBAAU,EAAC;QACd,IAAI,EAAE,eAAe;QACrB,WAAW,EACP,kFAAkF;YAClF,sEAAsE;QAC1E,KAAK,EAAE,cAAC,CAAC,MAAM,CAAC;YACZ,eAAe,EAAE,cAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;SAC/E,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,EAAE,GAAG,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,IAAA,gBAAO,EAAC,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,OAAO,6DAA6D;oBAChE,sBAAsB,MAAM,CAAC,UAAU,IAAI,UAAU,iBAAiB,CAAC;YAC/E,CAAC;YAED,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAA,oBAAW,EACtC,MAAM,EACN,KAAK,EACL,KAAK,EACL,iBAAiB,kBAAkB,CAAC,eAAe,CAAC,EAAE,CACzD,CAAC;YAEF,IAAI,MAAM,KAAK,GAAG;gBAAE,OAAO,0CAA0C,eAAe,IAAI,CAAC;YACzF,IAAI,MAAM,KAAK,GAAG;gBAAE,OAAO,8CAA8C,CAAC;YAC1E,IAAI,MAAM,KAAK,GAAG;gBAAE,OAAO,wBAAwB,IAAI,CAAC,KAAK,EAAE,CAAC;YAChE,IAAI,CAAC,IAAI,CAAC,KAAK;gBAAE,OAAO,wCAAwC,MAAM,MAAM,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YAEjH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,OAAO,CACH,GAAG,eAAe,MAAM,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK;gBACpD,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,CAAC,OAAO,EAAE;gBACvG,OAAO,CACV,CAAC;QACN,CAAC;KACJ,CAAC,CAAC;AACP,CAAC"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface AvalToolsConfig {
|
|
2
|
+
/** Where AVAL is reachable, e.g. "https://aval.internal" or "http://localhost:3000". */
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
/**
|
|
5
|
+
* Name of the credential inside ExecutionContext.secrets that holds this
|
|
6
|
+
* agent's AVAL key (aval_sk_… or a JWT). Defaults to "AVAL_KEY" so most
|
|
7
|
+
* callers never need to set this.
|
|
8
|
+
*/
|
|
9
|
+
secretName?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
maxAttempts?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface AvalResponse<T> {
|
|
14
|
+
status: number;
|
|
15
|
+
ok: boolean;
|
|
16
|
+
body: T;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* fetch() wrapper for AVAL, scoped to one call rather than one process: the
|
|
20
|
+
* bearer token is a parameter, not baked into the client, because it's the
|
|
21
|
+
* calling agent's own credential (ExecutionContext.secrets), not a shared
|
|
22
|
+
* process-wide key.
|
|
23
|
+
*
|
|
24
|
+
* Unlike a generic HTTP client, 4xx responses are NOT thrown here — AVAL's 400
|
|
25
|
+
* (denied), 401 (revoked/caducada), 403 (no es tuya), 404 (no existe) and 429
|
|
26
|
+
* (límite) are meaningful domain answers a tool has to turn into text an agent
|
|
27
|
+
* can act on, not transport failures. Only network errors and 5xx retry; only
|
|
28
|
+
* those, and a response body that isn't JSON, throw.
|
|
29
|
+
*/
|
|
30
|
+
export declare function avalRequest<T>(config: AvalToolsConfig, token: string, method: 'GET' | 'POST', path: string, body?: unknown): Promise<AvalResponse<T>>;
|
|
31
|
+
/** Reads this agent's AVAL credential from its own secrets, never a shared env var. */
|
|
32
|
+
export declare function avalKey(config: AvalToolsConfig, secrets: Record<string, string> | undefined): string | null;
|
|
33
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC5B,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,IAAI,EAAE,CAAC,CAAC;CACX;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAC/B,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAgD1B;AAED,uFAAuF;AACvF,wBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3G"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.avalRequest = avalRequest;
|
|
4
|
+
exports.avalKey = avalKey;
|
|
5
|
+
const retry_1 = require("@backendkit-labs/retry");
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
7
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
8
|
+
/**
|
|
9
|
+
* fetch() wrapper for AVAL, scoped to one call rather than one process: the
|
|
10
|
+
* bearer token is a parameter, not baked into the client, because it's the
|
|
11
|
+
* calling agent's own credential (ExecutionContext.secrets), not a shared
|
|
12
|
+
* process-wide key.
|
|
13
|
+
*
|
|
14
|
+
* Unlike a generic HTTP client, 4xx responses are NOT thrown here — AVAL's 400
|
|
15
|
+
* (denied), 401 (revoked/caducada), 403 (no es tuya), 404 (no existe) and 429
|
|
16
|
+
* (límite) are meaningful domain answers a tool has to turn into text an agent
|
|
17
|
+
* can act on, not transport failures. Only network errors and 5xx retry; only
|
|
18
|
+
* those, and a response body that isn't JSON, throw.
|
|
19
|
+
*/
|
|
20
|
+
async function avalRequest(config, token, method, path, body) {
|
|
21
|
+
const url = `${config.baseUrl.replace(/\/$/, '')}${path}`;
|
|
22
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
23
|
+
const attempt = async () => {
|
|
24
|
+
const controller = new AbortController();
|
|
25
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
26
|
+
let res;
|
|
27
|
+
try {
|
|
28
|
+
res = await fetch(url, {
|
|
29
|
+
method,
|
|
30
|
+
signal: controller.signal,
|
|
31
|
+
headers: {
|
|
32
|
+
'content-type': 'application/json',
|
|
33
|
+
authorization: `Bearer ${token}`,
|
|
34
|
+
},
|
|
35
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const aborted = err instanceof Error && err.name === 'AbortError';
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
41
|
+
throw {
|
|
42
|
+
type: 'network',
|
|
43
|
+
message: aborted
|
|
44
|
+
? `AVAL ${method} ${path} → timed out after ${timeoutMs}ms`
|
|
45
|
+
: `AVAL ${method} ${path} → network error: ${err.message}`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
}
|
|
51
|
+
if (res.status >= 500) {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-throw-literal
|
|
53
|
+
throw { type: 'http', status: res.status, message: `AVAL ${method} ${path} → ${res.status}` };
|
|
54
|
+
}
|
|
55
|
+
const json = (await res.json().catch(() => ({})));
|
|
56
|
+
return { status: res.status, ok: res.ok, body: json };
|
|
57
|
+
};
|
|
58
|
+
const result = await (0, retry_1.retry)(attempt, {
|
|
59
|
+
maxAttempts: config.maxAttempts ?? DEFAULT_MAX_ATTEMPTS,
|
|
60
|
+
backoff: { type: 'exponential', baseDelay: 500, maxDelay: 4_000, jitter: 'full' },
|
|
61
|
+
retryIf: (e) => e.type === 'network' || e.type === 'http',
|
|
62
|
+
});
|
|
63
|
+
if (!result.ok)
|
|
64
|
+
throw new Error(result.error.message);
|
|
65
|
+
return result.value;
|
|
66
|
+
}
|
|
67
|
+
/** Reads this agent's AVAL credential from its own secrets, never a shared env var. */
|
|
68
|
+
function avalKey(config, secrets) {
|
|
69
|
+
return secrets?.[config.secretName ?? 'AVAL_KEY'] ?? null;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type { AvalToolsConfig } from './client';
|
|
2
|
+
export { avalKey, avalRequest } from './client';
|
|
3
|
+
export { createPayTool } from './pay';
|
|
4
|
+
export { createCheckPaymentTool } from './check-payment';
|
|
5
|
+
import type { AvalToolsConfig } from './client';
|
|
6
|
+
/**
|
|
7
|
+
* The two AVAL tools together, ready to spread into an agent's tool list.
|
|
8
|
+
*
|
|
9
|
+
* Wiring an agent up to AVAL is then: give the agent's profile a `secrets`
|
|
10
|
+
* entry with its AVAL key, add "pay" and "check_payment" to its
|
|
11
|
+
* `allowedTools`, and register these with the engine's ToolRegistry.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createAvalTools } from '@backendkit-labs/aval-tools';
|
|
16
|
+
*
|
|
17
|
+
* const avalTools = createAvalTools({ baseUrl: 'https://aval.internal' });
|
|
18
|
+
* for (const tool of avalTools) tools.register(tool);
|
|
19
|
+
*
|
|
20
|
+
* const profile: AgentProfile = {
|
|
21
|
+
* // ...
|
|
22
|
+
* allowedTools: ['pay', 'check_payment', ...],
|
|
23
|
+
* secrets: { AVAL_KEY: process.env.EXPEDIENTES_BOT_AVAL_KEY! },
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function createAvalTools(config: AvalToolsConfig): readonly [import("@backendkit-labs/agent-core").ToolDefinition, import("@backendkit-labs/agent-core").ToolDefinition];
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,yHAEtD"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCheckPaymentTool = exports.createPayTool = exports.avalRequest = exports.avalKey = void 0;
|
|
4
|
+
exports.createAvalTools = createAvalTools;
|
|
5
|
+
const pay_1 = require("./pay");
|
|
6
|
+
const check_payment_1 = require("./check-payment");
|
|
7
|
+
var client_1 = require("./client");
|
|
8
|
+
Object.defineProperty(exports, "avalKey", { enumerable: true, get: function () { return client_1.avalKey; } });
|
|
9
|
+
Object.defineProperty(exports, "avalRequest", { enumerable: true, get: function () { return client_1.avalRequest; } });
|
|
10
|
+
var pay_2 = require("./pay");
|
|
11
|
+
Object.defineProperty(exports, "createPayTool", { enumerable: true, get: function () { return pay_2.createPayTool; } });
|
|
12
|
+
var check_payment_2 = require("./check-payment");
|
|
13
|
+
Object.defineProperty(exports, "createCheckPaymentTool", { enumerable: true, get: function () { return check_payment_2.createCheckPaymentTool; } });
|
|
14
|
+
/**
|
|
15
|
+
* The two AVAL tools together, ready to spread into an agent's tool list.
|
|
16
|
+
*
|
|
17
|
+
* Wiring an agent up to AVAL is then: give the agent's profile a `secrets`
|
|
18
|
+
* entry with its AVAL key, add "pay" and "check_payment" to its
|
|
19
|
+
* `allowedTools`, and register these with the engine's ToolRegistry.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { createAvalTools } from '@backendkit-labs/aval-tools';
|
|
24
|
+
*
|
|
25
|
+
* const avalTools = createAvalTools({ baseUrl: 'https://aval.internal' });
|
|
26
|
+
* for (const tool of avalTools) tools.register(tool);
|
|
27
|
+
*
|
|
28
|
+
* const profile: AgentProfile = {
|
|
29
|
+
* // ...
|
|
30
|
+
* allowedTools: ['pay', 'check_payment', ...],
|
|
31
|
+
* secrets: { AVAL_KEY: process.env.EXPEDIENTES_BOT_AVAL_KEY! },
|
|
32
|
+
* };
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function createAvalTools(config) {
|
|
36
|
+
return [(0, pay_1.createPayTool)(config), (0, check_payment_1.createCheckPaymentTool)(config)];
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AA+BA,0CAEC;AAjCD,+BAAsC;AACtC,mDAAyD;AAGzD,mCAAgD;AAAvC,iGAAA,OAAO,OAAA;AAAE,qGAAA,WAAW,OAAA;AAC7B,6BAAsC;AAA7B,oGAAA,aAAa,OAAA;AACtB,iDAAyD;AAAhD,uHAAA,sBAAsB,OAAA;AAI/B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,eAAe,CAAC,MAAuB;IACnD,OAAO,CAAC,IAAA,mBAAa,EAAC,MAAM,CAAC,EAAE,IAAA,sCAAsB,EAAC,MAAM,CAAC,CAAU,CAAC;AAC5E,CAAC"}
|
package/dist/pay.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AvalToolsConfig } from './client';
|
|
2
|
+
/**
|
|
3
|
+
* Creates the `pay` tool: the amarrada path into AVAL. The agent asks for a
|
|
4
|
+
* ceiling; AVAL decides and, if authorized, pays through its own rail — the
|
|
5
|
+
* agent never sees a vendor credential to bypass.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createPayTool(config: AvalToolsConfig): import("@backendkit-labs/agent-core").ToolDefinition;
|
|
8
|
+
//# sourceMappingURL=pay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pay.d.ts","sourceRoot":"","sources":["../src/pay.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAC;AAuDtE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,wDAuEpD"}
|
package/dist/pay.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPayTool = createPayTool;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const agent_core_1 = require("@backendkit-labs/agent-core");
|
|
6
|
+
const client_1 = require("./client");
|
|
7
|
+
const USD_PER_UNIT = 1_000_000;
|
|
8
|
+
/**
|
|
9
|
+
* A deterministic idempotency key from what the call actually is, not a
|
|
10
|
+
* random one: an agent that retries the exact same request after a timeout
|
|
11
|
+
* gets the exact same key and AVAL treats it as a replay instead of a second
|
|
12
|
+
* charge. A genuinely new request — different args, or a later turn — gets a
|
|
13
|
+
* different key on its own.
|
|
14
|
+
*/
|
|
15
|
+
function deriveIdempotencyKey(sessionId, counterparty, ceilingUsd, purpose) {
|
|
16
|
+
return (0, node_crypto_1.createHash)('sha256').update(`${sessionId}:${counterparty}:${ceilingUsd}:${purpose}`).digest('hex').slice(0, 24);
|
|
17
|
+
}
|
|
18
|
+
const usdFormat = new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 });
|
|
19
|
+
/** Mirrors AVAL's own money() formatter: two decimals normally, up to six for sub-cent amounts. */
|
|
20
|
+
function formatUsd(units) {
|
|
21
|
+
return `$${usdFormat.format(units / USD_PER_UNIT)}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates the `pay` tool: the amarrada path into AVAL. The agent asks for a
|
|
25
|
+
* ceiling; AVAL decides and, if authorized, pays through its own rail — the
|
|
26
|
+
* agent never sees a vendor credential to bypass.
|
|
27
|
+
*/
|
|
28
|
+
function createPayTool(config) {
|
|
29
|
+
return (0, agent_core_1.defineTool)({
|
|
30
|
+
name: 'pay',
|
|
31
|
+
description: 'Solicita un gasto a AVAL para pagar a una contraparte registrada. AVAL decide si se ' +
|
|
32
|
+
'autoriza, se retiene para aprobación humana, o se deniega, y si autoriza, paga él mismo ' +
|
|
33
|
+
'por el carril que corresponda. Nunca gastes por tu cuenta: si la respuesta no es ' +
|
|
34
|
+
'"authorized", no hay pago que hacer.',
|
|
35
|
+
input: agent_core_1.z.object({
|
|
36
|
+
counterparty: agent_core_1.z.string().describe('Identificador de la contraparte registrada en AVAL, p. ej. "ocr.docs".'),
|
|
37
|
+
ceilingUsd: agent_core_1.z.number().positive().describe('Techo del gasto en dólares, p. ej. 0.42 para 42 centavos. El coste real puede ser menor.'),
|
|
38
|
+
purpose: agent_core_1.z.string().describe('Motivo del gasto, en una frase clara: queda escrito en el registro de AVAL.'),
|
|
39
|
+
idempotencyKey: agent_core_1.z
|
|
40
|
+
.string()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe('Sólo si necesitas forzar una clave concreta. Por defecto se deriva de la sesión y los argumentos.'),
|
|
43
|
+
}),
|
|
44
|
+
execute: async ({ counterparty, ceilingUsd, purpose, idempotencyKey }, ctx) => {
|
|
45
|
+
const token = (0, client_1.avalKey)(config, ctx.secrets);
|
|
46
|
+
if (!token) {
|
|
47
|
+
return `Error: este agente no tiene credencial de AVAL configurada ` +
|
|
48
|
+
`(falta ctx.secrets.${config.secretName ?? 'AVAL_KEY'} en su perfil).`;
|
|
49
|
+
}
|
|
50
|
+
const ceiling = Math.round(ceilingUsd * USD_PER_UNIT);
|
|
51
|
+
const key = idempotencyKey ?? deriveIdempotencyKey(ctx.sessionId, counterparty, ceiling, purpose);
|
|
52
|
+
const { status, body } = await (0, client_1.avalRequest)(config, token, 'POST', '/api/v1/spend', {
|
|
53
|
+
counterparty,
|
|
54
|
+
ceiling,
|
|
55
|
+
purpose,
|
|
56
|
+
idempotencyKey: key,
|
|
57
|
+
});
|
|
58
|
+
if (status === 429) {
|
|
59
|
+
return `AVAL frenó la petición (demasiados intentos o llamadas). ` +
|
|
60
|
+
`Reintenta en ${body.retryAfterSeconds ?? '?'} s. ${body.error ?? ''}`.trim();
|
|
61
|
+
}
|
|
62
|
+
if (status === 401 || status === 403) {
|
|
63
|
+
return `Error de credencial: ${body.error}`;
|
|
64
|
+
}
|
|
65
|
+
if (!body.decision) {
|
|
66
|
+
return `Error: respuesta inesperada de AVAL (${status}): ${body.error ?? JSON.stringify(body)}`;
|
|
67
|
+
}
|
|
68
|
+
const warning = body.credential?.warning ? `\nAviso de credencial: ${body.credential.warning}` : '';
|
|
69
|
+
const reasons = body.reasons?.length ? `\nMotivo: ${body.reasons.join(' ')}` : '';
|
|
70
|
+
if (body.decision === 'denied') {
|
|
71
|
+
return `Denegado.${reasons}${warning}`;
|
|
72
|
+
}
|
|
73
|
+
if (body.decision === 'held') {
|
|
74
|
+
return (`Retenido para aprobación humana. authorizationId="${body.authorizationId}".` +
|
|
75
|
+
`${reasons}\nUsa check_payment con este authorizationId más tarde para saber si ya se resolvió — ` +
|
|
76
|
+
`no se ha gastado nada todavía.${warning}`);
|
|
77
|
+
}
|
|
78
|
+
// authorized
|
|
79
|
+
const cap = body.capability;
|
|
80
|
+
const actual = cap ? formatUsd(cap.actual) : formatUsd(body.amount ?? ceiling);
|
|
81
|
+
const nativo = cap ? `${cap.payment.nativeAmount} ${cap.payment.asset}` : '';
|
|
82
|
+
return (`Pagado. authorizationId="${body.authorizationId}", carril=${body.rail ?? cap?.rail}, ` +
|
|
83
|
+
`coste real=${actual}${nativo ? ` (${nativo})` : ''}, ref=${cap?.payment.ref ?? '—'}.` +
|
|
84
|
+
`${reasons}${warning}`);
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=pay.js.map
|
package/dist/pay.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pay.js","sourceRoot":"","sources":["../src/pay.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backendkit-labs/aval-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Payment tools for @backendkit-labs/agent-core agents to spend through an AVAL instance",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": ["dist/", "README.md"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/BackendKit-labs/backendkit-agent-framework.git",
|
|
11
|
+
"directory": "packages/aval-tools"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": { "access": "public" },
|
|
14
|
+
"engines": { "node": ">=20.0.0" },
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@backendkit-labs/agent-core": "*"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/jest": "^29.0.0",
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"jest": "^29.0.0",
|
|
29
|
+
"ts-jest": "^29.0.0",
|
|
30
|
+
"typescript": "^5.4.0"
|
|
31
|
+
}
|
|
32
|
+
}
|