@vaia-lab/sdk 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +107 -151
- package/dist/agent-1bVw0eB8.d.cts +222 -0
- package/dist/agent-1bVw0eB8.d.ts +222 -0
- package/dist/cli.js +252 -0
- package/dist/index.cjs +495 -12
- package/dist/index.d.cts +487 -5
- package/dist/index.d.ts +487 -5
- package/dist/index.js +477 -11
- package/dist/react/index.cjs +1240 -0
- package/dist/react/index.d.cts +95 -0
- package/dist/react/index.d.ts +95 -0
- package/dist/react/index.js +1228 -0
- package/package.json +45 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,28 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
|
+
AGENT_PROTOCOL_VERSION: () => AGENT_PROTOCOL_VERSION,
|
|
25
|
+
CONNECTOR_OF_OPERATION: () => CONNECTOR_OF_OPERATION,
|
|
24
26
|
VAIAError: () => VAIAError,
|
|
27
|
+
agentLoop: () => agentLoop,
|
|
28
|
+
capabilities: () => capabilities,
|
|
29
|
+
checkAuthority: () => checkAuthority,
|
|
25
30
|
defineCapability: () => defineCapability,
|
|
31
|
+
defineEcosystem: () => defineEcosystem,
|
|
32
|
+
fromMCPTool: () => fromMCPTool,
|
|
26
33
|
gandia: () => gandia,
|
|
27
34
|
handeia: () => handeia,
|
|
28
|
-
|
|
35
|
+
http: () => http,
|
|
36
|
+
httpTransport: () => httpTransport,
|
|
37
|
+
local: () => local,
|
|
38
|
+
mcp: () => mcp,
|
|
39
|
+
requiresApproval: () => requiresApproval,
|
|
40
|
+
toMCPTool: () => toMCPTool,
|
|
41
|
+
toMCPTools: () => toMCPTools,
|
|
42
|
+
toManifest: () => toManifest,
|
|
43
|
+
validateActionCall: () => validateActionCall,
|
|
44
|
+
validateAgentSurface: () => validateAgentSurface,
|
|
45
|
+
validatePieces: () => validatePieces
|
|
29
46
|
});
|
|
30
47
|
module.exports = __toCommonJS(src_exports);
|
|
31
48
|
|
|
@@ -47,10 +64,12 @@ __export(gandia_exports, {
|
|
|
47
64
|
// src/crypto.ts
|
|
48
65
|
var enc = new TextEncoder();
|
|
49
66
|
function hexToBytes(hex) {
|
|
50
|
-
if (hex.length % 2 !== 0
|
|
51
|
-
|
|
67
|
+
if (hex.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(hex)) {
|
|
68
|
+
throw new Error("Invalid hex string");
|
|
69
|
+
}
|
|
70
|
+
const bytes = new Uint8Array(new ArrayBuffer(hex.length / 2));
|
|
52
71
|
for (let i = 0; i < hex.length; i += 2) {
|
|
53
|
-
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
72
|
+
bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
|
|
54
73
|
}
|
|
55
74
|
return bytes;
|
|
56
75
|
}
|
|
@@ -94,9 +113,7 @@ async function verify(request, secret) {
|
|
|
94
113
|
const sigHeader = headers.get("x-gandia-signature") ?? "";
|
|
95
114
|
const tsHeader = headers.get("x-gandia-timestamp") ?? "";
|
|
96
115
|
const callId = headers.get("x-gandia-call-id") ?? "";
|
|
97
|
-
|
|
98
|
-
return { ctx: buildProbeCtx(callId), raw: rawBody };
|
|
99
|
-
}
|
|
116
|
+
const esProbe = headers.get(PROBE_HEADER) === "1";
|
|
100
117
|
if (!sigHeader || !tsHeader) {
|
|
101
118
|
throw new VAIAError(
|
|
102
119
|
"Faltan headers de autenticaci\xF3n (X-Gandia-Signature, X-Gandia-Timestamp)",
|
|
@@ -114,6 +131,9 @@ async function verify(request, secret) {
|
|
|
114
131
|
if (!valid) {
|
|
115
132
|
throw new VAIAError("Firma HMAC inv\xE1lida", "HMAC_INVALID", 401);
|
|
116
133
|
}
|
|
134
|
+
if (esProbe) {
|
|
135
|
+
return { ctx: buildProbeCtx(callId), raw: rawBody };
|
|
136
|
+
}
|
|
117
137
|
let body;
|
|
118
138
|
try {
|
|
119
139
|
body = JSON.parse(rawBody);
|
|
@@ -473,9 +493,7 @@ async function verify3(request, secret) {
|
|
|
473
493
|
const sigHeader = headers.get("x-handeia-signature") ?? "";
|
|
474
494
|
const tsHeader = headers.get("x-handeia-timestamp") ?? "";
|
|
475
495
|
const callId = headers.get("x-handeia-call-id") ?? "";
|
|
476
|
-
|
|
477
|
-
return { ctx: buildProbeCtx2(callId), raw: rawBody };
|
|
478
|
-
}
|
|
496
|
+
const esProbe = headers.get(PROBE_HEADER2) === "1";
|
|
479
497
|
if (!sigHeader || !tsHeader) {
|
|
480
498
|
throw new VAIAError(
|
|
481
499
|
"Faltan headers de autenticaci\xF3n (X-Handeia-Signature, X-Handeia-Timestamp)",
|
|
@@ -493,6 +511,9 @@ async function verify3(request, secret) {
|
|
|
493
511
|
if (!valid) {
|
|
494
512
|
throw new VAIAError("Firma HMAC inv\xE1lida", "HMAC_INVALID", 401);
|
|
495
513
|
}
|
|
514
|
+
if (esProbe) {
|
|
515
|
+
return { ctx: buildProbeCtx2(callId), raw: rawBody };
|
|
516
|
+
}
|
|
496
517
|
let body;
|
|
497
518
|
try {
|
|
498
519
|
body = JSON.parse(rawBody);
|
|
@@ -576,6 +597,154 @@ async function fromUrl2(url, secret) {
|
|
|
576
597
|
return verify4(token, secret);
|
|
577
598
|
}
|
|
578
599
|
|
|
600
|
+
// src/agent.ts
|
|
601
|
+
var AGENT_PROTOCOL_VERSION = 1;
|
|
602
|
+
var CONNECTOR_OF_OPERATION = {
|
|
603
|
+
"github.repos": "github",
|
|
604
|
+
"github.issues": "github",
|
|
605
|
+
"drive.files": "drive",
|
|
606
|
+
"calendar.events": "calendar",
|
|
607
|
+
"email.recent": "email",
|
|
608
|
+
"notion.pages": "notion"
|
|
609
|
+
};
|
|
610
|
+
var NOMBRE_ACCION = /^[a-z][a-z0-9_]{1,48}$/;
|
|
611
|
+
function validateAgentSurface(cfg) {
|
|
612
|
+
const errores = [];
|
|
613
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
614
|
+
for (const accion of cfg.actions ?? []) {
|
|
615
|
+
if (!NOMBRE_ACCION.test(accion.name)) {
|
|
616
|
+
errores.push(`Acci\xF3n "${accion.name}": el nombre debe ser min\xFAsculas, n\xFAmeros o guion bajo.`);
|
|
617
|
+
}
|
|
618
|
+
if (vistos.has(accion.name)) {
|
|
619
|
+
errores.push(`Acci\xF3n "${accion.name}": declarada dos veces.`);
|
|
620
|
+
}
|
|
621
|
+
vistos.add(accion.name);
|
|
622
|
+
if (!accion.description?.trim()) {
|
|
623
|
+
errores.push(`Acci\xF3n "${accion.name}": falta la descripci\xF3n, que es lo que el agente lee para elegirla.`);
|
|
624
|
+
}
|
|
625
|
+
if (accion.writes && !accion.permission) {
|
|
626
|
+
errores.push(`Acci\xF3n "${accion.name}": modifica datos, as\xED que necesita un permiso declarado.`);
|
|
627
|
+
}
|
|
628
|
+
for (const p of accion.params ?? []) {
|
|
629
|
+
if (!p.name?.trim()) errores.push(`Acci\xF3n "${accion.name}": un par\xE1metro no tiene nombre.`);
|
|
630
|
+
if (!p.description?.trim()) {
|
|
631
|
+
errores.push(`Acci\xF3n "${accion.name}", par\xE1metro "${p.name}": falta la descripci\xF3n.`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (cfg.queryEndpoint && !cfg.queryEndpoint.startsWith("/")) {
|
|
636
|
+
errores.push('queryEndpoint debe ser una ruta de tu propio servidor, empezando por "/".');
|
|
637
|
+
}
|
|
638
|
+
return errores;
|
|
639
|
+
}
|
|
640
|
+
function validateActionCall(llamada, declaradas) {
|
|
641
|
+
const action = declaradas.find((a) => a.name === llamada.name);
|
|
642
|
+
if (!action) return { ok: false, reason: `La acci\xF3n "${llamada.name}" no est\xE1 declarada por el espacio.` };
|
|
643
|
+
const args = llamada.args ?? {};
|
|
644
|
+
for (const p of action.params ?? []) {
|
|
645
|
+
const v = args[p.name];
|
|
646
|
+
if (v === void 0 || v === null) {
|
|
647
|
+
if (p.required) return { ok: false, reason: `Falta el par\xE1metro obligatorio "${p.name}".` };
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
if (typeof v !== p.type) {
|
|
651
|
+
return { ok: false, reason: `El par\xE1metro "${p.name}" debe ser ${p.type}.` };
|
|
652
|
+
}
|
|
653
|
+
if (p.enum && !p.enum.includes(String(v))) {
|
|
654
|
+
return { ok: false, reason: `El par\xE1metro "${p.name}" no admite el valor "${String(v)}".` };
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
const permitidos = new Set((action.params ?? []).map((p) => p.name));
|
|
658
|
+
for (const k of Object.keys(args)) {
|
|
659
|
+
if (!permitidos.has(k)) return { ok: false, reason: `El par\xE1metro "${k}" no est\xE1 declarado.` };
|
|
660
|
+
}
|
|
661
|
+
return { ok: true, action };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/pieces.ts
|
|
665
|
+
var NOMBRE = /^[a-z][a-z0-9_]{1,48}$/;
|
|
666
|
+
var ORDEN = {
|
|
667
|
+
prohibida: 0,
|
|
668
|
+
requiere_aprobacion: 1,
|
|
669
|
+
autonoma: 2
|
|
670
|
+
};
|
|
671
|
+
function validatePieces(cfg) {
|
|
672
|
+
const errores = [];
|
|
673
|
+
const vistos = /* @__PURE__ */ new Set();
|
|
674
|
+
const revisarNombre = (pieza, name) => {
|
|
675
|
+
if (!NOMBRE.test(name)) errores.push(`${pieza} "${name}": el nombre debe ser min\xFAsculas, n\xFAmeros o guion bajo.`);
|
|
676
|
+
if (vistos.has(name)) errores.push(`"${name}": hay dos piezas con el mismo nombre.`);
|
|
677
|
+
vistos.add(name);
|
|
678
|
+
};
|
|
679
|
+
const revisarAutoridad = (pieza, a) => {
|
|
680
|
+
if (a.consequence === "irreversible" && a.level === "autonoma") {
|
|
681
|
+
errores.push(`${pieza}: una acci\xF3n irreversible no puede ser aut\xF3noma \u2014 como m\xEDnimo requiere aprobaci\xF3n.`);
|
|
682
|
+
}
|
|
683
|
+
if (a.maxAmount !== void 0) {
|
|
684
|
+
if (a.maxAmount <= 0) errores.push(`${pieza}: el tope de gasto debe ser mayor que cero.`);
|
|
685
|
+
if (!a.currency) errores.push(`${pieza}: hay tope de gasto pero no se declar\xF3 la moneda.`);
|
|
686
|
+
}
|
|
687
|
+
if (a.consequence === "costosa" && a.level === "autonoma" && a.maxAmount === void 0) {
|
|
688
|
+
errores.push(`${pieza}: es aut\xF3noma y cuesta dinero, as\xED que necesita un tope declarado.`);
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
for (const s2 of cfg.skills ?? []) {
|
|
692
|
+
revisarNombre("Skill", s2.name);
|
|
693
|
+
if (!s2.description?.trim()) errores.push(`Skill "${s2.name}": falta la descripci\xF3n, que es lo que el modelo lee para elegirla.`);
|
|
694
|
+
if (s2.evidence?.required && (s2.evidence.accepts?.length ?? 0) === 0) {
|
|
695
|
+
errores.push(`Skill "${s2.name}": exige evidencia pero no declara qu\xE9 tipos acepta.`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
for (const t of cfg.tools ?? []) {
|
|
699
|
+
revisarNombre("Herramienta", t.name);
|
|
700
|
+
if (!t.description?.trim()) errores.push(`Herramienta "${t.name}": falta la descripci\xF3n.`);
|
|
701
|
+
if (!t.permission?.trim()) errores.push(`Herramienta "${t.name}": toca el mundo real, as\xED que necesita un permiso declarado.`);
|
|
702
|
+
revisarAutoridad(`Herramienta "${t.name}"`, t.authority);
|
|
703
|
+
}
|
|
704
|
+
for (const w of cfg.workflows ?? []) {
|
|
705
|
+
revisarNombre("Workflow", w.name);
|
|
706
|
+
if ((w.steps?.length ?? 0) === 0) errores.push(`Workflow "${w.name}": no tiene pasos.`);
|
|
707
|
+
revisarAutoridad(`Workflow "${w.name}"`, w.authority);
|
|
708
|
+
}
|
|
709
|
+
const porNombre = new Map((cfg.tools ?? []).map((t) => [t.name, t]));
|
|
710
|
+
for (const a of cfg.agents ?? []) {
|
|
711
|
+
revisarNombre("Agente", a.name);
|
|
712
|
+
if (!a.purpose?.trim()) {
|
|
713
|
+
errores.push(`Agente "${a.name}": falta el prop\xF3sito \u2014 para qu\xE9 existe.`);
|
|
714
|
+
}
|
|
715
|
+
revisarAutoridad(`Agente "${a.name}"`, a.authority);
|
|
716
|
+
for (const nombreTool of a.tools ?? []) {
|
|
717
|
+
const tool = porNombre.get(nombreTool);
|
|
718
|
+
if (!tool) {
|
|
719
|
+
errores.push(`Agente "${a.name}": usa la herramienta "${nombreTool}", que no est\xE1 declarada.`);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
if (ORDEN[tool.authority.level] > ORDEN[a.authority.level]) {
|
|
723
|
+
errores.push(
|
|
724
|
+
`Agente "${a.name}": su herramienta "${nombreTool}" tiene m\xE1s autoridad que \xE9l. Ninguna pieza puede superar el techo de su agente.`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return errores;
|
|
730
|
+
}
|
|
731
|
+
function requiresApproval(a) {
|
|
732
|
+
return a.level !== "autonoma";
|
|
733
|
+
}
|
|
734
|
+
function checkAuthority(a, intento = {}) {
|
|
735
|
+
if (a.level === "prohibida") return { ok: false, reason: "Esta acci\xF3n est\xE1 prohibida para esta pieza." };
|
|
736
|
+
if (intento.amount !== void 0) {
|
|
737
|
+
if (a.maxAmount === void 0) return { ok: false, reason: "Mueve dinero pero no hay tope declarado." };
|
|
738
|
+
if (intento.currency && a.currency && intento.currency !== a.currency) {
|
|
739
|
+
return { ok: false, reason: `Moneda distinta a la declarada (${a.currency}).` };
|
|
740
|
+
}
|
|
741
|
+
if (intento.amount > a.maxAmount) {
|
|
742
|
+
return { ok: false, reason: `Excede el tope declarado (${a.maxAmount} ${a.currency ?? ""}).`.trim() };
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return { ok: true };
|
|
746
|
+
}
|
|
747
|
+
|
|
579
748
|
// src/define.ts
|
|
580
749
|
function defineCapability(config) {
|
|
581
750
|
const required = ["id", "name", "version", "target", "type", "sector", "permissions", "risk"];
|
|
@@ -592,6 +761,20 @@ function defineCapability(config) {
|
|
|
592
761
|
if (Object.keys(config.surfaces).length === 0) {
|
|
593
762
|
throw new Error(`[@vaia/sdk] defineCapability: 'surfaces' no puede estar vac\xEDo. Define al menos un surface con su endpoint.`);
|
|
594
763
|
}
|
|
764
|
+
if (config.pieces) {
|
|
765
|
+
const errores = validatePieces(config.pieces);
|
|
766
|
+
if (errores.length > 0) {
|
|
767
|
+
throw new Error(`[@vaia/sdk] defineCapability: piezas inv\xE1lidas:
|
|
768
|
+
- ${errores.join("\n - ")}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (config.agent) {
|
|
772
|
+
const errores = validateAgentSurface(config.agent);
|
|
773
|
+
if (errores.length > 0) {
|
|
774
|
+
throw new Error(`[@vaia/sdk] defineCapability: superficie de agente inv\xE1lida:
|
|
775
|
+
- ${errores.join("\n - ")}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
595
778
|
return config;
|
|
596
779
|
}
|
|
597
780
|
function toManifest(config) {
|
|
@@ -606,6 +789,16 @@ function toManifest(config) {
|
|
|
606
789
|
level: config.level,
|
|
607
790
|
sector: config.sector,
|
|
608
791
|
surfaces,
|
|
792
|
+
// El agente viaja en el manifest para que el portal y Handeia sepan qué
|
|
793
|
+
// puede hacer este espacio sin abrir su código.
|
|
794
|
+
agent: config.agent ? {
|
|
795
|
+
protocol: AGENT_PROTOCOL_VERSION,
|
|
796
|
+
actions: config.agent.actions ?? [],
|
|
797
|
+
query_endpoint: config.agent.queryEndpoint
|
|
798
|
+
} : void 0,
|
|
799
|
+
// Las piezas viajan al manifest: el portal necesita mostrar qué autoridad
|
|
800
|
+
// pide una capacidad ANTES de que alguien la instale.
|
|
801
|
+
pieces: config.pieces,
|
|
609
802
|
permissions: config.permissions,
|
|
610
803
|
risk: config.risk,
|
|
611
804
|
has_own_auth: config.has_own_auth ?? false,
|
|
@@ -620,15 +813,305 @@ function toManifest(config) {
|
|
|
620
813
|
};
|
|
621
814
|
}
|
|
622
815
|
|
|
816
|
+
// src/capabilities.ts
|
|
817
|
+
async function conAutoridad(tools, call, ejecutar) {
|
|
818
|
+
const tool = tools.find((t) => t.name === call.name);
|
|
819
|
+
if (!tool) {
|
|
820
|
+
return { ok: false, error: `La operaci\xF3n "${call.name}" no est\xE1 declarada en esta capacidad.` };
|
|
821
|
+
}
|
|
822
|
+
const permitido = checkAuthority(tool.authority, {
|
|
823
|
+
amount: call.amount,
|
|
824
|
+
currency: call.currency
|
|
825
|
+
});
|
|
826
|
+
if (!permitido.ok) return { ok: false, error: permitido.reason };
|
|
827
|
+
if (tool.authority.level === "prohibida") {
|
|
828
|
+
return { ok: false, error: `"${call.name}" est\xE1 prohibida.` };
|
|
829
|
+
}
|
|
830
|
+
if (tool.authority.level !== "autonoma" && !call.approved) {
|
|
831
|
+
return { ok: false, needsApproval: true, error: "Requiere aprobaci\xF3n humana antes de ejecutarse." };
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
return await ejecutar();
|
|
835
|
+
} catch (e) {
|
|
836
|
+
return { ok: false, error: e instanceof Error ? e.message : "La capacidad fall\xF3." };
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
function local(opts) {
|
|
840
|
+
return {
|
|
841
|
+
id: opts.id,
|
|
842
|
+
tools: opts.tools,
|
|
843
|
+
run: (call) => conAutoridad(opts.tools, call, async () => ({
|
|
844
|
+
ok: true,
|
|
845
|
+
data: await opts.handler(call.name, call.args ?? {}),
|
|
846
|
+
evidence: { source: "local", label: opts.id }
|
|
847
|
+
}))
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function http(opts) {
|
|
851
|
+
const base = opts.baseUrl.replace(/\/$/, "");
|
|
852
|
+
return {
|
|
853
|
+
id: opts.id,
|
|
854
|
+
tools: opts.tools,
|
|
855
|
+
run: (call) => conAutoridad(opts.tools, call, async () => {
|
|
856
|
+
const res = await fetch(`${base}/${call.name}`, {
|
|
857
|
+
method: "POST",
|
|
858
|
+
headers: { "Content-Type": "application/json; charset=utf-8", ...opts.headers },
|
|
859
|
+
body: JSON.stringify(call.args ?? {}),
|
|
860
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
861
|
+
});
|
|
862
|
+
const texto = await res.text();
|
|
863
|
+
if (!res.ok) return { ok: false, error: `${opts.id} respondi\xF3 ${res.status}` };
|
|
864
|
+
let data;
|
|
865
|
+
try {
|
|
866
|
+
data = JSON.parse(texto);
|
|
867
|
+
} catch {
|
|
868
|
+
data = texto;
|
|
869
|
+
}
|
|
870
|
+
return { ok: true, data, evidence: { source: "http", label: `${opts.id}/${call.name}` } };
|
|
871
|
+
})
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function httpTransport(url, headers) {
|
|
875
|
+
return {
|
|
876
|
+
async send(mensaje) {
|
|
877
|
+
const res = await fetch(url, {
|
|
878
|
+
method: "POST",
|
|
879
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", ...headers },
|
|
880
|
+
body: JSON.stringify(mensaje),
|
|
881
|
+
signal: AbortSignal.timeout(2e4)
|
|
882
|
+
});
|
|
883
|
+
if (!res.ok) throw new VAIAError(`Servidor MCP respondi\xF3 ${res.status}`, "MCP_HTTP_ERROR", 502);
|
|
884
|
+
return res.json();
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
var PROHIBIDA = { level: "prohibida", consequence: "irreversible" };
|
|
889
|
+
async function mcp(opts) {
|
|
890
|
+
let siguienteId = 1;
|
|
891
|
+
const rpc = async (method, params) => {
|
|
892
|
+
const r = await opts.transport.send({
|
|
893
|
+
jsonrpc: "2.0",
|
|
894
|
+
id: siguienteId++,
|
|
895
|
+
method,
|
|
896
|
+
...params ? { params } : {}
|
|
897
|
+
});
|
|
898
|
+
if (r?.["error"]) {
|
|
899
|
+
const e = r["error"];
|
|
900
|
+
throw new VAIAError(e?.message ?? "Error del servidor MCP", "MCP_ERROR", 502);
|
|
901
|
+
}
|
|
902
|
+
return r?.["result"] ?? {};
|
|
903
|
+
};
|
|
904
|
+
await rpc("initialize", {
|
|
905
|
+
protocolVersion: "2024-11-05",
|
|
906
|
+
capabilities: {},
|
|
907
|
+
clientInfo: { name: "@vaia-lab/sdk", version: "0.3.0" }
|
|
908
|
+
});
|
|
909
|
+
const listadas = (await rpc("tools/list"))["tools"] ?? [];
|
|
910
|
+
const tools = listadas.map((t) => ({
|
|
911
|
+
name: normalizar(t.name),
|
|
912
|
+
description: t.description?.trim() || `Herramienta MCP "${t.name}".`,
|
|
913
|
+
authority: opts.authority[t.name] ?? opts.defaultAuthority ?? PROHIBIDA,
|
|
914
|
+
permission: opts.permission ?? `mcp:${opts.id}`
|
|
915
|
+
}));
|
|
916
|
+
const original = new Map(listadas.map((t) => [normalizar(t.name), t.name]));
|
|
917
|
+
return {
|
|
918
|
+
id: opts.id,
|
|
919
|
+
tools,
|
|
920
|
+
run: (call) => conAutoridad(tools, call, async () => {
|
|
921
|
+
const nombreReal = original.get(call.name) ?? call.name;
|
|
922
|
+
const r = await rpc("tools/call", { name: nombreReal, arguments: call.args ?? {} });
|
|
923
|
+
return {
|
|
924
|
+
ok: !r["isError"],
|
|
925
|
+
data: r["content"] ?? r,
|
|
926
|
+
error: r["isError"] ? "La herramienta MCP devolvi\xF3 un error." : void 0,
|
|
927
|
+
evidence: { source: "mcp", label: `${opts.id}/${nombreReal}` }
|
|
928
|
+
};
|
|
929
|
+
}),
|
|
930
|
+
dispose: () => opts.transport.close?.()
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
function normalizar(n) {
|
|
934
|
+
return n.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").slice(0, 48);
|
|
935
|
+
}
|
|
936
|
+
var capabilities = { local, http, mcp, httpTransport };
|
|
937
|
+
|
|
938
|
+
// src/ecosystem.ts
|
|
939
|
+
function coincide(patron, nombre) {
|
|
940
|
+
if (patron === "*") return true;
|
|
941
|
+
if (patron.endsWith("*")) return nombre.startsWith(patron.slice(0, -1));
|
|
942
|
+
return patron === nombre;
|
|
943
|
+
}
|
|
944
|
+
var ORDEN2 = { prohibida: 0, requiere_aprobacion: 1, autonoma: 2 };
|
|
945
|
+
function aplicarReglas(tool, reglas) {
|
|
946
|
+
if (!reglas) return tool;
|
|
947
|
+
let resultado = tool.authority;
|
|
948
|
+
for (const [patron, regla] of Object.entries(reglas)) {
|
|
949
|
+
if (!coincide(patron, tool.name)) continue;
|
|
950
|
+
if (ORDEN2[regla.level] < ORDEN2[resultado.level]) resultado = regla;
|
|
951
|
+
else if (regla.maxAmount !== void 0 && (resultado.maxAmount === void 0 || regla.maxAmount < resultado.maxAmount)) {
|
|
952
|
+
resultado = { ...resultado, maxAmount: regla.maxAmount, currency: regla.currency ?? resultado.currency };
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return { ...tool, authority: resultado };
|
|
956
|
+
}
|
|
957
|
+
function defineEcosystem(cfg) {
|
|
958
|
+
const caps = new Map(cfg.capabilities.map((c) => [c.id, c]));
|
|
959
|
+
const recopilar = () => [...caps.values()].flatMap(
|
|
960
|
+
(c) => c.tools.map((t) => ({ ...aplicarReglas(t, cfg.authority), capability: c.id }))
|
|
961
|
+
);
|
|
962
|
+
let tools = recopilar();
|
|
963
|
+
return {
|
|
964
|
+
name: cfg.name,
|
|
965
|
+
get tools() {
|
|
966
|
+
return tools;
|
|
967
|
+
},
|
|
968
|
+
async run(call) {
|
|
969
|
+
const due\u00F1o = [...caps.values()].find((c) => c.tools.some((t) => t.name === call.name));
|
|
970
|
+
if (!due\u00F1o) {
|
|
971
|
+
return { ok: false, error: `Ninguna capacidad de "${cfg.name}" declara "${call.name}".` };
|
|
972
|
+
}
|
|
973
|
+
const conReglas = tools.find((t) => t.name === call.name);
|
|
974
|
+
if (conReglas && conReglas.authority.level === "prohibida") {
|
|
975
|
+
return { ok: false, error: `"${call.name}" est\xE1 prohibida por las reglas de ${cfg.name}.` };
|
|
976
|
+
}
|
|
977
|
+
if (conReglas && conReglas.authority.level !== "autonoma" && !call.approved) {
|
|
978
|
+
return { ok: false, needsApproval: true, error: "Requiere aprobaci\xF3n humana antes de ejecutarse." };
|
|
979
|
+
}
|
|
980
|
+
return due\u00F1o.run(call);
|
|
981
|
+
},
|
|
982
|
+
add(capability) {
|
|
983
|
+
caps.set(capability.id, capability);
|
|
984
|
+
tools = recopilar();
|
|
985
|
+
},
|
|
986
|
+
async remove(capabilityId) {
|
|
987
|
+
const c = caps.get(capabilityId);
|
|
988
|
+
if (!c) return;
|
|
989
|
+
await c.dispose?.();
|
|
990
|
+
caps.delete(capabilityId);
|
|
991
|
+
tools = recopilar();
|
|
992
|
+
},
|
|
993
|
+
async dispose() {
|
|
994
|
+
for (const c of caps.values()) await c.dispose?.();
|
|
995
|
+
caps.clear();
|
|
996
|
+
tools = [];
|
|
997
|
+
}
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
async function agentLoop(opts) {
|
|
1001
|
+
const maxSteps = opts.maxSteps ?? 6;
|
|
1002
|
+
return async function turno(message, history = []) {
|
|
1003
|
+
const steps = [];
|
|
1004
|
+
let lastResult;
|
|
1005
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
1006
|
+
const salida = await opts.model({
|
|
1007
|
+
message,
|
|
1008
|
+
tools: opts.ecosystem.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
1009
|
+
history,
|
|
1010
|
+
lastResult
|
|
1011
|
+
});
|
|
1012
|
+
if (!salida.action) return { text: salida.text, steps };
|
|
1013
|
+
let resultado = await opts.ecosystem.run(salida.action);
|
|
1014
|
+
if (resultado.needsApproval && opts.onApproval) {
|
|
1015
|
+
const aprobado = await opts.onApproval(salida.action.name, salida.action.args ?? {});
|
|
1016
|
+
if (aprobado) {
|
|
1017
|
+
resultado = await ejecutarAprobado(opts.ecosystem, salida.action);
|
|
1018
|
+
} else {
|
|
1019
|
+
resultado = { ok: false, error: "El usuario no lo autoriz\xF3." };
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
steps.push({ action: salida.action.name, ok: resultado.ok, error: resultado.error });
|
|
1023
|
+
lastResult = resultado;
|
|
1024
|
+
}
|
|
1025
|
+
return { text: "No pude completarlo en los pasos disponibles.", steps };
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
async function ejecutarAprobado(eco, action) {
|
|
1029
|
+
const t = eco.tools.find((x) => x.name === action.name);
|
|
1030
|
+
if (!t) return { ok: false, error: "La herramienta ya no existe." };
|
|
1031
|
+
if (t.authority.level === "prohibida") {
|
|
1032
|
+
return { ok: false, error: "Prohibida: ni con aprobaci\xF3n se ejecuta." };
|
|
1033
|
+
}
|
|
1034
|
+
return eco.run({ ...action, approved: true });
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/mcp.ts
|
|
1038
|
+
function fromMCPTool(tool, authority, permission) {
|
|
1039
|
+
const warnings = [];
|
|
1040
|
+
if (tool.annotations?.destructiveHint && authority.consequence === "reversible") {
|
|
1041
|
+
warnings.push(
|
|
1042
|
+
`"${tool.name}": el servidor MCP la marca como destructiva, pero se declar\xF3 como reversible. Rev\xEDsalo.`
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
if (tool.annotations?.readOnlyHint === false && authority.level === "autonoma") {
|
|
1046
|
+
warnings.push(
|
|
1047
|
+
`"${tool.name}": escribe y se le dio autoridad aut\xF3noma. Aseg\xFArate de que sea lo que quieres.`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
if (!tool.description?.trim()) {
|
|
1051
|
+
warnings.push(`"${tool.name}": el servidor MCP no la describe, as\xED que el agente no sabr\xE1 cu\xE1ndo usarla.`);
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
tool: {
|
|
1055
|
+
// Prefijo para que se vea de dónde viene y no choque con lo propio.
|
|
1056
|
+
name: normalizarNombre(`mcp_${tool.name}`),
|
|
1057
|
+
description: tool.description?.trim() || `Herramienta MCP "${tool.name}" (sin descripci\xF3n del servidor).`,
|
|
1058
|
+
authority,
|
|
1059
|
+
permission
|
|
1060
|
+
},
|
|
1061
|
+
warnings
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
function toMCPTool(tool) {
|
|
1065
|
+
if (tool.authority.level !== "autonoma") return null;
|
|
1066
|
+
return {
|
|
1067
|
+
name: tool.name,
|
|
1068
|
+
description: tool.description,
|
|
1069
|
+
annotations: {
|
|
1070
|
+
readOnlyHint: tool.authority.consequence === "reversible",
|
|
1071
|
+
destructiveHint: tool.authority.consequence === "irreversible",
|
|
1072
|
+
idempotentHint: false
|
|
1073
|
+
}
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
function toMCPTools(tools) {
|
|
1077
|
+
const published = [];
|
|
1078
|
+
const withheld = [];
|
|
1079
|
+
for (const t of tools) {
|
|
1080
|
+
const m = toMCPTool(t);
|
|
1081
|
+
if (m) published.push(m);
|
|
1082
|
+
else withheld.push(t.name);
|
|
1083
|
+
}
|
|
1084
|
+
return { published, withheld };
|
|
1085
|
+
}
|
|
1086
|
+
function normalizarNombre(n) {
|
|
1087
|
+
return n.toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").slice(0, 48);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
623
1090
|
// src/index.ts
|
|
624
1091
|
var gandia = gandia_exports;
|
|
625
1092
|
var handeia = handeia_exports;
|
|
626
1093
|
// Annotate the CommonJS export names for ESM import in node:
|
|
627
1094
|
0 && (module.exports = {
|
|
1095
|
+
AGENT_PROTOCOL_VERSION,
|
|
1096
|
+
CONNECTOR_OF_OPERATION,
|
|
628
1097
|
VAIAError,
|
|
1098
|
+
agentLoop,
|
|
1099
|
+
capabilities,
|
|
1100
|
+
checkAuthority,
|
|
629
1101
|
defineCapability,
|
|
1102
|
+
defineEcosystem,
|
|
1103
|
+
fromMCPTool,
|
|
630
1104
|
gandia,
|
|
631
1105
|
handeia,
|
|
632
|
-
|
|
1106
|
+
http,
|
|
1107
|
+
httpTransport,
|
|
1108
|
+
local,
|
|
1109
|
+
mcp,
|
|
1110
|
+
requiresApproval,
|
|
1111
|
+
toMCPTool,
|
|
1112
|
+
toMCPTools,
|
|
1113
|
+
toManifest,
|
|
1114
|
+
validateActionCall,
|
|
1115
|
+
validateAgentSurface,
|
|
1116
|
+
validatePieces
|
|
633
1117
|
});
|
|
634
|
-
//# sourceMappingURL=index.cjs.map
|