@viztor/dsh-tinyfish 0.3.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.

Potentially problematic release.


This version of @viztor/dsh-tinyfish might be problematic. Click here for more details.

@@ -0,0 +1,405 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { WebError, WebFetchProvider, WebFetchRequest, WebFetchResult, WebSearchProvider, WebSearchRequest, WebSearchResult } from "@deepseek-ai/dsh-web";
3
+ import { Context } from "@deepseek-ai/cordis";
4
+ //#region src/client.d.ts
5
+ /**
6
+ * TinyFish transport for the DSH web capability seam.
7
+ *
8
+ * Two channels reach the same upstream:
9
+ *
10
+ * monid — POST https://api.monid.ai/v1/run {provider:"tinyfish", …}
11
+ * Auth: `Authorization: Bearer <monid platform key>`
12
+ * Response: `{ status, output, … }`
13
+ *
14
+ * direct — the TinyFish API directly
15
+ * Search: GET https://api.search.tinyfish.ai?query=…
16
+ * Fetch: POST https://api.fetch.tinyfish.ai
17
+ * Auth: `X-API-Key: <tinyfish key>`
18
+ * Response: the payload itself
19
+ *
20
+ * Monid is a thin envelope: its `output` is byte-for-byte the direct response,
21
+ * and it forwards the parameter names unchanged (`query`, `location`,
22
+ * `include_domains`, `after_date`, `urls`, `format`, …). So both channels
23
+ * normalise to one shape here and the providers above never branch.
24
+ *
25
+ * @module dsh-tinyfish/client
26
+ */
27
+ /** Which upstream route a call takes. */
28
+ type TinyfishChannel = "monid" | "direct";
29
+ /** Monid REST base. */
30
+ export declare const DEFAULT_MONID_BASE = "https://api.monid.ai";
31
+ /** TinyFish's own search and fetch bases. */
32
+ export declare const DEFAULT_SEARCH_BASE = "https://api.search.tinyfish.ai";
33
+ export declare const DEFAULT_FETCH_BASE = "https://api.fetch.tinyfish.ai";
34
+ export declare const WEB_PROVIDER_ERROR = "WEB_PROVIDER_ERROR";
35
+ export declare const WEB_PROVIDER_CREDENTIAL_MISSING = "WEB_PROVIDER_CREDENTIAL_MISSING";
36
+ export declare const WEB_ABORTED = "WEB_ABORTED";
37
+ /** One hit from TinyFish's `/search`. Only the fields this adapter reads. */
38
+ interface TinyfishSearchHit {
39
+ position?: number;
40
+ site_name?: string;
41
+ title?: string;
42
+ snippet?: string;
43
+ description?: string;
44
+ url?: string;
45
+ date?: string;
46
+ publisher?: string;
47
+ }
48
+ /** The upstream search payload, byte-identical on both channels. */
49
+ interface TinyfishSearchPayload {
50
+ query?: string;
51
+ results?: TinyfishSearchHit[];
52
+ total_results?: number;
53
+ page?: number;
54
+ }
55
+ /** One page from TinyFish's `/fetch`. */
56
+ interface TinyfishFetchPage {
57
+ url?: string;
58
+ final_url?: string;
59
+ title?: string;
60
+ text?: string;
61
+ published_date?: string;
62
+ latency_ms?: number;
63
+ }
64
+ /** One per-URL failure from `/fetch`. A 404 arrives here, not in `results`. */
65
+ interface TinyfishFetchFailure {
66
+ url?: string;
67
+ error?: string;
68
+ status?: number;
69
+ }
70
+ /** The upstream fetch payload, byte-identical on both channels. */
71
+ interface TinyfishFetchPayload {
72
+ results?: TinyfishFetchPage[];
73
+ errors?: TinyfishFetchFailure[];
74
+ }
75
+ /**
76
+ * Resolves a stored credential by reference name.
77
+ *
78
+ * The shape matches `@deepseek-ai/dsh-credentials`: given a name, return the
79
+ * stored value or undefined. Passed in rather than imported so the service
80
+ * stays an optional peer — a host without it falls through to the CLI stores.
81
+ */
82
+ type CredentialResolver = (name: string) => Promise<string | undefined>;
83
+ /** Inputs for credential resolution, in precedence order. */
84
+ interface ResolveApiKeyOptions {
85
+ /** Explicit key; wins over everything. */
86
+ apiKey?: string;
87
+ /**
88
+ * Name of a stored credential to ask the harness service for.
89
+ *
90
+ * Checked after the literal key and before the ambient environment, because
91
+ * a stored credential is more specific than whatever happens to be exported.
92
+ * This is the **direct** channel's ref; the monid channel reads
93
+ * {@link ResolveApiKeyOptions.monidKeyEnv}.
94
+ */
95
+ apiKeyEnv?: string;
96
+ /**
97
+ * The monid channel's stored-credential name, kept separate from
98
+ * {@link ResolveApiKeyOptions.apiKeyEnv} so a user can save both keys.
99
+ *
100
+ * One shared ref would hand a TinyFish key to Monid as its bearer token,
101
+ * which fails upstream as a 401 — indistinguishable from "your Monid key is
102
+ * wrong". Left unset, the monid channel skips the store and falls through to
103
+ * its own rungs (`MONID_API_KEY`, `MONID_MCP_TOKEN`, the CLI store).
104
+ */
105
+ monidKeyEnv?: string;
106
+ /** The harness credentials service, when the host provides one. */
107
+ resolveCredential?: CredentialResolver;
108
+ /** Override the Monid CLI credential path. */
109
+ credentialsPath?: string;
110
+ /** Override the TinyFish CLI config path. */
111
+ tinyfishConfigPath?: string;
112
+ /** Environment to read. Defaults to `process.env`; injectable for tests. */
113
+ env?: Record<string, string | undefined>;
114
+ /** Caller cancellation, honoured by the async credential lookup. */
115
+ signal?: AbortSignal;
116
+ }
117
+ /**
118
+ * Resolve the credential for one channel.
119
+ *
120
+ * Explicit config wins, then the environment, then the channel's own
121
+ * credential store. Stores are re-read per call rather than cached at module
122
+ * load: they are a few hundred bytes, and caching would pin a rotated key
123
+ * inside a long-lived host process.
124
+ */
125
+ export declare function resolveApiKey(channel: TinyfishChannel, options?: ResolveApiKeyOptions): string;
126
+ /** Everything a search call can be pointed at. */
127
+ interface TinyfishSearchOptions extends ResolveApiKeyOptions {
128
+ channel: TinyfishChannel;
129
+ query: string;
130
+ /** Upstream-named filters, already snake_case. */
131
+ filters?: Record<string, string | number>;
132
+ monidBase?: string;
133
+ searchBase?: string;
134
+ signal?: AbortSignal;
135
+ attempts?: number;
136
+ /** Base backoff between attempts, in ms; doubles per attempt. Default 1200. */
137
+ delayMs?: number;
138
+ onRetry?: (attempt: number, total: number) => void;
139
+ /** Poll cadence for an async Monid run. */
140
+ pollMs?: number;
141
+ maxPolls?: number;
142
+ }
143
+ /** Everything a fetch call can be pointed at. */
144
+ interface TinyfishFetchOptions extends ResolveApiKeyOptions {
145
+ channel: TinyfishChannel;
146
+ urls: string[];
147
+ /** Goal statement forwarded upstream; TinyFish ranks on it. */
148
+ purpose?: string;
149
+ monidBase?: string;
150
+ fetchBase?: string;
151
+ signal?: AbortSignal;
152
+ attempts?: number;
153
+ /** Base backoff between attempts, in ms; doubles per attempt. Default 1200. */
154
+ delayMs?: number;
155
+ onRetry?: (attempt: number, total: number) => void;
156
+ }
157
+ /**
158
+ * Search TinyFish. Returns the upstream payload for both channels:
159
+ * `{ query, results[], total_results, page }`.
160
+ */
161
+ export declare function tinyfishSearch(options: TinyfishSearchOptions): Promise<TinyfishSearchPayload>;
162
+ /**
163
+ * Fetch up to 10 URLs as clean Markdown. Returns the upstream payload for both
164
+ * channels: `{ results[], errors[] }`.
165
+ */
166
+ export declare function tinyfishFetch(options: TinyfishFetchOptions): Promise<TinyfishFetchPayload>;
167
+ //#endregion
168
+ //#region src/provider.d.ts
169
+ /**
170
+ * The two `ctx.web` providers.
171
+ *
172
+ * Both are thin: dispatch through the transport, then normalise TinyFish's
173
+ * payload into the seam's vocabulary. The seam owns `maxResults` truncation,
174
+ * cancellation, error codes and the tool card, so nothing here re-implements
175
+ * any of that.
176
+ *
177
+ * @module dsh-tinyfish/provider
178
+ */
179
+ /** Stable id these providers register under. */
180
+ export declare const TINYFISH_PROVIDER_ID = "tinyfish";
181
+ /** Options a provider snapshots once per operation. */
182
+ interface TinyfishProviderOptions {
183
+ readonly channel: TinyfishChannel;
184
+ /** A literal credential, if the settings row carries one. */
185
+ readonly apiKey?: string;
186
+ /**
187
+ * Name of a stored credential or environment variable to read.
188
+ *
189
+ * Resolved through the harness credentials service when one is present, so
190
+ * the value can be rotated or supplied from Settings without editing a patch
191
+ * file — and without the plugin ever holding a copy.
192
+ */
193
+ readonly apiKeyEnv: string;
194
+ /**
195
+ * The monid channel's stored-credential name.
196
+ *
197
+ * Separate from {@link TinyfishProviderOptions.apiKeyEnv} because the two
198
+ * channels authenticate against different services, so a user who has both
199
+ * keys must be able to save both without one overwriting the other.
200
+ */
201
+ readonly monidKeyEnv: string;
202
+ /**
203
+ * Resolves a stored credential by name, when the host provides a service.
204
+ *
205
+ * Built by `apply` from `ctx`, so the plugin uses the *host's* credentials
206
+ * service instance rather than a private copy — a second instance would have
207
+ * its own store and never see what the user changed in Settings.
208
+ */
209
+ readonly resolveCredential?: CredentialResolver;
210
+ /** Goal statement forwarded upstream; TinyFish ranks on it. */
211
+ readonly purpose?: string;
212
+ /** Upstream-named search filters, already snake_case. */
213
+ readonly filters: Readonly<Record<string, string>>;
214
+ readonly attempts: number;
215
+ readonly monidBase: string;
216
+ readonly searchBase: string;
217
+ readonly fetchBase: string;
218
+ /**
219
+ * Whether to *offer* this kind. Both providers always register, so a disabled
220
+ * kind reports itself unavailable rather than missing — `dsh-web` distinguishes
221
+ * `WEB_PROVIDER_CONFIGURED_MISSING` (a named id that was never registered,
222
+ * usually a broken install) from `WEB_PROVIDER_UNAVAILABLE` (a registered
223
+ * provider that declined), and the second is what "I turned this off" means.
224
+ */
225
+ readonly search: boolean;
226
+ readonly fetch: boolean;
227
+ }
228
+ /** Resolves the options for the *next* operation. */
229
+ type TinyfishOptionsSource = () => TinyfishProviderOptions;
230
+ /**
231
+ * TinyFish reports dates as human strings ("Apr 30, 2026", "1 year ago"), but
232
+ * `WebSearchSource.publishedAt` is contractually an ISO-8601 string. Rather
233
+ * than pass a value the type does not promise, coerce what parses and drop the
234
+ * rest — a missing date is honest, a malformed one is not.
235
+ *
236
+ * A date carrying no timezone is read as UTC, not local. `Date.parse("Apr 30,
237
+ * 2026")` means local midnight, so `.toISOString()` would shift the day for any
238
+ * host east or west of Greenwich — the same page would report a different
239
+ * `publishedAt` depending on where the Worker ran. These strings are date-only,
240
+ * so UTC midnight is both the stable reading and the one that keeps the day the
241
+ * publisher actually meant.
242
+ */
243
+ export declare function toIsoDate(value: string | undefined): string | undefined;
244
+ /**
245
+ * TinyFish search through the `ctx.web` search seam.
246
+ *
247
+ * The seam's request is only `{query, maxResults}`; everything else
248
+ * (`domainType`, `location`, `includeDomains`, …) comes from plugin config and
249
+ * applies to every query, which is the shape these filters actually want.
250
+ */
251
+ export declare class TinyfishSearchProvider implements WebSearchProvider {
252
+ readonly id = "tinyfish";
253
+ private readonly resolveOptions;
254
+ /**
255
+ * @param resolveOptions - a thunk, not a value: the plugin's settings section
256
+ * can change between searches, and re-registering the provider to carry a
257
+ * new config would make the seam's selection flicker for the user.
258
+ */
259
+ constructor(resolveOptions: TinyfishOptionsSource);
260
+ /**
261
+ * Cheap local usability check. Must not make network calls — the seam calls
262
+ * this to decide between providers, and a network call here would turn
263
+ * selection into a latency spike on every search.
264
+ *
265
+ * Checks the same things the shipped providers do: a credential is
266
+ * resolvable *and* both endpoints parse as URLs. A misconfigured base is a
267
+ * setup mistake worth surfacing at selection time rather than as a 404 later.
268
+ */
269
+ available(): boolean;
270
+ search(request: WebSearchRequest, signal?: AbortSignal): Promise<WebSearchResult>;
271
+ }
272
+ /**
273
+ * TinyFish fetch through the `ctx.web` fetch seam.
274
+ *
275
+ * Returns `kind: "text"` on purpose: TinyFish already extracts clean Markdown,
276
+ * so `dsh-tool-web` passes it straight through. The `http` provider instead
277
+ * returns `kind: "html"` and pays for a turndown conversion this path skips.
278
+ */
279
+ export declare class TinyfishFetchProvider implements WebFetchProvider {
280
+ readonly id = "tinyfish";
281
+ /** A thunk, not a value; see {@link TinyfishSearchProvider} for why. */
282
+ private readonly resolveOptions;
283
+ constructor(resolveOptions: TinyfishOptionsSource);
284
+ /**
285
+ * Cheap local usability check. Must not make network calls — the seam calls
286
+ * this to decide between providers, and a network call here would turn
287
+ * selection into a latency spike on every search.
288
+ *
289
+ * Checks the same things the shipped providers do: a credential is
290
+ * resolvable *and* both endpoints parse as URLs. A misconfigured base is a
291
+ * setup mistake worth surfacing at selection time rather than as a 404 later.
292
+ */
293
+ available(): boolean;
294
+ fetch(request: WebFetchRequest, signal?: AbortSignal): Promise<WebFetchResult>;
295
+ }
296
+ //#endregion
297
+ //#region src/index.d.ts
298
+ /**
299
+ * `dsh-tinyfish` — TinyFish-backed search and fetch for the DSH web
300
+ * capability seam.
301
+ *
302
+ * Registers two providers under one id, `tinyfish`, and lets the profile pick
303
+ * the channel:
304
+ *
305
+ * monid — reach TinyFish through the Monid REST API. Costs nothing on the
306
+ * Monid wallet, and reuses the credential the MCP mount already
307
+ * holds, so it keeps working when no TinyFish account is configured.
308
+ * direct — call TinyFish's own API, using the key the `tinyfish` CLI already
309
+ * stored in ~/.tinyfish/config.json.
310
+ *
311
+ * Both channels return the same upstream payload, so nothing above this file
312
+ * branches on which one is active.
313
+ *
314
+ * @module dsh-tinyfish
315
+ */
316
+ /** Settings namespace, matching the `<kind>-<provider>` convention. */
317
+ export declare const WEB_TINYFISH_SETTINGS_NAMESPACE = "web-tinyfish";
318
+ /**
319
+ * The plugin's settings schema.
320
+ *
321
+ * This is the harness's own `@deepseek-ai/schemastery` fork rather than the
322
+ * public package, because the fork is what implements the `.role()`,
323
+ * `.volatile()` and `.get()` surface the loader and the settings UI both rely
324
+ * on, and the public 3.18.x line does not have it. Cordis resolves a plugin's
325
+ * `Config` through `resolveConfig` and falls back to the raw row when a plugin
326
+ * exports none — so exporting this is what makes the row render as a real
327
+ * settings section rather than free-form YAML.
328
+ *
329
+ * - `role("secret")` keeps a literal key out of any redacted dump.
330
+ * - `role("credential-ref")` makes a field a *reference* to a stored
331
+ * credential, resolved through `ctx.get("credentials")` — the way the shipped
332
+ * search provider does it, so the value is manageable from Settings instead
333
+ * of only from a patch file.
334
+ * - `volatile()` marks a field that must be re-read at the start of every
335
+ * operation. Everything here is: a key can be rotated while the harness is
336
+ * running, and a provider that captured one at load time would keep sending
337
+ * a dead credential.
338
+ */
339
+ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
340
+ channel: z<"direct" | "monid", "direct" | "monid", "defined">;
341
+ apiKey: z<string, string, "volatile">;
342
+ apiKeyEnv: z<string, string, "volatile-defined">;
343
+ monidKeyEnv: z<string, string, "volatile-defined">;
344
+ purpose: z<string, string, "volatile">;
345
+ attempts: z<number, number, "volatile-defined">;
346
+ filters: z<NoInfer<Schemastery.ObjectS<NoInfer<{
347
+ domainType: z<"news" | "research_paper" | "web", "news" | "research_paper" | "web", "plain">;
348
+ language: z<string, string, "plain">;
349
+ location: z<string, string, "plain">;
350
+ includeDomains: z<string, string, "plain">;
351
+ excludeDomains: z<string, string, "plain">;
352
+ }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
353
+ domainType: z<"news" | "research_paper" | "web", "news" | "research_paper" | "web", "plain">;
354
+ language: z<string, string, "plain">;
355
+ location: z<string, string, "plain">;
356
+ includeDomains: z<string, string, "plain">;
357
+ excludeDomains: z<string, string, "plain">;
358
+ }>>>, "volatile">;
359
+ monidBase: z<string, string, "defined">;
360
+ searchBase: z<string, string, "defined">;
361
+ fetchBase: z<string, string, "defined">;
362
+ search: z<boolean, boolean, "defined">;
363
+ fetch: z<boolean, boolean, "defined">;
364
+ }>>, Schemastery.ObjectT<NoInfer<{
365
+ channel: z<"direct" | "monid", "direct" | "monid", "defined">;
366
+ apiKey: z<string, string, "volatile">;
367
+ apiKeyEnv: z<string, string, "volatile-defined">;
368
+ monidKeyEnv: z<string, string, "volatile-defined">;
369
+ purpose: z<string, string, "volatile">;
370
+ attempts: z<number, number, "volatile-defined">;
371
+ filters: z<NoInfer<Schemastery.ObjectS<NoInfer<{
372
+ domainType: z<"news" | "research_paper" | "web", "news" | "research_paper" | "web", "plain">;
373
+ language: z<string, string, "plain">;
374
+ location: z<string, string, "plain">;
375
+ includeDomains: z<string, string, "plain">;
376
+ excludeDomains: z<string, string, "plain">;
377
+ }>>>, NoInfer<Schemastery.ObjectT<NoInfer<{
378
+ domainType: z<"news" | "research_paper" | "web", "news" | "research_paper" | "web", "plain">;
379
+ language: z<string, string, "plain">;
380
+ location: z<string, string, "plain">;
381
+ includeDomains: z<string, string, "plain">;
382
+ excludeDomains: z<string, string, "plain">;
383
+ }>>>, "volatile">;
384
+ monidBase: z<string, string, "defined">;
385
+ searchBase: z<string, string, "defined">;
386
+ fetchBase: z<string, string, "defined">;
387
+ search: z<boolean, boolean, "defined">;
388
+ fetch: z<boolean, boolean, "defined">;
389
+ }>>, "plain">;
390
+ /** Cordis service dependencies. */
391
+ export declare const inject: string[];
392
+ /** The bundle name. Must equal the manifest `name`: the loader matches on it. */
393
+ export declare const name = "dsh-tinyfish";
394
+ /** Project one resolved section into the options the next operation serves. */
395
+ export declare function resolveOptions(config: unknown, ctx?: Context, env?: Record<string, string | undefined>): TinyfishProviderOptions;
396
+ /**
397
+ * Register both providers with `ctx.web`.
398
+ *
399
+ * Selection stays the profile's call: this plugin only offers `tinyfish`, and
400
+ * `dsh-web`'s `searchProvider` / `fetchProvider` decide whether it is used.
401
+ * Reverting is two words in the patch, with this plugin still mounted.
402
+ */
403
+ export declare function apply(ctx: Context, config?: unknown): void;
404
+ //#endregion
405
+ export { type TinyfishChannel, type TinyfishFetchPayload, type TinyfishOptionsSource, type TinyfishProviderOptions, type TinyfishSearchPayload, WebError };