@aopslab/aops-server 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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +21 -0
- package/aops-server-runtime.json +11 -0
- package/build/client/_app/immutable/chunks/2_TffmqH.js +1 -0
- package/build/client/_app/immutable/chunks/3DOfIKW0.js +1 -0
- package/build/client/_app/immutable/chunks/B2EkC-sy.js +1 -0
- package/build/client/_app/immutable/chunks/CMkIcmi1.js +2 -0
- package/build/client/_app/immutable/chunks/rqnVXeyB.js +1 -0
- package/build/client/_app/immutable/chunks/sZYZbj9W.js +1 -0
- package/build/client/_app/immutable/chunks/wbPk3Yxo.js +81 -0
- package/build/client/_app/immutable/entry/app.C7STLTX8.js +2 -0
- package/build/client/_app/immutable/entry/start.BC94hOaC.js +1 -0
- package/build/client/_app/immutable/nodes/0.BYBfjxZ5.js +1 -0
- package/build/client/_app/immutable/nodes/1.BNXFXajG.js +1 -0
- package/build/client/_app/version.json +1 -0
- package/build/env.js +70 -0
- package/build/env.js.map +1 -0
- package/build/handler.js +16 -0
- package/build/handler.js.map +1 -0
- package/build/index.js +139 -0
- package/build/index.js.map +1 -0
- package/build/server/chunks/0-RfB6m2nq.js +7 -0
- package/build/server/chunks/0-RfB6m2nq.js.map +1 -0
- package/build/server/chunks/1-_gg8Q0UT.js +9 -0
- package/build/server/chunks/1-_gg8Q0UT.js.map +1 -0
- package/build/server/chunks/_server.ts-6276EQIF.js +44 -0
- package/build/server/chunks/_server.ts-6276EQIF.js.map +1 -0
- package/build/server/chunks/_server.ts-B59WFQzX.js +174 -0
- package/build/server/chunks/_server.ts-B59WFQzX.js.map +1 -0
- package/build/server/chunks/_server.ts-BauJsLZI.js +40 -0
- package/build/server/chunks/_server.ts-BauJsLZI.js.map +1 -0
- package/build/server/chunks/_server.ts-BnGzJw2-.js +81 -0
- package/build/server/chunks/_server.ts-BnGzJw2-.js.map +1 -0
- package/build/server/chunks/_server.ts-CP7qM24V.js +34 -0
- package/build/server/chunks/_server.ts-CP7qM24V.js.map +1 -0
- package/build/server/chunks/_server.ts-Cbq_u99d.js +9 -0
- package/build/server/chunks/_server.ts-Cbq_u99d.js.map +1 -0
- package/build/server/chunks/_server.ts-Ch8fPT-o.js +92 -0
- package/build/server/chunks/_server.ts-Ch8fPT-o.js.map +1 -0
- package/build/server/chunks/_server.ts-Cv-yaXZI.js +7 -0
- package/build/server/chunks/_server.ts-Cv-yaXZI.js.map +1 -0
- package/build/server/chunks/_server.ts-D6lBwOON.js +34 -0
- package/build/server/chunks/_server.ts-D6lBwOON.js.map +1 -0
- package/build/server/chunks/_server.ts-D84K3pp9.js +11 -0
- package/build/server/chunks/_server.ts-D84K3pp9.js.map +1 -0
- package/build/server/chunks/_server.ts-DNzU1ToA.js +277 -0
- package/build/server/chunks/_server.ts-DNzU1ToA.js.map +1 -0
- package/build/server/chunks/_server.ts-DQQnw77t.js +17 -0
- package/build/server/chunks/_server.ts-DQQnw77t.js.map +1 -0
- package/build/server/chunks/_server.ts-DSxr5l15.js +18 -0
- package/build/server/chunks/_server.ts-DSxr5l15.js.map +1 -0
- package/build/server/chunks/_server.ts-DZYokvuR.js +80 -0
- package/build/server/chunks/_server.ts-DZYokvuR.js.map +1 -0
- package/build/server/chunks/_server.ts-Dcqwpz2t.js +144 -0
- package/build/server/chunks/_server.ts-Dcqwpz2t.js.map +1 -0
- package/build/server/chunks/_server.ts-Di9VJnF4.js +81 -0
- package/build/server/chunks/_server.ts-Di9VJnF4.js.map +1 -0
- package/build/server/chunks/_server.ts-FBEHFT8b.js +33 -0
- package/build/server/chunks/_server.ts-FBEHFT8b.js.map +1 -0
- package/build/server/chunks/_server.ts-gZytCuiJ.js +10 -0
- package/build/server/chunks/_server.ts-gZytCuiJ.js.map +1 -0
- package/build/server/chunks/_server.ts-tNULLyEO.js +261 -0
- package/build/server/chunks/_server.ts-tNULLyEO.js.map +1 -0
- package/build/server/chunks/agent-gateway-runtime-DTZ4SxaC.js +2598 -0
- package/build/server/chunks/agent-gateway-runtime-DTZ4SxaC.js.map +1 -0
- package/build/server/chunks/aops-runtime-config-wpVmg7T-.js +179 -0
- package/build/server/chunks/aops-runtime-config-wpVmg7T-.js.map +1 -0
- package/build/server/chunks/discovery-D9JnSgfu.js +894 -0
- package/build/server/chunks/discovery-D9JnSgfu.js.map +1 -0
- package/build/server/chunks/error.svelte-DgUOqi-L.js +45 -0
- package/build/server/chunks/error.svelte-DgUOqi-L.js.map +1 -0
- package/build/server/chunks/handler-BN3-p5rP.js +1733 -0
- package/build/server/chunks/handler-BN3-p5rP.js.map +1 -0
- package/build/server/chunks/hooks.server-C4wxiH5m.js +52 -0
- package/build/server/chunks/hooks.server-C4wxiH5m.js.map +1 -0
- package/build/server/chunks/index.js-nbqdVuWC.js +10698 -0
- package/build/server/chunks/index.js-nbqdVuWC.js.map +1 -0
- package/build/server/chunks/manifest.js-_Re-Wj-y.js +167 -0
- package/build/server/chunks/manifest.js-_Re-Wj-y.js.map +1 -0
- package/build/server/chunks/plugin-error-normalize-Bq14Qbz3.js +297 -0
- package/build/server/chunks/plugin-error-normalize-Bq14Qbz3.js.map +1 -0
- package/build/server/chunks/project-alias-DqE2v58Q.js +29 -0
- package/build/server/chunks/project-alias-DqE2v58Q.js.map +1 -0
- package/build/server/chunks/registry-5amIZito.js +289 -0
- package/build/server/chunks/registry-5amIZito.js.map +1 -0
- package/build/server/chunks/request-base-url-BDdrsaga.js +24 -0
- package/build/server/chunks/request-base-url-BDdrsaga.js.map +1 -0
- package/build/server/chunks/request-context-tNeFQjgb.js +422 -0
- package/build/server/chunks/request-context-tNeFQjgb.js.map +1 -0
- package/build/server/chunks/xf-result-BRDjvftr.js +17 -0
- package/build/server/chunks/xf-result-BRDjvftr.js.map +1 -0
- package/build/shims.js +33 -0
- package/build/shims.js.map +1 -0
- package/cockpit/.vite/manifest.json +198 -0
- package/cockpit/assets/dm-sans-latin-400-normal-BwCSEQnW.woff +0 -0
- package/cockpit/assets/dm-sans-latin-400-normal-CW0RaeGs.woff2 +0 -0
- package/cockpit/assets/dm-sans-latin-500-normal-B9HHJjqV.woff2 +0 -0
- package/cockpit/assets/dm-sans-latin-500-normal-Dr3UlScf.woff +0 -0
- package/cockpit/assets/dm-sans-latin-700-normal-CUSSCpQX.woff +0 -0
- package/cockpit/assets/dm-sans-latin-700-normal-DvUfVpUG.woff2 +0 -0
- package/cockpit/assets/dm-sans-latin-ext-400-normal-BjWJ59Pq.woff +0 -0
- package/cockpit/assets/dm-sans-latin-ext-400-normal-BtiwyxMk.woff2 +0 -0
- package/cockpit/assets/dm-sans-latin-ext-500-normal-BJfUCQsA.woff2 +0 -0
- package/cockpit/assets/dm-sans-latin-ext-500-normal-DR84L5F-.woff +0 -0
- package/cockpit/assets/dm-sans-latin-ext-700-normal-BLI3TTWz.woff +0 -0
- package/cockpit/assets/dm-sans-latin-ext-700-normal-CJIcxD6K.woff2 +0 -0
- package/cockpit/assets/index-DDswO4B1.css +1 -0
- package/cockpit/assets/index-bovtnZ98.js +18 -0
- package/cockpit/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/cockpit/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/cockpit/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/cockpit/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/cockpit/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/cockpit/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/cockpit/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/cockpit/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/cockpit/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/cockpit/assets/outfit-latin-400-normal-BGsTXAXT.woff2 +0 -0
- package/cockpit/assets/outfit-latin-400-normal-DMwTpYkH.woff +0 -0
- package/cockpit/assets/outfit-latin-600-normal-B7SfZ07L.woff2 +0 -0
- package/cockpit/assets/outfit-latin-600-normal-BEfTtDA7.woff +0 -0
- package/cockpit/assets/outfit-latin-ext-400-normal-5tcqmc2S.woff2 +0 -0
- package/cockpit/assets/outfit-latin-ext-400-normal-DHm7mdGe.woff +0 -0
- package/cockpit/assets/outfit-latin-ext-600-normal-B85nYjL1.woff2 +0 -0
- package/cockpit/assets/outfit-latin-ext-600-normal-CWJcPgd7.woff +0 -0
- package/cockpit/assets/unbounded-cyrillic-600-normal-B-s5c2Kn.woff +0 -0
- package/cockpit/assets/unbounded-cyrillic-600-normal-DW6uzIMB.woff2 +0 -0
- package/cockpit/assets/unbounded-latin-600-normal-BPdB_PEv.woff +0 -0
- package/cockpit/assets/unbounded-latin-600-normal-oRSANpZr.woff2 +0 -0
- package/cockpit/assets/unbounded-latin-ext-600-normal-0iZLtTDV.woff2 +0 -0
- package/cockpit/assets/unbounded-latin-ext-600-normal-ClMPf-bu.woff +0 -0
- package/cockpit/assets/unbounded-vietnamese-600-normal-BzfrgZ_C.woff +0 -0
- package/cockpit/assets/unbounded-vietnamese-600-normal-Cx2bTSHN.woff2 +0 -0
- package/cockpit/community.module-inventory.json +2389 -0
- package/cockpit/index.html +14 -0
- package/community-postgres.json +13 -0
- package/migrations/agentspace/0000_thin_carlie_cooper.sql +764 -0
- package/migrations/agentspace/meta/0000_snapshot.json +6885 -0
- package/migrations/agentspace/meta/_journal.json +13 -0
- package/migrations/chatv3/0000_dry_ego.sql +218 -0
- package/migrations/chatv3/0001_nosy_obadiah_stane.sql +35 -0
- package/migrations/chatv3/0002_narrow_darkhawk.sql +4 -0
- package/migrations/chatv3/0003_damp_zarek.sql +2 -0
- package/migrations/chatv3/0004_nervous_martin_li.sql +60 -0
- package/migrations/chatv3/0005_chatv3_server_encrypted_mode.sql +1 -0
- package/migrations/chatv3/meta/0000_snapshot.json +1887 -0
- package/migrations/chatv3/meta/0001_snapshot.json +2175 -0
- package/migrations/chatv3/meta/0002_snapshot.json +2229 -0
- package/migrations/chatv3/meta/0003_snapshot.json +2241 -0
- package/migrations/chatv3/meta/0004_snapshot.json +2746 -0
- package/migrations/chatv3/meta/_journal.json +48 -0
- package/migrations/docman/0000_curvy_valeria_richards.sql +257 -0
- package/migrations/docman/0001_massive_risque.sql +2 -0
- package/migrations/docman/0002_simple_logan.sql +50 -0
- package/migrations/docman/0003_quick_index.sql +45 -0
- package/migrations/docman/0004_known_summary.sql +9 -0
- package/migrations/docman/0005_vector_signal.sql +9 -0
- package/migrations/docman/0006_document_version_current_invariant.sql +15 -0
- package/migrations/docman/meta/_journal.json +55 -0
- package/migrations/projectman/0000_smooth_klaw.sql +257 -0
- package/migrations/projectman/0001_worthless_zodiak.sql +9 -0
- package/migrations/projectman/0002_board_rename.sql +108 -0
- package/migrations/projectman/0003_review_request.sql +62 -0
- package/migrations/projectman/0004_review_request_idempotency.sql +3 -0
- package/migrations/projectman/0005_allow_multi_sprint_per_task.sql +1 -0
- package/migrations/projectman/meta/_journal.json +48 -0
- package/migrations/sys/0000_loose_praxagora.sql +31 -0
- package/migrations/sys/0001_awesome_ronan.sql +2 -0
- package/migrations/sys/0002_steady_maximus.sql +2 -0
- package/migrations/sys/0003_escalating_rate_limiter.sql +2 -0
- package/migrations/sys/0004_rate_limiter_timestamptz.sql +22 -0
- package/migrations/sys/0005_sys_counters.sql +21 -0
- package/migrations/sys/meta/_journal.json +48 -0
- package/npm-shrinkwrap.json +4375 -0
- package/package.json +62 -0
- package/runtime/agentspace-host-adapter.mjs +64 -0
- package/runtime/agentspace-tooling.mjs +389 -0
- package/runtime/docman-host-adapter.mjs +177 -0
- package/runtime/docman-policy.mjs +450 -0
- package/runtime/docman-tooling.mjs +20 -0
- package/runtime/projectman-host-adapter.mjs +597 -0
- package/runtime/scope-context.mjs +99 -0
- package/scripts/community-host.mjs +626 -0
- package/scripts/community-migration-policy-v1.json +296 -0
|
@@ -0,0 +1,2598 @@
|
|
|
1
|
+
import { dispatchDomainRequest } from '@aopslab/host-core';
|
|
2
|
+
import { buildAgentGatewayOpenApi, buildFederatedToolCatalog, validateManifest } from '@aopslab/manifest';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { g as getLogger, h as hasProjectAliasValue } from './project-alias-DqE2v58Q.js';
|
|
10
|
+
import Ajv from 'ajv';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { g as getHostConfig, r as resetHostPluginRegistry, c as getHostPluginRegistry } from './registry-5amIZito.js';
|
|
13
|
+
|
|
14
|
+
function isRecord$3(value) {
|
|
15
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
function normalizeBaseUrl(value) {
|
|
18
|
+
return value.trim().replace(/\/+$/g, "");
|
|
19
|
+
}
|
|
20
|
+
function normalizeApiBasePath(value) {
|
|
21
|
+
const raw = (value ?? "/api").trim();
|
|
22
|
+
if (!raw) return "/api";
|
|
23
|
+
return raw.startsWith("/") ? raw.replace(/\/+$/g, "") || "/api" : `/${raw.replace(/\/+$/g, "")}`;
|
|
24
|
+
}
|
|
25
|
+
function normalizeDomain(value) {
|
|
26
|
+
return String(value ?? "").trim().toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
function normalizeToolId(value) {
|
|
29
|
+
return String(value ?? "").trim().toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
function normalizeSourceId(value) {
|
|
32
|
+
if (!value) return void 0;
|
|
33
|
+
const normalized = value.trim().toLowerCase();
|
|
34
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
35
|
+
}
|
|
36
|
+
function findTagValue(tags, prefix) {
|
|
37
|
+
if (!Array.isArray(tags)) return void 0;
|
|
38
|
+
const found = tags.find((tag) => typeof tag === "string" && tag.startsWith(prefix));
|
|
39
|
+
if (!found) return void 0;
|
|
40
|
+
const value = found.slice(prefix.length).trim().toLowerCase();
|
|
41
|
+
return value.length > 0 ? value : void 0;
|
|
42
|
+
}
|
|
43
|
+
function toFriendlyResourceName(tool) {
|
|
44
|
+
const tagged = findTagValue(tool.tags, "resource:");
|
|
45
|
+
const raw = tagged ?? tool.operationId.split(".")[0] ?? "resource";
|
|
46
|
+
return raw.replace(/[-_]+/g, " ").trim();
|
|
47
|
+
}
|
|
48
|
+
function resolveOperationKind(tool) {
|
|
49
|
+
return findTagValue(tool.tags, "kind:");
|
|
50
|
+
}
|
|
51
|
+
function buildToolSummary(tool) {
|
|
52
|
+
const resource = toFriendlyResourceName(tool);
|
|
53
|
+
const kind = resolveOperationKind(tool);
|
|
54
|
+
if (kind === "list") return `List ${resource} records with optional filter and options.`;
|
|
55
|
+
if (kind === "get") return `Get a ${resource} record by identifier.`;
|
|
56
|
+
if (kind === "create") return `Create a ${resource} record from data payload.`;
|
|
57
|
+
if (kind === "update") return `Update a ${resource} record.`;
|
|
58
|
+
if (kind === "delete") return `Delete a ${resource} record by identifier.`;
|
|
59
|
+
return `Run ${tool.operationId} on ${resource}.`;
|
|
60
|
+
}
|
|
61
|
+
function shouldRewriteSummary(summary) {
|
|
62
|
+
const normalized = summary.trim();
|
|
63
|
+
if (!normalized) return true;
|
|
64
|
+
if (normalized.length > 96) return false;
|
|
65
|
+
if (/[():]/.test(normalized)) return false;
|
|
66
|
+
if (/\b(alias|wrapper|route|compose|markdown|safe delete)\b/i.test(normalized)) return false;
|
|
67
|
+
return /^(list|get|create|update|remove|delete|add|set|link|unlink|record|attach|end|start|search)\b/i.test(
|
|
68
|
+
normalized
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
function toCanonicalInvokeExamplePayload(operationKind, payload) {
|
|
72
|
+
if (isRecord$3(payload) && hasInputEnvelope(payload)) {
|
|
73
|
+
return payload;
|
|
74
|
+
}
|
|
75
|
+
if (operationKind === "get" && isRecord$3(payload)) {
|
|
76
|
+
const id = normalizePathParamValue(payload.id);
|
|
77
|
+
if (id) {
|
|
78
|
+
const { id: _ignored, ...rest } = payload;
|
|
79
|
+
return {
|
|
80
|
+
pathParams: { id },
|
|
81
|
+
...Object.keys(rest).length > 0 ? { query: rest } : {}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const envelopeKey = operationKind === "list" || operationKind === "get" ? "query" : "body";
|
|
86
|
+
return { [envelopeKey]: payload };
|
|
87
|
+
}
|
|
88
|
+
function normalizeToolExample(example, operationKind) {
|
|
89
|
+
if (typeof example !== "string") return null;
|
|
90
|
+
const raw = example.trim();
|
|
91
|
+
if (!raw) return null;
|
|
92
|
+
let payload;
|
|
93
|
+
try {
|
|
94
|
+
payload = JSON.parse(raw);
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
if (payload !== void 0) {
|
|
98
|
+
return JSON.stringify(toCanonicalInvokeExamplePayload(operationKind, payload));
|
|
99
|
+
}
|
|
100
|
+
const inputMatch = raw.match(/--input\\s+'([^']+)'/) ?? raw.match(/--input\\s+\"([^\"]+)\"/) ?? raw.match(/--input\\s+([^\\s]+)/);
|
|
101
|
+
if (!inputMatch || typeof inputMatch[1] !== "string") return null;
|
|
102
|
+
const candidate = inputMatch[1].trim();
|
|
103
|
+
if (!candidate) return null;
|
|
104
|
+
try {
|
|
105
|
+
payload = JSON.parse(candidate);
|
|
106
|
+
return JSON.stringify(toCanonicalInvokeExamplePayload(operationKind, payload));
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function normalizeToolExamples(examples, operationKind) {
|
|
112
|
+
if (!Array.isArray(examples)) return void 0;
|
|
113
|
+
const normalized = examples.map((entry) => normalizeToolExample(entry, operationKind)).filter((entry) => Boolean(entry));
|
|
114
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
115
|
+
}
|
|
116
|
+
function normalizeToolIdShape(tool) {
|
|
117
|
+
const normalizedToolId = normalizeToolId(tool.toolId);
|
|
118
|
+
const domain = normalizeDomain(tool.domain);
|
|
119
|
+
if (!domain) return normalizedToolId;
|
|
120
|
+
const duplicatedDomainPrefix = `${domain}.${domain}.`;
|
|
121
|
+
if (normalizedToolId.startsWith(duplicatedDomainPrefix)) {
|
|
122
|
+
return `${domain}.${normalizedToolId.slice(duplicatedDomainPrefix.length)}`;
|
|
123
|
+
}
|
|
124
|
+
return normalizedToolId;
|
|
125
|
+
}
|
|
126
|
+
function normalizeCatalogTool(tool) {
|
|
127
|
+
const operationKind = resolveOperationKind(tool);
|
|
128
|
+
const fallbackExamplesByToolId = {
|
|
129
|
+
"jsonexcel.json.to-excel": ['{"jsonFile":"data.json","outputFile":"data.xlsx"}'],
|
|
130
|
+
"jsonexcel.excel.to-json": ['{"excelFile":"data.xlsx","outputFile":"data.json"}']
|
|
131
|
+
};
|
|
132
|
+
const normalizedToolId = normalizeToolIdShape(tool);
|
|
133
|
+
const examples = normalizeToolExamples(tool.examples, operationKind) ?? normalizeToolExamples(fallbackExamplesByToolId[normalizedToolId], operationKind);
|
|
134
|
+
const summaryCandidate = typeof tool.summary === "string" ? tool.summary : "";
|
|
135
|
+
const summary = shouldRewriteSummary(summaryCandidate) ? buildToolSummary(tool) : summaryCandidate;
|
|
136
|
+
const titleCandidate = typeof tool.title === "string" ? tool.title : "";
|
|
137
|
+
const title = shouldRewriteSummary(titleCandidate) ? summary : titleCandidate;
|
|
138
|
+
return {
|
|
139
|
+
...tool,
|
|
140
|
+
toolId: normalizedToolId,
|
|
141
|
+
...title ? { title } : {},
|
|
142
|
+
...summary ? { summary } : {},
|
|
143
|
+
...examples ? { examples } : {}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function normalizePathParams(input) {
|
|
147
|
+
if (!isRecord$3(input)) return {};
|
|
148
|
+
const out = {};
|
|
149
|
+
for (const [keyRaw, value] of Object.entries(input)) {
|
|
150
|
+
const key = keyRaw.trim();
|
|
151
|
+
if (!key) continue;
|
|
152
|
+
if (value === void 0 || value === null) continue;
|
|
153
|
+
out[key] = String(value);
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
function appendQueryParamValue(params, key, value) {
|
|
158
|
+
if (value === void 0 || value === null) return;
|
|
159
|
+
if (Array.isArray(value)) {
|
|
160
|
+
for (const item of value) appendQueryParamValue(params, key, item);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (typeof value === "object") {
|
|
164
|
+
params.append(key, JSON.stringify(value));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
params.append(key, String(value));
|
|
168
|
+
}
|
|
169
|
+
function normalizeQueryParams(input) {
|
|
170
|
+
if (!isRecord$3(input)) return new URLSearchParams();
|
|
171
|
+
const params = new URLSearchParams();
|
|
172
|
+
for (const [keyRaw, value] of Object.entries(input)) {
|
|
173
|
+
const key = keyRaw.trim();
|
|
174
|
+
if (!key) continue;
|
|
175
|
+
appendQueryParamValue(params, key, value);
|
|
176
|
+
}
|
|
177
|
+
return params;
|
|
178
|
+
}
|
|
179
|
+
function hasInputEnvelope(input) {
|
|
180
|
+
return Object.prototype.hasOwnProperty.call(input, "pathParams") || Object.prototype.hasOwnProperty.call(input, "params") || Object.prototype.hasOwnProperty.call(input, "query") || Object.prototype.hasOwnProperty.call(input, "body") || Object.prototype.hasOwnProperty.call(input, "context");
|
|
181
|
+
}
|
|
182
|
+
function parseRouteInvokeInput(input) {
|
|
183
|
+
if (!isRecord$3(input) || !hasInputEnvelope(input)) {
|
|
184
|
+
return {
|
|
185
|
+
pathParams: {},
|
|
186
|
+
query: new URLSearchParams(),
|
|
187
|
+
body: input,
|
|
188
|
+
context: {}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
pathParams: normalizePathParams(input.pathParams ?? input.params),
|
|
193
|
+
query: normalizeQueryParams(input.query),
|
|
194
|
+
body: input.body,
|
|
195
|
+
context: isRecord$3(input.context) ? input.context : {}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function extractPathParamKeys(pattern) {
|
|
199
|
+
const normalized = pattern.trim().replace(/^\/+|\/+$/g, "");
|
|
200
|
+
if (!normalized) return [];
|
|
201
|
+
return normalized.split("/").map((segment) => segment.trim()).filter((segment) => segment.startsWith(":")).map((segment) => segment.slice(1).trim()).filter(Boolean);
|
|
202
|
+
}
|
|
203
|
+
function normalizePathParamValue(value) {
|
|
204
|
+
if (value === void 0 || value === null) return void 0;
|
|
205
|
+
const normalized = String(value).trim();
|
|
206
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
207
|
+
}
|
|
208
|
+
function findManifestForDomain$1(manifests, domain) {
|
|
209
|
+
return manifests.find((manifest) => normalizeDomain(manifest.domain.id) === normalizeDomain(domain)) ?? null;
|
|
210
|
+
}
|
|
211
|
+
function normalizeAliasedArgName(value) {
|
|
212
|
+
const normalized = value.trim();
|
|
213
|
+
if (!normalized) return normalized;
|
|
214
|
+
const parts = normalized.split(".").map((part) => part.trim()).filter(Boolean);
|
|
215
|
+
return parts[parts.length - 1] ?? normalized;
|
|
216
|
+
}
|
|
217
|
+
function extractRouteParamAliases(tool, manifests) {
|
|
218
|
+
const manifest = findManifestForDomain$1(manifests, tool.domain);
|
|
219
|
+
const notes = manifest?.docs?.operations?.[tool.operationId]?.notes;
|
|
220
|
+
if (!Array.isArray(notes) || notes.length === 0) return /* @__PURE__ */ new Map();
|
|
221
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
222
|
+
for (const rawNote of notes) {
|
|
223
|
+
const note = String(rawNote ?? "").trim();
|
|
224
|
+
if (!note) continue;
|
|
225
|
+
const match = note.match(/resolves\s+([a-zA-Z0-9_.]+)\s+from\s+:([a-zA-Z0-9_]+)/i);
|
|
226
|
+
if (!match) continue;
|
|
227
|
+
const targetArg = normalizeAliasedArgName(match[1] ?? "");
|
|
228
|
+
const pathKey = String(match[2] ?? "").trim();
|
|
229
|
+
if (!targetArg || !pathKey) continue;
|
|
230
|
+
aliases.set(targetArg, pathKey);
|
|
231
|
+
}
|
|
232
|
+
return aliases;
|
|
233
|
+
}
|
|
234
|
+
function normalizeInvokeInputForRoute(params) {
|
|
235
|
+
const pathParamKeys = extractPathParamKeys(params.routePattern);
|
|
236
|
+
const routeMethod = String(params.routeMethod ?? "").trim().toUpperCase();
|
|
237
|
+
const shouldMoveBodyToQuery = routeMethod === "GET" || routeMethod === "HEAD";
|
|
238
|
+
if (pathParamKeys.length === 0) {
|
|
239
|
+
if (!shouldMoveBodyToQuery || !isRecord$3(params.parsedInput.body)) return params.parsedInput;
|
|
240
|
+
const query2 = new URLSearchParams(params.parsedInput.query);
|
|
241
|
+
for (const [keyRaw, value] of Object.entries(params.parsedInput.body)) {
|
|
242
|
+
const key = keyRaw.trim();
|
|
243
|
+
if (!key) continue;
|
|
244
|
+
appendQueryParamValue(query2, key, value);
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
...params.parsedInput,
|
|
248
|
+
query: query2,
|
|
249
|
+
body: void 0
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const pathParams = { ...params.parsedInput.pathParams };
|
|
253
|
+
const body = isRecord$3(params.parsedInput.body) ? { ...params.parsedInput.body } : params.parsedInput.body;
|
|
254
|
+
const bodyRecord = isRecord$3(body) ? body : null;
|
|
255
|
+
for (const pathKey of pathParamKeys) {
|
|
256
|
+
if (normalizePathParamValue(pathParams[pathKey])) continue;
|
|
257
|
+
const fallbackValue = bodyRecord ? normalizePathParamValue(bodyRecord[pathKey]) : void 0;
|
|
258
|
+
if (fallbackValue) {
|
|
259
|
+
pathParams[pathKey] = fallbackValue;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const aliases = extractRouteParamAliases(params.tool, params.manifests);
|
|
263
|
+
if (bodyRecord) {
|
|
264
|
+
for (const [targetArg, pathKey] of aliases.entries()) {
|
|
265
|
+
if (!normalizePathParamValue(pathParams[pathKey])) {
|
|
266
|
+
const targetValue = normalizePathParamValue(bodyRecord[targetArg]);
|
|
267
|
+
if (targetValue) {
|
|
268
|
+
pathParams[pathKey] = targetValue;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
const resolvedPathValue = normalizePathParamValue(pathParams[pathKey]);
|
|
272
|
+
if (!resolvedPathValue) continue;
|
|
273
|
+
if (!normalizePathParamValue(bodyRecord[targetArg])) {
|
|
274
|
+
bodyRecord[targetArg] = resolvedPathValue;
|
|
275
|
+
}
|
|
276
|
+
if (targetArg !== pathKey && Object.prototype.hasOwnProperty.call(bodyRecord, pathKey)) {
|
|
277
|
+
delete bodyRecord[pathKey];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const normalizedInput = {
|
|
282
|
+
...params.parsedInput,
|
|
283
|
+
pathParams,
|
|
284
|
+
body
|
|
285
|
+
};
|
|
286
|
+
if (!shouldMoveBodyToQuery || !isRecord$3(body)) {
|
|
287
|
+
return normalizedInput;
|
|
288
|
+
}
|
|
289
|
+
const query = new URLSearchParams(normalizedInput.query);
|
|
290
|
+
for (const [keyRaw, value] of Object.entries(body)) {
|
|
291
|
+
const key = keyRaw.trim();
|
|
292
|
+
if (!key || pathParamKeys.includes(key)) continue;
|
|
293
|
+
appendQueryParamValue(query, key, value);
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
...normalizedInput,
|
|
297
|
+
query,
|
|
298
|
+
body: void 0
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function patternToPathSegments(pattern, pathParams) {
|
|
302
|
+
const normalized = pattern.trim().replace(/^\/+|\/+$/g, "");
|
|
303
|
+
if (!normalized) return [];
|
|
304
|
+
const parts = [];
|
|
305
|
+
for (const segment of normalized.split("/").map((raw) => raw.trim()).filter(Boolean)) {
|
|
306
|
+
if (segment === "*") {
|
|
307
|
+
const splat = pathParams.splat?.trim();
|
|
308
|
+
if (!splat) continue;
|
|
309
|
+
for (const part of splat.split("/").map((raw) => raw.trim()).filter(Boolean)) {
|
|
310
|
+
parts.push(encodeURIComponent(part));
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (segment.startsWith(":")) {
|
|
315
|
+
const key = segment.slice(1).trim();
|
|
316
|
+
const value = pathParams[key];
|
|
317
|
+
if (!key || value === void 0 || value.trim().length === 0) {
|
|
318
|
+
throw new Error(`tool_input_missing_path_param:${key || "<empty>"}`);
|
|
319
|
+
}
|
|
320
|
+
parts.push(encodeURIComponent(value.trim()));
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
parts.push(segment);
|
|
324
|
+
}
|
|
325
|
+
return parts;
|
|
326
|
+
}
|
|
327
|
+
function normalizeRouteSummaryEntry(raw) {
|
|
328
|
+
if (!isRecord$3(raw)) return null;
|
|
329
|
+
const id = String(raw.id ?? "").trim();
|
|
330
|
+
const method = String(raw.method ?? "").trim().toUpperCase();
|
|
331
|
+
const pattern = String(raw.pattern ?? "").trim();
|
|
332
|
+
const operation = String(raw.operation ?? "").trim();
|
|
333
|
+
if (!id || !method || !pattern || !operation) return null;
|
|
334
|
+
const summary = typeof raw.summary === "string" ? raw.summary.trim() : void 0;
|
|
335
|
+
return {
|
|
336
|
+
id,
|
|
337
|
+
method,
|
|
338
|
+
pattern,
|
|
339
|
+
operation,
|
|
340
|
+
summary: summary && summary.length > 0 ? summary : void 0
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function resolveHeaderBindingValue(binding) {
|
|
344
|
+
let resolved;
|
|
345
|
+
if (typeof binding.value === "string") {
|
|
346
|
+
resolved = binding.value;
|
|
347
|
+
} else if (typeof binding.fromEnv === "string" && binding.fromEnv.length > 0) {
|
|
348
|
+
resolved = process.env[binding.fromEnv];
|
|
349
|
+
}
|
|
350
|
+
if ((!resolved || resolved.length === 0) && typeof binding.default === "string") {
|
|
351
|
+
resolved = binding.default;
|
|
352
|
+
}
|
|
353
|
+
if ((!resolved || resolved.length === 0) && binding.required) {
|
|
354
|
+
throw new Error(`agent_gateway_header_missing:${binding.fromEnv || "<binding>"}`);
|
|
355
|
+
}
|
|
356
|
+
if (!resolved || resolved.length === 0) return null;
|
|
357
|
+
return resolved;
|
|
358
|
+
}
|
|
359
|
+
function resolveSourceHeaders(source) {
|
|
360
|
+
const out = {};
|
|
361
|
+
for (const [nameRaw, binding] of Object.entries(source.headers ?? {})) {
|
|
362
|
+
const name = nameRaw.trim();
|
|
363
|
+
if (!name) continue;
|
|
364
|
+
const value = resolveHeaderBindingValue(binding);
|
|
365
|
+
if (!value) continue;
|
|
366
|
+
out[name] = value;
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
async function parseFetchBody(response) {
|
|
371
|
+
const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
|
|
372
|
+
if (contentType.includes("application/json")) {
|
|
373
|
+
try {
|
|
374
|
+
return await response.json();
|
|
375
|
+
} catch {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const text = await response.text();
|
|
380
|
+
if (!text) return null;
|
|
381
|
+
try {
|
|
382
|
+
return JSON.parse(text);
|
|
383
|
+
} catch {
|
|
384
|
+
return text;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function createTimeoutController(timeoutMs) {
|
|
388
|
+
const controller = new AbortController();
|
|
389
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
390
|
+
return {
|
|
391
|
+
controller,
|
|
392
|
+
cleanup: () => clearTimeout(timer)
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function pickResponseHeaders(response) {
|
|
396
|
+
const out = {};
|
|
397
|
+
for (const [key, value] of response.headers.entries()) {
|
|
398
|
+
const normalized = key.trim().toLowerCase();
|
|
399
|
+
if (!normalized || normalized === "set-cookie") continue;
|
|
400
|
+
out[normalized] = value;
|
|
401
|
+
}
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
function toFederatedSourceFromRemote(source) {
|
|
405
|
+
return {
|
|
406
|
+
id: source.id,
|
|
407
|
+
kind: source.kind,
|
|
408
|
+
domain: source.domain,
|
|
409
|
+
enabled: source.enabled,
|
|
410
|
+
priority: source.priority,
|
|
411
|
+
baseUrl: source.baseUrl,
|
|
412
|
+
apiBasePath: source.apiBasePath
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function findDomainRouteByOperation(routes, operationId) {
|
|
416
|
+
return routes.find((route) => route.operation === operationId) ?? null;
|
|
417
|
+
}
|
|
418
|
+
function findRemoteRouteByOperation(routes, operationId) {
|
|
419
|
+
return routes.find((route) => route.operation === operationId) ?? null;
|
|
420
|
+
}
|
|
421
|
+
function chooseSourceForInvocation(tool, sourceId) {
|
|
422
|
+
if (tool.sources.length === 0) {
|
|
423
|
+
throw new Error(`tool_source_unavailable:${tool.toolId}`);
|
|
424
|
+
}
|
|
425
|
+
if (sourceId) {
|
|
426
|
+
const explicit = tool.sources.find((source) => source.id === sourceId);
|
|
427
|
+
if (!explicit) {
|
|
428
|
+
throw new Error(`tool_source_not_found:${sourceId}`);
|
|
429
|
+
}
|
|
430
|
+
return explicit;
|
|
431
|
+
}
|
|
432
|
+
if (tool.defaultSourceId) {
|
|
433
|
+
const preferred = tool.sources.find((source) => source.id === tool.defaultSourceId);
|
|
434
|
+
if (preferred) return preferred;
|
|
435
|
+
}
|
|
436
|
+
return tool.sources[0];
|
|
437
|
+
}
|
|
438
|
+
function shouldFallbackFromLocalError(error) {
|
|
439
|
+
const message = error instanceof Error ? error.message : String(error ?? "unknown_error");
|
|
440
|
+
return message.startsWith("domain_not_registered:") || message.startsWith("operation_route_not_found:");
|
|
441
|
+
}
|
|
442
|
+
function findFirstRemoteSource(tool) {
|
|
443
|
+
return tool.sources.find((source) => source.kind !== "local-route") ?? null;
|
|
444
|
+
}
|
|
445
|
+
const runtimeImport = new Function("specifier", "return import(specifier)");
|
|
446
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
447
|
+
promisify(execFile);
|
|
448
|
+
let manifestImportVersion = 0;
|
|
449
|
+
function withImportVersion(specifier) {
|
|
450
|
+
if (!specifier.startsWith("file://")) return specifier;
|
|
451
|
+
const url = new URL(specifier);
|
|
452
|
+
url.searchParams.set("v", String(manifestImportVersion));
|
|
453
|
+
return url.href;
|
|
454
|
+
}
|
|
455
|
+
function resolveManifestModuleSpecifier(moduleName, configPath) {
|
|
456
|
+
const trimmed = moduleName.trim();
|
|
457
|
+
if (!trimmed) return trimmed;
|
|
458
|
+
if (trimmed.startsWith("file://")) return trimmed;
|
|
459
|
+
if (trimmed.startsWith(".")) {
|
|
460
|
+
const baseDir = configPath ? path.dirname(configPath) : process.cwd();
|
|
461
|
+
return pathToFileURL(path.resolve(baseDir, trimmed)).href;
|
|
462
|
+
}
|
|
463
|
+
if (path.isAbsolute(trimmed)) {
|
|
464
|
+
return pathToFileURL(trimmed).href;
|
|
465
|
+
}
|
|
466
|
+
const resolvePaths = [process.cwd(), path.resolve(process.cwd(), "apps/aops-server")];
|
|
467
|
+
try {
|
|
468
|
+
const resolved = nodeRequire.resolve(trimmed, { paths: resolvePaths });
|
|
469
|
+
return pathToFileURL(resolved).href;
|
|
470
|
+
} catch {
|
|
471
|
+
return trimmed;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function toManifestErrorCode(provider, error) {
|
|
475
|
+
const message = error instanceof Error ? error.message : String(error ?? "unknown_error");
|
|
476
|
+
return {
|
|
477
|
+
sourceId: `manifest:${provider.id}`,
|
|
478
|
+
error: message
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
async function loadManifestFromProvider(provider, configPath) {
|
|
482
|
+
const specifier = resolveManifestModuleSpecifier(provider.module, configPath);
|
|
483
|
+
const manifestRaw = await loadManifestFromCurrentProcess(specifier, provider);
|
|
484
|
+
const validation = validateManifest(manifestRaw);
|
|
485
|
+
if (!validation.ok) {
|
|
486
|
+
const firstSemantic = validation.issues[0]?.message;
|
|
487
|
+
const firstSchema = validation.zodIssues?.[0]?.message;
|
|
488
|
+
throw new Error(`manifest_validation_failed:${provider.id}:${firstSemantic ?? firstSchema ?? "invalid_manifest"}`);
|
|
489
|
+
}
|
|
490
|
+
if (normalizeDomain(validation.manifest.domain.id) !== normalizeDomain(provider.domain)) {
|
|
491
|
+
throw new Error(`manifest_domain_mismatch:${provider.id}:${validation.manifest.domain.id}:${provider.domain}`);
|
|
492
|
+
}
|
|
493
|
+
return validation.manifest;
|
|
494
|
+
}
|
|
495
|
+
async function loadManifestFromCurrentProcess(specifier, provider) {
|
|
496
|
+
const mod = await runtimeImport(withImportVersion(specifier));
|
|
497
|
+
const exportCandidate = mod[provider.exportName] ?? (provider.exportName === "default" ? mod.default : void 0);
|
|
498
|
+
if (exportCandidate === void 0) {
|
|
499
|
+
throw new Error(`manifest_export_not_found:${provider.id}:${provider.exportName}`);
|
|
500
|
+
}
|
|
501
|
+
return typeof exportCandidate === "function" ? await Promise.resolve(
|
|
502
|
+
exportCandidate(provider.options)
|
|
503
|
+
) : exportCandidate;
|
|
504
|
+
}
|
|
505
|
+
async function fetchRemoteRoutes(source) {
|
|
506
|
+
const baseUrl = normalizeBaseUrl(source.baseUrl);
|
|
507
|
+
const apiBasePath = normalizeApiBasePath(source.apiBasePath);
|
|
508
|
+
const url = `${baseUrl}${apiBasePath}/${source.domain}`;
|
|
509
|
+
const headers = resolveSourceHeaders(source);
|
|
510
|
+
const { controller, cleanup } = createTimeoutController(source.timeoutMs);
|
|
511
|
+
try {
|
|
512
|
+
const response = await fetch(url, {
|
|
513
|
+
method: "GET",
|
|
514
|
+
headers,
|
|
515
|
+
signal: controller.signal
|
|
516
|
+
});
|
|
517
|
+
const body = await parseFetchBody(response);
|
|
518
|
+
if (!response.ok) {
|
|
519
|
+
throw new Error(`remote_route_fetch_failed:${response.status}:${url}`);
|
|
520
|
+
}
|
|
521
|
+
if (!isRecord$3(body) || !Array.isArray(body.routes)) {
|
|
522
|
+
throw new Error(`remote_route_payload_invalid:${url}`);
|
|
523
|
+
}
|
|
524
|
+
return body.routes.map((route) => normalizeRouteSummaryEntry(route)).filter((route) => route !== null);
|
|
525
|
+
} finally {
|
|
526
|
+
cleanup();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
async function buildGatewayCatalogSnapshot(params) {
|
|
530
|
+
const { config } = params;
|
|
531
|
+
const errors = [];
|
|
532
|
+
const manifests = [];
|
|
533
|
+
const domains = /* @__PURE__ */ new Set();
|
|
534
|
+
if (config.catalog.enabled) {
|
|
535
|
+
for (const provider of config.catalog.manifestProviders) {
|
|
536
|
+
if (!provider.enabled) continue;
|
|
537
|
+
try {
|
|
538
|
+
const manifest = await loadManifestFromProvider(provider, params.configPath);
|
|
539
|
+
manifests.push(manifest);
|
|
540
|
+
domains.add(normalizeDomain(manifest.domain.id));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
errors.push(toManifestErrorCode(provider, error));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
for (const source of config.sources) {
|
|
547
|
+
if (!source.enabled) continue;
|
|
548
|
+
domains.add(normalizeDomain(source.domain));
|
|
549
|
+
}
|
|
550
|
+
const sources = config.sources.filter((source) => source.enabled).map((source) => toFederatedSourceFromRemote(source));
|
|
551
|
+
if (config.includeLocal) {
|
|
552
|
+
for (const domain of domains) {
|
|
553
|
+
sources.push({
|
|
554
|
+
id: "local",
|
|
555
|
+
kind: "local-route",
|
|
556
|
+
domain,
|
|
557
|
+
enabled: true,
|
|
558
|
+
priority: 0
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const catalog = buildFederatedToolCatalog({
|
|
563
|
+
manifests,
|
|
564
|
+
sources
|
|
565
|
+
});
|
|
566
|
+
const normalizedCatalog = {
|
|
567
|
+
...catalog,
|
|
568
|
+
tools: catalog.tools.map((tool) => normalizeCatalogTool(tool))
|
|
569
|
+
};
|
|
570
|
+
const toolsById = /* @__PURE__ */ new Map();
|
|
571
|
+
for (const tool of normalizedCatalog.tools) {
|
|
572
|
+
toolsById.set(normalizeToolId(tool.toolId), tool);
|
|
573
|
+
}
|
|
574
|
+
return {
|
|
575
|
+
catalog: normalizedCatalog,
|
|
576
|
+
manifests,
|
|
577
|
+
errors,
|
|
578
|
+
toolsById,
|
|
579
|
+
loadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
const INVOKE_REQUEST_DESCRIPTION = "Send either the raw tool input JSON directly, or use the canonical envelope { sourceId?, input, preview?, apply?, confirm?, idempotencyKey? }. Use preview for preflight, apply for guarded writes, confirm for destructive writes, and idempotencyKey for best-effort guarded-write deduplication.";
|
|
583
|
+
function isRecord$2(value) {
|
|
584
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
585
|
+
}
|
|
586
|
+
function isEnvelopeOnlyInvokeSchema(schema) {
|
|
587
|
+
if (!isRecord$2(schema)) return false;
|
|
588
|
+
if (Array.isArray(schema.oneOf)) return false;
|
|
589
|
+
if (schema.type !== "object") return false;
|
|
590
|
+
const properties = isRecord$2(schema.properties) ? schema.properties : null;
|
|
591
|
+
if (!properties || !Object.prototype.hasOwnProperty.call(properties, "input")) return false;
|
|
592
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((value) => typeof value === "string") : [];
|
|
593
|
+
return required.includes("input");
|
|
594
|
+
}
|
|
595
|
+
function normalizeInvokeSchema(schema) {
|
|
596
|
+
if (!isEnvelopeOnlyInvokeSchema(schema)) return schema;
|
|
597
|
+
const properties = isRecord$2(schema.properties) ? schema.properties : {};
|
|
598
|
+
const rawInputSchema = properties.input ?? { type: "object", additionalProperties: true };
|
|
599
|
+
const envelopeSchema = {
|
|
600
|
+
...schema,
|
|
601
|
+
properties: {
|
|
602
|
+
...properties,
|
|
603
|
+
...Object.prototype.hasOwnProperty.call(properties, "preview") ? {} : { preview: { type: "boolean" } },
|
|
604
|
+
...Object.prototype.hasOwnProperty.call(properties, "apply") ? {} : { apply: { type: "boolean" } },
|
|
605
|
+
...Object.prototype.hasOwnProperty.call(properties, "confirm") ? {} : { confirm: { type: "boolean" } },
|
|
606
|
+
...Object.prototype.hasOwnProperty.call(properties, "idempotencyKey") ? {} : { idempotencyKey: { type: "string" } }
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
return {
|
|
610
|
+
description: typeof schema.description === "string" && schema.description.trim().length > 0 ? schema.description : INVOKE_REQUEST_DESCRIPTION,
|
|
611
|
+
oneOf: [rawInputSchema, envelopeSchema]
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
function normalizeAgentGatewayOpenApiDocument(document) {
|
|
615
|
+
const paths = isRecord$2(document.paths) ? document.paths : null;
|
|
616
|
+
if (!paths) return document;
|
|
617
|
+
let changed = false;
|
|
618
|
+
const normalizedPaths = {};
|
|
619
|
+
for (const [pathKey, pathValue] of Object.entries(paths)) {
|
|
620
|
+
if (!isRecord$2(pathValue) || !isRecord$2(pathValue.post)) {
|
|
621
|
+
normalizedPaths[pathKey] = pathValue;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const post = pathValue.post;
|
|
625
|
+
const requestBody = isRecord$2(post.requestBody) ? post.requestBody : null;
|
|
626
|
+
const content = requestBody && isRecord$2(requestBody.content) ? requestBody.content : null;
|
|
627
|
+
const jsonContent = content && isRecord$2(content["application/json"]) ? content["application/json"] : null;
|
|
628
|
+
const schema = jsonContent?.schema;
|
|
629
|
+
const nextSchema = normalizeInvokeSchema(schema);
|
|
630
|
+
if (nextSchema === schema) {
|
|
631
|
+
normalizedPaths[pathKey] = pathValue;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
changed = true;
|
|
635
|
+
normalizedPaths[pathKey] = {
|
|
636
|
+
...pathValue,
|
|
637
|
+
post: {
|
|
638
|
+
...post,
|
|
639
|
+
requestBody: {
|
|
640
|
+
...requestBody,
|
|
641
|
+
content: {
|
|
642
|
+
...content,
|
|
643
|
+
"application/json": {
|
|
644
|
+
...jsonContent,
|
|
645
|
+
schema: nextSchema
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
if (!changed) return document;
|
|
653
|
+
return {
|
|
654
|
+
...document,
|
|
655
|
+
paths: normalizedPaths
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const AGENT_INVOKE_AUDIT_POLICY = {
|
|
659
|
+
enabled: true,
|
|
660
|
+
sink: "structured-server-log",
|
|
661
|
+
persistence: "best-effort",
|
|
662
|
+
events: ["preview", "blocked", "success", "failure", "replayed"],
|
|
663
|
+
approvalMode: "explicit-confirm-flag"
|
|
664
|
+
};
|
|
665
|
+
const GUARDED_WRITE_IDEMPOTENCY_MODE = "best-effort-host-cache";
|
|
666
|
+
const IDEMPOTENCY_ENTRY_TTL_MS = 10 * 60 * 1e3;
|
|
667
|
+
const idempotencyStore = /* @__PURE__ */ new Map();
|
|
668
|
+
let loggerPromise = null;
|
|
669
|
+
function normalizeText$1(value) {
|
|
670
|
+
if (typeof value !== "string") return "";
|
|
671
|
+
const trimmed = value.trim();
|
|
672
|
+
return trimmed.length > 0 ? trimmed : "";
|
|
673
|
+
}
|
|
674
|
+
function normalizeRoles(value) {
|
|
675
|
+
if (!Array.isArray(value)) return void 0;
|
|
676
|
+
const roles = value.map((entry) => normalizeText$1(entry)).filter(Boolean);
|
|
677
|
+
return roles.length > 0 ? roles : void 0;
|
|
678
|
+
}
|
|
679
|
+
function isRecord$1(value) {
|
|
680
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
681
|
+
}
|
|
682
|
+
function cloneHeaders(headers) {
|
|
683
|
+
if (!headers) return void 0;
|
|
684
|
+
return { ...headers };
|
|
685
|
+
}
|
|
686
|
+
function cloneResult(result) {
|
|
687
|
+
return {
|
|
688
|
+
tool: { ...result.tool },
|
|
689
|
+
status: result.status,
|
|
690
|
+
data: result.data,
|
|
691
|
+
headers: cloneHeaders(result.headers)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function attachHeaders(result, headers) {
|
|
695
|
+
return {
|
|
696
|
+
...cloneResult(result),
|
|
697
|
+
headers: {
|
|
698
|
+
...result.headers ?? {},
|
|
699
|
+
...headers
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function toCanonicalValue(value) {
|
|
704
|
+
if (Array.isArray(value)) return value.map((entry) => toCanonicalValue(entry));
|
|
705
|
+
if (!isRecord$1(value)) return value;
|
|
706
|
+
const output = {};
|
|
707
|
+
for (const key of Object.keys(value).sort((left, right) => left.localeCompare(right))) {
|
|
708
|
+
const nextValue = toCanonicalValue(value[key]);
|
|
709
|
+
if (nextValue === void 0) continue;
|
|
710
|
+
output[key] = nextValue;
|
|
711
|
+
}
|
|
712
|
+
return output;
|
|
713
|
+
}
|
|
714
|
+
function stableStringify(value) {
|
|
715
|
+
return JSON.stringify(toCanonicalValue(value));
|
|
716
|
+
}
|
|
717
|
+
function isGuardedWriteTool(tool) {
|
|
718
|
+
const policy = isRecord$1(tool.policy) ? tool.policy : null;
|
|
719
|
+
const safety = policy && isRecord$1(policy.safety) ? policy.safety : null;
|
|
720
|
+
return safety?.applyRequired === true || safety?.destructive === true || safety?.confirmationRequired === true;
|
|
721
|
+
}
|
|
722
|
+
function buildApprovalMode(tool) {
|
|
723
|
+
const policy = isRecord$1(tool.policy) ? tool.policy : null;
|
|
724
|
+
const safety = policy && isRecord$1(policy.safety) ? policy.safety : null;
|
|
725
|
+
return safety?.confirmationRequired === true ? "explicit-confirm-flag" : "none";
|
|
726
|
+
}
|
|
727
|
+
function buildInvokeFingerprint(selection) {
|
|
728
|
+
const fingerprintInput = {
|
|
729
|
+
toolId: selection.tool.toolId,
|
|
730
|
+
requestedSourceId: selection.source.id,
|
|
731
|
+
input: selection.args.input ?? null,
|
|
732
|
+
context: {
|
|
733
|
+
tenantId: normalizeText$1(selection.args.context?.tenantId) || null,
|
|
734
|
+
scopeId: normalizeText$1(selection.args.context?.scopeId) || normalizeText$1(selection.args.context?.projectId) || null,
|
|
735
|
+
locale: normalizeText$1(selection.args.context?.locale) || null,
|
|
736
|
+
fallbackLocale: normalizeText$1(selection.args.context?.fallbackLocale) || null
|
|
737
|
+
},
|
|
738
|
+
apply: selection.args.apply === true,
|
|
739
|
+
confirm: selection.args.confirm === true
|
|
740
|
+
};
|
|
741
|
+
return createHash("sha256").update(stableStringify(fingerprintInput)).digest("hex");
|
|
742
|
+
}
|
|
743
|
+
function buildRecommendedInvokeIdempotencyKey(selection) {
|
|
744
|
+
const digest = buildInvokeFingerprint(selection).slice(0, 16);
|
|
745
|
+
return `${selection.tool.toolId}:${digest}`;
|
|
746
|
+
}
|
|
747
|
+
function buildStoreKey(selection, idempotencyKey) {
|
|
748
|
+
return [
|
|
749
|
+
selection.tool.toolId,
|
|
750
|
+
selection.source.id,
|
|
751
|
+
normalizeText$1(selection.args.context?.tenantId) || "tenant",
|
|
752
|
+
normalizeText$1(selection.args.context?.scopeId) || normalizeText$1(selection.args.context?.projectId) || "scope",
|
|
753
|
+
idempotencyKey
|
|
754
|
+
].join("::");
|
|
755
|
+
}
|
|
756
|
+
function cleanupExpiredEntries(now = Date.now()) {
|
|
757
|
+
for (const [key, entry] of idempotencyStore.entries()) {
|
|
758
|
+
if (entry.expiresAt > now) continue;
|
|
759
|
+
idempotencyStore.delete(key);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function buildIdempotencyPayload(selection, args) {
|
|
763
|
+
return {
|
|
764
|
+
tool: {
|
|
765
|
+
...selection.tool,
|
|
766
|
+
sourceId: selection.source.id,
|
|
767
|
+
sourceKind: selection.source.kind,
|
|
768
|
+
sourceBaseUrl: selection.source.baseUrl,
|
|
769
|
+
sourceApiBasePath: selection.source.apiBasePath
|
|
770
|
+
},
|
|
771
|
+
status: 409,
|
|
772
|
+
data: {
|
|
773
|
+
ok: false,
|
|
774
|
+
error: args.error,
|
|
775
|
+
toolId: selection.tool.toolId,
|
|
776
|
+
domain: selection.tool.domain,
|
|
777
|
+
operationId: selection.tool.operationId,
|
|
778
|
+
message: args.message,
|
|
779
|
+
idempotency: {
|
|
780
|
+
mode: GUARDED_WRITE_IDEMPOTENCY_MODE,
|
|
781
|
+
key: args.key,
|
|
782
|
+
recommendedKey: args.recommendedKey
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
headers: {
|
|
786
|
+
"x-agent-idempotency-status": args.error === "idempotency_in_progress" ? "in-progress" : "conflict",
|
|
787
|
+
"x-agent-idempotency-key": args.key
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
function isSuccessfulResult(result) {
|
|
792
|
+
if (result.status >= 400) return false;
|
|
793
|
+
if (!isRecord$1(result.data)) return true;
|
|
794
|
+
return result.data.ok !== false;
|
|
795
|
+
}
|
|
796
|
+
function buildInvokeGovernancePreview(selection, requirements, options = {}) {
|
|
797
|
+
const providedKey = normalizeText$1(selection.args.idempotencyKey);
|
|
798
|
+
const guarded = isGuardedWriteTool(selection.tool);
|
|
799
|
+
const overrideRecommendedKey = normalizeText$1(options.recommendedKey);
|
|
800
|
+
const recommendedKey = guarded ? overrideRecommendedKey || buildRecommendedInvokeIdempotencyKey(selection) : null;
|
|
801
|
+
const recommendedKeySource = guarded ? overrideRecommendedKey ? options.recommendedKeySource ?? "domain-natural-key" : "host-derived" : void 0;
|
|
802
|
+
const nextIdempotencyKey = providedKey || recommendedKey || void 0;
|
|
803
|
+
return {
|
|
804
|
+
audit: AGENT_INVOKE_AUDIT_POLICY,
|
|
805
|
+
approval: {
|
|
806
|
+
mode: buildApprovalMode(selection.tool),
|
|
807
|
+
required: requirements.confirmationRequired,
|
|
808
|
+
satisfied: requirements.confirmationSatisfied,
|
|
809
|
+
provided: selection.args.confirm === true
|
|
810
|
+
},
|
|
811
|
+
idempotency: {
|
|
812
|
+
supported: isGuardedWriteTool(selection.tool),
|
|
813
|
+
mode: isGuardedWriteTool(selection.tool) ? GUARDED_WRITE_IDEMPOTENCY_MODE : "not-applicable",
|
|
814
|
+
provided: Boolean(providedKey),
|
|
815
|
+
key: providedKey || null,
|
|
816
|
+
recommendedKey,
|
|
817
|
+
...recommendedKeySource ? { recommendedKeySource } : {},
|
|
818
|
+
enforced: false
|
|
819
|
+
},
|
|
820
|
+
...nextIdempotencyKey ? { nextIdempotencyKey } : {}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
function reserveInvokeIdempotency(selection, options = {}) {
|
|
824
|
+
const guarded = isGuardedWriteTool(selection.tool);
|
|
825
|
+
const overrideRecommendedKey = normalizeText$1(options.recommendedKey);
|
|
826
|
+
const recommendedKey = guarded ? overrideRecommendedKey || buildRecommendedInvokeIdempotencyKey(selection) : "";
|
|
827
|
+
const key = normalizeText$1(selection.args.idempotencyKey) || normalizeText$1(options.effectiveKey);
|
|
828
|
+
if (!guarded || selection.args.preview === true || !key) {
|
|
829
|
+
return {
|
|
830
|
+
kind: "skip",
|
|
831
|
+
...recommendedKey ? { recommendedKey } : {}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
cleanupExpiredEntries();
|
|
835
|
+
const storeKey = buildStoreKey(selection, key);
|
|
836
|
+
const fingerprint = buildInvokeFingerprint(selection);
|
|
837
|
+
const existing = idempotencyStore.get(storeKey);
|
|
838
|
+
if (existing) {
|
|
839
|
+
if (existing.fingerprint !== fingerprint) {
|
|
840
|
+
return {
|
|
841
|
+
kind: "resolved",
|
|
842
|
+
result: buildIdempotencyPayload(selection, {
|
|
843
|
+
error: "idempotency_key_conflict",
|
|
844
|
+
key,
|
|
845
|
+
recommendedKey,
|
|
846
|
+
message: `idempotency_key_conflict:${selection.tool.toolId}`
|
|
847
|
+
}),
|
|
848
|
+
idempotencyStatus: "conflict",
|
|
849
|
+
recommendedKey
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
if (existing.state === "pending") {
|
|
853
|
+
return {
|
|
854
|
+
kind: "resolved",
|
|
855
|
+
result: buildIdempotencyPayload(selection, {
|
|
856
|
+
error: "idempotency_in_progress",
|
|
857
|
+
key,
|
|
858
|
+
recommendedKey,
|
|
859
|
+
message: `idempotency_in_progress:${selection.tool.toolId}`
|
|
860
|
+
}),
|
|
861
|
+
idempotencyStatus: "in-progress",
|
|
862
|
+
recommendedKey
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
if (existing.state === "completed" && existing.result) {
|
|
866
|
+
return {
|
|
867
|
+
kind: "resolved",
|
|
868
|
+
result: attachHeaders(cloneResult(existing.result), {
|
|
869
|
+
"x-agent-idempotency-status": "replayed",
|
|
870
|
+
"x-agent-idempotency-key": key
|
|
871
|
+
}),
|
|
872
|
+
idempotencyStatus: "replayed",
|
|
873
|
+
recommendedKey
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
idempotencyStore.set(storeKey, {
|
|
878
|
+
fingerprint,
|
|
879
|
+
expiresAt: Date.now() + IDEMPOTENCY_ENTRY_TTL_MS,
|
|
880
|
+
state: "pending"
|
|
881
|
+
});
|
|
882
|
+
return {
|
|
883
|
+
kind: "active",
|
|
884
|
+
key,
|
|
885
|
+
recommendedKey,
|
|
886
|
+
settle(result) {
|
|
887
|
+
if (isSuccessfulResult(result)) {
|
|
888
|
+
idempotencyStore.set(storeKey, {
|
|
889
|
+
fingerprint,
|
|
890
|
+
expiresAt: Date.now() + IDEMPOTENCY_ENTRY_TTL_MS,
|
|
891
|
+
state: "completed",
|
|
892
|
+
result: attachHeaders(cloneResult(result), {
|
|
893
|
+
"x-agent-idempotency-status": "fresh",
|
|
894
|
+
"x-agent-idempotency-key": key
|
|
895
|
+
})
|
|
896
|
+
});
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
idempotencyStore.delete(storeKey);
|
|
900
|
+
},
|
|
901
|
+
release() {
|
|
902
|
+
idempotencyStore.delete(storeKey);
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
async function resolveAuditLogger() {
|
|
907
|
+
if (!loggerPromise) {
|
|
908
|
+
loggerPromise = getLogger({ level: "info", file: false, console: true }).catch(() => void 0);
|
|
909
|
+
}
|
|
910
|
+
return await loggerPromise;
|
|
911
|
+
}
|
|
912
|
+
function toFailureStatus(message) {
|
|
913
|
+
if (message === "unauthorized") return 401;
|
|
914
|
+
if (message === "forbidden") return 403;
|
|
915
|
+
if (message.startsWith("apply_required:") || message.startsWith("confirmation_required:") || message === "project_context_required" || message === "project_required" || message.startsWith("missing_required_arg:") || message.startsWith("unknown_input_arg:") || message.startsWith("tool_input_schema_invalid:") || message.startsWith("validation_failed:") || message.startsWith("idempotency_in_progress:") || message.startsWith("idempotency_key_conflict:")) {
|
|
916
|
+
return 409;
|
|
917
|
+
}
|
|
918
|
+
if (message.startsWith("tool_not_found:")) return 404;
|
|
919
|
+
return 500;
|
|
920
|
+
}
|
|
921
|
+
function buildInvokeFailureStatus(error) {
|
|
922
|
+
const message = error instanceof Error ? error.message : String(error ?? "unknown_error");
|
|
923
|
+
return toFailureStatus(message);
|
|
924
|
+
}
|
|
925
|
+
async function writeAgentInvokeAuditEvent(event) {
|
|
926
|
+
const payload = {
|
|
927
|
+
owner: "agent-gateway",
|
|
928
|
+
action: event.preview === true ? "preview" : "invoke",
|
|
929
|
+
toolId: event.tool.toolId,
|
|
930
|
+
domain: event.tool.domain,
|
|
931
|
+
operationId: event.tool.operationId,
|
|
932
|
+
requestedSourceId: normalizeText$1(event.requestedSourceId),
|
|
933
|
+
effectiveSourceId: normalizeText$1(event.effectiveSourceId),
|
|
934
|
+
result: event.outcome,
|
|
935
|
+
status: event.status,
|
|
936
|
+
durationMs: typeof event.durationMs === "number" && Number.isFinite(event.durationMs) ? event.durationMs : void 0,
|
|
937
|
+
tenantId: normalizeText$1(event.context?.tenantId),
|
|
938
|
+
scopeId: normalizeText$1(event.context?.scopeId) || normalizeText$1(event.context?.projectId),
|
|
939
|
+
actorId: normalizeText$1(event.context?.principal && isRecord$1(event.context.principal) ? event.context.principal.id : void 0) || void 0,
|
|
940
|
+
roles: normalizeRoles(event.context?.principal && isRecord$1(event.context.principal) ? event.context.principal.roles : void 0),
|
|
941
|
+
preview: event.preview === true,
|
|
942
|
+
apply: event.apply === true,
|
|
943
|
+
confirm: event.confirm === true,
|
|
944
|
+
approvalMode: buildApprovalMode(event.tool),
|
|
945
|
+
idempotencyKey: normalizeText$1(event.idempotencyKey) || void 0,
|
|
946
|
+
idempotencyStatus: event.idempotencyStatus,
|
|
947
|
+
errorCode: normalizeText$1(event.errorCode) || void 0,
|
|
948
|
+
audit: AGENT_INVOKE_AUDIT_POLICY
|
|
949
|
+
};
|
|
950
|
+
const message = `[agent-invoke:audit] ${payload.toolId} ${payload.result}`;
|
|
951
|
+
try {
|
|
952
|
+
const logger = await resolveAuditLogger();
|
|
953
|
+
if (payload.result === "failure" || payload.result === "blocked") {
|
|
954
|
+
if (logger?.warn) {
|
|
955
|
+
await logger.warn(payload, message);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
console.warn(message, payload);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
if (logger?.info) {
|
|
962
|
+
await logger.info(payload, message);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
console.info(message, payload);
|
|
966
|
+
} catch {
|
|
967
|
+
if (payload.result === "failure" || payload.result === "blocked") {
|
|
968
|
+
console.warn(message, payload);
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
console.info(message, payload);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function normalizeText(value) {
|
|
975
|
+
if (typeof value !== "string") return "";
|
|
976
|
+
const trimmed = value.trim();
|
|
977
|
+
return trimmed.length > 0 ? trimmed : "";
|
|
978
|
+
}
|
|
979
|
+
function normalizePositiveInteger(value) {
|
|
980
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return Math.trunc(value);
|
|
981
|
+
if (typeof value === "string") {
|
|
982
|
+
const trimmed = value.trim();
|
|
983
|
+
if (!trimmed) return void 0;
|
|
984
|
+
const parsed = Number(trimmed);
|
|
985
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.trunc(parsed);
|
|
986
|
+
}
|
|
987
|
+
return void 0;
|
|
988
|
+
}
|
|
989
|
+
function asRecord(value) {
|
|
990
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
991
|
+
return value;
|
|
992
|
+
}
|
|
993
|
+
function asArray(value) {
|
|
994
|
+
return Array.isArray(value) ? value : [];
|
|
995
|
+
}
|
|
996
|
+
function readCollection(input, collectionKey) {
|
|
997
|
+
const payload = asRecord(input);
|
|
998
|
+
if (Array.isArray(payload[collectionKey])) return payload[collectionKey];
|
|
999
|
+
if (Array.isArray(payload.data)) return payload.data;
|
|
1000
|
+
const data = asRecord(payload.data);
|
|
1001
|
+
if (Array.isArray(data[collectionKey])) return data[collectionKey];
|
|
1002
|
+
return [];
|
|
1003
|
+
}
|
|
1004
|
+
function buildBulkPushPreview(args) {
|
|
1005
|
+
const payload = asRecord(args.input);
|
|
1006
|
+
const preset = args.presetOptional === false ? "" : normalizeText(payload.preset);
|
|
1007
|
+
const count = readCollection(payload, args.collectionKey).length;
|
|
1008
|
+
if (!preset && count <= 0) return void 0;
|
|
1009
|
+
return {
|
|
1010
|
+
kind: "bulk-push",
|
|
1011
|
+
summary: preset ? `Push ${args.targetLabel} from preset ${preset}.` : `Push ${count} ${args.targetLabel}.`,
|
|
1012
|
+
change: {
|
|
1013
|
+
action: "bulk-push",
|
|
1014
|
+
basis: "payload-counts"
|
|
1015
|
+
},
|
|
1016
|
+
...preset ? { sourceMode: "preset", preset } : { sourceMode: "inline-data" },
|
|
1017
|
+
...count > 0 ? { counts: { [args.collectionKey]: count } } : {}
|
|
1018
|
+
};
|
|
1019
|
+
}
|
|
1020
|
+
function buildUnitPushPreview(input) {
|
|
1021
|
+
const payload = asRecord(input);
|
|
1022
|
+
const preset = normalizeText(payload.preset);
|
|
1023
|
+
const data = asRecord(payload.data);
|
|
1024
|
+
const unitTypes = asArray(payload.unitTypes).length > 0 ? asArray(payload.unitTypes) : asArray(data.unitTypes);
|
|
1025
|
+
const units = asArray(payload.units).length > 0 ? asArray(payload.units) : asArray(data.units);
|
|
1026
|
+
const subUnits = units.reduce(
|
|
1027
|
+
(count, entry) => count + asArray(asRecord(entry).subUnits).length,
|
|
1028
|
+
0
|
|
1029
|
+
);
|
|
1030
|
+
if (!preset && unitTypes.length <= 0 && units.length <= 0) return void 0;
|
|
1031
|
+
return {
|
|
1032
|
+
kind: "bulk-push",
|
|
1033
|
+
summary: preset ? `Push inventory units from preset ${preset}.` : `Push ${units.length} units across ${unitTypes.length} unit types.`,
|
|
1034
|
+
change: {
|
|
1035
|
+
action: "bulk-push",
|
|
1036
|
+
basis: "payload-counts"
|
|
1037
|
+
},
|
|
1038
|
+
...preset ? { sourceMode: "preset", preset } : { sourceMode: "inline-data" },
|
|
1039
|
+
counts: {
|
|
1040
|
+
unitTypes: unitTypes.length,
|
|
1041
|
+
units: units.length,
|
|
1042
|
+
subUnits
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function buildDeletePreview(input, key, label) {
|
|
1047
|
+
const payload = asRecord(input);
|
|
1048
|
+
const targetUid = normalizeText(payload[key]);
|
|
1049
|
+
if (!targetUid) return void 0;
|
|
1050
|
+
return {
|
|
1051
|
+
kind: "delete-target",
|
|
1052
|
+
summary: `Delete ${label} ${targetUid}.`,
|
|
1053
|
+
change: {
|
|
1054
|
+
action: "delete",
|
|
1055
|
+
basis: "target-uid"
|
|
1056
|
+
},
|
|
1057
|
+
target: {
|
|
1058
|
+
[key]: targetUid
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
function buildReceivingSourceRevisionKey(orderUid, revisionNumber) {
|
|
1063
|
+
return `${orderUid}::r${String(revisionNumber)}`;
|
|
1064
|
+
}
|
|
1065
|
+
function buildReceivingImportPreview(input) {
|
|
1066
|
+
const payload = asRecord(input);
|
|
1067
|
+
const handoff = asRecord(payload.handoff ?? payload.data ?? payload.input ?? payload);
|
|
1068
|
+
const orderUid = normalizeText(handoff.orderUid);
|
|
1069
|
+
const revisionNumber = normalizePositiveInteger(handoff.revisionNumber);
|
|
1070
|
+
if (!orderUid || !revisionNumber) return void 0;
|
|
1071
|
+
const lineCount = normalizePositiveInteger(handoff.lineCount) ?? asArray(handoff.lines).length;
|
|
1072
|
+
const sourceRevisionKey = buildReceivingSourceRevisionKey(orderUid, revisionNumber);
|
|
1073
|
+
return {
|
|
1074
|
+
kind: "handoff-import",
|
|
1075
|
+
summary: `Import receiving handoff for purchase order ${orderUid} revision ${revisionNumber}.`,
|
|
1076
|
+
change: {
|
|
1077
|
+
action: "import",
|
|
1078
|
+
basis: "source-revision"
|
|
1079
|
+
},
|
|
1080
|
+
target: {
|
|
1081
|
+
orderUid,
|
|
1082
|
+
revisionNumber,
|
|
1083
|
+
...lineCount > 0 ? { lineCount } : {}
|
|
1084
|
+
},
|
|
1085
|
+
...lineCount > 0 ? { counts: { lines: lineCount } } : {},
|
|
1086
|
+
idempotency: {
|
|
1087
|
+
owner: "domain-natural-key",
|
|
1088
|
+
recommendedKey: `inventory.receiving.import-purchase-order-handoff:${sourceRevisionKey}`,
|
|
1089
|
+
scope: "orderUid+revisionNumber"
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function buildBomSheetPreview(operationId, input) {
|
|
1094
|
+
const payload = asRecord(input);
|
|
1095
|
+
const rows = asArray(payload.rows);
|
|
1096
|
+
const sheetName = normalizeText(payload.sheetName);
|
|
1097
|
+
if (rows.length <= 0 && !sheetName) return void 0;
|
|
1098
|
+
return {
|
|
1099
|
+
kind: "sheet-import",
|
|
1100
|
+
summary: operationId === "bom.import-sheet-preview" ? `Preview BOM sheet import for ${rows.length} rows.` : `Create BOM from sheet import with ${rows.length} rows.`,
|
|
1101
|
+
change: {
|
|
1102
|
+
action: "import",
|
|
1103
|
+
basis: "sheet-rows"
|
|
1104
|
+
},
|
|
1105
|
+
...sheetName ? { target: { sheetName } } : {},
|
|
1106
|
+
counts: {
|
|
1107
|
+
rows: rows.length
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function buildInventorySemanticPreview(operationId, input) {
|
|
1112
|
+
switch (operationId) {
|
|
1113
|
+
case "category.push":
|
|
1114
|
+
return buildBulkPushPreview({
|
|
1115
|
+
collectionKey: "categories",
|
|
1116
|
+
targetLabel: "inventory categories",
|
|
1117
|
+
input
|
|
1118
|
+
});
|
|
1119
|
+
case "attribute-def.push":
|
|
1120
|
+
return buildBulkPushPreview({
|
|
1121
|
+
collectionKey: "attributeDefs",
|
|
1122
|
+
targetLabel: "attribute definitions",
|
|
1123
|
+
input
|
|
1124
|
+
});
|
|
1125
|
+
case "attribute-def.delete":
|
|
1126
|
+
return buildDeletePreview(input, "attributeDefUid", "attribute definition");
|
|
1127
|
+
case "category-attribute.push":
|
|
1128
|
+
return buildBulkPushPreview({
|
|
1129
|
+
collectionKey: "categoryAttributes",
|
|
1130
|
+
targetLabel: "category attribute bindings",
|
|
1131
|
+
input
|
|
1132
|
+
});
|
|
1133
|
+
case "manufacturer.push":
|
|
1134
|
+
return buildBulkPushPreview({
|
|
1135
|
+
collectionKey: "manufacturers",
|
|
1136
|
+
targetLabel: "manufacturers",
|
|
1137
|
+
input,
|
|
1138
|
+
presetOptional: false
|
|
1139
|
+
});
|
|
1140
|
+
case "distributor.push":
|
|
1141
|
+
return buildBulkPushPreview({
|
|
1142
|
+
collectionKey: "distributors",
|
|
1143
|
+
targetLabel: "distributors",
|
|
1144
|
+
input,
|
|
1145
|
+
presetOptional: false
|
|
1146
|
+
});
|
|
1147
|
+
case "supplier.push":
|
|
1148
|
+
return buildBulkPushPreview({
|
|
1149
|
+
collectionKey: "suppliers",
|
|
1150
|
+
targetLabel: "suppliers",
|
|
1151
|
+
input,
|
|
1152
|
+
presetOptional: false
|
|
1153
|
+
});
|
|
1154
|
+
case "unit.push":
|
|
1155
|
+
return buildUnitPushPreview(input);
|
|
1156
|
+
case "unit.delete":
|
|
1157
|
+
return buildDeletePreview(input, "unitUid", "inventory unit");
|
|
1158
|
+
case "subunit.delete":
|
|
1159
|
+
return buildDeletePreview(input, "subUnitUid", "inventory sub-unit");
|
|
1160
|
+
case "receiving.import-purchase-order-handoff":
|
|
1161
|
+
return buildReceivingImportPreview(input);
|
|
1162
|
+
case "bom.import-sheet-preview":
|
|
1163
|
+
case "bom.import-sheet-create":
|
|
1164
|
+
return buildBomSheetPreview(operationId, input);
|
|
1165
|
+
default:
|
|
1166
|
+
return void 0;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function buildAgentSemanticPreview(args) {
|
|
1170
|
+
if (normalizeText(args.domain).toLowerCase() !== "inventory") return void 0;
|
|
1171
|
+
return buildInventorySemanticPreview(args.operationId, args.input);
|
|
1172
|
+
}
|
|
1173
|
+
function resolveAgentSemanticIdempotencyKey(args) {
|
|
1174
|
+
return buildAgentSemanticPreview(args)?.idempotency?.recommendedKey;
|
|
1175
|
+
}
|
|
1176
|
+
const schemaCacheByDomain = /* @__PURE__ */ new Map();
|
|
1177
|
+
const TOOLING_CONTEXT_INPUT_KEYS = /* @__PURE__ */ new Set([
|
|
1178
|
+
"projectId",
|
|
1179
|
+
"scopeId",
|
|
1180
|
+
"tenantId",
|
|
1181
|
+
"locale",
|
|
1182
|
+
"fallbackLocale"
|
|
1183
|
+
]);
|
|
1184
|
+
function parseMaybeJson(value) {
|
|
1185
|
+
const trimmed = value.trim();
|
|
1186
|
+
if (!trimmed) return value;
|
|
1187
|
+
if (trimmed === "true") return true;
|
|
1188
|
+
if (trimmed === "false") return false;
|
|
1189
|
+
if (!Number.isNaN(Number(trimmed)) && trimmed !== "") return Number(trimmed);
|
|
1190
|
+
if (trimmed.startsWith("{") && trimmed.endsWith("}") || trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
1191
|
+
try {
|
|
1192
|
+
return JSON.parse(trimmed);
|
|
1193
|
+
} catch {
|
|
1194
|
+
return value;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return value;
|
|
1198
|
+
}
|
|
1199
|
+
function toQueryObject(query) {
|
|
1200
|
+
const out = {};
|
|
1201
|
+
for (const [key, value] of query.entries()) {
|
|
1202
|
+
out[key] = parseMaybeJson(value);
|
|
1203
|
+
}
|
|
1204
|
+
return out;
|
|
1205
|
+
}
|
|
1206
|
+
function toValidationPayload(parsedInput, tool, manifests = []) {
|
|
1207
|
+
const queryPayload = toQueryObject(parsedInput.query);
|
|
1208
|
+
const pathPayload = { ...parsedInput.pathParams };
|
|
1209
|
+
if (tool) {
|
|
1210
|
+
const aliases = extractRouteParamAliases(tool, manifests);
|
|
1211
|
+
for (const [targetArg, pathKey] of aliases.entries()) {
|
|
1212
|
+
const pathValue = pathPayload[pathKey];
|
|
1213
|
+
if (pathValue === void 0) continue;
|
|
1214
|
+
if (pathPayload[targetArg] === void 0) {
|
|
1215
|
+
pathPayload[targetArg] = pathValue;
|
|
1216
|
+
}
|
|
1217
|
+
if (targetArg !== pathKey) {
|
|
1218
|
+
delete pathPayload[pathKey];
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (isRecord$3(parsedInput.body)) {
|
|
1223
|
+
return {
|
|
1224
|
+
...queryPayload,
|
|
1225
|
+
...parsedInput.body,
|
|
1226
|
+
...pathPayload
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
const hasQueryOrPath = Object.keys(queryPayload).length > 0 || Object.keys(pathPayload).length > 0;
|
|
1230
|
+
if (!hasQueryOrPath) {
|
|
1231
|
+
return parsedInput.body === void 0 || parsedInput.body === null ? {} : parsedInput.body;
|
|
1232
|
+
}
|
|
1233
|
+
if (parsedInput.body === void 0 || parsedInput.body === null) {
|
|
1234
|
+
return { ...queryPayload, ...pathPayload };
|
|
1235
|
+
}
|
|
1236
|
+
return {
|
|
1237
|
+
...queryPayload,
|
|
1238
|
+
...pathPayload,
|
|
1239
|
+
body: parsedInput.body
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function normalizeArgName(value) {
|
|
1243
|
+
return String(value ?? "").trim();
|
|
1244
|
+
}
|
|
1245
|
+
function normalizeContractArgName(value) {
|
|
1246
|
+
return normalizeArgName(value);
|
|
1247
|
+
}
|
|
1248
|
+
function parseArgsFromOperationNotes(notes, marker) {
|
|
1249
|
+
if (!Array.isArray(notes)) return [];
|
|
1250
|
+
const normalizedMarker = marker.toLowerCase();
|
|
1251
|
+
const out = [];
|
|
1252
|
+
for (const rawNote of notes) {
|
|
1253
|
+
const note = String(rawNote ?? "").trim();
|
|
1254
|
+
if (!note) continue;
|
|
1255
|
+
const lowered = note.toLowerCase();
|
|
1256
|
+
if (!lowered.startsWith(normalizedMarker)) continue;
|
|
1257
|
+
const rawArgs = note.slice(normalizedMarker.length).trim();
|
|
1258
|
+
if (!rawArgs) continue;
|
|
1259
|
+
for (const argRaw of rawArgs.split(",")) {
|
|
1260
|
+
const normalized = normalizeContractArgName(argRaw);
|
|
1261
|
+
if (!normalized) continue;
|
|
1262
|
+
out.push(normalized);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
return [...new Set(out)];
|
|
1266
|
+
}
|
|
1267
|
+
function toSchemaArgContract(schema) {
|
|
1268
|
+
if (!isRecord$3(schema)) return null;
|
|
1269
|
+
if (schema.type !== "object") return null;
|
|
1270
|
+
const properties = isRecord$3(schema.properties) ? schema.properties : {};
|
|
1271
|
+
const propertyNames = Object.keys(properties).map((name) => normalizeContractArgName(name)).filter(Boolean);
|
|
1272
|
+
const requiredArgs = Array.isArray(schema.required) ? [...new Set(schema.required.map((value) => normalizeContractArgName(value)).filter(Boolean))] : [];
|
|
1273
|
+
const optionalArgs = propertyNames.filter((name) => !requiredArgs.includes(name));
|
|
1274
|
+
const disallowUnknownArgs = schema.additionalProperties === false;
|
|
1275
|
+
if (propertyNames.length === 0 && requiredArgs.length === 0) return null;
|
|
1276
|
+
return {
|
|
1277
|
+
requiredArgs,
|
|
1278
|
+
optionalArgs,
|
|
1279
|
+
disallowUnknownArgs
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
function resolveOperationInputSchema(manifest, tool) {
|
|
1283
|
+
const schemaRef = normalizeArgName(tool.inputSchemaRef);
|
|
1284
|
+
if (!schemaRef) return null;
|
|
1285
|
+
const schemas = isRecord$3(manifest.contracts?.schemas) ? manifest.contracts.schemas : {};
|
|
1286
|
+
if (!isRecord$3(schemas)) return null;
|
|
1287
|
+
return schemas[schemaRef] ?? null;
|
|
1288
|
+
}
|
|
1289
|
+
function resolveToolArgContract(tool, manifests) {
|
|
1290
|
+
const manifest = findManifestForDomain(manifests, tool.domain);
|
|
1291
|
+
if (!manifest) return null;
|
|
1292
|
+
const notes = manifest.docs?.operations?.[tool.operationId]?.notes;
|
|
1293
|
+
const notesRequiredArgs = parseArgsFromOperationNotes(notes, "required args:");
|
|
1294
|
+
const notesOptionalArgs = parseArgsFromOperationNotes(notes, "optional args:");
|
|
1295
|
+
const schemaContract = toSchemaArgContract(resolveOperationInputSchema(manifest, tool));
|
|
1296
|
+
const requiredArgs = /* @__PURE__ */ new Set([...notesRequiredArgs, ...schemaContract?.requiredArgs ?? []]);
|
|
1297
|
+
const optionalArgs = /* @__PURE__ */ new Set([...notesOptionalArgs, ...schemaContract?.optionalArgs ?? []]);
|
|
1298
|
+
for (const required2 of requiredArgs) optionalArgs.delete(required2);
|
|
1299
|
+
const required = [...requiredArgs];
|
|
1300
|
+
const optional = [...optionalArgs];
|
|
1301
|
+
const disallowUnknownArgs = notesRequiredArgs.length > 0 || notesOptionalArgs.length > 0 || Boolean(schemaContract?.disallowUnknownArgs);
|
|
1302
|
+
if (required.length === 0 && optional.length === 0 && !disallowUnknownArgs) {
|
|
1303
|
+
return null;
|
|
1304
|
+
}
|
|
1305
|
+
return {
|
|
1306
|
+
requiredArgs: required,
|
|
1307
|
+
optionalArgs: optional,
|
|
1308
|
+
disallowUnknownArgs
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
function hasNonEmptyValue(value) {
|
|
1312
|
+
if (value === void 0 || value === null) return false;
|
|
1313
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
1314
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1315
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1316
|
+
return true;
|
|
1317
|
+
}
|
|
1318
|
+
function hasRequiredArg(payload, context, argName) {
|
|
1319
|
+
if (argName === "scopeId") {
|
|
1320
|
+
return hasNonEmptyValue(payload.scopeId) || hasProjectAliasValue(payload, context?.projectId) || hasNonEmptyValue(context?.scopeId);
|
|
1321
|
+
}
|
|
1322
|
+
if (argName === "projectId") {
|
|
1323
|
+
return hasProjectAliasValue(payload, context?.projectId);
|
|
1324
|
+
}
|
|
1325
|
+
return hasNonEmptyValue(payload[argName]);
|
|
1326
|
+
}
|
|
1327
|
+
function toContractValidationPayload(parsedInput, tool, manifests, context) {
|
|
1328
|
+
const payload = toValidationPayload(parsedInput, tool, manifests);
|
|
1329
|
+
if (!isRecord$3(payload)) return null;
|
|
1330
|
+
const merged = { ...payload };
|
|
1331
|
+
if (!hasNonEmptyValue(merged.projectId) && hasNonEmptyValue(context?.projectId)) {
|
|
1332
|
+
merged.projectId = String(context?.projectId);
|
|
1333
|
+
}
|
|
1334
|
+
if (!hasNonEmptyValue(merged.scopeId)) {
|
|
1335
|
+
const resolvedScopeId = context?.scopeId ?? context?.projectId;
|
|
1336
|
+
if (hasNonEmptyValue(resolvedScopeId)) {
|
|
1337
|
+
merged.scopeId = String(resolvedScopeId);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
if (!hasNonEmptyValue(merged.tenantId) && hasNonEmptyValue(context?.tenantId)) {
|
|
1341
|
+
merged.tenantId = String(context?.tenantId);
|
|
1342
|
+
}
|
|
1343
|
+
if (!hasNonEmptyValue(merged.locale) && hasNonEmptyValue(context?.locale)) {
|
|
1344
|
+
merged.locale = String(context?.locale);
|
|
1345
|
+
}
|
|
1346
|
+
if (!hasNonEmptyValue(merged.fallbackLocale) && hasNonEmptyValue(context?.fallbackLocale)) {
|
|
1347
|
+
merged.fallbackLocale = String(context?.fallbackLocale);
|
|
1348
|
+
}
|
|
1349
|
+
return merged;
|
|
1350
|
+
}
|
|
1351
|
+
function toContractValidationSchema(contract, operationId, context) {
|
|
1352
|
+
const shape = {};
|
|
1353
|
+
for (const argName of contract.requiredArgs) shape[argName] = z.unknown().optional();
|
|
1354
|
+
for (const argName of contract.optionalArgs) shape[argName] = z.unknown().optional();
|
|
1355
|
+
for (const contextKey of TOOLING_CONTEXT_INPUT_KEYS) shape[contextKey] = z.unknown().optional();
|
|
1356
|
+
const base = contract.disallowUnknownArgs ? z.object(shape).strict() : z.object(shape).passthrough();
|
|
1357
|
+
return base.superRefine((payload, refineContext) => {
|
|
1358
|
+
for (const requiredArg of contract.requiredArgs) {
|
|
1359
|
+
if (hasRequiredArg(payload, context, requiredArg)) continue;
|
|
1360
|
+
refineContext.addIssue({
|
|
1361
|
+
code: z.ZodIssueCode.custom,
|
|
1362
|
+
message: `missing_required_arg:${requiredArg};operation:${operationId}`,
|
|
1363
|
+
path: [requiredArg]
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
function formatContractValidationError(tool, error) {
|
|
1369
|
+
const firstIssue = error.issues[0];
|
|
1370
|
+
if (!firstIssue) return `validation_failed:${tool.operationId}`;
|
|
1371
|
+
if (firstIssue.message.startsWith("missing_required_arg:")) return firstIssue.message;
|
|
1372
|
+
if (firstIssue.code === "unrecognized_keys") {
|
|
1373
|
+
const unknownArg = Array.isArray(firstIssue.keys) && firstIssue.keys ? firstIssue.keys?.[0] : void 0;
|
|
1374
|
+
if (unknownArg) {
|
|
1375
|
+
return `unknown_input_arg:${unknownArg};operation:${tool.operationId}`;
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return `validation_failed:${tool.operationId}`;
|
|
1379
|
+
}
|
|
1380
|
+
function resolveToolPolicyRecord(tool) {
|
|
1381
|
+
return isRecord$3(tool.policy) ? tool.policy : null;
|
|
1382
|
+
}
|
|
1383
|
+
function resolveToolPolicySafety(tool) {
|
|
1384
|
+
const policy = resolveToolPolicyRecord(tool);
|
|
1385
|
+
return policy && isRecord$3(policy.safety) ? policy.safety : {};
|
|
1386
|
+
}
|
|
1387
|
+
function resolveToolPolicyContext(tool) {
|
|
1388
|
+
const policy = resolveToolPolicyRecord(tool);
|
|
1389
|
+
return policy && isRecord$3(policy.context) ? policy.context : {};
|
|
1390
|
+
}
|
|
1391
|
+
function resolveToolPolicyAuth(tool) {
|
|
1392
|
+
const policy = resolveToolPolicyRecord(tool);
|
|
1393
|
+
return policy && isRecord$3(policy.auth) ? policy.auth : {};
|
|
1394
|
+
}
|
|
1395
|
+
function toNormalizedStringList(value) {
|
|
1396
|
+
if (!Array.isArray(value)) return [];
|
|
1397
|
+
return [...new Set(value.map((entry) => normalizeArgName(entry).toLowerCase()).filter(Boolean))];
|
|
1398
|
+
}
|
|
1399
|
+
function resolvePrincipalRecord(context) {
|
|
1400
|
+
return context?.principal && isRecord$3(context.principal) ? context.principal : null;
|
|
1401
|
+
}
|
|
1402
|
+
function hasAuthenticatedPrincipal(context) {
|
|
1403
|
+
const principal = resolvePrincipalRecord(context);
|
|
1404
|
+
if (!principal) return false;
|
|
1405
|
+
const principalId = normalizeArgName(principal.id ?? principal.userId);
|
|
1406
|
+
return principalId.length > 0;
|
|
1407
|
+
}
|
|
1408
|
+
function resolvePrincipalRoles(context) {
|
|
1409
|
+
return new Set(toNormalizedStringList(resolvePrincipalRecord(context)?.roles));
|
|
1410
|
+
}
|
|
1411
|
+
function resolvePrincipalCapabilities(context) {
|
|
1412
|
+
const principal = resolvePrincipalRecord(context);
|
|
1413
|
+
return new Set(toNormalizedStringList(principal?.permissions ?? principal?.capabilities));
|
|
1414
|
+
}
|
|
1415
|
+
function hasResolvedScopeContext(context) {
|
|
1416
|
+
return typeof context?.scopeId === "string" && context.scopeId.trim().length > 0 || typeof context?.projectId === "string" && context.projectId.trim().length > 0;
|
|
1417
|
+
}
|
|
1418
|
+
function resolveToolInvokeRequirements(params) {
|
|
1419
|
+
const auth = resolveToolPolicyAuth(params.tool);
|
|
1420
|
+
const safety = resolveToolPolicySafety(params.tool);
|
|
1421
|
+
const contextPolicy = resolveToolPolicyContext(params.tool);
|
|
1422
|
+
const rolesRequired = toNormalizedStringList(auth.roles);
|
|
1423
|
+
const capabilitiesRequired = toNormalizedStringList(auth.capabilities);
|
|
1424
|
+
const principalPresent = hasAuthenticatedPrincipal(params.context);
|
|
1425
|
+
const principalRoles = resolvePrincipalRoles(params.context);
|
|
1426
|
+
const principalCapabilities = resolvePrincipalCapabilities(params.context);
|
|
1427
|
+
const rolesSatisfied = rolesRequired.length === 0 || rolesRequired.some((role) => principalRoles.has(role));
|
|
1428
|
+
const capabilitiesSatisfied = capabilitiesRequired.length === 0 || principalCapabilities.has("*") || capabilitiesRequired.some((capability) => principalCapabilities.has(capability));
|
|
1429
|
+
const authRequired = auth.required === true || rolesRequired.length > 0 || capabilitiesRequired.length > 0;
|
|
1430
|
+
const accessGrantRequired = rolesRequired.length > 0 || capabilitiesRequired.length > 0;
|
|
1431
|
+
const applyRequired = safety.applyRequired === true;
|
|
1432
|
+
const confirmationRequired = safety.confirmationRequired === true;
|
|
1433
|
+
const scopeRequired = contextPolicy.scopeRequired === true || contextPolicy.projectRequired === true;
|
|
1434
|
+
return {
|
|
1435
|
+
authRequired,
|
|
1436
|
+
principalPresent,
|
|
1437
|
+
authSatisfied: (!authRequired || principalPresent) && (!accessGrantRequired || rolesSatisfied || capabilitiesSatisfied),
|
|
1438
|
+
rolesRequired,
|
|
1439
|
+
rolesSatisfied,
|
|
1440
|
+
capabilitiesRequired,
|
|
1441
|
+
capabilitiesSatisfied,
|
|
1442
|
+
applyRequired,
|
|
1443
|
+
applySatisfied: !applyRequired || params.apply === true,
|
|
1444
|
+
confirmationRequired,
|
|
1445
|
+
confirmationSatisfied: !confirmationRequired || params.confirm === true,
|
|
1446
|
+
scopeRequired,
|
|
1447
|
+
scopeSatisfied: !scopeRequired || hasResolvedScopeContext(params.context)
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function validateToolInvokeSafety(params) {
|
|
1451
|
+
const requirements = resolveToolInvokeRequirements({
|
|
1452
|
+
tool: params.tool,
|
|
1453
|
+
apply: params.apply,
|
|
1454
|
+
confirm: params.confirm
|
|
1455
|
+
});
|
|
1456
|
+
if (!requirements.applySatisfied) {
|
|
1457
|
+
return {
|
|
1458
|
+
ok: false,
|
|
1459
|
+
message: `apply_required:${params.tool.toolId}`
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
if (!requirements.confirmationSatisfied) {
|
|
1463
|
+
return {
|
|
1464
|
+
ok: false,
|
|
1465
|
+
message: `confirmation_required:${params.tool.toolId}`
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
return { ok: true };
|
|
1469
|
+
}
|
|
1470
|
+
function validateToolInvokeAuthorization(params) {
|
|
1471
|
+
const requirements = resolveToolInvokeRequirements({
|
|
1472
|
+
tool: params.tool,
|
|
1473
|
+
context: params.context
|
|
1474
|
+
});
|
|
1475
|
+
if (requirements.authRequired && !requirements.principalPresent) {
|
|
1476
|
+
return {
|
|
1477
|
+
ok: false,
|
|
1478
|
+
message: "unauthorized"
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
if (!requirements.authSatisfied) {
|
|
1482
|
+
return {
|
|
1483
|
+
ok: false,
|
|
1484
|
+
message: "forbidden"
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
return { ok: true };
|
|
1488
|
+
}
|
|
1489
|
+
function validateToolInvokeScope(params) {
|
|
1490
|
+
const requirements = resolveToolInvokeRequirements({
|
|
1491
|
+
tool: params.tool,
|
|
1492
|
+
context: params.context
|
|
1493
|
+
});
|
|
1494
|
+
if (!requirements.scopeSatisfied) {
|
|
1495
|
+
return {
|
|
1496
|
+
ok: false,
|
|
1497
|
+
message: "project_context_required"
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
return { ok: true };
|
|
1501
|
+
}
|
|
1502
|
+
function validateToolInputByContract(params) {
|
|
1503
|
+
const contract = resolveToolArgContract(params.tool, params.manifests);
|
|
1504
|
+
if (!contract) return { ok: true };
|
|
1505
|
+
const payload = toContractValidationPayload(params.parsedInput, params.tool, params.manifests, params.context);
|
|
1506
|
+
if (!payload) {
|
|
1507
|
+
if (contract.requiredArgs.length === 0) return { ok: true };
|
|
1508
|
+
const firstRequired = contract.requiredArgs[0] ?? "input";
|
|
1509
|
+
if (firstRequired === "scopeId" || firstRequired === "projectId") {
|
|
1510
|
+
return { ok: false, message: "project_required" };
|
|
1511
|
+
}
|
|
1512
|
+
return {
|
|
1513
|
+
ok: false,
|
|
1514
|
+
message: `missing_required_arg:${firstRequired};operation:${params.tool.operationId}`
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
if ((contract.requiredArgs.includes("scopeId") || contract.requiredArgs.includes("projectId")) && !contract.requiredArgs.every(
|
|
1518
|
+
(argName) => argName === "scopeId" || argName === "projectId" ? hasRequiredArg(payload, params.context, argName) : true
|
|
1519
|
+
)) {
|
|
1520
|
+
return { ok: false, message: "project_required" };
|
|
1521
|
+
}
|
|
1522
|
+
const parsed = toContractValidationSchema(contract, params.tool.operationId, params.context).safeParse(payload);
|
|
1523
|
+
if (parsed.success) return { ok: true };
|
|
1524
|
+
return {
|
|
1525
|
+
ok: false,
|
|
1526
|
+
message: formatContractValidationError(params.tool, parsed.error)
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
function formatSchemaErrors(errors) {
|
|
1530
|
+
if (!errors || errors.length === 0) return "invalid_input";
|
|
1531
|
+
const first = errors[0];
|
|
1532
|
+
const path2 = first.instancePath && first.instancePath.length > 0 ? first.instancePath : "/";
|
|
1533
|
+
const message = first.message ?? first.keyword;
|
|
1534
|
+
return `${path2} ${message}`.trim();
|
|
1535
|
+
}
|
|
1536
|
+
function findManifestForDomain(manifests, domain) {
|
|
1537
|
+
return manifests.find((manifest) => normalizeDomain(manifest.domain.id) === normalizeDomain(domain)) ?? null;
|
|
1538
|
+
}
|
|
1539
|
+
function buildDomainSchemaCache(domain, manifest) {
|
|
1540
|
+
const ajv = new Ajv({
|
|
1541
|
+
allErrors: true,
|
|
1542
|
+
strict: false,
|
|
1543
|
+
coerceTypes: false,
|
|
1544
|
+
allowUnionTypes: true
|
|
1545
|
+
});
|
|
1546
|
+
const schemas = isRecord$3(manifest.contracts?.schemas) ? manifest.contracts?.schemas : {};
|
|
1547
|
+
for (const [schemaRef, schema] of Object.entries(schemas ?? {})) {
|
|
1548
|
+
if (!schemaRef.trim()) continue;
|
|
1549
|
+
if (!isRecord$3(schema)) continue;
|
|
1550
|
+
ajv.addSchema(schema, `${domain}:${schemaRef.trim()}`);
|
|
1551
|
+
}
|
|
1552
|
+
const cache = {
|
|
1553
|
+
ajv,
|
|
1554
|
+
validators: /* @__PURE__ */ new Map()
|
|
1555
|
+
};
|
|
1556
|
+
schemaCacheByDomain.set(domain, cache);
|
|
1557
|
+
return cache;
|
|
1558
|
+
}
|
|
1559
|
+
function getDomainSchemaCache(domain, manifests) {
|
|
1560
|
+
const normalizedDomain = normalizeDomain(domain);
|
|
1561
|
+
if (!normalizedDomain) return null;
|
|
1562
|
+
const existing = schemaCacheByDomain.get(normalizedDomain);
|
|
1563
|
+
if (existing) return existing;
|
|
1564
|
+
const manifest = findManifestForDomain(manifests, normalizedDomain);
|
|
1565
|
+
if (!manifest) return null;
|
|
1566
|
+
return buildDomainSchemaCache(normalizedDomain, manifest);
|
|
1567
|
+
}
|
|
1568
|
+
function resetToolSchemaValidationCache(domain) {
|
|
1569
|
+
const normalizedDomain = normalizeDomain("");
|
|
1570
|
+
if (normalizedDomain) {
|
|
1571
|
+
schemaCacheByDomain.delete(normalizedDomain);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
schemaCacheByDomain.clear();
|
|
1575
|
+
}
|
|
1576
|
+
function resolveInputValidator(tool, manifests) {
|
|
1577
|
+
const schemaRef = typeof tool.inputSchemaRef === "string" ? tool.inputSchemaRef.trim() : "";
|
|
1578
|
+
if (!schemaRef) return null;
|
|
1579
|
+
const domain = normalizeDomain(tool.domain);
|
|
1580
|
+
const cache = getDomainSchemaCache(domain, manifests);
|
|
1581
|
+
if (!cache) return null;
|
|
1582
|
+
const schemaId = `${domain}:${schemaRef}`;
|
|
1583
|
+
const existing = cache.validators.get(schemaId);
|
|
1584
|
+
if (existing) return { validate: existing, schemaId };
|
|
1585
|
+
const validate = cache.ajv.getSchema(schemaId);
|
|
1586
|
+
if (!validate) return null;
|
|
1587
|
+
cache.validators.set(schemaId, validate);
|
|
1588
|
+
return { validate, schemaId };
|
|
1589
|
+
}
|
|
1590
|
+
function validateToolInputBySchema(params) {
|
|
1591
|
+
const validator = resolveInputValidator(params.tool, params.manifests);
|
|
1592
|
+
if (!validator) return { ok: true };
|
|
1593
|
+
const payload = toValidationPayload(params.parsedInput, params.tool, params.manifests);
|
|
1594
|
+
const valid = validator.validate(payload);
|
|
1595
|
+
if (valid) return { ok: true };
|
|
1596
|
+
const detail = formatSchemaErrors(validator.validate.errors);
|
|
1597
|
+
return {
|
|
1598
|
+
ok: false,
|
|
1599
|
+
message: `tool_input_schema_invalid:${params.tool.toolId}:${detail}`
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
1602
|
+
const CATALOG_REFRESH_ERROR_PREFIXES = [
|
|
1603
|
+
"tool_not_found:",
|
|
1604
|
+
"tool_source_unavailable:",
|
|
1605
|
+
"tool_source_not_found:",
|
|
1606
|
+
"domain_not_registered:",
|
|
1607
|
+
"operation_route_not_found:"
|
|
1608
|
+
];
|
|
1609
|
+
const LOCAL_RUNTIME_REFRESH_ERROR_PREFIXES = [
|
|
1610
|
+
"domain_not_registered:",
|
|
1611
|
+
"operation_route_not_found:"
|
|
1612
|
+
];
|
|
1613
|
+
const MUTATING_ACTIVITY_OPERATION_PATTERN = /(^|[.])(create|update|remove|delete|archive|publish|add|save|upsert|write|link|attach|enqueue|start|stop|interrupt|cancel|approve|dispatch|operate|loop|work|restore|copy|rebase|zip|clean)([.-]|$)/;
|
|
1614
|
+
function resolveGatewayConfig(config) {
|
|
1615
|
+
return config ?? {
|
|
1616
|
+
enabled: true,
|
|
1617
|
+
includeLocal: true,
|
|
1618
|
+
sources: [],
|
|
1619
|
+
catalog: { enabled: false, manifestProviders: [] }
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
function normalizeNonEmpty(value) {
|
|
1623
|
+
if (typeof value !== "string") return void 0;
|
|
1624
|
+
const normalized = value.trim();
|
|
1625
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
1626
|
+
}
|
|
1627
|
+
function isRecord(value) {
|
|
1628
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1629
|
+
}
|
|
1630
|
+
function sanitizeActivityPayload(value, depth = 0) {
|
|
1631
|
+
if (depth > 3) return "[truncated]";
|
|
1632
|
+
if (typeof value === "string") {
|
|
1633
|
+
const normalized = value.trim();
|
|
1634
|
+
return normalized.length <= 400 ? normalized : `${normalized.slice(0, 397)}...`;
|
|
1635
|
+
}
|
|
1636
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null || value === void 0) {
|
|
1637
|
+
return value;
|
|
1638
|
+
}
|
|
1639
|
+
if (Array.isArray(value)) {
|
|
1640
|
+
return value.slice(0, 12).map((entry) => sanitizeActivityPayload(entry, depth + 1));
|
|
1641
|
+
}
|
|
1642
|
+
if (!isRecord(value)) return String(value);
|
|
1643
|
+
const next = {};
|
|
1644
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1645
|
+
const lower = key.toLowerCase();
|
|
1646
|
+
if (lower.includes("token") || lower.includes("secret") || lower.includes("password") || lower.includes("authorization")) {
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
next[key] = sanitizeActivityPayload(entry, depth + 1);
|
|
1650
|
+
}
|
|
1651
|
+
return next;
|
|
1652
|
+
}
|
|
1653
|
+
function maybePushActivityRef(target, type, id, label) {
|
|
1654
|
+
const normalizedId = normalizeNonEmpty(id);
|
|
1655
|
+
if (!normalizedId) return;
|
|
1656
|
+
if (target.some((entry) => entry.type === type && entry.id === normalizedId)) return;
|
|
1657
|
+
target.push({ type, id: normalizedId });
|
|
1658
|
+
}
|
|
1659
|
+
function collectActivityRefs(value, refs) {
|
|
1660
|
+
if (!isRecord(value)) return;
|
|
1661
|
+
maybePushActivityRef(refs, "scope", value.scopeId);
|
|
1662
|
+
maybePushActivityRef(refs, "project", value.projectId);
|
|
1663
|
+
maybePushActivityRef(refs, "task", value.taskId);
|
|
1664
|
+
maybePushActivityRef(refs, "sprint", value.sprintId);
|
|
1665
|
+
maybePushActivityRef(refs, "microtask", value.microtaskId);
|
|
1666
|
+
maybePushActivityRef(refs, "issue", value.issueId);
|
|
1667
|
+
maybePushActivityRef(refs, "feedback", value.feedbackId);
|
|
1668
|
+
maybePushActivityRef(refs, "prompt", value.promptId);
|
|
1669
|
+
maybePushActivityRef(refs, "prompt-version", value.promptVersionId);
|
|
1670
|
+
maybePushActivityRef(refs, "skill", value.skillId);
|
|
1671
|
+
maybePushActivityRef(refs, "skill-version", value.skillVersionId);
|
|
1672
|
+
maybePushActivityRef(refs, "resource", value.resourceId);
|
|
1673
|
+
maybePushActivityRef(refs, "artifact", value.artifactId);
|
|
1674
|
+
maybePushActivityRef(refs, "run", value.runId ?? value.runRecordId);
|
|
1675
|
+
maybePushActivityRef(refs, "workflow", value.workflowId ?? value.workflowInstanceId);
|
|
1676
|
+
maybePushActivityRef(refs, "workflow-definition", value.workflowDefinitionId);
|
|
1677
|
+
maybePushActivityRef(refs, "document", value.documentId);
|
|
1678
|
+
maybePushActivityRef(refs, "document-version", value.documentVersionId);
|
|
1679
|
+
maybePushActivityRef(refs, "page", value.pageId);
|
|
1680
|
+
maybePushActivityRef(refs, "page-version", value.pageVersionId);
|
|
1681
|
+
maybePushActivityRef(refs, "section", value.sectionId);
|
|
1682
|
+
if (isRecord(value.data)) collectActivityRefs(value.data, refs);
|
|
1683
|
+
if (isRecord(value.input)) collectActivityRefs(value.input, refs);
|
|
1684
|
+
if (isRecord(value.response)) collectActivityRefs(value.response, refs);
|
|
1685
|
+
if (isRecord(value.result)) collectActivityRefs(value.result, refs);
|
|
1686
|
+
}
|
|
1687
|
+
function resolveGatewayActivityAction(tool) {
|
|
1688
|
+
const segments = String(tool.operationId ?? "").split(".").filter(Boolean);
|
|
1689
|
+
return segments[segments.length - 1] ?? "invoke";
|
|
1690
|
+
}
|
|
1691
|
+
function summarizeGatewayActivity(tool, status) {
|
|
1692
|
+
const action = resolveGatewayActivityAction(tool);
|
|
1693
|
+
const entity = String(tool.toolId ?? tool.operationId ?? "operation").replace(/^aops\./, "").replace(/^agentspace\./, "").replace(/^tasker\./, "").replace(/^projectman\./, "").replace(/^docman\./, "").replace(/^fileman\./, "").split(".").slice(0, 2).join(" ").replace(/[-_]+/g, " ").trim() || "operation";
|
|
1694
|
+
const verbMap = {
|
|
1695
|
+
create: "created",
|
|
1696
|
+
add: "added",
|
|
1697
|
+
update: "updated",
|
|
1698
|
+
set: "updated",
|
|
1699
|
+
publish: "published",
|
|
1700
|
+
remove: "removed",
|
|
1701
|
+
delete: "deleted",
|
|
1702
|
+
restore: "restored",
|
|
1703
|
+
copy: "copied",
|
|
1704
|
+
rebase: "rebased",
|
|
1705
|
+
zip: "archived",
|
|
1706
|
+
clean: "cleaned",
|
|
1707
|
+
link: "linked",
|
|
1708
|
+
attach: "attached",
|
|
1709
|
+
enqueue: "enqueued",
|
|
1710
|
+
start: "started",
|
|
1711
|
+
stop: "stopped",
|
|
1712
|
+
cancel: "cancelled",
|
|
1713
|
+
interrupt: "interrupted",
|
|
1714
|
+
approve: "approved",
|
|
1715
|
+
save: "saved",
|
|
1716
|
+
write: "written",
|
|
1717
|
+
record: "recorded",
|
|
1718
|
+
store: "stored"
|
|
1719
|
+
};
|
|
1720
|
+
const actionKey = action.split("-")[0] ?? action;
|
|
1721
|
+
const verb = verbMap[actionKey] ?? `${action.replace(/[-_]+/g, " ")} completed`;
|
|
1722
|
+
return status === "success" ? `${entity} ${verb}` : `${entity} ${action.replace(/[-_]+/g, " ")} failed`;
|
|
1723
|
+
}
|
|
1724
|
+
function shouldAppendDurableGatewayActivity(tool, args) {
|
|
1725
|
+
const normalizedToolId = normalizeToolId(tool.toolId);
|
|
1726
|
+
if (!normalizedToolId) return false;
|
|
1727
|
+
if (args.preview === true) return false;
|
|
1728
|
+
if (normalizedToolId.startsWith("agentspace.activity-item.")) return false;
|
|
1729
|
+
if (args.apply !== true && args.confirm !== true) return false;
|
|
1730
|
+
return MUTATING_ACTIVITY_OPERATION_PATTERN.test(normalizedToolId) || MUTATING_ACTIVITY_OPERATION_PATTERN.test(String(tool.operationId ?? "").toLowerCase());
|
|
1731
|
+
}
|
|
1732
|
+
function resolveGatewayActivitySourceKind(context) {
|
|
1733
|
+
const candidate = normalizeNonEmpty(context?.activitySourceKind);
|
|
1734
|
+
if (candidate === "aops-cli" || candidate === "desktop" || candidate === "runner" || candidate === "system") {
|
|
1735
|
+
return candidate;
|
|
1736
|
+
}
|
|
1737
|
+
return "system";
|
|
1738
|
+
}
|
|
1739
|
+
function cloneStringList(values) {
|
|
1740
|
+
if (!Array.isArray(values)) return void 0;
|
|
1741
|
+
const normalized = values.map((value) => String(value ?? "").trim()).filter(Boolean);
|
|
1742
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
1743
|
+
}
|
|
1744
|
+
function mergeStringLists(...lists) {
|
|
1745
|
+
const values = /* @__PURE__ */ new Set();
|
|
1746
|
+
for (const list of lists) {
|
|
1747
|
+
for (const entry of list ?? []) {
|
|
1748
|
+
const normalized = String(entry ?? "").trim();
|
|
1749
|
+
if (!normalized) continue;
|
|
1750
|
+
values.add(normalized);
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
return values.size > 0 ? [...values] : void 0;
|
|
1754
|
+
}
|
|
1755
|
+
function toOperationDocs(value) {
|
|
1756
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1757
|
+
const input = value;
|
|
1758
|
+
const summary = typeof input.summary === "string" && input.summary.trim().length > 0 ? input.summary.trim() : void 0;
|
|
1759
|
+
const notes = cloneStringList(input.notes);
|
|
1760
|
+
const examples = cloneStringList(input.examples);
|
|
1761
|
+
const antiPatterns = cloneStringList(input.antiPatterns);
|
|
1762
|
+
const preconditions = cloneStringList(input.preconditions);
|
|
1763
|
+
const postconditions = cloneStringList(input.postconditions);
|
|
1764
|
+
const docs = {
|
|
1765
|
+
...summary ? { summary } : {},
|
|
1766
|
+
...notes ? { notes } : {},
|
|
1767
|
+
...examples ? { examples } : {},
|
|
1768
|
+
...antiPatterns ? { antiPatterns } : {},
|
|
1769
|
+
...preconditions ? { preconditions } : {},
|
|
1770
|
+
...postconditions ? { postconditions } : {}
|
|
1771
|
+
};
|
|
1772
|
+
return Object.keys(docs).length > 0 ? docs : null;
|
|
1773
|
+
}
|
|
1774
|
+
function toDomainDocs(value) {
|
|
1775
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1776
|
+
const input = value;
|
|
1777
|
+
const summary = typeof input.summary === "string" && input.summary.trim().length > 0 ? input.summary.trim() : void 0;
|
|
1778
|
+
const notes = cloneStringList(input.notes);
|
|
1779
|
+
const docs = {
|
|
1780
|
+
...summary ? { summary } : {},
|
|
1781
|
+
...notes ? { notes } : {}
|
|
1782
|
+
};
|
|
1783
|
+
return Object.keys(docs).length > 0 ? docs : null;
|
|
1784
|
+
}
|
|
1785
|
+
function buildDomainMetadataByDomain(snapshot, domain) {
|
|
1786
|
+
const normalizedDomain = normalizeDomain(domain ?? "");
|
|
1787
|
+
const metadataByDomain = {};
|
|
1788
|
+
for (const manifest of snapshot.manifests) {
|
|
1789
|
+
const manifestDomain = normalizeDomain(manifest.domain.id);
|
|
1790
|
+
if (!manifestDomain) continue;
|
|
1791
|
+
if (normalizedDomain.length > 0 && manifestDomain !== normalizedDomain) continue;
|
|
1792
|
+
const existing = metadataByDomain[manifestDomain];
|
|
1793
|
+
const domainDocs = toDomainDocs(manifest.docs?.domain);
|
|
1794
|
+
const resources = manifest.capabilities.resources ?? [];
|
|
1795
|
+
const resourceDocsMap = manifest.docs?.resources && typeof manifest.docs.resources === "object" && !Array.isArray(manifest.docs.resources) ? manifest.docs.resources : {};
|
|
1796
|
+
const mergedResources = new Map(
|
|
1797
|
+
(existing?.resources ?? []).map((resource) => [normalizeDomain(resource.resourceId), resource])
|
|
1798
|
+
);
|
|
1799
|
+
for (const resource of resources) {
|
|
1800
|
+
const resourceId = String(resource.resourceId ?? "").trim().toLowerCase();
|
|
1801
|
+
if (!resourceId) continue;
|
|
1802
|
+
const docEntryRaw = resourceDocsMap[resource.resourceId] ?? resourceDocsMap[resourceId];
|
|
1803
|
+
const docEntry = toDomainDocs(docEntryRaw);
|
|
1804
|
+
const current = mergedResources.get(resourceId);
|
|
1805
|
+
const title = current?.title ?? (String(resource.title ?? "").trim() || resourceId);
|
|
1806
|
+
const kind = current?.kind ?? (typeof resource.kind === "string" && resource.kind.trim().length > 0 ? resource.kind.trim() : void 0);
|
|
1807
|
+
const schemaRef = current?.schemaRef ?? (typeof resource.schemaRef === "string" && resource.schemaRef.trim().length > 0 ? resource.schemaRef.trim() : void 0);
|
|
1808
|
+
mergedResources.set(resourceId, {
|
|
1809
|
+
resourceId,
|
|
1810
|
+
title,
|
|
1811
|
+
kind,
|
|
1812
|
+
schemaRef,
|
|
1813
|
+
summary: current?.summary ?? docEntry?.summary,
|
|
1814
|
+
notes: mergeStringLists(current?.notes, docEntry?.notes)
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
const description = existing?.description ?? (typeof manifest.domain.description === "string" && manifest.domain.description.trim().length > 0 ? manifest.domain.description.trim() : void 0);
|
|
1818
|
+
const summary = existing?.summary ?? domainDocs?.summary ?? description;
|
|
1819
|
+
const notes = mergeStringLists(existing?.notes, domainDocs?.notes);
|
|
1820
|
+
metadataByDomain[manifestDomain] = {
|
|
1821
|
+
domain: manifestDomain,
|
|
1822
|
+
displayName: existing?.displayName ?? (typeof manifest.domain.displayName === "string" && manifest.domain.displayName.trim().length > 0 ? manifest.domain.displayName.trim() : void 0),
|
|
1823
|
+
...description ? { description } : {},
|
|
1824
|
+
...summary ? { summary } : {},
|
|
1825
|
+
...notes ? { notes } : {},
|
|
1826
|
+
...mergedResources.size > 0 ? {
|
|
1827
|
+
resources: [...mergedResources.values()].sort((left, right) => left.title.localeCompare(right.title))
|
|
1828
|
+
} : {}
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
return Object.keys(metadataByDomain).length > 0 ? metadataByDomain : void 0;
|
|
1832
|
+
}
|
|
1833
|
+
function buildOperationDocsByOperationId(snapshot, domain) {
|
|
1834
|
+
const normalizedDomain = normalizeDomain(domain ?? "");
|
|
1835
|
+
const docsByOperationId = {};
|
|
1836
|
+
for (const manifest of snapshot.manifests) {
|
|
1837
|
+
if (normalizedDomain.length > 0 && normalizeDomain(manifest.domain.id) !== normalizedDomain) continue;
|
|
1838
|
+
const operations = manifest.docs?.operations;
|
|
1839
|
+
if (!operations || typeof operations !== "object" || Array.isArray(operations)) continue;
|
|
1840
|
+
for (const [operationIdRaw, value] of Object.entries(operations)) {
|
|
1841
|
+
const operationId = String(operationIdRaw ?? "").trim().toLowerCase();
|
|
1842
|
+
if (!operationId) continue;
|
|
1843
|
+
const docs = toOperationDocs(value);
|
|
1844
|
+
if (!docs) continue;
|
|
1845
|
+
docsByOperationId[operationId] = docs;
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return Object.keys(docsByOperationId).length > 0 ? docsByOperationId : void 0;
|
|
1849
|
+
}
|
|
1850
|
+
function buildListResult(snapshot, domain) {
|
|
1851
|
+
const normalizedDomain = normalizeDomain(domain ?? "");
|
|
1852
|
+
const tools = normalizedDomain.length > 0 ? snapshot.catalog.tools.filter((tool) => normalizeDomain(tool.domain) === normalizedDomain) : snapshot.catalog.tools;
|
|
1853
|
+
const operationDocsByOperationId = buildOperationDocsByOperationId(snapshot, domain);
|
|
1854
|
+
const domainMetadataByDomain = buildDomainMetadataByDomain(snapshot, domain);
|
|
1855
|
+
return {
|
|
1856
|
+
catalogVersion: snapshot.catalog.catalogVersion,
|
|
1857
|
+
generatedAt: snapshot.catalog.generatedAt,
|
|
1858
|
+
tools,
|
|
1859
|
+
errors: snapshot.errors,
|
|
1860
|
+
...operationDocsByOperationId ? { operationDocsByOperationId } : {},
|
|
1861
|
+
...domainMetadataByDomain ? { domainMetadataByDomain } : {}
|
|
1862
|
+
};
|
|
1863
|
+
}
|
|
1864
|
+
function findToolInputJsonSchemaInManifests(snapshot, tool) {
|
|
1865
|
+
const inputSchemaRef = typeof tool.inputSchemaRef === "string" ? tool.inputSchemaRef.trim() : "";
|
|
1866
|
+
if (!inputSchemaRef) return null;
|
|
1867
|
+
const toolDomain = normalizeDomain(tool.domain);
|
|
1868
|
+
for (const manifest of snapshot.manifests) {
|
|
1869
|
+
if (normalizeDomain(manifest.domain.id) !== toolDomain) continue;
|
|
1870
|
+
const schema = manifest.contracts?.schemas?.[inputSchemaRef];
|
|
1871
|
+
if (schema && typeof schema === "object" && !Array.isArray(schema)) {
|
|
1872
|
+
return schema;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
function buildToolResponse(tool, source, status, data, headers) {
|
|
1878
|
+
return {
|
|
1879
|
+
tool: {
|
|
1880
|
+
...tool,
|
|
1881
|
+
sourceId: source.id,
|
|
1882
|
+
sourceKind: source.kind,
|
|
1883
|
+
sourceBaseUrl: source.baseUrl,
|
|
1884
|
+
sourceApiBasePath: source.apiBasePath
|
|
1885
|
+
},
|
|
1886
|
+
status,
|
|
1887
|
+
data,
|
|
1888
|
+
headers
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
function isSuccessfulInvokeResult(result) {
|
|
1892
|
+
if (result.status >= 400) return false;
|
|
1893
|
+
if (!result.data || typeof result.data !== "object" || Array.isArray(result.data)) return true;
|
|
1894
|
+
return result.data.ok !== false;
|
|
1895
|
+
}
|
|
1896
|
+
function appendPreviewQueryValue(target, key, value) {
|
|
1897
|
+
const existing = target[key];
|
|
1898
|
+
if (existing === void 0) {
|
|
1899
|
+
target[key] = value;
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
if (Array.isArray(existing)) {
|
|
1903
|
+
existing.push(value);
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
target[key] = [existing, value];
|
|
1907
|
+
}
|
|
1908
|
+
function toPreviewQueryObject(query) {
|
|
1909
|
+
const out = {};
|
|
1910
|
+
for (const [key, value] of query.entries()) {
|
|
1911
|
+
appendPreviewQueryValue(out, key, value);
|
|
1912
|
+
}
|
|
1913
|
+
return out;
|
|
1914
|
+
}
|
|
1915
|
+
function buildPreviewHeaders(context) {
|
|
1916
|
+
const headers = {};
|
|
1917
|
+
if (typeof context?.tenantId === "string" && context.tenantId.trim().length > 0) {
|
|
1918
|
+
headers["x-tenant-id"] = context.tenantId.trim();
|
|
1919
|
+
}
|
|
1920
|
+
if (typeof context?.locale === "string" && context.locale.trim().length > 0) {
|
|
1921
|
+
headers["x-locale"] = context.locale.trim();
|
|
1922
|
+
}
|
|
1923
|
+
if (typeof context?.fallbackLocale === "string" && context.fallbackLocale.trim().length > 0) {
|
|
1924
|
+
headers["x-fallback-locale"] = context.fallbackLocale.trim();
|
|
1925
|
+
}
|
|
1926
|
+
if (typeof context?.projectId === "string" && context.projectId.trim().length > 0) {
|
|
1927
|
+
headers["x-project-id"] = context.projectId.trim();
|
|
1928
|
+
}
|
|
1929
|
+
return headers;
|
|
1930
|
+
}
|
|
1931
|
+
async function appendDurableGatewayActivityBestEffort(params) {
|
|
1932
|
+
const scopeId = normalizeNonEmpty(params.args.context?.scopeId);
|
|
1933
|
+
const projectId = normalizeNonEmpty(params.args.context?.projectId);
|
|
1934
|
+
if (!scopeId && !projectId) return;
|
|
1935
|
+
const resolvedScopeId = scopeId || projectId;
|
|
1936
|
+
const refs = [];
|
|
1937
|
+
maybePushActivityRef(refs, "scope", resolvedScopeId);
|
|
1938
|
+
maybePushActivityRef(refs, "project", projectId);
|
|
1939
|
+
collectActivityRefs(params.args.input, refs);
|
|
1940
|
+
collectActivityRefs(params.responseData, refs);
|
|
1941
|
+
const requestHeaders = new Headers();
|
|
1942
|
+
const tenantId = normalizeNonEmpty(params.args.context?.tenantId);
|
|
1943
|
+
if (tenantId) requestHeaders.set("x-tenant-id", tenantId);
|
|
1944
|
+
const url = new URL("https://xf-host.local/api/agentspace/operations/activity-item/add-activity-item");
|
|
1945
|
+
const payload = params.status === "error" ? {
|
|
1946
|
+
input: params.args.input,
|
|
1947
|
+
error: params.errorMessage ? { message: params.errorMessage } : void 0
|
|
1948
|
+
} : {
|
|
1949
|
+
input: params.args.input,
|
|
1950
|
+
result: params.responseData
|
|
1951
|
+
};
|
|
1952
|
+
try {
|
|
1953
|
+
await params.registry.ensureSetup("agentspace");
|
|
1954
|
+
await dispatchDomainRequest({
|
|
1955
|
+
registry: params.registry,
|
|
1956
|
+
request: {
|
|
1957
|
+
method: "POST",
|
|
1958
|
+
domain: "agentspace",
|
|
1959
|
+
path: ["operations", "activity-item", "add-activity-item"],
|
|
1960
|
+
query: new URLSearchParams(),
|
|
1961
|
+
body: {
|
|
1962
|
+
data: {
|
|
1963
|
+
scopeId: resolvedScopeId,
|
|
1964
|
+
projectId,
|
|
1965
|
+
sourceKind: resolveGatewayActivitySourceKind(params.args.context),
|
|
1966
|
+
sourceId: params.tool.toolId,
|
|
1967
|
+
action: resolveGatewayActivityAction(params.tool),
|
|
1968
|
+
status: params.status,
|
|
1969
|
+
summary: summarizeGatewayActivity(params.tool, params.status),
|
|
1970
|
+
refs,
|
|
1971
|
+
payload: sanitizeActivityPayload(payload)
|
|
1972
|
+
}
|
|
1973
|
+
},
|
|
1974
|
+
headers: requestHeaders,
|
|
1975
|
+
url,
|
|
1976
|
+
context: {
|
|
1977
|
+
...params.args.context ?? {},
|
|
1978
|
+
scopeId: resolvedScopeId,
|
|
1979
|
+
...projectId ? { projectId } : {}
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
});
|
|
1983
|
+
} catch {
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function buildPreflightInvokeBody(args, requirements, nextIdempotencyKey) {
|
|
1987
|
+
return {
|
|
1988
|
+
...args.sourceId ? { sourceId: args.sourceId } : {},
|
|
1989
|
+
input: args.input ?? {},
|
|
1990
|
+
...nextIdempotencyKey ? { idempotencyKey: nextIdempotencyKey } : {},
|
|
1991
|
+
...requirements.applyRequired ? { apply: true } : {},
|
|
1992
|
+
...requirements.confirmationRequired ? { confirm: true } : {}
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
function buildToolInvokePreviewPayload(params) {
|
|
1996
|
+
const semantic = buildAgentSemanticPreview({
|
|
1997
|
+
domain: params.tool.domain,
|
|
1998
|
+
operationId: params.tool.operationId,
|
|
1999
|
+
input: params.parsedInput.body ?? params.args.input ?? {}
|
|
2000
|
+
});
|
|
2001
|
+
const requirements = resolveToolInvokeRequirements({
|
|
2002
|
+
tool: params.tool,
|
|
2003
|
+
context: params.context,
|
|
2004
|
+
apply: params.args.apply,
|
|
2005
|
+
confirm: params.args.confirm
|
|
2006
|
+
});
|
|
2007
|
+
const governance = buildInvokeGovernancePreview(
|
|
2008
|
+
{
|
|
2009
|
+
tool: params.tool,
|
|
2010
|
+
source: params.source,
|
|
2011
|
+
args: params.args
|
|
2012
|
+
},
|
|
2013
|
+
requirements,
|
|
2014
|
+
{
|
|
2015
|
+
recommendedKey: semantic?.idempotency?.recommendedKey,
|
|
2016
|
+
recommendedKeySource: semantic?.idempotency ? "domain-natural-key" : void 0
|
|
2017
|
+
}
|
|
2018
|
+
);
|
|
2019
|
+
const path2 = `/api/${normalizeDomain(params.tool.domain)}${params.pathSegments.length > 0 ? `/${params.pathSegments.join("/")}` : ""}`;
|
|
2020
|
+
const headers = buildPreviewHeaders(params.context);
|
|
2021
|
+
return {
|
|
2022
|
+
ok: true,
|
|
2023
|
+
preview: true,
|
|
2024
|
+
mode: "preflight",
|
|
2025
|
+
ready: requirements.authSatisfied && requirements.scopeSatisfied && requirements.applySatisfied && requirements.confirmationSatisfied,
|
|
2026
|
+
tool: {
|
|
2027
|
+
toolId: params.tool.toolId,
|
|
2028
|
+
domain: params.tool.domain,
|
|
2029
|
+
operationId: params.tool.operationId,
|
|
2030
|
+
sourceId: params.source.id,
|
|
2031
|
+
sourceKind: params.source.kind
|
|
2032
|
+
},
|
|
2033
|
+
route: {
|
|
2034
|
+
method: params.route.method,
|
|
2035
|
+
pattern: params.route.pattern,
|
|
2036
|
+
path: path2,
|
|
2037
|
+
...params.requestUrl ? { requestUrl: params.requestUrl } : {}
|
|
2038
|
+
},
|
|
2039
|
+
requirements: {
|
|
2040
|
+
auth: {
|
|
2041
|
+
required: requirements.authRequired,
|
|
2042
|
+
principalPresent: requirements.principalPresent,
|
|
2043
|
+
satisfied: requirements.authSatisfied,
|
|
2044
|
+
...requirements.rolesRequired.length > 0 ? { roles: requirements.rolesRequired } : {},
|
|
2045
|
+
...requirements.capabilitiesRequired.length > 0 ? { capabilities: requirements.capabilitiesRequired } : {}
|
|
2046
|
+
},
|
|
2047
|
+
scope: {
|
|
2048
|
+
required: requirements.scopeRequired,
|
|
2049
|
+
satisfied: requirements.scopeSatisfied,
|
|
2050
|
+
...typeof params.context.scopeId === "string" && params.context.scopeId.trim().length > 0 ? { scopeId: params.context.scopeId.trim() } : typeof params.context.projectId === "string" && params.context.projectId.trim().length > 0 ? { projectId: params.context.projectId.trim() } : {}
|
|
2051
|
+
},
|
|
2052
|
+
apply: {
|
|
2053
|
+
required: requirements.applyRequired,
|
|
2054
|
+
satisfied: requirements.applySatisfied
|
|
2055
|
+
},
|
|
2056
|
+
confirm: {
|
|
2057
|
+
required: requirements.confirmationRequired,
|
|
2058
|
+
satisfied: requirements.confirmationSatisfied
|
|
2059
|
+
}
|
|
2060
|
+
},
|
|
2061
|
+
policy: params.tool.policy ?? null,
|
|
2062
|
+
audit: governance.audit,
|
|
2063
|
+
approval: governance.approval,
|
|
2064
|
+
idempotency: governance.idempotency,
|
|
2065
|
+
...semantic ? { semantic } : {},
|
|
2066
|
+
normalizedInvoke: {
|
|
2067
|
+
pathParams: { ...params.parsedInput.pathParams },
|
|
2068
|
+
query: toPreviewQueryObject(params.parsedInput.query),
|
|
2069
|
+
body: params.parsedInput.body ?? null,
|
|
2070
|
+
context: params.context,
|
|
2071
|
+
headers
|
|
2072
|
+
},
|
|
2073
|
+
nextInvoke: {
|
|
2074
|
+
headers,
|
|
2075
|
+
body: buildPreflightInvokeBody(params.args, requirements, governance.nextIdempotencyKey)
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
}
|
|
2079
|
+
function createAgentGateway(options) {
|
|
2080
|
+
const config = resolveGatewayConfig(options.config);
|
|
2081
|
+
let catalogSnapshotPromise = null;
|
|
2082
|
+
let latestCatalogSnapshot = null;
|
|
2083
|
+
function normalizeErrorMessage(error) {
|
|
2084
|
+
return error instanceof Error ? error.message : String(error ?? "unknown_error");
|
|
2085
|
+
}
|
|
2086
|
+
function shouldRefreshCatalogForError(error) {
|
|
2087
|
+
const message = normalizeErrorMessage(error);
|
|
2088
|
+
return CATALOG_REFRESH_ERROR_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
2089
|
+
}
|
|
2090
|
+
function shouldRefreshLocalRuntimeForError(error) {
|
|
2091
|
+
const message = normalizeErrorMessage(error);
|
|
2092
|
+
return LOCAL_RUNTIME_REFRESH_ERROR_PREFIXES.some((prefix) => message.startsWith(prefix));
|
|
2093
|
+
}
|
|
2094
|
+
function invalidateCatalogSnapshot() {
|
|
2095
|
+
catalogSnapshotPromise = null;
|
|
2096
|
+
latestCatalogSnapshot = null;
|
|
2097
|
+
resetToolSchemaValidationCache();
|
|
2098
|
+
}
|
|
2099
|
+
async function getCatalogSnapshot(params = {}) {
|
|
2100
|
+
if (params.forceRefresh) invalidateCatalogSnapshot();
|
|
2101
|
+
if (catalogSnapshotPromise) return catalogSnapshotPromise;
|
|
2102
|
+
const snapshotPromise = buildGatewayCatalogSnapshot({
|
|
2103
|
+
config,
|
|
2104
|
+
configPath: options.configPath
|
|
2105
|
+
}).then((snapshot) => {
|
|
2106
|
+
latestCatalogSnapshot = snapshot;
|
|
2107
|
+
return snapshot;
|
|
2108
|
+
}).catch((error) => {
|
|
2109
|
+
if (catalogSnapshotPromise === snapshotPromise) {
|
|
2110
|
+
catalogSnapshotPromise = null;
|
|
2111
|
+
latestCatalogSnapshot = null;
|
|
2112
|
+
}
|
|
2113
|
+
throw error;
|
|
2114
|
+
});
|
|
2115
|
+
catalogSnapshotPromise = snapshotPromise;
|
|
2116
|
+
return catalogSnapshotPromise;
|
|
2117
|
+
}
|
|
2118
|
+
async function refreshCatalogSnapshot() {
|
|
2119
|
+
return getCatalogSnapshot({ forceRefresh: true });
|
|
2120
|
+
}
|
|
2121
|
+
async function refreshLocalRuntimeSnapshot() {
|
|
2122
|
+
options.registryResetter?.();
|
|
2123
|
+
return refreshCatalogSnapshot();
|
|
2124
|
+
}
|
|
2125
|
+
function getDiagnostics() {
|
|
2126
|
+
return {
|
|
2127
|
+
enabled: config.enabled !== false,
|
|
2128
|
+
snapshotLoaded: latestCatalogSnapshot !== null,
|
|
2129
|
+
loadedAt: latestCatalogSnapshot?.loadedAt,
|
|
2130
|
+
catalogVersion: latestCatalogSnapshot?.catalog.catalogVersion,
|
|
2131
|
+
generatedAt: latestCatalogSnapshot?.catalog.generatedAt,
|
|
2132
|
+
toolCount: latestCatalogSnapshot?.catalog.tools.length ?? 0,
|
|
2133
|
+
errorCount: latestCatalogSnapshot?.errors.length ?? 0
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
async function listTools(domain) {
|
|
2137
|
+
if (config.enabled === false) {
|
|
2138
|
+
return { catalogVersion: "1.0.0", generatedAt: (/* @__PURE__ */ new Date()).toISOString(), tools: [], errors: [] };
|
|
2139
|
+
}
|
|
2140
|
+
const snapshot = await getCatalogSnapshot();
|
|
2141
|
+
return buildListResult(snapshot, domain);
|
|
2142
|
+
}
|
|
2143
|
+
async function getToolInputJsonSchema(toolId) {
|
|
2144
|
+
if (config.enabled === false) return null;
|
|
2145
|
+
const normalized = normalizeToolId(toolId);
|
|
2146
|
+
const snapshot = await getCatalogSnapshot();
|
|
2147
|
+
const tool = snapshot.catalog.tools.find((entry) => normalizeToolId(entry.toolId) === normalized);
|
|
2148
|
+
if (!tool) return null;
|
|
2149
|
+
const manifestSchema = findToolInputJsonSchemaInManifests(snapshot, tool);
|
|
2150
|
+
if (manifestSchema) return manifestSchema;
|
|
2151
|
+
const registry = await options.registryResolver();
|
|
2152
|
+
const plugin = registry.get(normalizeDomain(tool.domain));
|
|
2153
|
+
if (!plugin) return null;
|
|
2154
|
+
const route = findDomainRouteByOperation(plugin.manifest.routes, tool.operationId);
|
|
2155
|
+
if (!route) return null;
|
|
2156
|
+
const schema = route.inputJsonSchema;
|
|
2157
|
+
return schema && typeof schema === "object" && !Array.isArray(schema) ? schema : null;
|
|
2158
|
+
}
|
|
2159
|
+
async function invokeLocalSource(snapshot, tool, source, args, invokeOptions) {
|
|
2160
|
+
const registry = await options.registryResolver();
|
|
2161
|
+
const domain = normalizeDomain(tool.domain);
|
|
2162
|
+
const plugin = registry.get(domain);
|
|
2163
|
+
if (!plugin) {
|
|
2164
|
+
throw new Error(`domain_not_registered:${domain}`);
|
|
2165
|
+
}
|
|
2166
|
+
await registry.ensureSetup(domain);
|
|
2167
|
+
const route = findDomainRouteByOperation(plugin.manifest.routes, tool.operationId);
|
|
2168
|
+
if (!route) {
|
|
2169
|
+
throw new Error(`operation_route_not_found:${domain}:${tool.operationId}`);
|
|
2170
|
+
}
|
|
2171
|
+
const parsedInput = normalizeInvokeInputForRoute({
|
|
2172
|
+
parsedInput: parseRouteInvokeInput(args.input),
|
|
2173
|
+
routePattern: route.pattern,
|
|
2174
|
+
routeMethod: route.method,
|
|
2175
|
+
tool,
|
|
2176
|
+
manifests: snapshot.manifests
|
|
2177
|
+
});
|
|
2178
|
+
const context = { ...parsedInput.context, ...args.context ?? {} };
|
|
2179
|
+
const authValidation = validateToolInvokeAuthorization({
|
|
2180
|
+
tool,
|
|
2181
|
+
context
|
|
2182
|
+
});
|
|
2183
|
+
if (!authValidation.ok && args.preview !== true) {
|
|
2184
|
+
throw new Error(authValidation.message);
|
|
2185
|
+
}
|
|
2186
|
+
const scopeValidation = validateToolInvokeScope({
|
|
2187
|
+
tool,
|
|
2188
|
+
context
|
|
2189
|
+
});
|
|
2190
|
+
if (!scopeValidation.ok && args.preview !== true) {
|
|
2191
|
+
throw new Error(scopeValidation.message);
|
|
2192
|
+
}
|
|
2193
|
+
const safetyValidation = validateToolInvokeSafety({
|
|
2194
|
+
tool,
|
|
2195
|
+
apply: args.apply,
|
|
2196
|
+
confirm: args.confirm
|
|
2197
|
+
});
|
|
2198
|
+
if (!safetyValidation.ok && args.preview !== true) {
|
|
2199
|
+
throw new Error(safetyValidation.message);
|
|
2200
|
+
}
|
|
2201
|
+
const contractValidation = validateToolInputByContract({
|
|
2202
|
+
tool,
|
|
2203
|
+
manifests: snapshot.manifests,
|
|
2204
|
+
parsedInput,
|
|
2205
|
+
context
|
|
2206
|
+
});
|
|
2207
|
+
if (!contractValidation.ok) {
|
|
2208
|
+
throw new Error(contractValidation.message);
|
|
2209
|
+
}
|
|
2210
|
+
const schemaValidation = validateToolInputBySchema({
|
|
2211
|
+
tool,
|
|
2212
|
+
manifests: snapshot.manifests,
|
|
2213
|
+
parsedInput
|
|
2214
|
+
});
|
|
2215
|
+
if (!schemaValidation.ok) {
|
|
2216
|
+
throw new Error(schemaValidation.message);
|
|
2217
|
+
}
|
|
2218
|
+
const pathSegments = patternToPathSegments(route.pattern, parsedInput.pathParams);
|
|
2219
|
+
const query = parsedInput.query;
|
|
2220
|
+
const url = new URL(`https://xf-host.local/api/${domain}${pathSegments.length > 0 ? `/${pathSegments.join("/")}` : ""}`);
|
|
2221
|
+
for (const [key, value] of query.entries()) {
|
|
2222
|
+
url.searchParams.append(key, value);
|
|
2223
|
+
}
|
|
2224
|
+
if (args.preview === true) {
|
|
2225
|
+
return buildToolResponse(
|
|
2226
|
+
tool,
|
|
2227
|
+
source,
|
|
2228
|
+
200,
|
|
2229
|
+
buildToolInvokePreviewPayload({
|
|
2230
|
+
tool,
|
|
2231
|
+
source,
|
|
2232
|
+
route,
|
|
2233
|
+
pathSegments,
|
|
2234
|
+
parsedInput,
|
|
2235
|
+
context,
|
|
2236
|
+
args
|
|
2237
|
+
})
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
const requestHeaders = new Headers();
|
|
2241
|
+
if (typeof invokeOptions.effectiveIdempotencyKey === "string" && invokeOptions.effectiveIdempotencyKey.length > 0) {
|
|
2242
|
+
requestHeaders.set("x-idempotency-key", invokeOptions.effectiveIdempotencyKey);
|
|
2243
|
+
}
|
|
2244
|
+
const response = await dispatchDomainRequest({
|
|
2245
|
+
registry,
|
|
2246
|
+
request: {
|
|
2247
|
+
method: route.method,
|
|
2248
|
+
domain,
|
|
2249
|
+
path: pathSegments,
|
|
2250
|
+
query,
|
|
2251
|
+
body: parsedInput.body,
|
|
2252
|
+
headers: requestHeaders,
|
|
2253
|
+
url,
|
|
2254
|
+
context
|
|
2255
|
+
}
|
|
2256
|
+
});
|
|
2257
|
+
return buildToolResponse(tool, source, response.status, response.data, response.headers);
|
|
2258
|
+
}
|
|
2259
|
+
async function invokeRemoteSource(snapshot, tool, source, args, invokeOptions) {
|
|
2260
|
+
const remoteSource = config.sources.find(
|
|
2261
|
+
(candidate) => candidate.enabled && candidate.id === source.id && normalizeDomain(candidate.domain) === normalizeDomain(tool.domain)
|
|
2262
|
+
);
|
|
2263
|
+
if (!remoteSource) {
|
|
2264
|
+
throw new Error(`tool_source_not_found:${source.id}`);
|
|
2265
|
+
}
|
|
2266
|
+
const routes = await fetchRemoteRoutes(remoteSource);
|
|
2267
|
+
const route = findRemoteRouteByOperation(routes, tool.operationId);
|
|
2268
|
+
if (!route) {
|
|
2269
|
+
throw new Error(`operation_route_not_found:${tool.domain}:${tool.operationId}`);
|
|
2270
|
+
}
|
|
2271
|
+
const parsedInput = normalizeInvokeInputForRoute({
|
|
2272
|
+
parsedInput: parseRouteInvokeInput(args.input),
|
|
2273
|
+
routePattern: route.pattern,
|
|
2274
|
+
routeMethod: route.method,
|
|
2275
|
+
tool,
|
|
2276
|
+
manifests: snapshot.manifests
|
|
2277
|
+
});
|
|
2278
|
+
const mergedContext = { ...parsedInput.context, ...args.context ?? {} };
|
|
2279
|
+
const authValidation = validateToolInvokeAuthorization({
|
|
2280
|
+
tool,
|
|
2281
|
+
context: mergedContext
|
|
2282
|
+
});
|
|
2283
|
+
if (!authValidation.ok && args.preview !== true) {
|
|
2284
|
+
throw new Error(authValidation.message);
|
|
2285
|
+
}
|
|
2286
|
+
const scopeValidation = validateToolInvokeScope({
|
|
2287
|
+
tool,
|
|
2288
|
+
context: mergedContext
|
|
2289
|
+
});
|
|
2290
|
+
if (!scopeValidation.ok && args.preview !== true) {
|
|
2291
|
+
throw new Error(scopeValidation.message);
|
|
2292
|
+
}
|
|
2293
|
+
const safetyValidation = validateToolInvokeSafety({
|
|
2294
|
+
tool,
|
|
2295
|
+
apply: args.apply,
|
|
2296
|
+
confirm: args.confirm
|
|
2297
|
+
});
|
|
2298
|
+
if (!safetyValidation.ok && args.preview !== true) {
|
|
2299
|
+
throw new Error(safetyValidation.message);
|
|
2300
|
+
}
|
|
2301
|
+
const contractValidation = validateToolInputByContract({
|
|
2302
|
+
tool,
|
|
2303
|
+
manifests: snapshot.manifests,
|
|
2304
|
+
parsedInput,
|
|
2305
|
+
context: mergedContext
|
|
2306
|
+
});
|
|
2307
|
+
if (!contractValidation.ok) {
|
|
2308
|
+
throw new Error(contractValidation.message);
|
|
2309
|
+
}
|
|
2310
|
+
const schemaValidation = validateToolInputBySchema({
|
|
2311
|
+
tool,
|
|
2312
|
+
manifests: snapshot.manifests,
|
|
2313
|
+
parsedInput
|
|
2314
|
+
});
|
|
2315
|
+
if (!schemaValidation.ok) {
|
|
2316
|
+
throw new Error(schemaValidation.message);
|
|
2317
|
+
}
|
|
2318
|
+
const pathSegments = patternToPathSegments(route.pattern, parsedInput.pathParams);
|
|
2319
|
+
const baseUrl = normalizeBaseUrl(remoteSource.baseUrl);
|
|
2320
|
+
const apiBasePath = normalizeApiBasePath(remoteSource.apiBasePath);
|
|
2321
|
+
const requestUrl = new URL(
|
|
2322
|
+
`${baseUrl}${apiBasePath}/${remoteSource.domain}${pathSegments.length > 0 ? `/${pathSegments.join("/")}` : ""}`
|
|
2323
|
+
);
|
|
2324
|
+
for (const [key, value] of parsedInput.query.entries()) {
|
|
2325
|
+
requestUrl.searchParams.append(key, value);
|
|
2326
|
+
}
|
|
2327
|
+
if (args.preview === true) {
|
|
2328
|
+
return buildToolResponse(
|
|
2329
|
+
tool,
|
|
2330
|
+
source,
|
|
2331
|
+
200,
|
|
2332
|
+
buildToolInvokePreviewPayload({
|
|
2333
|
+
tool,
|
|
2334
|
+
source,
|
|
2335
|
+
route,
|
|
2336
|
+
pathSegments,
|
|
2337
|
+
parsedInput,
|
|
2338
|
+
context: mergedContext,
|
|
2339
|
+
args,
|
|
2340
|
+
requestUrl: requestUrl.toString()
|
|
2341
|
+
})
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
const headers = resolveSourceHeaders(remoteSource);
|
|
2345
|
+
if (typeof mergedContext.tenantId === "string" && mergedContext.tenantId.trim().length > 0) {
|
|
2346
|
+
headers["x-tenant-id"] = mergedContext.tenantId;
|
|
2347
|
+
}
|
|
2348
|
+
if (typeof mergedContext.locale === "string" && mergedContext.locale.trim().length > 0) {
|
|
2349
|
+
headers["x-locale"] = mergedContext.locale;
|
|
2350
|
+
}
|
|
2351
|
+
if (typeof mergedContext.fallbackLocale === "string" && mergedContext.fallbackLocale.trim().length > 0) {
|
|
2352
|
+
headers["x-fallback-locale"] = mergedContext.fallbackLocale;
|
|
2353
|
+
}
|
|
2354
|
+
if (typeof mergedContext.projectId === "string" && mergedContext.projectId.trim().length > 0) {
|
|
2355
|
+
headers["x-project-id"] = mergedContext.projectId.trim();
|
|
2356
|
+
}
|
|
2357
|
+
if (typeof invokeOptions.effectiveIdempotencyKey === "string" && invokeOptions.effectiveIdempotencyKey.length > 0) {
|
|
2358
|
+
headers["x-idempotency-key"] = invokeOptions.effectiveIdempotencyKey;
|
|
2359
|
+
}
|
|
2360
|
+
const method = route.method.toUpperCase();
|
|
2361
|
+
let bodyPayload;
|
|
2362
|
+
if (method !== "GET" && method !== "HEAD" && parsedInput.body !== void 0) {
|
|
2363
|
+
headers["content-type"] = "application/json";
|
|
2364
|
+
bodyPayload = JSON.stringify(parsedInput.body);
|
|
2365
|
+
}
|
|
2366
|
+
const { controller, cleanup } = createTimeoutController(remoteSource.timeoutMs);
|
|
2367
|
+
try {
|
|
2368
|
+
const response = await fetch(requestUrl, {
|
|
2369
|
+
method,
|
|
2370
|
+
headers,
|
|
2371
|
+
body: bodyPayload,
|
|
2372
|
+
signal: controller.signal
|
|
2373
|
+
});
|
|
2374
|
+
const responseData = await parseFetchBody(response);
|
|
2375
|
+
return buildToolResponse(tool, source, response.status, responseData, pickResponseHeaders(response));
|
|
2376
|
+
} finally {
|
|
2377
|
+
cleanup();
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
async function resolveToolFromSnapshot(toolIdRaw) {
|
|
2381
|
+
const toolId = normalizeToolId(toolIdRaw);
|
|
2382
|
+
if (!toolId) throw new Error("tool_id_missing");
|
|
2383
|
+
let snapshot = await getCatalogSnapshot();
|
|
2384
|
+
let tool = snapshot.toolsById.get(toolId);
|
|
2385
|
+
if (tool) {
|
|
2386
|
+
return { snapshot, toolId, tool };
|
|
2387
|
+
}
|
|
2388
|
+
snapshot = await refreshCatalogSnapshot();
|
|
2389
|
+
tool = snapshot.toolsById.get(toolId);
|
|
2390
|
+
if (!tool) {
|
|
2391
|
+
throw new Error(`tool_not_found:${toolIdRaw}`);
|
|
2392
|
+
}
|
|
2393
|
+
return { snapshot, toolId, tool };
|
|
2394
|
+
}
|
|
2395
|
+
async function invokeResolvedTool(snapshot, tool, args, sourceId, options2) {
|
|
2396
|
+
const source = chooseSourceForInvocation(tool, sourceId);
|
|
2397
|
+
if (source.kind === "local-route") {
|
|
2398
|
+
try {
|
|
2399
|
+
return await invokeLocalSource(snapshot, tool, source, args, {
|
|
2400
|
+
effectiveIdempotencyKey: options2.effectiveIdempotencyKey
|
|
2401
|
+
});
|
|
2402
|
+
} catch (error) {
|
|
2403
|
+
if (options2.preferRefreshOnLocalMiss && shouldRefreshLocalRuntimeForError(error)) {
|
|
2404
|
+
throw error;
|
|
2405
|
+
}
|
|
2406
|
+
if (sourceId || !shouldFallbackFromLocalError(error)) {
|
|
2407
|
+
throw error;
|
|
2408
|
+
}
|
|
2409
|
+
const fallbackSource = findFirstRemoteSource(tool);
|
|
2410
|
+
if (!fallbackSource) {
|
|
2411
|
+
throw error;
|
|
2412
|
+
}
|
|
2413
|
+
return invokeRemoteSource(snapshot, tool, fallbackSource, args, {
|
|
2414
|
+
effectiveIdempotencyKey: options2.effectiveIdempotencyKey
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
return invokeRemoteSource(snapshot, tool, source, args, {
|
|
2419
|
+
effectiveIdempotencyKey: options2.effectiveIdempotencyKey
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
async function invokeTool(args) {
|
|
2423
|
+
if (config.enabled === false) {
|
|
2424
|
+
throw new Error("agent_gateway_disabled");
|
|
2425
|
+
}
|
|
2426
|
+
const startedAt = Date.now();
|
|
2427
|
+
const { snapshot, toolId, tool } = await resolveToolFromSnapshot(args.toolId);
|
|
2428
|
+
const sourceId = normalizeSourceId(args.sourceId);
|
|
2429
|
+
const requestedSource = chooseSourceForInvocation(tool, sourceId);
|
|
2430
|
+
const semanticRecommendedIdempotencyKey = resolveAgentSemanticIdempotencyKey({
|
|
2431
|
+
domain: tool.domain,
|
|
2432
|
+
operationId: tool.operationId,
|
|
2433
|
+
input: args.input ?? {}
|
|
2434
|
+
});
|
|
2435
|
+
const effectiveIdempotencyKey = typeof args.idempotencyKey === "string" && args.idempotencyKey.trim().length > 0 ? args.idempotencyKey.trim() : semanticRecommendedIdempotencyKey;
|
|
2436
|
+
const reservation = reserveInvokeIdempotency({
|
|
2437
|
+
tool,
|
|
2438
|
+
source: requestedSource,
|
|
2439
|
+
args: {
|
|
2440
|
+
...args,
|
|
2441
|
+
sourceId: requestedSource.id
|
|
2442
|
+
}
|
|
2443
|
+
}, {
|
|
2444
|
+
recommendedKey: semanticRecommendedIdempotencyKey,
|
|
2445
|
+
effectiveKey: effectiveIdempotencyKey
|
|
2446
|
+
});
|
|
2447
|
+
if (reservation.kind === "resolved") {
|
|
2448
|
+
await writeAgentInvokeAuditEvent({
|
|
2449
|
+
tool,
|
|
2450
|
+
requestedSourceId: requestedSource.id,
|
|
2451
|
+
effectiveSourceId: reservation.result.tool.sourceId,
|
|
2452
|
+
outcome: reservation.idempotencyStatus === "replayed" ? "replayed" : "blocked",
|
|
2453
|
+
status: reservation.result.status,
|
|
2454
|
+
durationMs: Date.now() - startedAt,
|
|
2455
|
+
context: args.context,
|
|
2456
|
+
apply: args.apply,
|
|
2457
|
+
confirm: args.confirm,
|
|
2458
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
2459
|
+
idempotencyStatus: reservation.idempotencyStatus,
|
|
2460
|
+
preview: args.preview,
|
|
2461
|
+
errorCode: reservation.idempotencyStatus === "replayed" ? void 0 : reservation.idempotencyStatus === "conflict" ? "idempotency_key_conflict" : "idempotency_in_progress"
|
|
2462
|
+
});
|
|
2463
|
+
return reservation.result;
|
|
2464
|
+
}
|
|
2465
|
+
try {
|
|
2466
|
+
let result;
|
|
2467
|
+
try {
|
|
2468
|
+
result = await invokeResolvedTool(snapshot, tool, args, sourceId, {
|
|
2469
|
+
preferRefreshOnLocalMiss: true,
|
|
2470
|
+
effectiveIdempotencyKey
|
|
2471
|
+
});
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
if (!shouldRefreshCatalogForError(error)) {
|
|
2474
|
+
throw error;
|
|
2475
|
+
}
|
|
2476
|
+
const refreshedSnapshot = shouldRefreshLocalRuntimeForError(error) ? await refreshLocalRuntimeSnapshot() : await refreshCatalogSnapshot();
|
|
2477
|
+
const refreshedTool = refreshedSnapshot.toolsById.get(toolId);
|
|
2478
|
+
if (!refreshedTool) {
|
|
2479
|
+
throw new Error(`tool_not_found:${args.toolId}`);
|
|
2480
|
+
}
|
|
2481
|
+
result = await invokeResolvedTool(refreshedSnapshot, refreshedTool, args, sourceId, {
|
|
2482
|
+
preferRefreshOnLocalMiss: false,
|
|
2483
|
+
effectiveIdempotencyKey
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
if (reservation.kind === "active") {
|
|
2487
|
+
reservation.settle(result);
|
|
2488
|
+
}
|
|
2489
|
+
const responseResult = reservation.kind === "active" && effectiveIdempotencyKey ? {
|
|
2490
|
+
...result,
|
|
2491
|
+
headers: {
|
|
2492
|
+
...result.headers ?? {},
|
|
2493
|
+
"x-agent-idempotency-status": "fresh",
|
|
2494
|
+
"x-agent-idempotency-key": effectiveIdempotencyKey
|
|
2495
|
+
}
|
|
2496
|
+
} : result;
|
|
2497
|
+
await writeAgentInvokeAuditEvent({
|
|
2498
|
+
tool,
|
|
2499
|
+
requestedSourceId: requestedSource.id,
|
|
2500
|
+
effectiveSourceId: responseResult.tool.sourceId,
|
|
2501
|
+
outcome: args.preview === true ? "preview" : isSuccessfulInvokeResult(responseResult) ? "success" : "failure",
|
|
2502
|
+
status: responseResult.status,
|
|
2503
|
+
durationMs: Date.now() - startedAt,
|
|
2504
|
+
context: args.context,
|
|
2505
|
+
apply: args.apply,
|
|
2506
|
+
confirm: args.confirm,
|
|
2507
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
2508
|
+
idempotencyStatus: reservation.kind === "active" && effectiveIdempotencyKey ? "fresh" : void 0,
|
|
2509
|
+
preview: args.preview
|
|
2510
|
+
});
|
|
2511
|
+
if (shouldAppendDurableGatewayActivity(tool, args)) {
|
|
2512
|
+
await appendDurableGatewayActivityBestEffort({
|
|
2513
|
+
registry: await options.registryResolver(),
|
|
2514
|
+
tool,
|
|
2515
|
+
args,
|
|
2516
|
+
status: isSuccessfulInvokeResult(responseResult) ? "success" : "error",
|
|
2517
|
+
responseData: responseResult.data
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
return responseResult;
|
|
2521
|
+
} catch (error) {
|
|
2522
|
+
if (reservation.kind === "active") {
|
|
2523
|
+
reservation.release();
|
|
2524
|
+
}
|
|
2525
|
+
await writeAgentInvokeAuditEvent({
|
|
2526
|
+
tool,
|
|
2527
|
+
requestedSourceId: requestedSource.id,
|
|
2528
|
+
effectiveSourceId: requestedSource.id,
|
|
2529
|
+
outcome: buildInvokeFailureStatus(error) === 409 ? "blocked" : "failure",
|
|
2530
|
+
status: buildInvokeFailureStatus(error),
|
|
2531
|
+
durationMs: Date.now() - startedAt,
|
|
2532
|
+
context: args.context,
|
|
2533
|
+
apply: args.apply,
|
|
2534
|
+
confirm: args.confirm,
|
|
2535
|
+
idempotencyKey: effectiveIdempotencyKey,
|
|
2536
|
+
preview: args.preview,
|
|
2537
|
+
errorCode: error instanceof Error ? error.message : String(error ?? "unknown_error")
|
|
2538
|
+
});
|
|
2539
|
+
if (shouldAppendDurableGatewayActivity(tool, args)) {
|
|
2540
|
+
await appendDurableGatewayActivityBestEffort({
|
|
2541
|
+
registry: await options.registryResolver(),
|
|
2542
|
+
tool,
|
|
2543
|
+
args,
|
|
2544
|
+
status: "error",
|
|
2545
|
+
errorMessage: error instanceof Error ? error.message : String(error ?? "unknown_error")
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
throw error;
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
async function getOpenApi(args = {}) {
|
|
2552
|
+
const snapshot = await getCatalogSnapshot();
|
|
2553
|
+
const normalizedDomain = normalizeDomain(args.domain ?? "");
|
|
2554
|
+
const manifests = normalizedDomain.length > 0 ? snapshot.manifests.filter((manifest) => normalizeDomain(manifest.domain?.id ?? "") === normalizedDomain) : snapshot.manifests;
|
|
2555
|
+
const tools = normalizedDomain.length > 0 ? snapshot.catalog.tools.filter((tool) => normalizeDomain(tool.domain) === normalizedDomain) : snapshot.catalog.tools;
|
|
2556
|
+
const catalog = {
|
|
2557
|
+
...snapshot.catalog,
|
|
2558
|
+
tools
|
|
2559
|
+
};
|
|
2560
|
+
const document = buildAgentGatewayOpenApi({
|
|
2561
|
+
manifests,
|
|
2562
|
+
catalog,
|
|
2563
|
+
serverBaseUrl: args.serverBaseUrl
|
|
2564
|
+
});
|
|
2565
|
+
return normalizeAgentGatewayOpenApiDocument(document);
|
|
2566
|
+
}
|
|
2567
|
+
return {
|
|
2568
|
+
listTools,
|
|
2569
|
+
getToolInputJsonSchema,
|
|
2570
|
+
invokeTool,
|
|
2571
|
+
getOpenApi,
|
|
2572
|
+
getDiagnostics,
|
|
2573
|
+
refreshCatalogSnapshot,
|
|
2574
|
+
invalidateCatalogSnapshot
|
|
2575
|
+
};
|
|
2576
|
+
}
|
|
2577
|
+
let singleton = null;
|
|
2578
|
+
function getAgentGateway() {
|
|
2579
|
+
if (singleton) return singleton;
|
|
2580
|
+
const config = getHostConfig();
|
|
2581
|
+
singleton = createAgentGateway({
|
|
2582
|
+
registryResolver: getHostPluginRegistry,
|
|
2583
|
+
registryResetter: resetHostPluginRegistry,
|
|
2584
|
+
config: config.agentGateway
|
|
2585
|
+
});
|
|
2586
|
+
return singleton;
|
|
2587
|
+
}
|
|
2588
|
+
function getAgentGatewayDiagnostics() {
|
|
2589
|
+
return singleton?.getDiagnostics() ?? {
|
|
2590
|
+
enabled: getHostConfig().agentGateway?.enabled !== false,
|
|
2591
|
+
snapshotLoaded: false,
|
|
2592
|
+
toolCount: 0,
|
|
2593
|
+
errorCount: 0
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
export { normalizeToolId as a, getAgentGatewayDiagnostics as b, getAgentGateway as g, normalizeDomain as n, resolveOperationKind as r };
|
|
2598
|
+
//# sourceMappingURL=agent-gateway-runtime-DTZ4SxaC.js.map
|