import { requireAgentApiConfig, requireApiConfig, requireEndpointApiConfig, requireUserApiConfig, requireUserOrAgentApiConfig, type ApiConfig, type Config, type CredentialledApiConfig } from "../config.js"; /** * The SpecGuard HTTP client — a Bearer key and a path, and nothing else. * * Authorization is enforced by the deployment (`Api::BaseController`), never * here: this carries the operator's key and reports what came back. The bridge * adds no credentials of its own and makes no access decisions, so there is no * second place for the permission model to be got wrong. * * The key is PRESENT-CONDITIONAL since SPGD-1200: every credentialled helper * binds one and it rides every request as before, while a credential-free ask * (`requireEndpointApiConfig` — the unauthenticated `/version`, and since * SPGD-1331 the schema mirror beside it) presents NO * `Authorization` header at all rather than `Bearer ` with nothing after it. */ export declare function getJson(api: ApiConfig, path: string, query: Record, fetchImpl: typeof globalThis.fetch): Promise; /** * `GET` that hands back the RAW BODY TEXT — the read half of `deleteJson`'s * argument, for a route whose bytes are the answer. * * `requestJson` JSON-parses every 2xx it sees and, when that parse fails, * throws a sentence telling the operator to check that the endpoint points at * a SpecGuard deployment "and not, say, a proxy or login page". That is the * right diagnosis for a body that should have been JSON and was not; it is the * WRONG one for a caller whose contract is the bytes themselves, because it * names a cause (misconfigured endpoint) that the correct response in hand * refutes. `deleteJson` already carries this shape for its own reason — its * `204` has no body at all — and the argument generalises: what a SUCCESS body * is for differs per caller, while the status check and the "reached and * refused" hand-off to `describeFailure` do not. * * So this shares through `requestText` — the one total deadline, the abort, * the reached-and-stopped vs could-not-reach split, and every crafted status * sentence — and differs from `getJson` in exactly one thing: it returns what * came back instead of what it parsed. A caller that wants a parsed value * parses it, and owns the diagnosis for its own body, which is the only place * that diagnosis can be correct. */ export declare function getText(api: ApiConfig, path: string, fetchImpl: typeof globalThis.fetch): Promise; /** * `POST` with a JSON body — the write half of the transport, and deliberately * the SAME function underneath. * * It shares `fetchWithTimeout` rather than standing beside it. The one-total- * budget deadline, the explicit race, the `unref`'d timer, the abort and the * "reached and stopped" vs "could not reach" split are the expensive part of * this module and every argument for them is written above them — none of it is * about the verb. A second transport re-deriving them is how the two come to * disagree about what `SPECGUARD_TIMEOUT_MS` bounds, and the write path is the * one where a call that never returns costs the most: the agent has already * committed to a registration by the time it hangs. * * The body is serialized HERE rather than taken as a string, so no caller can * send a body whose `Content-Type` says JSON and whose bytes are not. */ export declare function postJson(api: ApiConfig, path: string, body: Record, fetchImpl: typeof globalThis.fetch): Promise; /** * `PATCH` with a JSON body — the mutating-without-replacing half of the * transport, and deliberately the SAME function underneath `postJson`, for the * reason that header states: everything expensive about this module is about * the deadline, not the verb. * * Routed through `requestJson` rather than `deleteJson`'s raw-body handling, * because every PATCH this surface serves answers `200` WITH a JSON body — * `requestJson`'s parse is correct here, and re-deriving success handling would * be a third copy of the one status check. */ export declare function patchJson(api: ApiConfig, path: string, body: Record, fetchImpl: typeof globalThis.fetch): Promise; /** * `DELETE` — the destructive half of the transport, and deliberately the SAME * function underneath `postJson` rather than beside it, for the reason * `postJson`'s header states: everything expensive about this module is about * the deadline, not the verb. * * Returns the RAW BODY TEXT rather than a parsed value, because the endpoints * this serves answer `204` with NO body at all — the one response in the `sgu_` * surface that is deliberately not JSON. `requestJson` JSON-parses every 2xx it * sees, so routing a `204` through it would turn a successful delete into * "answered 204 but the body was not JSON" — the trap this verb specifically * introduces, and the reason the DELETE path has its own success handling * instead of sharing `requestJson`'s. The status check and the * "reached and refused" hand-off to `describeFailure` are still shared through * `requestText`: only what happens to a SUCCESS body differs. */ export declare function deleteJson(api: ApiConfig, path: string, fetchImpl: typeof globalThis.fetch): Promise; /** * `DELETE` that answers with a JSON body — the destructive verb's one non-empty * answer, routed through `requestJson` rather than `deleteJson`'s raw-body * handling. * * Every DELETE this surface served until now answered `204` with NO body — the * reason `deleteJson` exists and returns raw text. The agent-key revoke * (`DELETE /api/v1/repositories/:repository_id/agent_keys/:id`) answers `200` * WITH a JSON disclosure body: the platform's substitute for the confirm dialog * the web face shows, naming the full stored set the cut just landed on. The * body is the point of the response, so this verb parses it — and re-deriving * the status check and the not-JSON diagnosis here would be a third copy of the * one check `requestJson` owns, which is exactly the argument `patchJson`'s * header makes for its own 200-with-body case. `deleteJson` stays exported and * byte-unchanged for the endpoints that legitimately answer empty. */ export declare function deleteJsonObject(api: ApiConfig, path: string, fetchImpl: typeof globalThis.fetch): Promise>; /** * `postJson`, narrowed exactly as `getJsonObject` narrows `getJson`. * * The write path needs the same guard for the same reason, and the reason is not * about reading: `ToolResult.structured` is a `Record`, so a * body that is an array or a bare scalar is not something a tool can pass * through whichever verb fetched it. Shipping only the raw `postJson` would * leave the first write tool to re-type the three-clause check and its sentence * — which is precisely the duplication `getJsonObject`'s header says no tool * should have to repeat. * * The pair is mirrored rather than collapsed for the reason the read pair is: * `postJson` stays exported un-narrowed for an endpoint that legitimately * answers with an array. */ export declare function postJsonObject(api: ApiConfig, path: string, body: Record, fetchImpl: typeof globalThis.fetch): Promise>; /** * `patchJson`, narrowed exactly as `postJsonObject` narrows `postJson` — same * guard, same sentence, same reason: `ToolResult.structured` is a * `Record`, so no tool should re-type the three-clause check * on this verb either. */ export declare function patchJsonObject(api: ApiConfig, path: string, body: Record, fetchImpl: typeof globalThis.fetch): Promise>; /** * `getJson`, narrowed to the object every tool here actually asks it for. * * MCP hands a tool result back as an object, so an array or a bare JSON scalar * is not something a tool can pass through: it surfaces as a protocol error * rather than as something the agent can read. Every HTTP tool therefore * needed the same three-clause guard, and the same sentence, immediately after * its own `getJson` call — which made both the check and its wording the one * thing each new tool had to remember to write for itself, and get identical. * * It belongs here for the reason `requireApiConfig` parses the endpoint rather * than leaving that to callers: every HTTP-backed tool added later comes * through this function and inherits the check, the same way it inherits the * URL check and the 401 wording. `getJson` stays exported un-narrowed for an * endpoint that legitimately serves an array — the point is not that objects * are the only legal body, it is that no tool re-types this guard. */ export declare function getJsonObject(api: ApiConfig, path: string, query: Record, fetchImpl: typeof globalThis.fetch): Promise>; /** * The (credential, path) pair ONE repository ask must travel as — both arms * of the singular/plural branch, chosen together or not at all. * * A tool asks either the whole deployment — the `sgk_` slot * (`SPECGUARD_API_KEY`) against the singular `/api/v1/repository` — or one * repository inside it — either member credential (the agent key preferred, * the user key when no agent key is set; the SPGD-1106 widening; the route * has served both since SPGD-952) against the plural * `/api/v1/repositories/…`. The pair (path, credential) must not be mixable: * a plural path under the `sgk_` slot, or a singular path under a member * key, is a 401 at the deployment by design, and both mistakes are refused * HERE, legibly, instead of reaching the deployment. Holding the two * ternaries in one function is what keeps the two arms one branch point * rather than two lines a call site can re-pair — the module header's "no * second place for the permission model to be got wrong", applied to the * one branch that used to be carried as a copy in each of the two tools * that ask over this axis. * * Both such tools call it — `near-duplicate-clusters.ts` and * `repository-overview.ts` — and a tool that grows a `repository` argument * joins by calling this, not by re-spelling the branch. Blank-string and * `optionalString` handling stays in the tools: this decides transport and * credential from the ask exactly as handed to it, `undefined` being the * one spelling of "no ask". */ export declare function repositoryTarget(config: Config, repository: string | undefined): { api: CredentialledApiConfig; path: string; }; export { requireApiConfig, requireUserApiConfig, requireAgentApiConfig, requireUserOrAgentApiConfig, requireEndpointApiConfig, };