dsh-plugin-show-me-data 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +27 -0
- package/README.md +96 -0
- package/cordis.patch.yml +40 -0
- package/docs/01-product-effect.md +178 -0
- package/docs/02-architecture.md +275 -0
- package/docs/03-data-contracts.md +291 -0
- package/docs/04-sources.md +342 -0
- package/docs/05-ui-spec.md +167 -0
- package/docs/06-ai-layer.md +194 -0
- package/docs/07-implementation-plan.md +399 -0
- package/docs/08-test-plan.md +133 -0
- package/docs/09-packaging-install.md +249 -0
- package/docs/10-kickoff-prompt.md +94 -0
- package/docs/11-decisions.md +203 -0
- package/docs/12-runtime-verified.md +115 -0
- package/docs/13-acceptance.md +153 -0
- package/docs/14-progress.md +150 -0
- package/docs/15-publish.md +185 -0
- package/lib/app/ai-deterministic.js +327 -0
- package/lib/app/ai-validate.js +284 -0
- package/lib/app/ai.js +440 -0
- package/lib/app/health.js +77 -0
- package/lib/app/overview.js +349 -0
- package/lib/app/propose-indicator.js +122 -0
- package/lib/app/refresh.js +251 -0
- package/lib/app/series-view.js +195 -0
- package/lib/app/watchlist.js +102 -0
- package/lib/client.js +4322 -0
- package/lib/core/ai/prompts.js +213 -0
- package/lib/core/chart/axis.js +133 -0
- package/lib/core/chart/bar.js +58 -0
- package/lib/core/chart/candle.js +216 -0
- package/lib/core/chart/line.js +186 -0
- package/lib/core/chart/scale.js +132 -0
- package/lib/core/format.js +143 -0
- package/lib/core/indicators/catalog.js +1011 -0
- package/lib/core/indicators/resolve.js +196 -0
- package/lib/core/insight/digest.js +250 -0
- package/lib/core/insight/rank.js +115 -0
- package/lib/core/insight/related.js +90 -0
- package/lib/core/insight/rules.js +417 -0
- package/lib/core/stats/derive.js +123 -0
- package/lib/core/stats/series.js +465 -0
- package/lib/core/time/range.js +242 -0
- package/lib/core/types.js +478 -0
- package/lib/host/ai/discussion.js +559 -0
- package/lib/host/ai/dsh-llm-gateway.js +333 -0
- package/lib/host/config.js +194 -0
- package/lib/host/http/respond.js +165 -0
- package/lib/host/http/routes.js +689 -0
- package/lib/host/index.js +293 -0
- package/lib/host/infra/fs-repos.js +179 -0
- package/lib/host/infra/memory-fallback.js +64 -0
- package/lib/host/tools/define-tool.js +295 -0
- package/lib/host/tools/register.js +431 -0
- package/lib/host.js +7 -0
- package/lib/ports/clock.js +57 -0
- package/lib/ports/snapshot-repo.js +48 -0
- package/lib/sources/eastmoney-macro.js +197 -0
- package/lib/sources/eastmoney-quote.js +201 -0
- package/lib/sources/ecb.js +179 -0
- package/lib/sources/fred.js +207 -0
- package/lib/sources/http.js +136 -0
- package/lib/sources/ohlc.js +36 -0
- package/lib/sources/quote-cascade.js +177 -0
- package/lib/sources/registry.js +153 -0
- package/lib/sources/sina-cn.js +197 -0
- package/lib/sources/sina-us.js +187 -0
- package/lib/sources/tencent.js +158 -0
- package/lib/sources/us-treasury-rates.js +275 -0
- package/lib/sources/us-treasury.js +196 -0
- package/lib/sources/worldbank.js +170 -0
- package/package.json +69 -0
- package/src/app/ai-deterministic.js +327 -0
- package/src/app/ai-validate.js +284 -0
- package/src/app/ai.js +440 -0
- package/src/app/health.js +77 -0
- package/src/app/overview.js +349 -0
- package/src/app/propose-indicator.js +122 -0
- package/src/app/refresh.js +251 -0
- package/src/app/series-view.js +195 -0
- package/src/app/watchlist.js +102 -0
- package/src/client/api.js +323 -0
- package/src/client/components.js +1877 -0
- package/src/client/copy.js +368 -0
- package/src/client/index.js +169 -0
- package/src/client/store.js +219 -0
- package/src/core/ai/prompts.js +213 -0
- package/src/core/chart/axis.js +133 -0
- package/src/core/chart/bar.js +58 -0
- package/src/core/chart/candle.js +216 -0
- package/src/core/chart/line.js +186 -0
- package/src/core/chart/scale.js +132 -0
- package/src/core/format.js +143 -0
- package/src/core/indicators/catalog.js +1011 -0
- package/src/core/indicators/resolve.js +196 -0
- package/src/core/insight/digest.js +250 -0
- package/src/core/insight/rank.js +115 -0
- package/src/core/insight/related.js +90 -0
- package/src/core/insight/rules.js +417 -0
- package/src/core/stats/derive.js +123 -0
- package/src/core/stats/series.js +465 -0
- package/src/core/time/range.js +242 -0
- package/src/core/types.js +478 -0
- package/src/host/ai/discussion.js +559 -0
- package/src/host/ai/dsh-llm-gateway.js +333 -0
- package/src/host/config.js +194 -0
- package/src/host/http/respond.js +165 -0
- package/src/host/http/routes.js +689 -0
- package/src/host/index.js +293 -0
- package/src/host/infra/fs-repos.js +179 -0
- package/src/host/infra/memory-fallback.js +64 -0
- package/src/host/tools/define-tool.js +295 -0
- package/src/host/tools/register.js +431 -0
- package/src/ports/clock.js +57 -0
- package/src/ports/snapshot-repo.js +48 -0
- package/src/sources/eastmoney-macro.js +197 -0
- package/src/sources/eastmoney-quote.js +201 -0
- package/src/sources/ecb.js +179 -0
- package/src/sources/fred.js +207 -0
- package/src/sources/http.js +136 -0
- package/src/sources/ohlc.js +36 -0
- package/src/sources/quote-cascade.js +177 -0
- package/src/sources/registry.js +153 -0
- package/src/sources/sina-cn.js +197 -0
- package/src/sources/sina-us.js +187 -0
- package/src/sources/tencent.js +158 -0
- package/src/sources/us-treasury-rates.js +275 -0
- package/src/sources/us-treasury.js +196 -0
- package/src/sources/worldbank.js +170 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The panel's single transport layer (docs/05 §3).
|
|
3
|
+
*
|
|
4
|
+
* Every call goes to the host half under '/api/show-me-data/*'; the browser never
|
|
5
|
+
* talks to an upstream data source (CORS and provenance both depend on that).
|
|
6
|
+
* Each request has a timeout, is cancellable, and turns a structured error
|
|
7
|
+
* payload into a value the UI can render instead of an exception the UI must
|
|
8
|
+
* catch blindly.
|
|
9
|
+
*
|
|
10
|
+
* @module client/api
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Request timeout in milliseconds (docs/05 §3). */
|
|
14
|
+
export const REQUEST_TIMEOUT_MS = 8000
|
|
15
|
+
|
|
16
|
+
/** The API root. */
|
|
17
|
+
export const API_ROOT = '/api/show-me-data'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Perform one JSON request.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} path - path under {@link API_ROOT}, e.g. '/overview?range=1Y'.
|
|
23
|
+
* @param {{ method?: string, body?: any, signal?: AbortSignal, timeoutMs?: number, fetchImpl?: Function }} [options] - request options.
|
|
24
|
+
* @returns {Promise<{ ok: boolean, status: number, data?: any, error?: { kind: string, detail: string, retryable: boolean } }>} response.
|
|
25
|
+
*/
|
|
26
|
+
export async function request(path, { method = 'GET', body, signal, timeoutMs = REQUEST_TIMEOUT_MS, fetchImpl } = {}) {
|
|
27
|
+
const doFetch = fetchImpl ?? (typeof fetch === 'function' ? fetch : undefined)
|
|
28
|
+
if (doFetch === undefined) {
|
|
29
|
+
return { ok: false, status: 0, error: { kind: 'network', detail: 'fetch is unavailable in this environment', retryable: false } }
|
|
30
|
+
}
|
|
31
|
+
const controller = new AbortController()
|
|
32
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
33
|
+
const composed = typeof AbortSignal !== 'undefined' && typeof AbortSignal.any === 'function' && signal !== undefined
|
|
34
|
+
? AbortSignal.any([signal, controller.signal])
|
|
35
|
+
: controller.signal
|
|
36
|
+
try {
|
|
37
|
+
const response = await doFetch(`${API_ROOT}${path}`, {
|
|
38
|
+
method,
|
|
39
|
+
headers: body === undefined ? undefined : { 'content-type': 'application/json' },
|
|
40
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
41
|
+
signal: composed,
|
|
42
|
+
})
|
|
43
|
+
const text = await response.text()
|
|
44
|
+
let payload
|
|
45
|
+
try {
|
|
46
|
+
payload = text === '' ? undefined : JSON.parse(text)
|
|
47
|
+
} catch {
|
|
48
|
+
payload = undefined
|
|
49
|
+
}
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
status: response.status,
|
|
54
|
+
// The payload rides along on failures too: a discussion whose session was
|
|
55
|
+
// created but whose first turn failed still returns its `sessionId`, and
|
|
56
|
+
// the panel needs it to offer 「打开会话」.
|
|
57
|
+
...(payload === undefined ? {} : { data: payload }),
|
|
58
|
+
error: payload?.error ?? {
|
|
59
|
+
kind: response.status === 404 ? 'not-found' : 'http',
|
|
60
|
+
detail: `请求失败(HTTP ${response.status})`,
|
|
61
|
+
retryable: response.status >= 500,
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, status: response.status, data: payload }
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const aborted = error?.name === 'AbortError'
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
status: 0,
|
|
71
|
+
error: {
|
|
72
|
+
kind: aborted ? 'timeout' : 'network',
|
|
73
|
+
detail: aborted ? `请求超过 ${timeoutMs}ms 未返回` : `网络错误:${error?.message ?? error}`,
|
|
74
|
+
retryable: true,
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
clearTimeout(timer)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Start an SSE request, invoking handlers as events arrive.
|
|
84
|
+
*
|
|
85
|
+
* Uses 'fetch' + a stream reader rather than 'EventSource' so the POST payload
|
|
86
|
+
* and the caller's abort signal both work.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} path - path under {@link API_ROOT}.
|
|
89
|
+
* @param {{ body?: any, signal?: AbortSignal, onText?: (text: string) => void, onDone?: (result: any) => void, onError?: (error: object) => void, fetchImpl?: Function }} options - options.
|
|
90
|
+
* @returns {Promise<{ ok: boolean, result?: any, error?: object }>} completion.
|
|
91
|
+
*/
|
|
92
|
+
export async function stream(path, { body, signal, onText, onDone, onError, fetchImpl } = {}) {
|
|
93
|
+
const doFetch = fetchImpl ?? (typeof fetch === 'function' ? fetch : undefined)
|
|
94
|
+
if (doFetch === undefined) {
|
|
95
|
+
const error = { kind: 'network', detail: 'fetch is unavailable', retryable: false }
|
|
96
|
+
onError?.(error)
|
|
97
|
+
return { ok: false, error }
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const response = await doFetch(`${API_ROOT}${path}`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: { 'content-type': 'application/json', accept: 'text/event-stream' },
|
|
103
|
+
body: JSON.stringify(body ?? {}),
|
|
104
|
+
signal,
|
|
105
|
+
})
|
|
106
|
+
if (!response.ok || response.body === undefined || response.body === null) {
|
|
107
|
+
// The host answers with plain JSON when streaming is unsupported; accept it.
|
|
108
|
+
const text = await response.text()
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(text)
|
|
111
|
+
onDone?.(parsed)
|
|
112
|
+
return { ok: true, result: parsed }
|
|
113
|
+
} catch {
|
|
114
|
+
const error = { kind: 'http', detail: `流式请求失败(HTTP ${response.status})`, retryable: response.status >= 500 }
|
|
115
|
+
onError?.(error)
|
|
116
|
+
return { ok: false, error }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const reader = response.body.getReader()
|
|
120
|
+
const decoder = new TextDecoder()
|
|
121
|
+
let buffer = ''
|
|
122
|
+
let result
|
|
123
|
+
for (;;) {
|
|
124
|
+
const { value, done } = await reader.read()
|
|
125
|
+
if (done) break
|
|
126
|
+
buffer += decoder.decode(value, { stream: true })
|
|
127
|
+
const frames = buffer.split('\n\n')
|
|
128
|
+
buffer = frames.pop() ?? ''
|
|
129
|
+
for (const frame of frames) {
|
|
130
|
+
const parsed = parseSseFrame(frame)
|
|
131
|
+
if (parsed === undefined) continue
|
|
132
|
+
if (parsed.event === 'text') onText?.(parsed.data?.text ?? '')
|
|
133
|
+
else if (parsed.event === 'done') {
|
|
134
|
+
result = parsed.data?.result
|
|
135
|
+
onDone?.(result)
|
|
136
|
+
} else if (parsed.event === 'error') onError?.({ kind: parsed.data?.kind ?? 'internal', detail: parsed.data?.detail ?? '', retryable: true })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { ok: true, result }
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const aborted = error?.name === 'AbortError'
|
|
142
|
+
const payload = { kind: aborted ? 'aborted' : 'network', detail: `${error?.message ?? error}`, retryable: !aborted }
|
|
143
|
+
onError?.(payload)
|
|
144
|
+
return { ok: false, error: payload }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Parse one SSE frame.
|
|
150
|
+
*
|
|
151
|
+
* @param {string} frame - frame text ('event: x\ndata: {...}').
|
|
152
|
+
* @returns {{ event: string, data: any }|undefined} parsed frame.
|
|
153
|
+
*/
|
|
154
|
+
export function parseSseFrame(frame) {
|
|
155
|
+
const trimmed = String(frame ?? '').trim()
|
|
156
|
+
if (trimmed === '') return undefined
|
|
157
|
+
let event = 'message'
|
|
158
|
+
const dataLines = []
|
|
159
|
+
for (const line of trimmed.split('\n')) {
|
|
160
|
+
if (line.startsWith('event:')) event = line.slice(6).trim()
|
|
161
|
+
else if (line.startsWith('data:')) dataLines.push(line.slice(5).trim())
|
|
162
|
+
}
|
|
163
|
+
if (dataLines.length === 0) return { event, data: undefined }
|
|
164
|
+
const raw = dataLines.join('\n')
|
|
165
|
+
try {
|
|
166
|
+
return { event, data: JSON.parse(raw) }
|
|
167
|
+
} catch {
|
|
168
|
+
return { event, data: raw }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The endpoints the panel uses, so no string is built ad hoc in a component. */
|
|
173
|
+
export const api = {
|
|
174
|
+
/**
|
|
175
|
+
* @param {{ range?: string, groups?: string[], ids?: string[], limit?: number, force?: boolean, signal?: AbortSignal }} [options] - request.
|
|
176
|
+
* @returns {Promise<object>} response.
|
|
177
|
+
*/
|
|
178
|
+
overview(options = {}) {
|
|
179
|
+
const query = []
|
|
180
|
+
if (options.range !== undefined) query.push(`range=${encodeURIComponent(options.range)}`)
|
|
181
|
+
if (options.groups !== undefined && options.groups.length > 0) query.push(`groups=${encodeURIComponent(options.groups.join(','))}`)
|
|
182
|
+
if (options.ids !== undefined && options.ids.length > 0) query.push(`ids=${encodeURIComponent(options.ids.join(','))}`)
|
|
183
|
+
if (options.limit !== undefined) query.push(`limit=${options.limit}`)
|
|
184
|
+
if (options.force === true) query.push('force=1')
|
|
185
|
+
return request(`/overview${query.length === 0 ? '' : `?${query.join('&')}`}`, { signal: options.signal })
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* @param {object} options - request.
|
|
189
|
+
* @returns {Promise<object>} response.
|
|
190
|
+
*/
|
|
191
|
+
series(options) {
|
|
192
|
+
const query = [`indicator=${encodeURIComponent(options.indicator)}`]
|
|
193
|
+
if (options.range !== undefined) query.push(`range=${encodeURIComponent(options.range)}`)
|
|
194
|
+
if (options.transform !== undefined) query.push(`transform=${encodeURIComponent(options.transform)}`)
|
|
195
|
+
if (options.compareWith !== undefined) query.push(`compareWith=${encodeURIComponent(options.compareWith)}`)
|
|
196
|
+
if (options.freq !== undefined) query.push(`freq=${encodeURIComponent(options.freq)}`)
|
|
197
|
+
return request(`/series?${query.join('&')}`, { signal: options.signal })
|
|
198
|
+
},
|
|
199
|
+
/**
|
|
200
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
201
|
+
* @returns {Promise<object>} response.
|
|
202
|
+
*/
|
|
203
|
+
watchlist(signal) {
|
|
204
|
+
return request('/watchlist', { signal })
|
|
205
|
+
},
|
|
206
|
+
/**
|
|
207
|
+
* @param {object} body - action payload.
|
|
208
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
209
|
+
* @returns {Promise<object>} response.
|
|
210
|
+
*/
|
|
211
|
+
watchlistAction(body, signal) {
|
|
212
|
+
return request('/watchlist', { method: 'POST', body, signal })
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* @param {string} query - search text.
|
|
216
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
217
|
+
* @returns {Promise<object>} response.
|
|
218
|
+
*/
|
|
219
|
+
search(query, signal) {
|
|
220
|
+
return request(`/catalog/search?q=${encodeURIComponent(query)}`, { signal })
|
|
221
|
+
},
|
|
222
|
+
/**
|
|
223
|
+
* @param {object} body - '{ indicator, range }'.
|
|
224
|
+
* @param {object} handlers - SSE handlers.
|
|
225
|
+
* @returns {Promise<object>} completion.
|
|
226
|
+
*/
|
|
227
|
+
explain(body, handlers = {}) {
|
|
228
|
+
return stream('/ai/explain', { body: { ...body, stream: true }, ...handlers })
|
|
229
|
+
},
|
|
230
|
+
/**
|
|
231
|
+
* @param {object} body - '{ range, indicators }'.
|
|
232
|
+
* @param {object} handlers - SSE handlers.
|
|
233
|
+
* @returns {Promise<object>} completion.
|
|
234
|
+
*/
|
|
235
|
+
summary(body, handlers = {}) {
|
|
236
|
+
return stream('/ai/summary', { body: { ...body, stream: true }, ...handlers })
|
|
237
|
+
},
|
|
238
|
+
/**
|
|
239
|
+
* @param {object} body - '{ question, range }'.
|
|
240
|
+
* @param {object} handlers - SSE handlers.
|
|
241
|
+
* @returns {Promise<object>} completion.
|
|
242
|
+
*/
|
|
243
|
+
ask(body, handlers = {}) {
|
|
244
|
+
return stream('/ai/ask', { body: { ...body, stream: true }, ...handlers })
|
|
245
|
+
},
|
|
246
|
+
/**
|
|
247
|
+
* @param {object} body - '{ text }'.
|
|
248
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
249
|
+
* @returns {Promise<object>} response.
|
|
250
|
+
*/
|
|
251
|
+
propose(body, signal) {
|
|
252
|
+
return request('/ai/propose', { method: 'POST', body, signal })
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* Open (or continue) an independent DSH session about one indicator.
|
|
256
|
+
*
|
|
257
|
+
* @param {{ indicator?: string, group?: string, noteworthy?: string[], question?: string, range?: string, mode?: 'new'|'topic', limit?: number }} body - request; exactly one of 'indicator'/'group' names the subject.
|
|
258
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
259
|
+
* @returns {Promise<object>} response with `{ sessionId, reply, pending, mode }`.
|
|
260
|
+
*/
|
|
261
|
+
discuss(body, signal) {
|
|
262
|
+
// `wait: false` returns as soon as the session exists and the question is
|
|
263
|
+
// queued: the answer streams in the session the GUI switches to, so the
|
|
264
|
+
// panel never blocks for the length of a model turn.
|
|
265
|
+
return request('/discuss', { method: 'POST', body: { wait: false, ...body }, signal, timeoutMs: 30_000 })
|
|
266
|
+
},
|
|
267
|
+
/**
|
|
268
|
+
* Analyse the panel as a whole, or one group of it.
|
|
269
|
+
*
|
|
270
|
+
* @param {{ range?: string, groups?: string[] }} body - request.
|
|
271
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
272
|
+
* @returns {Promise<object>} response with `{ markdown, mode, scope, indicators, noteworthy }`.
|
|
273
|
+
*/
|
|
274
|
+
aiOverview(body, signal) {
|
|
275
|
+
return request('/ai/overview', { method: 'POST', body, signal, timeoutMs: 120_000 })
|
|
276
|
+
},
|
|
277
|
+
/**
|
|
278
|
+
* Open a session in the plugin workspace to add a data source.
|
|
279
|
+
*
|
|
280
|
+
* @param {{ request?: string, reasons?: string[], question?: string }} body - request.
|
|
281
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
282
|
+
* @returns {Promise<object>} response with `{ sessionId }`.
|
|
283
|
+
*/
|
|
284
|
+
iterate(body, signal) {
|
|
285
|
+
return request('/iterate', { method: 'POST', body, signal, timeoutMs: 30_000 })
|
|
286
|
+
},
|
|
287
|
+
/**
|
|
288
|
+
* Collect the answer a discussion session produced.
|
|
289
|
+
*
|
|
290
|
+
* Opening a discussion returns before the model finishes, so the panel asks
|
|
291
|
+
* for the text on a short poll instead of leaving its block empty while the
|
|
292
|
+
* answer streams in a session the reader has to go and find.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} sessionId - session id from `discuss()`.
|
|
295
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
296
|
+
* @returns {Promise<object>} response with `{ status, reply?, error? }`.
|
|
297
|
+
*/
|
|
298
|
+
discussAnswer(sessionId, signal) {
|
|
299
|
+
return request(`/discuss/answer?session=${encodeURIComponent(sessionId)}`, { signal, timeoutMs: 10_000 })
|
|
300
|
+
},
|
|
301
|
+
/**
|
|
302
|
+
* The mount's effective configuration, for the settings screen.
|
|
303
|
+
*
|
|
304
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
305
|
+
* @returns {Promise<object>} response.
|
|
306
|
+
*/
|
|
307
|
+
settings(signal) {
|
|
308
|
+
return request('/settings', { signal, timeoutMs: 10_000 })
|
|
309
|
+
},
|
|
310
|
+
/**
|
|
311
|
+
* @param {AbortSignal} [signal] - cancellation.
|
|
312
|
+
* @returns {Promise<object>} response.
|
|
313
|
+
*/
|
|
314
|
+
health({ probe, ids, range } = {}, signal) {
|
|
315
|
+
const query = []
|
|
316
|
+
if (probe === true) query.push('probe=1')
|
|
317
|
+
if (Array.isArray(ids) && ids.length > 0) query.push(`ids=${encodeURIComponent(ids.join(','))}`)
|
|
318
|
+
if (range !== undefined) query.push(`range=${encodeURIComponent(range)}`)
|
|
319
|
+
// A probe re-fetches upstream series, so it needs a longer budget than a
|
|
320
|
+
// cached read.
|
|
321
|
+
return request(`/health${query.length === 0 ? '' : `?${query.join('&')}`}`, { signal, timeoutMs: probe === true ? 60_000 : REQUEST_TIMEOUT_MS })
|
|
322
|
+
},
|
|
323
|
+
}
|