scoutline 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/capabilities/quota.d.ts +11 -0
- package/dist/capabilities/quota.d.ts.map +1 -1
- package/dist/capabilities/quota.js.map +1 -1
- package/dist/capabilities/search.d.ts +11 -1
- package/dist/capabilities/search.d.ts.map +1 -1
- package/dist/commands/crawl.js +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +13 -10
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/map.js +1 -1
- package/dist/commands/quota.d.ts +23 -0
- package/dist/commands/quota.d.ts.map +1 -1
- package/dist/commands/quota.js +21 -4
- package/dist/commands/quota.js.map +1 -1
- package/dist/commands/read.js +1 -1
- package/dist/commands/repo.js +1 -1
- package/dist/commands/research.js +1 -1
- package/dist/commands/search.d.ts +2 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +21 -12
- package/dist/commands/search.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/redact.d.ts +2 -2
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +8 -2
- package/dist/lib/redact.js.map +1 -1
- package/dist/providers/brave/adapter.d.ts +42 -0
- package/dist/providers/brave/adapter.d.ts.map +1 -0
- package/dist/providers/brave/adapter.js +504 -0
- package/dist/providers/brave/adapter.js.map +1 -0
- package/dist/providers/brave/client.d.ts +161 -0
- package/dist/providers/brave/client.d.ts.map +1 -0
- package/dist/providers/brave/client.js +297 -0
- package/dist/providers/brave/client.js.map +1 -0
- package/dist/providers/brave/credentials.d.ts +33 -0
- package/dist/providers/brave/credentials.d.ts.map +1 -0
- package/dist/providers/brave/credentials.js +55 -0
- package/dist/providers/brave/credentials.js.map +1 -0
- package/dist/providers/brave/diagnostics.d.ts +46 -0
- package/dist/providers/brave/diagnostics.d.ts.map +1 -0
- package/dist/providers/brave/diagnostics.js +76 -0
- package/dist/providers/brave/diagnostics.js.map +1 -0
- package/dist/providers/brave/quota.d.ts +86 -0
- package/dist/providers/brave/quota.d.ts.map +1 -0
- package/dist/providers/brave/quota.js +247 -0
- package/dist/providers/brave/quota.js.map +1 -0
- package/dist/providers/exa/adapter.d.ts +58 -0
- package/dist/providers/exa/adapter.d.ts.map +1 -0
- package/dist/providers/exa/adapter.js +892 -0
- package/dist/providers/exa/adapter.js.map +1 -0
- package/dist/providers/exa/client.d.ts +143 -0
- package/dist/providers/exa/client.d.ts.map +1 -0
- package/dist/providers/exa/client.js +328 -0
- package/dist/providers/exa/client.js.map +1 -0
- package/dist/providers/exa/credentials.d.ts +34 -0
- package/dist/providers/exa/credentials.d.ts.map +1 -0
- package/dist/providers/exa/credentials.js +56 -0
- package/dist/providers/exa/credentials.js.map +1 -0
- package/dist/providers/exa/diagnostics.d.ts +42 -0
- package/dist/providers/exa/diagnostics.d.ts.map +1 -0
- package/dist/providers/exa/diagnostics.js +69 -0
- package/dist/providers/exa/diagnostics.js.map +1 -0
- package/dist/providers/minimax/adapter.js +1 -1
- package/dist/providers/minimax/adapter.js.map +1 -1
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +4 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/tavily/adapter.d.ts.map +1 -1
- package/dist/providers/tavily/adapter.js +5 -0
- package/dist/providers/tavily/adapter.js.map +1 -1
- package/dist/providers/types.d.ts +1 -1
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/zai/adapter.d.ts.map +1 -1
- package/dist/providers/zai/adapter.js +7 -1
- package/dist/providers/zai/adapter.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave direct HTTP transport skeleton (brave-tech-plan §5, §6).
|
|
3
|
+
*
|
|
4
|
+
* Performs direct GETs against the Brave Search REST API with an
|
|
5
|
+
* `X-Subscription-Token: <apiKey>` header. There is NO internal retry —
|
|
6
|
+
* shared execution owns retry policy. Fetch and timers are injectable
|
|
7
|
+
* for tests.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `providers/tavily/client.ts` in structure, but SIMPLER in
|
|
10
|
+
* T1: the foundation only ships the GET transport and a thin
|
|
11
|
+
* HTTP-status → Scoutline error map. Capability-specific request body
|
|
12
|
+
* mapping arrives in later tickets (search, quota, etc.) once those
|
|
13
|
+
* Capability Modules land.
|
|
14
|
+
*
|
|
15
|
+
* The fetch dep's response type is the header-bearing
|
|
16
|
+
* {@link ProviderImageFetchResponse} (not the narrower
|
|
17
|
+
* {@link ProviderQuotaFetchResponse}) so the future Brave quota
|
|
18
|
+
* Capability can read `X-RateLimit-*` headers off responses without a
|
|
19
|
+
* second transport seam. T1 never reads those headers, but reusing the
|
|
20
|
+
* existing type keeps the seam stable.
|
|
21
|
+
*
|
|
22
|
+
* Boundary rules (ARCHITECTURE.md §2):
|
|
23
|
+
* - May import Adapter-local config and normalized errors.
|
|
24
|
+
* - May import {@link ProviderImageFetchResponse} from `providers/types.ts`.
|
|
25
|
+
* - Must NOT import command presentation, capability contracts, or
|
|
26
|
+
* another Provider's Adapter.
|
|
27
|
+
* - Must NOT perform response field normalization — the Adapter owns
|
|
28
|
+
* that. This module declares Provider-native request paths and
|
|
29
|
+
* params only; it never imports a capability contract.
|
|
30
|
+
*/
|
|
31
|
+
import type { ProviderImageFetchResponse } from "../types.js";
|
|
32
|
+
/** Injectable transport dependencies (fetch, timers, env). */
|
|
33
|
+
export interface BraveTransportDeps {
|
|
34
|
+
readonly fetch?: (input: string, init: Record<string, unknown>) => Promise<ProviderImageFetchResponse>;
|
|
35
|
+
readonly setTimeout?: typeof setTimeout;
|
|
36
|
+
readonly clearTimeout?: typeof clearTimeout;
|
|
37
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Provider-native search request query params (Brave API field names).
|
|
41
|
+
* The Adapter maps the Provider-neutral `SearchControls` into these
|
|
42
|
+
* before calling {@link fetchBraveSearch}; the transport never imports
|
|
43
|
+
* a capability contract.
|
|
44
|
+
*
|
|
45
|
+
* NOTE: `count` is intentionally ABSENT here. Result-count projection
|
|
46
|
+
* is a client-side concern owned by shared execution (`applyCount`)
|
|
47
|
+
* AFTER `invoke`+cache; `SearchRequest`/`SearchControls` carry NO
|
|
48
|
+
* `count` field, so the transport sends only `q` plus the mapped
|
|
49
|
+
* controls below (`country`/`freshness`). Brave therefore returns its
|
|
50
|
+
* default page size; `--count` above that is silently capped (no
|
|
51
|
+
* pagination in T2).
|
|
52
|
+
*/
|
|
53
|
+
export interface BraveSearchParams {
|
|
54
|
+
readonly country?: string;
|
|
55
|
+
readonly freshness?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Perform ONE GET against the Brave Search API. No retry; no response
|
|
59
|
+
* body in public errors. Returns the parsed JSON body (raw; the
|
|
60
|
+
* Adapter post-processes into a normalized shape).
|
|
61
|
+
*
|
|
62
|
+
* The transport carries `X-Subscription-Token` (NOT
|
|
63
|
+
* `Authorization: Bearer`) and a `User-Agent: scoutline/<version>`
|
|
64
|
+
* string read from `package.json` at module load.
|
|
65
|
+
*/
|
|
66
|
+
export declare function getBraveJson(apiKey: string, path: string, params?: Readonly<Record<string, unknown>>, deps?: BraveTransportDeps): Promise<unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Perform ONE GET against the Brave `/res/v1/web/search` endpoint. No
|
|
69
|
+
* retry; no response body in public errors. Returns the parsed JSON
|
|
70
|
+
* body (raw; the Adapter post-processes into normalized search
|
|
71
|
+
* sources).
|
|
72
|
+
*
|
|
73
|
+
* `params` carries Brave-native API fields already mapped from
|
|
74
|
+
* `SearchControls` by the Adapter (`country`/`freshness`). The query
|
|
75
|
+
* is sent as the `q` query-string parameter.
|
|
76
|
+
*/
|
|
77
|
+
export declare function fetchBraveSearch(apiKey: string, query: string, params?: BraveSearchParams, deps?: BraveTransportDeps): Promise<unknown>;
|
|
78
|
+
/**
|
|
79
|
+
* Perform ONE GET against the Brave `/res/v1/news/search` endpoint. No
|
|
80
|
+
* retry; no response body in public errors. Returns the parsed JSON
|
|
81
|
+
* body (raw; the Adapter post-processes into normalized search
|
|
82
|
+
* sources).
|
|
83
|
+
*
|
|
84
|
+
* Dispatched when `controls.topic === "news"`; the web endpoint is the
|
|
85
|
+
* default. `params` carries the same Brave-native fields as
|
|
86
|
+
* {@link fetchBraveSearch}.
|
|
87
|
+
*/
|
|
88
|
+
export declare function fetchBraveNewsSearch(apiKey: string, query: string, params?: BraveSearchParams, deps?: BraveTransportDeps): Promise<unknown>;
|
|
89
|
+
/**
|
|
90
|
+
* Perform ONE GET against the Brave `/res/v1/videos/search` endpoint. No
|
|
91
|
+
* retry; no response body in public errors. Returns the parsed JSON
|
|
92
|
+
* body (raw; the Adapter post-processes into normalized search
|
|
93
|
+
* sources).
|
|
94
|
+
*
|
|
95
|
+
* Dispatched when `controls.type === "video"`; precedence is
|
|
96
|
+
* `video > news > web`. `params` carries the same Brave-native fields
|
|
97
|
+
* as {@link fetchBraveSearch}.
|
|
98
|
+
*
|
|
99
|
+
* NOTE: Brave documents the videos endpoint as a POST body, but GET
|
|
100
|
+
* with query params covers every field we send (`q`/`country`/
|
|
101
|
+
* `freshness`) — confirm live, fall back to POST only if GET is
|
|
102
|
+
* rejected. `count` is intentionally absent (see
|
|
103
|
+
* {@link BraveSearchParams}); video results are client-side-truncated
|
|
104
|
+
* via `applyCount` like every provider, and the video endpoint's
|
|
105
|
+
* default page-size cap is unconfirmed (live gate).
|
|
106
|
+
*/
|
|
107
|
+
export declare function fetchBraveVideoSearch(apiKey: string, query: string, params?: BraveSearchParams, deps?: BraveTransportDeps): Promise<unknown>;
|
|
108
|
+
/**
|
|
109
|
+
* Perform ONE GET against the Brave LLM Context endpoint
|
|
110
|
+
* (`/res/v1/llm/context`). No retry; no response body in public errors.
|
|
111
|
+
* Returns the parsed JSON body (raw; the Adapter post-processes into
|
|
112
|
+
* normalized search sources).
|
|
113
|
+
*
|
|
114
|
+
* Unlike the web/news/video endpoints, LLM Context is **query-keyed** —
|
|
115
|
+
* it is a richer search that returns extracted passages
|
|
116
|
+
* (`grounding.generic[]`), NOT a URL-keyed summarizer. It is dispatched
|
|
117
|
+
* when `controls.contentSize === "high"` (the accepted 4th-meaning
|
|
118
|
+
* overload of `--content-size`); precedence is `video > high > news >
|
|
119
|
+
* web`. `high` overrides `topic`, so this path must receive a CLEAN
|
|
120
|
+
* query (no `topic` keyword appendage); the Adapter is responsible for
|
|
121
|
+
* that suppression.
|
|
122
|
+
*
|
|
123
|
+
* The mapped `country`/`freshness` controls are forwarded alongside
|
|
124
|
+
* `q` (consistent with the web/news/video paths) so a `high` search
|
|
125
|
+
* honors `--recency`/`--location` rather than silently dropping them.
|
|
126
|
+
* Whether LLM Context honors `country`/`freshness` is a live gate; if
|
|
127
|
+
* it rejects them the failure surfaces as a mapped error rather than a
|
|
128
|
+
* silent filter drop. `count` is intentionally never forwarded
|
|
129
|
+
* (`--count` is applied client-side by shared execution); LLM
|
|
130
|
+
* Context's source-count/token-budget param name is UNCONFIRMED.
|
|
131
|
+
*/
|
|
132
|
+
export declare function fetchBraveLlmContext(apiKey: string, query: string, params?: BraveSearchParams, deps?: BraveTransportDeps): Promise<unknown>;
|
|
133
|
+
/**
|
|
134
|
+
* The four Brave `X-RateLimit-*` response headers, read as raw strings.
|
|
135
|
+
* Each is `null` when the header is absent. The quota normalizer parses
|
|
136
|
+
* these into windows; this transport layer only collects them.
|
|
137
|
+
*/
|
|
138
|
+
export interface BraveRateLimitHeaders {
|
|
139
|
+
readonly limit: string | null;
|
|
140
|
+
readonly policy: string | null;
|
|
141
|
+
readonly remaining: string | null;
|
|
142
|
+
readonly reset: string | null;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Perform ONE GET against `/res/v1/web/search` with a stub query and
|
|
146
|
+
* return the four `X-RateLimit-*` response headers. Brave has NO
|
|
147
|
+
* `/usage` endpoint, so quota is read from these headers on a 1-query
|
|
148
|
+
* probe. The probe costs exactly ONE request and sends ONLY `q` — no
|
|
149
|
+
* `count` (shared execution applies count client-side) and no other
|
|
150
|
+
* controls.
|
|
151
|
+
*
|
|
152
|
+
* Mirrors {@link getBraveJson}'s transport setup (X-Subscription-Token/
|
|
153
|
+
* Accept/User-Agent headers, AbortController timeout via `BRAVE_TIMEOUT`,
|
|
154
|
+
* the same HTTP-status → error {@link mapStatusError}, and
|
|
155
|
+
* {@link normalizeTransportError}). On 2xx the body is DRAINED
|
|
156
|
+
* (`res.text()`) and discarded — only the headers are needed. On non-2xx
|
|
157
|
+
* the body is drained and dropped before throwing the mapped error; no
|
|
158
|
+
* raw Brave body ever reaches an error message.
|
|
159
|
+
*/
|
|
160
|
+
export declare function fetchBraveRateLimit(apiKey: string, deps?: BraveTransportDeps): Promise<BraveRateLimitHeaders>;
|
|
161
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/brave/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAW9D,8DAA8D;AAC9D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,CACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,UAAU,CAAC;IACxC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,YAAY,CAAC;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CAClC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAwED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC1C,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CA8ClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,iBAAiB,EAC1B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,iBAAiB,EAC1B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,iBAAiB,EAC1B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,iBAAiB,EAC1B,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,OAAO,CAAC,CAElB;AAMD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,qBAAqB,CAAC,CAgDhC"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave direct HTTP transport skeleton (brave-tech-plan §5, §6).
|
|
3
|
+
*
|
|
4
|
+
* Performs direct GETs against the Brave Search REST API with an
|
|
5
|
+
* `X-Subscription-Token: <apiKey>` header. There is NO internal retry —
|
|
6
|
+
* shared execution owns retry policy. Fetch and timers are injectable
|
|
7
|
+
* for tests.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `providers/tavily/client.ts` in structure, but SIMPLER in
|
|
10
|
+
* T1: the foundation only ships the GET transport and a thin
|
|
11
|
+
* HTTP-status → Scoutline error map. Capability-specific request body
|
|
12
|
+
* mapping arrives in later tickets (search, quota, etc.) once those
|
|
13
|
+
* Capability Modules land.
|
|
14
|
+
*
|
|
15
|
+
* The fetch dep's response type is the header-bearing
|
|
16
|
+
* {@link ProviderImageFetchResponse} (not the narrower
|
|
17
|
+
* {@link ProviderQuotaFetchResponse}) so the future Brave quota
|
|
18
|
+
* Capability can read `X-RateLimit-*` headers off responses without a
|
|
19
|
+
* second transport seam. T1 never reads those headers, but reusing the
|
|
20
|
+
* existing type keeps the seam stable.
|
|
21
|
+
*
|
|
22
|
+
* Boundary rules (ARCHITECTURE.md §2):
|
|
23
|
+
* - May import Adapter-local config and normalized errors.
|
|
24
|
+
* - May import {@link ProviderImageFetchResponse} from `providers/types.ts`.
|
|
25
|
+
* - Must NOT import command presentation, capability contracts, or
|
|
26
|
+
* another Provider's Adapter.
|
|
27
|
+
* - Must NOT perform response field normalization — the Adapter owns
|
|
28
|
+
* that. This module declares Provider-native request paths and
|
|
29
|
+
* params only; it never imports a capability contract.
|
|
30
|
+
*/
|
|
31
|
+
import { createRequire } from "node:module";
|
|
32
|
+
import { ApiError, AuthError, NetworkError, TimeoutError } from "../../lib/errors.js";
|
|
33
|
+
const require = createRequire(import.meta.url);
|
|
34
|
+
const { version: VERSION } = require("../../../package.json");
|
|
35
|
+
const BASE_URL = "https://api.search.brave.com";
|
|
36
|
+
const DEFAULT_TIMEOUT_MS = 30000;
|
|
37
|
+
const USER_AGENT = `scoutline/${VERSION}`;
|
|
38
|
+
const TIMEOUT_HELP_TEXT = "Try again or increase timeout with BRAVE_TIMEOUT env var";
|
|
39
|
+
function resolveTimeoutMs(env) {
|
|
40
|
+
const raw = parseInt(env.BRAVE_TIMEOUT || String(DEFAULT_TIMEOUT_MS), 10);
|
|
41
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_TIMEOUT_MS;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Layer 1 — HTTP-status mapping. Runs BEFORE the body is parsed; on a
|
|
45
|
+
* non-200 response we discard the body and throw a typed error.
|
|
46
|
+
*
|
|
47
|
+
* `timeoutMs` is forwarded so 408/504 can throw `TimeoutError`
|
|
48
|
+
* carrying the configured duration (and the `BRAVE_TIMEOUT` help text).
|
|
49
|
+
* The transport never embeds credential material or raw response bodies
|
|
50
|
+
* in any error message.
|
|
51
|
+
*/
|
|
52
|
+
function mapStatusError(status, timeoutMs) {
|
|
53
|
+
if (status === 401 || status === 403) {
|
|
54
|
+
return new AuthError("Brave authentication failed", "BRAVE_SEARCH_API_KEY");
|
|
55
|
+
}
|
|
56
|
+
if (status === 408 || status === 504) {
|
|
57
|
+
return new TimeoutError(timeoutMs, TIMEOUT_HELP_TEXT);
|
|
58
|
+
}
|
|
59
|
+
if (status === 429) {
|
|
60
|
+
return new ApiError("Brave rate limit exceeded", 429);
|
|
61
|
+
}
|
|
62
|
+
if (status === 400 || status === 404 || status === 410 || status === 422) {
|
|
63
|
+
return new ApiError("Brave request failed", status);
|
|
64
|
+
}
|
|
65
|
+
if (status >= 500) {
|
|
66
|
+
return new ApiError("Brave request failed", status);
|
|
67
|
+
}
|
|
68
|
+
return new ApiError("Brave request failed", status);
|
|
69
|
+
}
|
|
70
|
+
function normalizeTransportError(err, timeoutMs) {
|
|
71
|
+
if (err instanceof AuthError || err instanceof ApiError || err instanceof TimeoutError) {
|
|
72
|
+
return err;
|
|
73
|
+
}
|
|
74
|
+
if (err instanceof Error) {
|
|
75
|
+
if (err.name === "AbortError") {
|
|
76
|
+
return new TimeoutError(timeoutMs, TIMEOUT_HELP_TEXT);
|
|
77
|
+
}
|
|
78
|
+
const lower = err.message.toLowerCase();
|
|
79
|
+
if (lower.includes("fetch") ||
|
|
80
|
+
lower.includes("econnrefused") ||
|
|
81
|
+
lower.includes("econnreset") ||
|
|
82
|
+
lower.includes("enotfound") ||
|
|
83
|
+
lower.includes("network")) {
|
|
84
|
+
return new NetworkError("Brave network error");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return new ApiError("Brave request failed", 500);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the query string for a GET. Skips `undefined`/`null` values.
|
|
91
|
+
* Uses `encodeURIComponent` so callers can pass raw strings without
|
|
92
|
+
* pre-encoding.
|
|
93
|
+
*/
|
|
94
|
+
function buildQueryString(params) {
|
|
95
|
+
if (!params)
|
|
96
|
+
return "";
|
|
97
|
+
const parts = [];
|
|
98
|
+
for (const [key, value] of Object.entries(params)) {
|
|
99
|
+
if (value === undefined || value === null)
|
|
100
|
+
continue;
|
|
101
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
102
|
+
}
|
|
103
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Perform ONE GET against the Brave Search API. No retry; no response
|
|
107
|
+
* body in public errors. Returns the parsed JSON body (raw; the
|
|
108
|
+
* Adapter post-processes into a normalized shape).
|
|
109
|
+
*
|
|
110
|
+
* The transport carries `X-Subscription-Token` (NOT
|
|
111
|
+
* `Authorization: Bearer`) and a `User-Agent: scoutline/<version>`
|
|
112
|
+
* string read from `package.json` at module load.
|
|
113
|
+
*/
|
|
114
|
+
export async function getBraveJson(apiKey, path, params, deps = {}) {
|
|
115
|
+
const f = deps.fetch ??
|
|
116
|
+
fetch;
|
|
117
|
+
const setT = deps.setTimeout ?? setTimeout;
|
|
118
|
+
const clearT = deps.clearTimeout ?? clearTimeout;
|
|
119
|
+
const env = deps.env ?? process.env;
|
|
120
|
+
const timeoutMs = resolveTimeoutMs(env);
|
|
121
|
+
const url = `${BASE_URL}${path}${buildQueryString(params)}`;
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
const timeoutId = setT(() => controller.abort(), timeoutMs);
|
|
124
|
+
try {
|
|
125
|
+
const res = await f(url, {
|
|
126
|
+
method: "GET",
|
|
127
|
+
headers: {
|
|
128
|
+
"X-Subscription-Token": apiKey,
|
|
129
|
+
Accept: "application/json",
|
|
130
|
+
"User-Agent": USER_AGENT,
|
|
131
|
+
},
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
});
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
// Drain the body to free the socket, then drop it. The body must
|
|
136
|
+
// NEVER reach the error message (NFR-006).
|
|
137
|
+
await res.text().catch(() => { });
|
|
138
|
+
throw mapStatusError(res.status, timeoutMs);
|
|
139
|
+
}
|
|
140
|
+
let parsed;
|
|
141
|
+
try {
|
|
142
|
+
parsed = await res.json();
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
throw new ApiError("Brave returned a malformed response", 500);
|
|
146
|
+
}
|
|
147
|
+
return parsed;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
throw normalizeTransportError(err, timeoutMs);
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
// Clear the timeout only after body consumption so the AbortController
|
|
154
|
+
// timeout still covers a stalled/slow response body read.
|
|
155
|
+
clearT(timeoutId);
|
|
156
|
+
controller.abort();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Perform ONE GET against the Brave `/res/v1/web/search` endpoint. No
|
|
161
|
+
* retry; no response body in public errors. Returns the parsed JSON
|
|
162
|
+
* body (raw; the Adapter post-processes into normalized search
|
|
163
|
+
* sources).
|
|
164
|
+
*
|
|
165
|
+
* `params` carries Brave-native API fields already mapped from
|
|
166
|
+
* `SearchControls` by the Adapter (`country`/`freshness`). The query
|
|
167
|
+
* is sent as the `q` query-string parameter.
|
|
168
|
+
*/
|
|
169
|
+
export async function fetchBraveSearch(apiKey, query, params, deps = {}) {
|
|
170
|
+
return getBraveJson(apiKey, "/res/v1/web/search", { q: query, ...(params ?? {}) }, deps);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Perform ONE GET against the Brave `/res/v1/news/search` endpoint. No
|
|
174
|
+
* retry; no response body in public errors. Returns the parsed JSON
|
|
175
|
+
* body (raw; the Adapter post-processes into normalized search
|
|
176
|
+
* sources).
|
|
177
|
+
*
|
|
178
|
+
* Dispatched when `controls.topic === "news"`; the web endpoint is the
|
|
179
|
+
* default. `params` carries the same Brave-native fields as
|
|
180
|
+
* {@link fetchBraveSearch}.
|
|
181
|
+
*/
|
|
182
|
+
export async function fetchBraveNewsSearch(apiKey, query, params, deps = {}) {
|
|
183
|
+
return getBraveJson(apiKey, "/res/v1/news/search", { q: query, ...(params ?? {}) }, deps);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Perform ONE GET against the Brave `/res/v1/videos/search` endpoint. No
|
|
187
|
+
* retry; no response body in public errors. Returns the parsed JSON
|
|
188
|
+
* body (raw; the Adapter post-processes into normalized search
|
|
189
|
+
* sources).
|
|
190
|
+
*
|
|
191
|
+
* Dispatched when `controls.type === "video"`; precedence is
|
|
192
|
+
* `video > news > web`. `params` carries the same Brave-native fields
|
|
193
|
+
* as {@link fetchBraveSearch}.
|
|
194
|
+
*
|
|
195
|
+
* NOTE: Brave documents the videos endpoint as a POST body, but GET
|
|
196
|
+
* with query params covers every field we send (`q`/`country`/
|
|
197
|
+
* `freshness`) — confirm live, fall back to POST only if GET is
|
|
198
|
+
* rejected. `count` is intentionally absent (see
|
|
199
|
+
* {@link BraveSearchParams}); video results are client-side-truncated
|
|
200
|
+
* via `applyCount` like every provider, and the video endpoint's
|
|
201
|
+
* default page-size cap is unconfirmed (live gate).
|
|
202
|
+
*/
|
|
203
|
+
export async function fetchBraveVideoSearch(apiKey, query, params, deps = {}) {
|
|
204
|
+
return getBraveJson(apiKey, "/res/v1/videos/search", { q: query, ...(params ?? {}) }, deps);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Perform ONE GET against the Brave LLM Context endpoint
|
|
208
|
+
* (`/res/v1/llm/context`). No retry; no response body in public errors.
|
|
209
|
+
* Returns the parsed JSON body (raw; the Adapter post-processes into
|
|
210
|
+
* normalized search sources).
|
|
211
|
+
*
|
|
212
|
+
* Unlike the web/news/video endpoints, LLM Context is **query-keyed** —
|
|
213
|
+
* it is a richer search that returns extracted passages
|
|
214
|
+
* (`grounding.generic[]`), NOT a URL-keyed summarizer. It is dispatched
|
|
215
|
+
* when `controls.contentSize === "high"` (the accepted 4th-meaning
|
|
216
|
+
* overload of `--content-size`); precedence is `video > high > news >
|
|
217
|
+
* web`. `high` overrides `topic`, so this path must receive a CLEAN
|
|
218
|
+
* query (no `topic` keyword appendage); the Adapter is responsible for
|
|
219
|
+
* that suppression.
|
|
220
|
+
*
|
|
221
|
+
* The mapped `country`/`freshness` controls are forwarded alongside
|
|
222
|
+
* `q` (consistent with the web/news/video paths) so a `high` search
|
|
223
|
+
* honors `--recency`/`--location` rather than silently dropping them.
|
|
224
|
+
* Whether LLM Context honors `country`/`freshness` is a live gate; if
|
|
225
|
+
* it rejects them the failure surfaces as a mapped error rather than a
|
|
226
|
+
* silent filter drop. `count` is intentionally never forwarded
|
|
227
|
+
* (`--count` is applied client-side by shared execution); LLM
|
|
228
|
+
* Context's source-count/token-budget param name is UNCONFIRMED.
|
|
229
|
+
*/
|
|
230
|
+
export async function fetchBraveLlmContext(apiKey, query, params, deps = {}) {
|
|
231
|
+
return getBraveJson(apiKey, "/res/v1/llm/context", { q: query, ...(params ?? {}) }, deps);
|
|
232
|
+
}
|
|
233
|
+
/** Cheapest-credible probe query (same stub as diagnostics). */
|
|
234
|
+
const RATE_LIMIT_PROBE_QUERY = "scoutline-quota-probe";
|
|
235
|
+
/**
|
|
236
|
+
* Perform ONE GET against `/res/v1/web/search` with a stub query and
|
|
237
|
+
* return the four `X-RateLimit-*` response headers. Brave has NO
|
|
238
|
+
* `/usage` endpoint, so quota is read from these headers on a 1-query
|
|
239
|
+
* probe. The probe costs exactly ONE request and sends ONLY `q` — no
|
|
240
|
+
* `count` (shared execution applies count client-side) and no other
|
|
241
|
+
* controls.
|
|
242
|
+
*
|
|
243
|
+
* Mirrors {@link getBraveJson}'s transport setup (X-Subscription-Token/
|
|
244
|
+
* Accept/User-Agent headers, AbortController timeout via `BRAVE_TIMEOUT`,
|
|
245
|
+
* the same HTTP-status → error {@link mapStatusError}, and
|
|
246
|
+
* {@link normalizeTransportError}). On 2xx the body is DRAINED
|
|
247
|
+
* (`res.text()`) and discarded — only the headers are needed. On non-2xx
|
|
248
|
+
* the body is drained and dropped before throwing the mapped error; no
|
|
249
|
+
* raw Brave body ever reaches an error message.
|
|
250
|
+
*/
|
|
251
|
+
export async function fetchBraveRateLimit(apiKey, deps = {}) {
|
|
252
|
+
const f = deps.fetch ??
|
|
253
|
+
fetch;
|
|
254
|
+
const setT = deps.setTimeout ?? setTimeout;
|
|
255
|
+
const clearT = deps.clearTimeout ?? clearTimeout;
|
|
256
|
+
const env = deps.env ?? process.env;
|
|
257
|
+
const timeoutMs = resolveTimeoutMs(env);
|
|
258
|
+
const url = `${BASE_URL}/res/v1/web/search${buildQueryString({ q: RATE_LIMIT_PROBE_QUERY })}`;
|
|
259
|
+
const controller = new AbortController();
|
|
260
|
+
const timeoutId = setT(() => controller.abort(), timeoutMs);
|
|
261
|
+
try {
|
|
262
|
+
const res = await f(url, {
|
|
263
|
+
method: "GET",
|
|
264
|
+
headers: {
|
|
265
|
+
"X-Subscription-Token": apiKey,
|
|
266
|
+
Accept: "application/json",
|
|
267
|
+
"User-Agent": USER_AGENT,
|
|
268
|
+
},
|
|
269
|
+
signal: controller.signal,
|
|
270
|
+
});
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
// Drain the body to free the socket, then drop it. The body must
|
|
273
|
+
// NEVER reach the error message (NFR-006).
|
|
274
|
+
await res.text().catch(() => { });
|
|
275
|
+
throw mapStatusError(res.status, timeoutMs);
|
|
276
|
+
}
|
|
277
|
+
// Only the headers are needed; drain the body to free the socket.
|
|
278
|
+
await res.text().catch(() => { });
|
|
279
|
+
const headers = res.headers;
|
|
280
|
+
return {
|
|
281
|
+
limit: headers.get("X-RateLimit-Limit"),
|
|
282
|
+
policy: headers.get("X-RateLimit-Policy"),
|
|
283
|
+
remaining: headers.get("X-RateLimit-Remaining"),
|
|
284
|
+
reset: headers.get("X-RateLimit-Reset"),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (err) {
|
|
288
|
+
throw normalizeTransportError(err, timeoutMs);
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
// Clear the timeout only after body consumption so the AbortController
|
|
292
|
+
// timeout still covers a stalled/slow response body read.
|
|
293
|
+
clearT(timeoutId);
|
|
294
|
+
controller.abort();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/providers/brave/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave credential resolution (brave-tech-plan §2, §6).
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the Brave Search API key. Whitespace-only
|
|
5
|
+
* values are treated as absent, matching the descriptor's `isConfigured`
|
|
6
|
+
* contract.
|
|
7
|
+
*
|
|
8
|
+
* Boundary rules (ARCHITECTURE.md §2):
|
|
9
|
+
* - May import the normalized-error contract only.
|
|
10
|
+
* - Must NOT import transport, command presentation, or another
|
|
11
|
+
* Provider's Adapter.
|
|
12
|
+
*
|
|
13
|
+
* Missing credentials are surfaced as {@link ConfigurationError}
|
|
14
|
+
* (exit 3), distinct from {@link AuthError} (exit 1) which means the
|
|
15
|
+
* Provider REJECTED a presented credential.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the Brave API key without throwing. Returns `undefined` when
|
|
19
|
+
* no non-blank value is present.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveBraveApiKey(env: NodeJS.ProcessEnv): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the Brave API key or throw {@link ConfigurationError} (exit 3)
|
|
24
|
+
* when it is missing. Call this at every Capability invocation gate.
|
|
25
|
+
*/
|
|
26
|
+
export declare function requireBraveApiKey(env: NodeJS.ProcessEnv): string;
|
|
27
|
+
/**
|
|
28
|
+
* True when a non-blank Brave API key is configured. Metadata-only:
|
|
29
|
+
* performs no transport construction and reads no other Provider's
|
|
30
|
+
* credentials.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isBraveConfigured(env: NodeJS.ProcessEnv): boolean;
|
|
33
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../../../src/providers/brave/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAgBH;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,GAAG,SAAS,CAE7E;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CASjE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,OAAO,CAEjE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave credential resolution (brave-tech-plan §2, §6).
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for the Brave Search API key. Whitespace-only
|
|
5
|
+
* values are treated as absent, matching the descriptor's `isConfigured`
|
|
6
|
+
* contract.
|
|
7
|
+
*
|
|
8
|
+
* Boundary rules (ARCHITECTURE.md §2):
|
|
9
|
+
* - May import the normalized-error contract only.
|
|
10
|
+
* - Must NOT import transport, command presentation, or another
|
|
11
|
+
* Provider's Adapter.
|
|
12
|
+
*
|
|
13
|
+
* Missing credentials are surfaced as {@link ConfigurationError}
|
|
14
|
+
* (exit 3), distinct from {@link AuthError} (exit 1) which means the
|
|
15
|
+
* Provider REJECTED a presented credential.
|
|
16
|
+
*/
|
|
17
|
+
import { ConfigurationError } from "../../lib/errors.js";
|
|
18
|
+
const MISSING_KEY_HELP = 'export BRAVE_SEARCH_API_KEY="your-api-key"';
|
|
19
|
+
/**
|
|
20
|
+
* Pick a non-blank raw value from the environment. Returns the original
|
|
21
|
+
* (untrimmed) string when it contains at least one non-whitespace
|
|
22
|
+
* character, otherwise `undefined`.
|
|
23
|
+
*/
|
|
24
|
+
function pickNonBlank(raw) {
|
|
25
|
+
if (typeof raw !== "string")
|
|
26
|
+
return undefined;
|
|
27
|
+
return raw.trim().length > 0 ? raw : undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the Brave API key without throwing. Returns `undefined` when
|
|
31
|
+
* no non-blank value is present.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveBraveApiKey(env) {
|
|
34
|
+
return pickNonBlank(env.BRAVE_SEARCH_API_KEY);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the Brave API key or throw {@link ConfigurationError} (exit 3)
|
|
38
|
+
* when it is missing. Call this at every Capability invocation gate.
|
|
39
|
+
*/
|
|
40
|
+
export function requireBraveApiKey(env) {
|
|
41
|
+
const key = resolveBraveApiKey(env);
|
|
42
|
+
if (key === undefined) {
|
|
43
|
+
throw new ConfigurationError("BRAVE_SEARCH_API_KEY environment variable is required", MISSING_KEY_HELP);
|
|
44
|
+
}
|
|
45
|
+
return key;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* True when a non-blank Brave API key is configured. Metadata-only:
|
|
49
|
+
* performs no transport construction and reads no other Provider's
|
|
50
|
+
* credentials.
|
|
51
|
+
*/
|
|
52
|
+
export function isBraveConfigured(env) {
|
|
53
|
+
return resolveBraveApiKey(env) !== undefined;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../../src/providers/brave/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,gBAAgB,GAAG,4CAA4C,CAAC;AAEtE;;;;GAIG;AACH,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAsB;IACvD,OAAO,YAAY,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAsB;IACvD,MAAM,GAAG,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,kBAAkB,CAC1B,uDAAuD,EACvD,gBAAgB,CACjB,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAsB;IACtD,OAAO,kBAAkB,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brave Diagnostics Capability (brave-tech-plan §3, §7; T5).
|
|
3
|
+
*
|
|
4
|
+
* Probes Brave connectivity with a single, cheapest-possible request
|
|
5
|
+
* — one `/res/v1/web/search` GET with a stub query — so the doctor
|
|
6
|
+
* command can verify a credential authenticates without a generative
|
|
7
|
+
* request. Brave has no `search_depth`-style knob (unlike Tavily), so
|
|
8
|
+
* the probe sends a bare `q` and no other params.
|
|
9
|
+
*
|
|
10
|
+
* The probe performs exactly ONE attempt. Shared execution owns the
|
|
11
|
+
* retry policy; this transport never retries. The doctor command
|
|
12
|
+
* catches the throw on failure and records a redacted error entry.
|
|
13
|
+
*
|
|
14
|
+
* When `options.probe` is false, `invoke` resolves immediately without
|
|
15
|
+
* touching the network — the doctor command skips probing unconfigured
|
|
16
|
+
* Providers (passing `probe: false`) before reaching this Capability.
|
|
17
|
+
*
|
|
18
|
+
* Boundary rules (ARCHITECTURE.md §2):
|
|
19
|
+
* - May import the diagnostics capability contract, Adapter-local
|
|
20
|
+
* credentials, Adapter-local search transport, and normalized errors.
|
|
21
|
+
* - Must NOT import command presentation or another Provider's Adapter.
|
|
22
|
+
*/
|
|
23
|
+
import type { DiagnosticsCapability } from "../../capabilities/diagnostics.js";
|
|
24
|
+
import { type BraveTransportDeps } from "./client.js";
|
|
25
|
+
/**
|
|
26
|
+
* Options for the Brave DiagnosticsCapability. The API key is resolved
|
|
27
|
+
* from `env`; transport dependencies (`fetch`, timer) are injectable
|
|
28
|
+
* for deterministic tests through the unified `transport` seam.
|
|
29
|
+
*/
|
|
30
|
+
export interface BraveDiagnosticsCapabilityOptions {
|
|
31
|
+
readonly env: NodeJS.ProcessEnv;
|
|
32
|
+
readonly transport?: BraveTransportDeps;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the Brave DiagnosticsCapability. The probe performs ONE
|
|
36
|
+
* `/res/v1/web/search` request with a stub query (`scoutline-doctor-
|
|
37
|
+
* probe`) and no params — the cheapest credible probe. Shared
|
|
38
|
+
* execution owns the retry policy; this transport performs exactly one
|
|
39
|
+
* attempt per invocation.
|
|
40
|
+
*
|
|
41
|
+
* When `options.probe` is false, `invoke` resolves immediately without
|
|
42
|
+
* touching the network — the doctor command skips probing unconfigured
|
|
43
|
+
* Providers before reaching this Capability.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createBraveDiagnosticsCapability(options: BraveDiagnosticsCapabilityOptions): DiagnosticsCapability;
|
|
46
|
+
//# sourceMappingURL=diagnostics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../../../src/providers/brave/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAqB,MAAM,mCAAmC,CAAC;AASlG,OAAO,EAAoB,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AA+BxE;;;;GAIG;AACH,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,iCAAiC,GACzC,qBAAqB,CAevB"}
|