wicked-crew 0.7.39 → 0.7.45
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/dist/api/audit.d.ts +3 -0
- package/dist/api/audit.d.ts.map +1 -1
- package/dist/api/audit.js +2 -0
- package/dist/api/audit.js.map +1 -1
- package/dist/api/capture.d.ts +149 -0
- package/dist/api/capture.d.ts.map +1 -0
- package/dist/api/capture.js +330 -0
- package/dist/api/capture.js.map +1 -0
- package/dist/api/chat-citations.d.ts +174 -0
- package/dist/api/chat-citations.d.ts.map +1 -0
- package/dist/api/chat-citations.js +528 -0
- package/dist/api/chat-citations.js.map +1 -0
- package/dist/api/chat-scope.d.ts +37 -1
- package/dist/api/chat-scope.d.ts.map +1 -1
- package/dist/api/chat-scope.js +131 -7
- package/dist/api/chat-scope.js.map +1 -1
- package/dist/api/chat-transcripts.d.ts +20 -1
- package/dist/api/chat-transcripts.d.ts.map +1 -1
- package/dist/api/chat-transcripts.js +29 -0
- package/dist/api/chat-transcripts.js.map +1 -1
- package/dist/api/delivery-cache.d.ts +2 -2
- package/dist/api/delivery-freeze.d.ts +66 -0
- package/dist/api/delivery-freeze.d.ts.map +1 -0
- package/dist/api/delivery-freeze.js +104 -0
- package/dist/api/delivery-freeze.js.map +1 -0
- package/dist/api/delivery-index.d.ts +53 -9
- package/dist/api/delivery-index.d.ts.map +1 -1
- package/dist/api/delivery-index.js +115 -22
- package/dist/api/delivery-index.js.map +1 -1
- package/dist/api/demo.d.ts +90 -0
- package/dist/api/demo.d.ts.map +1 -0
- package/dist/api/demo.js +492 -0
- package/dist/api/demo.js.map +1 -0
- package/dist/api/diagnostics.d.ts +64 -1
- package/dist/api/diagnostics.d.ts.map +1 -1
- package/dist/api/diagnostics.js +129 -1
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/eval-sample.d.ts +6 -6
- package/dist/api/governance-steering.d.ts +2 -2
- package/dist/api/mcp.d.ts +282 -0
- package/dist/api/mcp.d.ts.map +1 -0
- package/dist/api/mcp.js +521 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/plan-schema.d.ts +56 -0
- package/dist/api/plan-schema.d.ts.map +1 -0
- package/dist/api/plan-schema.js +26 -0
- package/dist/api/plan-schema.js.map +1 -0
- package/dist/api/roster-standing.d.ts +15 -2
- package/dist/api/roster-standing.d.ts.map +1 -1
- package/dist/api/roster-standing.js +55 -3
- package/dist/api/roster-standing.js.map +1 -1
- package/dist/api/routes.d.ts +647 -59
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +906 -146
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts.map +1 -1
- package/dist/api/run-files.js +31 -2
- package/dist/api/run-files.js.map +1 -1
- package/dist/api/run-liveness.d.ts +41 -0
- package/dist/api/run-liveness.d.ts.map +1 -0
- package/dist/api/run-liveness.js +166 -0
- package/dist/api/run-liveness.js.map +1 -0
- package/dist/api/run-timing-index.d.ts +10 -0
- package/dist/api/run-timing-index.d.ts.map +1 -1
- package/dist/api/run-timing-index.js +24 -0
- package/dist/api/run-timing-index.js.map +1 -1
- package/dist/api/seat-health.d.ts.map +1 -1
- package/dist/api/seat-health.js +20 -4
- package/dist/api/seat-health.js.map +1 -1
- package/dist/api/seat-probe.d.ts +141 -0
- package/dist/api/seat-probe.d.ts.map +1 -0
- package/dist/api/seat-probe.js +257 -0
- package/dist/api/seat-probe.js.map +1 -0
- package/dist/api/seat-record.d.ts +40 -0
- package/dist/api/seat-record.d.ts.map +1 -0
- package/dist/api/seat-record.js +218 -0
- package/dist/api/seat-record.js.map +1 -0
- package/dist/api/seat-signin.d.ts.map +1 -1
- package/dist/api/seat-signin.js +3 -1
- package/dist/api/seat-signin.js.map +1 -1
- package/dist/api/seat-standing.d.ts +24 -4
- package/dist/api/seat-standing.d.ts.map +1 -1
- package/dist/api/seat-standing.js +50 -3
- package/dist/api/seat-standing.js.map +1 -1
- package/dist/api/server.d.ts +34 -48
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +316 -116
- package/dist/api/server.js.map +1 -1
- package/dist/api/skills.d.ts +3 -1
- package/dist/api/skills.d.ts.map +1 -1
- package/dist/api/skills.js +7 -2
- package/dist/api/skills.js.map +1 -1
- package/dist/api/stall-watchdog.d.ts +34 -3
- package/dist/api/stall-watchdog.d.ts.map +1 -1
- package/dist/api/stall-watchdog.js +107 -8
- package/dist/api/stall-watchdog.js.map +1 -1
- package/dist/api/steering-landing.d.ts +6 -6
- package/dist/api/steering-landing.d.ts.map +1 -1
- package/dist/api/steering-landing.js +7 -9
- package/dist/api/steering-landing.js.map +1 -1
- package/dist/api/testing.d.ts +6 -4
- package/dist/api/testing.d.ts.map +1 -1
- package/dist/api/testing.js +82 -47
- package/dist/api/testing.js.map +1 -1
- package/dist/campaign/boot-resume.d.ts +34 -0
- package/dist/campaign/boot-resume.d.ts.map +1 -0
- package/dist/campaign/boot-resume.js +45 -0
- package/dist/campaign/boot-resume.js.map +1 -0
- package/dist/campaigns/rollup.d.ts +4 -1
- package/dist/campaigns/rollup.d.ts.map +1 -1
- package/dist/campaigns/rollup.js +6 -1
- package/dist/campaigns/rollup.js.map +1 -1
- package/dist/campaigns/routes.d.ts +2 -2
- package/dist/cli/bearer.d.ts +27 -1
- package/dist/cli/bearer.d.ts.map +1 -1
- package/dist/cli/bearer.js +62 -2
- package/dist/cli/bearer.js.map +1 -1
- package/dist/cli/governance.d.ts +11 -0
- package/dist/cli/governance.d.ts.map +1 -1
- package/dist/cli/governance.js +8 -2
- package/dist/cli/governance.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +140 -75
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp.d.ts +11 -0
- package/dist/cli/mcp.d.ts.map +1 -1
- package/dist/cli/mcp.js +40 -9
- package/dist/cli/mcp.js.map +1 -1
- package/dist/cli/service.d.ts +78 -0
- package/dist/cli/service.d.ts.map +1 -0
- package/dist/cli/service.js +334 -0
- package/dist/cli/service.js.map +1 -0
- package/dist/core/adapter.d.ts +247 -14
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +638 -161
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/bridge-reaper.d.ts +4 -1
- package/dist/core/bridge-reaper.d.ts.map +1 -1
- package/dist/core/bridge-reaper.js +15 -1
- package/dist/core/bridge-reaper.js.map +1 -1
- package/dist/core/bus-notice.d.ts +27 -0
- package/dist/core/bus-notice.d.ts.map +1 -0
- package/dist/core/bus-notice.js +24 -0
- package/dist/core/bus-notice.js.map +1 -0
- package/dist/core/bus.d.ts +99 -0
- package/dist/core/bus.d.ts.map +1 -0
- package/dist/core/bus.js +176 -0
- package/dist/core/bus.js.map +1 -0
- package/dist/core/deliver-exclusions.d.ts +71 -0
- package/dist/core/deliver-exclusions.d.ts.map +1 -0
- package/dist/core/deliver-exclusions.js +110 -0
- package/dist/core/deliver-exclusions.js.map +1 -0
- package/dist/core/deliver-text.d.ts +61 -14
- package/dist/core/deliver-text.d.ts.map +1 -1
- package/dist/core/deliver-text.js +178 -30
- package/dist/core/deliver-text.js.map +1 -1
- package/dist/core/deliver-triage.d.ts +11 -2
- package/dist/core/deliver-triage.d.ts.map +1 -1
- package/dist/core/deliver-triage.js +19 -1
- package/dist/core/deliver-triage.js.map +1 -1
- package/dist/core/deliver.d.ts +161 -3
- package/dist/core/deliver.d.ts.map +1 -1
- package/dist/core/deliver.js +463 -25
- package/dist/core/deliver.js.map +1 -1
- package/dist/core/deliverable-floor.d.ts.map +1 -1
- package/dist/core/deliverable-floor.js +5 -7
- package/dist/core/deliverable-floor.js.map +1 -1
- package/dist/core/engine-bus.d.ts +40 -0
- package/dist/core/engine-bus.d.ts.map +1 -0
- package/dist/core/engine-bus.js +27 -0
- package/dist/core/engine-bus.js.map +1 -0
- package/dist/core/estate-mcp-client.d.ts +87 -5
- package/dist/core/estate-mcp-client.d.ts.map +1 -1
- package/dist/core/estate-mcp-client.js +90 -11
- package/dist/core/estate-mcp-client.js.map +1 -1
- package/dist/core/linked-issues.d.ts +46 -0
- package/dist/core/linked-issues.d.ts.map +1 -0
- package/dist/core/linked-issues.js +123 -0
- package/dist/core/linked-issues.js.map +1 -0
- package/dist/core/run-identity.d.ts +100 -0
- package/dist/core/run-identity.d.ts.map +1 -0
- package/dist/core/run-identity.js +178 -0
- package/dist/core/run-identity.js.map +1 -0
- package/dist/core/shutdown.d.ts +16 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +39 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/types.d.ts +15 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +4 -0
- package/dist/core/types.js.map +1 -1
- package/dist/interactive/bridge-pool.d.ts +67 -3
- package/dist/interactive/bridge-pool.d.ts.map +1 -1
- package/dist/interactive/bridge-pool.js +129 -13
- package/dist/interactive/bridge-pool.js.map +1 -1
- package/dist/interactive/bridge-root.d.ts +9 -0
- package/dist/interactive/bridge-root.d.ts.map +1 -1
- package/dist/interactive/bridge-root.js +11 -4
- package/dist/interactive/bridge-root.js.map +1 -1
- package/dist/interactive/chat-events.d.ts +14 -9
- package/dist/interactive/chat-events.d.ts.map +1 -1
- package/dist/interactive/chat-events.js +55 -57
- package/dist/interactive/chat-events.js.map +1 -1
- package/dist/interactive/council-outcome.d.ts +1 -0
- package/dist/interactive/council-outcome.d.ts.map +1 -1
- package/dist/interactive/council-outcome.js +33 -0
- package/dist/interactive/council-outcome.js.map +1 -1
- package/dist/interactive/draft-events.d.ts +36 -17
- package/dist/interactive/draft-events.d.ts.map +1 -1
- package/dist/interactive/draft-events.js +121 -92
- package/dist/interactive/draft-events.js.map +1 -1
- package/dist/interactive/draft-skill.d.ts +59 -0
- package/dist/interactive/draft-skill.d.ts.map +1 -1
- package/dist/interactive/draft-skill.js +233 -0
- package/dist/interactive/draft-skill.js.map +1 -1
- package/dist/interactive/edit-events.d.ts +1 -14
- package/dist/interactive/edit-events.d.ts.map +1 -1
- package/dist/interactive/edit-events.js +37 -70
- package/dist/interactive/edit-events.js.map +1 -1
- package/dist/interactive/proxy-routes.d.ts +2 -2
- package/dist/interactive/proxy-routes.d.ts.map +1 -1
- package/dist/interactive/proxy-routes.js +4 -13
- package/dist/interactive/proxy-routes.js.map +1 -1
- package/dist/interactive/ws-relay.d.ts +9 -10
- package/dist/interactive/ws-relay.d.ts.map +1 -1
- package/dist/interactive/ws-relay.js +23 -50
- package/dist/interactive/ws-relay.js.map +1 -1
- package/dist/mcp/broker.d.ts +88 -0
- package/dist/mcp/broker.d.ts.map +1 -0
- package/dist/mcp/broker.js +307 -0
- package/dist/mcp/broker.js.map +1 -0
- package/dist/mcp/call-records.d.ts +61 -0
- package/dist/mcp/call-records.d.ts.map +1 -0
- package/dist/mcp/call-records.js +135 -0
- package/dist/mcp/call-records.js.map +1 -0
- package/dist/mcp/classify.d.ts +45 -0
- package/dist/mcp/classify.d.ts.map +1 -0
- package/dist/mcp/classify.js +83 -0
- package/dist/mcp/classify.js.map +1 -0
- package/dist/mcp/discovery.d.ts +24 -0
- package/dist/mcp/discovery.d.ts.map +1 -0
- package/dist/mcp/discovery.js +78 -0
- package/dist/mcp/discovery.js.map +1 -0
- package/dist/mcp/invoke.d.ts +36 -0
- package/dist/mcp/invoke.d.ts.map +1 -0
- package/dist/mcp/invoke.js +80 -0
- package/dist/mcp/invoke.js.map +1 -0
- package/dist/mcp/policies.d.ts +110 -0
- package/dist/mcp/policies.d.ts.map +1 -0
- package/dist/mcp/policies.js +400 -0
- package/dist/mcp/policies.js.map +1 -0
- package/dist/mcp/probe.d.ts +66 -0
- package/dist/mcp/probe.d.ts.map +1 -0
- package/dist/mcp/probe.js +116 -0
- package/dist/mcp/probe.js.map +1 -0
- package/dist/mcp/redact.d.ts +34 -0
- package/dist/mcp/redact.d.ts.map +1 -0
- package/dist/mcp/redact.js +84 -0
- package/dist/mcp/redact.js.map +1 -0
- package/dist/mcp/registry-store.d.ts +69 -0
- package/dist/mcp/registry-store.d.ts.map +1 -0
- package/dist/mcp/registry-store.js +82 -0
- package/dist/mcp/registry-store.js.map +1 -0
- package/dist/mcp/registry.d.ts +147 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +509 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/rest.d.ts +111 -0
- package/dist/mcp/rest.d.ts.map +1 -0
- package/dist/mcp/rest.js +606 -0
- package/dist/mcp/rest.js.map +1 -0
- package/dist/mcp/secrets.d.ts +66 -0
- package/dist/mcp/secrets.d.ts.map +1 -0
- package/dist/mcp/secrets.js +180 -0
- package/dist/mcp/secrets.js.map +1 -0
- package/dist/mcp/tool-list.d.ts +25 -0
- package/dist/mcp/tool-list.d.ts.map +1 -0
- package/dist/mcp/tool-list.js +62 -0
- package/dist/mcp/tool-list.js.map +1 -0
- package/dist/mcp/upstream-error.d.ts +12 -0
- package/dist/mcp/upstream-error.d.ts.map +1 -0
- package/dist/mcp/upstream-error.js +14 -0
- package/dist/mcp/upstream-error.js.map +1 -0
- package/dist/mcp/usage.d.ts +31 -0
- package/dist/mcp/usage.d.ts.map +1 -0
- package/dist/mcp/usage.js +159 -0
- package/dist/mcp/usage.js.map +1 -0
- package/dist/presets/routes.d.ts +53 -0
- package/dist/presets/routes.d.ts.map +1 -0
- package/dist/presets/routes.js +115 -0
- package/dist/presets/routes.js.map +1 -0
- package/dist/projects/activity.d.ts +6 -18
- package/dist/projects/activity.d.ts.map +1 -1
- package/dist/projects/activity.js +17 -60
- package/dist/projects/activity.js.map +1 -1
- package/dist/projects/events.d.ts +6 -5
- package/dist/projects/events.d.ts.map +1 -1
- package/dist/projects/events.js +33 -58
- package/dist/projects/events.js.map +1 -1
- package/dist/projects/graph.d.ts +11 -0
- package/dist/projects/graph.d.ts.map +1 -1
- package/dist/projects/graph.js +12 -0
- package/dist/projects/graph.js.map +1 -1
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +19 -0
- package/dist/projects/routes.js.map +1 -1
- package/dist/projects/state-home-preflight.d.ts.map +1 -1
- package/dist/projects/state-home-preflight.js +7 -0
- package/dist/projects/state-home-preflight.js.map +1 -1
- package/dist/projects/state-home-registry.d.ts.map +1 -1
- package/dist/projects/state-home-registry.js +9 -1
- package/dist/projects/state-home-registry.js.map +1 -1
- package/dist/qe/acceptance.d.ts +28 -28
- package/dist/qe/acceptance.d.ts.map +1 -1
- package/dist/qe/acceptance.js +60 -61
- package/dist/qe/acceptance.js.map +1 -1
- package/dist/qe/test-sets.d.ts +3 -5
- package/dist/qe/test-sets.d.ts.map +1 -1
- package/dist/qe/test-sets.js +5 -7
- package/dist/qe/test-sets.js.map +1 -1
- package/dist/skills/installed.d.ts +51 -0
- package/dist/skills/installed.d.ts.map +1 -0
- package/dist/skills/installed.js +46 -0
- package/dist/skills/installed.js.map +1 -0
- package/dist/skills/phase-skill-gaps.d.ts +108 -0
- package/dist/skills/phase-skill-gaps.d.ts.map +1 -0
- package/dist/skills/phase-skill-gaps.js +199 -0
- package/dist/skills/phase-skill-gaps.js.map +1 -0
- package/dist/skills/runtime.d.ts +1 -1
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +2 -1
- package/dist/skills/runtime.js.map +1 -1
- package/dist/standing-orders/evaluator.d.ts +113 -0
- package/dist/standing-orders/evaluator.d.ts.map +1 -0
- package/dist/standing-orders/evaluator.js +225 -0
- package/dist/standing-orders/evaluator.js.map +1 -0
- package/dist/standing-orders/history.d.ts +58 -0
- package/dist/standing-orders/history.d.ts.map +1 -0
- package/dist/standing-orders/history.js +117 -0
- package/dist/standing-orders/history.js.map +1 -0
- package/dist/standing-orders/parse.d.ts +19 -0
- package/dist/standing-orders/parse.d.ts.map +1 -0
- package/dist/standing-orders/parse.js +75 -0
- package/dist/standing-orders/parse.js.map +1 -0
- package/dist/standing-orders/routes.d.ts +180 -0
- package/dist/standing-orders/routes.d.ts.map +1 -0
- package/dist/standing-orders/routes.js +95 -0
- package/dist/standing-orders/routes.js.map +1 -0
- package/dist/standing-orders/store.d.ts +154 -0
- package/dist/standing-orders/store.d.ts.map +1 -0
- package/dist/standing-orders/store.js +189 -0
- package/dist/standing-orders/store.js.map +1 -0
- package/dist/studio/assets/index-CvV5qITf.css +32 -0
- package/dist/studio/assets/index-jCOy6Ns7.js +568 -0
- package/dist/studio/index.html +3 -3
- package/dist/studio/testid-inventory.json +3307 -897
- package/dist/team/key.d.ts +5 -0
- package/dist/team/key.d.ts.map +1 -0
- package/dist/team/key.js +21 -0
- package/dist/team/key.js.map +1 -0
- package/dist/team/routes.d.ts +196 -0
- package/dist/team/routes.d.ts.map +1 -0
- package/dist/team/routes.js +274 -0
- package/dist/team/routes.js.map +1 -0
- package/dist/team/ws-relay.d.ts +39 -0
- package/dist/team/ws-relay.d.ts.map +1 -0
- package/dist/team/ws-relay.js +63 -0
- package/dist/team/ws-relay.js.map +1 -0
- package/endpoint-manifest.json +502 -6
- package/package.json +6 -6
- package/dist/interactive/demo-events.d.ts +0 -367
- package/dist/interactive/demo-events.d.ts.map +0 -1
- package/dist/interactive/demo-events.js +0 -1293
- package/dist/interactive/demo-events.js.map +0 -1
- package/dist/qe/gate-events.d.ts +0 -113
- package/dist/qe/gate-events.d.ts.map +0 -1
- package/dist/qe/gate-events.js +0 -172
- package/dist/qe/gate-events.js.map +0 -1
- package/dist/studio/assets/index-BRDAN35i.js +0 -548
- package/dist/studio/assets/index-Drfcsy8b.css +0 -32
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rest.d.ts","sourceRoot":"","sources":["../../src/mcp/rest.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/rest.js
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST upstreams (DES-MCP-TOOLS-001 §2, §5, invariant I6; slice S5a): a plain HTTP API wrapped as
|
|
3
|
+
* brokered tools, one tool per chosen OpenAPI 3 operation.
|
|
4
|
+
*
|
|
5
|
+
* - **Import.** The OpenAPI document is fetched from `openapiUrl` (bounded in time and size; the
|
|
6
|
+
* server's auth header rides along only when the document is on the API's own origin) or taken as
|
|
7
|
+
* pasted. Local `$ref`s are resolved (a cycle becomes `{}`); an external `$ref` is not followed.
|
|
8
|
+
* Each operation becomes a tool whose input schema is its path, query and header parameters and
|
|
9
|
+
* its JSON body, and whose class comes from its method: `GET`/`HEAD`/`OPTIONS` read, `POST`
|
|
10
|
+
* write, `PUT`/`PATCH`/`DELETE` destructive (the broker's classification, §4.2).
|
|
11
|
+
* - **Mapping, an allowlist.** A tool carries its {@link McpRestMapping}: which argument fills
|
|
12
|
+
* which path variable, query parameter, header or body key. An argument no map names is DROPPED,
|
|
13
|
+
* never sent; its name is disclosed in the result's `_meta["wicked/rest"].droppedArgs`.
|
|
14
|
+
* - **Host pin (I6).** Every request is built on the server's base URL and checked before it is
|
|
15
|
+
* sent: its scheme, host and port must be the base URL's and its path must stay under the base
|
|
16
|
+
* path. A request that would leave, and a redirect to another host, are refused with
|
|
17
|
+
* {@link RestBoundaryError}, which the broker records as a `guard_error`. Redirects are never
|
|
18
|
+
* followed. The auth header can't be set or overridden by an argument.
|
|
19
|
+
*
|
|
20
|
+
* Ideas only from ContextForge (`openapi_service.py`, `tool_service.py`); no code is copied.
|
|
21
|
+
*/
|
|
22
|
+
import { parse as parseYaml } from 'yaml';
|
|
23
|
+
import { scrubSecrets } from './secrets.js';
|
|
24
|
+
import { UpstreamCallError } from './upstream-error.js';
|
|
25
|
+
export const REST_SPEC_TIMEOUT_MS = 10_000;
|
|
26
|
+
/** The largest OpenAPI document crew reads (fetched or pasted). */
|
|
27
|
+
export const REST_SPEC_MAX_BYTES = 1024 * 1024;
|
|
28
|
+
/** The largest response body handed back; the rest is cut and disclosed as `truncated`. */
|
|
29
|
+
export const REST_RESPONSE_MAX_BYTES = 1024 * 1024;
|
|
30
|
+
export const REST_DEFAULT_TIMEOUT_MS = 30_000;
|
|
31
|
+
const MAX_TOOLS = 512;
|
|
32
|
+
const MAX_REF_DEPTH = 32;
|
|
33
|
+
const TOOL_NAME_MAX = 64;
|
|
34
|
+
const METHODS = ['get', 'head', 'options', 'post', 'put', 'patch', 'delete'];
|
|
35
|
+
/**
|
|
36
|
+
* Headers an argument may never set: the ones that route or authenticate the request, and the
|
|
37
|
+
* hop-by-hop ones. The server's own auth header is added to this per server.
|
|
38
|
+
*/
|
|
39
|
+
const FORBIDDEN_HEADERS = new Set([
|
|
40
|
+
'authorization',
|
|
41
|
+
'proxy-authorization',
|
|
42
|
+
'cookie',
|
|
43
|
+
'host',
|
|
44
|
+
'content-length',
|
|
45
|
+
'content-type',
|
|
46
|
+
'transfer-encoding',
|
|
47
|
+
'connection',
|
|
48
|
+
'keep-alive',
|
|
49
|
+
'upgrade',
|
|
50
|
+
'te',
|
|
51
|
+
'trailer',
|
|
52
|
+
'expect',
|
|
53
|
+
'forwarded',
|
|
54
|
+
'x-forwarded-for',
|
|
55
|
+
'x-forwarded-host',
|
|
56
|
+
'x-forwarded-proto',
|
|
57
|
+
]);
|
|
58
|
+
/** A request that would leave the API's pinned base URL (I6). Never retried; recorded as `guard_error`. */
|
|
59
|
+
export class RestBoundaryError extends UpstreamCallError {
|
|
60
|
+
constructor(message) {
|
|
61
|
+
super('boundary', false, message);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// ── The base URL ────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
/** Parse a base URL: http(s), no credentials, no query or fragment. `null` = not acceptable. */
|
|
66
|
+
export function parseBaseUrl(raw) {
|
|
67
|
+
if (raw === null)
|
|
68
|
+
return null;
|
|
69
|
+
let u;
|
|
70
|
+
try {
|
|
71
|
+
u = new URL(raw);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
if (u.protocol !== 'https:' && u.protocol !== 'http:')
|
|
77
|
+
return null;
|
|
78
|
+
if (u.username !== '' || u.password !== '' || u.search !== '' || u.hash !== '')
|
|
79
|
+
return null;
|
|
80
|
+
return u;
|
|
81
|
+
}
|
|
82
|
+
function basePath(base) {
|
|
83
|
+
return base.pathname.replace(/\/+$/, '');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The request URL for `pathTemplate` filled with `values`, pinned to `base`: the same origin, and a
|
|
87
|
+
* path under the base path. Anything else is a {@link RestBoundaryError}.
|
|
88
|
+
*/
|
|
89
|
+
export function pinnedUrl(base, pathTemplate, values) {
|
|
90
|
+
let missing = null;
|
|
91
|
+
const filled = pathTemplate.replace(/\{([^{}]+)\}/g, (_m, name) => {
|
|
92
|
+
const v = values[name];
|
|
93
|
+
if (v === undefined) {
|
|
94
|
+
missing ??= name;
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
if (v === '.' || v === '..' || v === '')
|
|
98
|
+
throw new RestBoundaryError(`the path value for {${name}} (${JSON.stringify(v)}) would move the request off its path`);
|
|
99
|
+
return encodeURIComponent(v);
|
|
100
|
+
});
|
|
101
|
+
if (missing !== null)
|
|
102
|
+
throw new UpstreamCallError('protocol', false, `the path variable {${missing}} has no value`);
|
|
103
|
+
if (!filled.startsWith('/'))
|
|
104
|
+
throw new RestBoundaryError(`the path ${JSON.stringify(pathTemplate)} does not start with /`);
|
|
105
|
+
const url = new URL(base.href);
|
|
106
|
+
const prefix = basePath(base);
|
|
107
|
+
url.pathname = `${prefix}${filled}`;
|
|
108
|
+
// The URL parser normalizes `.`/`..` segments and may re-read the path; check what it produced.
|
|
109
|
+
const escape = boundaryEscape(base, url);
|
|
110
|
+
if (escape !== null)
|
|
111
|
+
throw new RestBoundaryError(escape);
|
|
112
|
+
return url;
|
|
113
|
+
}
|
|
114
|
+
/** Why `url` is outside `base` (another origin, or a path off the base path); `null` = inside. */
|
|
115
|
+
export function boundaryEscape(base, url) {
|
|
116
|
+
if (url.origin !== base.origin)
|
|
117
|
+
return `the request to ${url.origin} leaves the pinned host ${base.origin}`;
|
|
118
|
+
const prefix = basePath(base);
|
|
119
|
+
if (prefix !== '' && url.pathname !== prefix && !url.pathname.startsWith(`${prefix}/`)) {
|
|
120
|
+
return `the request path ${url.pathname} leaves the pinned base path ${prefix}`;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
function isObj(v) {
|
|
125
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
126
|
+
}
|
|
127
|
+
/** Resolve a local JSON pointer (`#/components/schemas/X`) in `doc`; `undefined` when absent. */
|
|
128
|
+
function pointer(doc, ref) {
|
|
129
|
+
if (!ref.startsWith('#/'))
|
|
130
|
+
return undefined;
|
|
131
|
+
let cur = doc;
|
|
132
|
+
for (const raw of ref.slice(2).split('/')) {
|
|
133
|
+
const key = raw.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
134
|
+
if (!isObj(cur) && !Array.isArray(cur))
|
|
135
|
+
return undefined;
|
|
136
|
+
cur = cur[key];
|
|
137
|
+
}
|
|
138
|
+
return cur;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `value` with every local `$ref` replaced by what it points to. A `$ref` already being resolved
|
|
142
|
+
* on the current path (a cycle), an unresolvable or external one, and anything deeper than
|
|
143
|
+
* {@link MAX_REF_DEPTH} become `{}` (any value): the schema is a hint to the caller, never a check.
|
|
144
|
+
*/
|
|
145
|
+
export function derefLocal(doc, value, seen = [], depth = 0) {
|
|
146
|
+
if (depth > MAX_REF_DEPTH)
|
|
147
|
+
return {};
|
|
148
|
+
if (Array.isArray(value))
|
|
149
|
+
return value.map((v) => derefLocal(doc, v, seen, depth + 1));
|
|
150
|
+
if (!isObj(value))
|
|
151
|
+
return value;
|
|
152
|
+
const ref = value['$ref'];
|
|
153
|
+
if (typeof ref === 'string') {
|
|
154
|
+
if (seen.includes(ref))
|
|
155
|
+
return {};
|
|
156
|
+
const target = pointer(doc, ref);
|
|
157
|
+
if (target === undefined)
|
|
158
|
+
return {};
|
|
159
|
+
return derefLocal(doc, target, [...seen, ref], depth + 1);
|
|
160
|
+
}
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const [k, v] of Object.entries(value))
|
|
163
|
+
out[k] = derefLocal(doc, v, seen, depth + 1);
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
/** Parse an OpenAPI document's text: JSON, else YAML. */
|
|
167
|
+
export function parseOpenApiText(text) {
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(text);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
try {
|
|
174
|
+
parsed = parseYaml(text, { maxAliasCount: 100 });
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
throw new Error(`the OpenAPI document is neither JSON nor YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!isObj(parsed))
|
|
181
|
+
throw new Error('the OpenAPI document is not an object');
|
|
182
|
+
return parsed;
|
|
183
|
+
}
|
|
184
|
+
function toolNameFor(method, path, operationId) {
|
|
185
|
+
const raw = typeof operationId === 'string' && operationId.trim() !== '' ? operationId : `${method}_${path}`;
|
|
186
|
+
const name = raw
|
|
187
|
+
.replace(/[^A-Za-z0-9_-]+/g, '_')
|
|
188
|
+
.replace(/_+/g, '_')
|
|
189
|
+
.replace(/^_+|_+$/g, '')
|
|
190
|
+
.slice(0, TOOL_NAME_MAX);
|
|
191
|
+
return name === '' ? method : name;
|
|
192
|
+
}
|
|
193
|
+
/** The class an HTTP method implies, as annotations (§4.2): the broker decides it, never the carrier. */
|
|
194
|
+
export function annotationsForMethod(method) {
|
|
195
|
+
switch (method) {
|
|
196
|
+
case 'GET':
|
|
197
|
+
case 'HEAD':
|
|
198
|
+
case 'OPTIONS':
|
|
199
|
+
return { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true };
|
|
200
|
+
case 'POST':
|
|
201
|
+
return { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
|
|
202
|
+
case 'PUT':
|
|
203
|
+
return { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true };
|
|
204
|
+
case 'PATCH':
|
|
205
|
+
case 'DELETE':
|
|
206
|
+
return { readOnlyHint: false, destructiveHint: true, idempotentHint: method === 'DELETE', openWorldHint: true };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function paramsOf(doc, pathItem, op) {
|
|
210
|
+
const merged = new Map();
|
|
211
|
+
for (const list of [pathItem['parameters'], op['parameters']]) {
|
|
212
|
+
if (!Array.isArray(list))
|
|
213
|
+
continue;
|
|
214
|
+
for (const rawParam of list) {
|
|
215
|
+
const p = derefLocal(doc, rawParam);
|
|
216
|
+
if (!isObj(p) || typeof p['name'] !== 'string' || p['name'] === '')
|
|
217
|
+
continue;
|
|
218
|
+
const where = p['in'];
|
|
219
|
+
if (where !== 'path' && where !== 'query' && where !== 'header')
|
|
220
|
+
continue; // cookie: never mapped
|
|
221
|
+
// Header names are case-insensitive: `X-Trace` and `x-trace` are one header, one argument.
|
|
222
|
+
merged.set(`${where}:${where === 'header' ? p['name'].toLowerCase() : p['name']}`, {
|
|
223
|
+
name: p['name'],
|
|
224
|
+
in: where,
|
|
225
|
+
required: where === 'path' || p['required'] === true,
|
|
226
|
+
schema: isObj(p['schema']) ? p['schema'] : { type: 'string' },
|
|
227
|
+
description: typeof p['description'] === 'string' ? p['description'] : null,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return [...merged.values()];
|
|
232
|
+
}
|
|
233
|
+
/** The JSON schema of an operation's JSON request body, `null` when none; `'unsupported'` = a non-JSON body. */
|
|
234
|
+
function jsonBodyOf(doc, op) {
|
|
235
|
+
const body = derefLocal(doc, op['requestBody']);
|
|
236
|
+
if (!isObj(body))
|
|
237
|
+
return null;
|
|
238
|
+
const content = body['content'];
|
|
239
|
+
if (!isObj(content))
|
|
240
|
+
return null;
|
|
241
|
+
const key = Object.keys(content).find((k) => /^application\/(?:[\w.+-]+\+)?json\b/i.test(k));
|
|
242
|
+
if (key === undefined)
|
|
243
|
+
return Object.keys(content).length === 0 ? null : 'unsupported';
|
|
244
|
+
const media = content[key];
|
|
245
|
+
const schema = isObj(media) && isObj(media['schema']) ? media['schema'] : {};
|
|
246
|
+
return { schema, required: body['required'] === true };
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Wrap an OpenAPI 3 document's operations as tools. `operations` (by `operationId` or tool name)
|
|
250
|
+
* picks which; a name that matches no operation is an error, so a typo never silently wraps less.
|
|
251
|
+
*/
|
|
252
|
+
export function importOpenApi(doc, opts = {}) {
|
|
253
|
+
const version = doc['openapi'];
|
|
254
|
+
if (typeof version !== 'string' || !version.startsWith('3.')) {
|
|
255
|
+
throw new Error('only OpenAPI 3.x documents are supported (the document has no "openapi": "3.x" field)');
|
|
256
|
+
}
|
|
257
|
+
const paths = doc['paths'];
|
|
258
|
+
if (!isObj(paths))
|
|
259
|
+
throw new Error('the OpenAPI document has no paths');
|
|
260
|
+
const forbidden = new Set(FORBIDDEN_HEADERS);
|
|
261
|
+
if (opts.authHeader !== undefined)
|
|
262
|
+
forbidden.add(opts.authHeader.toLowerCase());
|
|
263
|
+
const wanted = opts.operations === undefined || opts.operations === null ? null : new Set(opts.operations);
|
|
264
|
+
const matched = new Set();
|
|
265
|
+
const used = new Set();
|
|
266
|
+
const tools = [];
|
|
267
|
+
const skipped = [];
|
|
268
|
+
for (const [path, rawItem] of Object.entries(paths)) {
|
|
269
|
+
const pathItem = derefLocal(doc, rawItem);
|
|
270
|
+
if (!isObj(pathItem) || !path.startsWith('/'))
|
|
271
|
+
continue;
|
|
272
|
+
for (const m of METHODS) {
|
|
273
|
+
const op = pathItem[m];
|
|
274
|
+
if (!isObj(op))
|
|
275
|
+
continue;
|
|
276
|
+
const method = m.toUpperCase();
|
|
277
|
+
const opId = typeof op['operationId'] === 'string' ? op['operationId'] : null;
|
|
278
|
+
let name = toolNameFor(m, path, opId);
|
|
279
|
+
if (wanted !== null) {
|
|
280
|
+
const hit = [opId, name].find((n) => n !== null && wanted.has(n));
|
|
281
|
+
if (hit === undefined)
|
|
282
|
+
continue;
|
|
283
|
+
matched.add(hit);
|
|
284
|
+
}
|
|
285
|
+
const label = `${method} ${path}`;
|
|
286
|
+
if (tools.length >= MAX_TOOLS) {
|
|
287
|
+
skipped.push(`${label}: more than ${MAX_TOOLS} operations`);
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const body = jsonBodyOf(doc, op);
|
|
291
|
+
if (body === 'unsupported') {
|
|
292
|
+
skipped.push(`${label}: its request body is not JSON`);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
for (let n = 2; used.has(name); n++)
|
|
296
|
+
name = `${toolNameFor(m, path, opId).slice(0, TOOL_NAME_MAX - 4)}_${n}`;
|
|
297
|
+
const properties = {};
|
|
298
|
+
const required = [];
|
|
299
|
+
const mapping = {
|
|
300
|
+
method,
|
|
301
|
+
pathTemplate: path,
|
|
302
|
+
pathMap: {},
|
|
303
|
+
queryMap: {},
|
|
304
|
+
headerMap: {},
|
|
305
|
+
bodyMap: null,
|
|
306
|
+
bodyArg: null,
|
|
307
|
+
argAllowlist: [],
|
|
308
|
+
timeoutMs: opts.timeoutMs ?? REST_DEFAULT_TIMEOUT_MS,
|
|
309
|
+
};
|
|
310
|
+
const argName = (base, where) => (base in properties ? `${where}_${base}` : base);
|
|
311
|
+
const templateVars = new Set([...path.matchAll(/\{([^{}]+)\}/g)].map((x) => x[1]));
|
|
312
|
+
let unusable = null;
|
|
313
|
+
for (const p of paramsOf(doc, pathItem, op)) {
|
|
314
|
+
if (p.in === 'header' && forbidden.has(p.name.toLowerCase()))
|
|
315
|
+
continue; // never argument-settable
|
|
316
|
+
if (p.in === 'path' && !templateVars.has(p.name))
|
|
317
|
+
continue;
|
|
318
|
+
const arg = argName(p.name, p.in);
|
|
319
|
+
if (arg in properties)
|
|
320
|
+
continue;
|
|
321
|
+
properties[arg] = derefLocal(doc, p.description === null ? p.schema : { ...p.schema, description: p.description });
|
|
322
|
+
if (p.required)
|
|
323
|
+
required.push(arg);
|
|
324
|
+
if (p.in === 'path')
|
|
325
|
+
mapping.pathMap[arg] = p.name;
|
|
326
|
+
else if (p.in === 'query')
|
|
327
|
+
mapping.queryMap[arg] = p.name;
|
|
328
|
+
else
|
|
329
|
+
mapping.headerMap[arg] = p.name;
|
|
330
|
+
}
|
|
331
|
+
for (const v of templateVars) {
|
|
332
|
+
if (!Object.values(mapping.pathMap).includes(v)) {
|
|
333
|
+
// A path variable no parameter declares still has to be filled: it becomes a string argument.
|
|
334
|
+
const arg = argName(v, 'path');
|
|
335
|
+
if (arg in properties) {
|
|
336
|
+
unusable = `its path variable {${v}} clashes with another argument`;
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
properties[arg] = { type: 'string' };
|
|
340
|
+
required.push(arg);
|
|
341
|
+
mapping.pathMap[arg] = v;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (unusable !== null) {
|
|
345
|
+
skipped.push(`${label}: ${unusable}`);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (body !== null) {
|
|
349
|
+
const schema = derefLocal(doc, body.schema);
|
|
350
|
+
const props = isObj(schema['properties']) ? schema['properties'] : null;
|
|
351
|
+
const fieldsFit = schema['type'] === 'object' && props !== null && Object.keys(props).length > 0 && Object.keys(props).every((k) => !(k in properties));
|
|
352
|
+
if (fieldsFit && props !== null) {
|
|
353
|
+
mapping.bodyMap = {};
|
|
354
|
+
const bodyRequired = Array.isArray(schema['required']) ? schema['required'].filter((x) => typeof x === 'string') : [];
|
|
355
|
+
for (const [k, s] of Object.entries(props)) {
|
|
356
|
+
properties[k] = s;
|
|
357
|
+
mapping.bodyMap[k] = k;
|
|
358
|
+
if (body.required && bodyRequired.includes(k))
|
|
359
|
+
required.push(k);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
const arg = argName('body', 'request');
|
|
364
|
+
properties[arg] = schema;
|
|
365
|
+
mapping.bodyArg = arg;
|
|
366
|
+
if (body.required)
|
|
367
|
+
required.push(arg);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
mapping.argAllowlist = Object.keys(properties).sort();
|
|
371
|
+
const summary = typeof op['summary'] === 'string' ? op['summary'] : typeof op['description'] === 'string' ? op['description'] : '';
|
|
372
|
+
const description = `${summary.trim().slice(0, 1024)}${summary.trim() === '' ? '' : ' '}(${label})`;
|
|
373
|
+
const annotations = annotationsForMethod(method);
|
|
374
|
+
if (typeof op['summary'] === 'string' && op['summary'].trim() !== '')
|
|
375
|
+
annotations.title = op['summary'].trim().slice(0, 200);
|
|
376
|
+
used.add(name);
|
|
377
|
+
tools.push({
|
|
378
|
+
name,
|
|
379
|
+
description,
|
|
380
|
+
inputSchema: { type: 'object', properties, ...(required.length > 0 ? { required } : {}), additionalProperties: false },
|
|
381
|
+
outputSchema: null,
|
|
382
|
+
annotations,
|
|
383
|
+
rest: mapping,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (wanted !== null) {
|
|
388
|
+
const unknown = [...wanted].filter((w) => !matched.has(w));
|
|
389
|
+
if (unknown.length > 0)
|
|
390
|
+
throw new Error(`operations names ${unknown.map((u) => JSON.stringify(u)).join(', ')}, which the OpenAPI document does not have`);
|
|
391
|
+
}
|
|
392
|
+
if (tools.length === 0)
|
|
393
|
+
throw new Error(skipped.length > 0 ? `no operation could be wrapped: ${skipped.join('; ')}` : 'the OpenAPI document has no operations to wrap');
|
|
394
|
+
const info = doc['info'];
|
|
395
|
+
const serverInfo = isObj(info) && typeof info['title'] === 'string' ? { name: info['title'].slice(0, 200), version: typeof info['version'] === 'string' ? info['version'].slice(0, 64) : '' } : null;
|
|
396
|
+
return { serverInfo, tools, skipped };
|
|
397
|
+
}
|
|
398
|
+
// ── Reading bodies, bounded ─────────────────────────────────────────────────────────────────
|
|
399
|
+
async function readCapped(res, max) {
|
|
400
|
+
if (res.body === null)
|
|
401
|
+
return { text: '', truncated: false };
|
|
402
|
+
const reader = res.body.getReader();
|
|
403
|
+
const chunks = [];
|
|
404
|
+
let size = 0;
|
|
405
|
+
let truncated = false;
|
|
406
|
+
for (;;) {
|
|
407
|
+
const { done, value } = await reader.read();
|
|
408
|
+
if (done)
|
|
409
|
+
break;
|
|
410
|
+
if (size + value.byteLength > max) {
|
|
411
|
+
chunks.push(value.subarray(0, max - size));
|
|
412
|
+
size = max;
|
|
413
|
+
truncated = true;
|
|
414
|
+
await reader.cancel().catch(() => undefined);
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
chunks.push(value);
|
|
418
|
+
size += value.byteLength;
|
|
419
|
+
}
|
|
420
|
+
return { text: Buffer.concat(chunks).toString('utf8'), truncated };
|
|
421
|
+
}
|
|
422
|
+
function authHeaders(config, secret) {
|
|
423
|
+
if (config.auth?.header === undefined || secret === null)
|
|
424
|
+
return {};
|
|
425
|
+
return { [config.auth.header]: `${config.auth.prefix ?? ''}${secret}` };
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The `rest` probe: read the OpenAPI document and wrap its operations. The auth header goes to the
|
|
429
|
+
* document's URL only when it is on the base URL's own origin. Every string returned has the
|
|
430
|
+
* secret scrubbed out.
|
|
431
|
+
*
|
|
432
|
+
* The document's URL is NOT pinned to the base URL, by design: it is the operator's own input at
|
|
433
|
+
* preview time (an API's spec often lives elsewhere, e.g. a docs host), not a worker's call, and it
|
|
434
|
+
* carries no secret off the API's origin. The host pin (I6) is on the tools' calls.
|
|
435
|
+
*/
|
|
436
|
+
export async function probeRestServer(config, secret, fetchImpl = fetch) {
|
|
437
|
+
const secrets = secret === null ? [] : [secret];
|
|
438
|
+
try {
|
|
439
|
+
const base = parseBaseUrl(config.url);
|
|
440
|
+
if (base === null)
|
|
441
|
+
throw new Error('a rest server needs an http(s) base url with no credentials, query or fragment');
|
|
442
|
+
let doc;
|
|
443
|
+
if (config.openapi !== null && config.openapi !== undefined) {
|
|
444
|
+
doc = config.openapi;
|
|
445
|
+
}
|
|
446
|
+
else if (config.openapiUrl !== null && config.openapiUrl !== undefined) {
|
|
447
|
+
const specUrl = new URL(config.openapiUrl);
|
|
448
|
+
const headers = specUrl.origin === base.origin ? authHeaders(config, secret) : {};
|
|
449
|
+
const res = await fetchImpl(specUrl, { headers: { accept: 'application/json, application/yaml;q=0.9, */*;q=0.5', ...headers }, redirect: 'error', signal: AbortSignal.timeout(REST_SPEC_TIMEOUT_MS) });
|
|
450
|
+
if (!res.ok)
|
|
451
|
+
throw new Error(`fetching the OpenAPI document answered HTTP ${res.status}`);
|
|
452
|
+
const { text, truncated } = await readCapped(res, REST_SPEC_MAX_BYTES + 1);
|
|
453
|
+
if (truncated || Buffer.byteLength(text, 'utf8') > REST_SPEC_MAX_BYTES)
|
|
454
|
+
throw new Error(`the OpenAPI document is larger than ${REST_SPEC_MAX_BYTES} bytes`);
|
|
455
|
+
doc = parseOpenApiText(text);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
throw new Error('a rest server needs openapiUrl or openapi');
|
|
459
|
+
}
|
|
460
|
+
const imported = importOpenApi(doc, { authHeader: config.auth?.header, operations: config.operations ?? null });
|
|
461
|
+
// Every mapping must build a pinned URL; an operation whose path can't is refused here, not at call time.
|
|
462
|
+
for (const t of imported.tools) {
|
|
463
|
+
const sample = Object.fromEntries(Object.values(t.rest.pathMap).map((v) => [v, 'x']));
|
|
464
|
+
pinnedUrl(base, t.rest.pathTemplate, sample);
|
|
465
|
+
}
|
|
466
|
+
return scrubSecrets({ ok: true, serverInfo: imported.serverInfo, tools: imported.tools, skipped: imported.skipped }, secrets);
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
470
|
+
return scrubSecrets({ ok: false, error: message }, secrets);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// ── The call ────────────────────────────────────────────────────────────────────────────────
|
|
474
|
+
function scalar(v, what) {
|
|
475
|
+
if (typeof v === 'string')
|
|
476
|
+
return v;
|
|
477
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
478
|
+
return String(v);
|
|
479
|
+
throw new UpstreamCallError('protocol', false, `${what} must be a string, number or boolean`);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Build the one HTTP request a `rest` tool call makes, from the ALLOWLISTED arguments only, pinned
|
|
483
|
+
* to the base URL. Exported so the allowlist and the pin are testable without a network.
|
|
484
|
+
*/
|
|
485
|
+
export function buildRestRequest(config, secret, mapping, args) {
|
|
486
|
+
const base = parseBaseUrl(config.url);
|
|
487
|
+
if (base === null)
|
|
488
|
+
throw new RestBoundaryError('the server has no valid base url to pin the request to');
|
|
489
|
+
const allowed = new Set(mapping.argAllowlist);
|
|
490
|
+
const droppedArgs = Object.keys(args).filter((k) => !allowed.has(k)).sort();
|
|
491
|
+
// Only allowlisted arguments are ever read, whatever the maps say (a map is never wider than it).
|
|
492
|
+
const sent = Object.fromEntries(Object.entries(args).filter(([k]) => allowed.has(k)));
|
|
493
|
+
args = sent;
|
|
494
|
+
const pathValues = {};
|
|
495
|
+
for (const [arg, variable] of Object.entries(mapping.pathMap)) {
|
|
496
|
+
if (args[arg] !== undefined)
|
|
497
|
+
pathValues[variable] = scalar(args[arg], `the path argument ${arg}`);
|
|
498
|
+
}
|
|
499
|
+
const url = pinnedUrl(base, mapping.pathTemplate, pathValues);
|
|
500
|
+
for (const [arg, q] of Object.entries(mapping.queryMap)) {
|
|
501
|
+
const v = args[arg];
|
|
502
|
+
if (v === undefined || v === null)
|
|
503
|
+
continue;
|
|
504
|
+
if (Array.isArray(v))
|
|
505
|
+
for (const item of v)
|
|
506
|
+
url.searchParams.append(q, scalar(item, `the query argument ${arg}`));
|
|
507
|
+
else
|
|
508
|
+
url.searchParams.append(q, scalar(v, `the query argument ${arg}`));
|
|
509
|
+
}
|
|
510
|
+
const forbidden = new Set(FORBIDDEN_HEADERS);
|
|
511
|
+
if (config.auth?.header !== undefined)
|
|
512
|
+
forbidden.add(config.auth.header.toLowerCase());
|
|
513
|
+
const headers = { accept: 'application/json, */*;q=0.5' };
|
|
514
|
+
for (const [arg, h] of Object.entries(mapping.headerMap)) {
|
|
515
|
+
const v = args[arg];
|
|
516
|
+
if (v === undefined || v === null || forbidden.has(h.toLowerCase()))
|
|
517
|
+
continue;
|
|
518
|
+
const text = scalar(v, `the header argument ${arg}`);
|
|
519
|
+
if (/[\r\n\0]/.test(text))
|
|
520
|
+
throw new UpstreamCallError('protocol', false, `the header argument ${arg} must be one line`);
|
|
521
|
+
if (Object.keys(headers).some((k) => k.toLowerCase() === h.toLowerCase()))
|
|
522
|
+
continue; // one argument per header: the first wins
|
|
523
|
+
headers[h] = text;
|
|
524
|
+
}
|
|
525
|
+
let body;
|
|
526
|
+
if (mapping.bodyArg !== null && args[mapping.bodyArg] !== undefined) {
|
|
527
|
+
body = JSON.stringify(args[mapping.bodyArg]);
|
|
528
|
+
}
|
|
529
|
+
else if (mapping.bodyMap !== null) {
|
|
530
|
+
const obj = {};
|
|
531
|
+
for (const [arg, key] of Object.entries(mapping.bodyMap))
|
|
532
|
+
if (args[arg] !== undefined)
|
|
533
|
+
obj[key] = args[arg];
|
|
534
|
+
if (Object.keys(obj).length > 0 || mapping.method === 'POST' || mapping.method === 'PUT' || mapping.method === 'PATCH')
|
|
535
|
+
body = JSON.stringify(obj);
|
|
536
|
+
}
|
|
537
|
+
if (body !== undefined)
|
|
538
|
+
headers['content-type'] = 'application/json';
|
|
539
|
+
Object.assign(headers, authHeaders(config, secret));
|
|
540
|
+
return { url, init: { method: mapping.method, headers, ...(body !== undefined ? { body } : {}) }, droppedArgs };
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* Call one `rest` tool. Transport failures, 429 and 5xx are retryable {@link UpstreamCallError}s; a
|
|
544
|
+
* redirect is never followed (one to another host is a {@link RestBoundaryError}); any other
|
|
545
|
+
* status is handed back as the tool's own result (`isError` on 4xx). The result is RAW: the broker
|
|
546
|
+
* scrubs it.
|
|
547
|
+
*/
|
|
548
|
+
export async function invokeRestTool(config, secret, mapping, args, timeoutMs, fetchImpl = fetch) {
|
|
549
|
+
const { url, init, droppedArgs } = buildRestRequest(config, secret, mapping, args);
|
|
550
|
+
const deadline = Math.max(1, Math.min(timeoutMs, mapping.timeoutMs));
|
|
551
|
+
let res;
|
|
552
|
+
try {
|
|
553
|
+
res = await fetchImpl(url, { ...init, redirect: 'manual', signal: AbortSignal.timeout(deadline) });
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
const name = err instanceof Error ? err.name : '';
|
|
557
|
+
if (name === 'TimeoutError' || name === 'AbortError')
|
|
558
|
+
throw new UpstreamCallError('timeout', false, `timed out after ${Math.round(deadline / 1000)} s`);
|
|
559
|
+
throw new UpstreamCallError('transport', true, err instanceof Error ? err.message : String(err));
|
|
560
|
+
}
|
|
561
|
+
if (res.status >= 300 && res.status < 400) {
|
|
562
|
+
await res.body?.cancel().catch(() => undefined);
|
|
563
|
+
const location = res.headers.get('location');
|
|
564
|
+
let target = null;
|
|
565
|
+
try {
|
|
566
|
+
target = location === null ? null : new URL(location, url);
|
|
567
|
+
}
|
|
568
|
+
catch {
|
|
569
|
+
target = null;
|
|
570
|
+
}
|
|
571
|
+
const base = parseBaseUrl(config.url);
|
|
572
|
+
const escape = target !== null && base !== null ? boundaryEscape(base, target) : null;
|
|
573
|
+
if (escape !== null)
|
|
574
|
+
throw new RestBoundaryError(`the API redirected off its pin (${escape}); not followed`);
|
|
575
|
+
throw new UpstreamCallError('http', false, `the API answered HTTP ${res.status} (a redirect); redirects are not followed`);
|
|
576
|
+
}
|
|
577
|
+
if (res.status === 429 || res.status >= 500) {
|
|
578
|
+
await res.body?.cancel().catch(() => undefined);
|
|
579
|
+
throw new UpstreamCallError('http', true, `the API answered HTTP ${res.status}`);
|
|
580
|
+
}
|
|
581
|
+
let read;
|
|
582
|
+
try {
|
|
583
|
+
read = await readCapped(res, REST_RESPONSE_MAX_BYTES);
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
throw new UpstreamCallError('transport', true, `reading the response failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
587
|
+
}
|
|
588
|
+
let structured;
|
|
589
|
+
if (!read.truncated && /json/i.test(res.headers.get('content-type') ?? '')) {
|
|
590
|
+
try {
|
|
591
|
+
const parsed = JSON.parse(read.text);
|
|
592
|
+
if (isObj(parsed))
|
|
593
|
+
structured = parsed;
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
structured = undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return {
|
|
600
|
+
content: [{ type: 'text', text: read.text }],
|
|
601
|
+
...(structured !== undefined ? { structuredContent: structured } : {}),
|
|
602
|
+
isError: res.status >= 400,
|
|
603
|
+
_meta: { 'wicked/rest': { status: res.status, droppedArgs, truncated: read.truncated } },
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
//# sourceMappingURL=rest.js.map
|