@hilbras/omnihilbras 1.86.1 → 1.87.1

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/index.d.ts CHANGED
@@ -24,6 +24,7 @@ export * from './providers/deepseek-web/index.js';
24
24
  export * from './providers/qwen-web/index.js';
25
25
  export * from './providers/gemini-web/index.js';
26
26
  export * from './providers/tokenharbor-web/index.js';
27
+ export * from './providers/duckduckgo-web/index.js';
27
28
  export * from './core/url.js';
28
29
  export * from './core/bounded-map.js';
29
30
  export * from './core/provider-slugs.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AACrD,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ export * from './providers/deepseek-web/index.js';
24
24
  export * from './providers/qwen-web/index.js';
25
25
  export * from './providers/gemini-web/index.js';
26
26
  export * from './providers/tokenharbor-web/index.js';
27
+ export * from './providers/duckduckgo-web/index.js';
27
28
  export * from './core/url.js';
28
29
  export * from './core/bounded-map.js';
29
30
  export * from './core/provider-slugs.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AACrD,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gCAAgC,CAAC;AAC/C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uCAAuC,CAAC;AACtD,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,0CAA0C,CAAC;AACzD,cAAc,mCAAmC,CAAC;AAClD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC"}
@@ -44,7 +44,7 @@ const defaultRefreshSkewMs = 60_000;
44
44
  /** Refreshes in flight, keyed by the login they renew, so adapters that share a login share one renewal. */
45
45
  const inFlightRefreshes = new Map();
46
46
  /** Reported to Cline as this client's version. */
47
- const omnihilbrasVersion = '1.86.1';
47
+ const omnihilbrasVersion = '1.87.1';
48
48
  /** Cline only accepts WorkOS JWTs with an explicit prefix. */
49
49
  export function toClineAccessToken(token) {
50
50
  const trimmed = token.trim();
@@ -0,0 +1,264 @@
1
+ import { ProviderError } from '../../core/errors.js';
2
+ import type { ChatChunk, ChatRequest, ChatResponse, CredentialValidation, Model, ProviderAdapter, ProviderCredential, ProviderHealth, ProviderRequestContext } from '../../core/types.js';
3
+ /**
4
+ * DuckDuckGo AI Chat (`duck.ai`), as a web-cookie provider whose cookie is **optional**.
5
+ *
6
+ * ## Why the cookie is optional, and why the card still offers one
7
+ *
8
+ * The service is anonymous: `/duckchat/v1/models` answers 200 with no credential at all, and a turn works
9
+ * without one — measured from this host, twice, before any of this was written. So a pasted cookie is
10
+ * hardening against rate limits rather than a requirement, and the card says so. Two things follow that the
11
+ * code has to get right: a credential of `{ type: 'none' }` must be accepted everywhere a cookie is, and a
12
+ * pasted cookie must never be *claimed* to be verified, because this service does not read it.
13
+ *
14
+ * ## The three traps, all measured rather than read
15
+ *
16
+ * 1. **The delta arrives in `message`, not `content`.** A reader that checks only `content` returns HTTP 200
17
+ * with an **empty answer** — the spike's own first attempt did exactly that, which is why
18
+ * `duckDuckGoWebContent` checks both and why the test pins the empty case.
19
+ * 2. **Free models are `accessTier` on the model itself.** The live catalog marks 6 models free there, and
20
+ * only 3 by `reasoningEffortAccess[].accessTier` — a parser keyed on the wrong field silently drops half
21
+ * the free lineup. See `freeDuckDuckGoWebModels`.
22
+ * 3. **The stream opens with `data: [CHAT_TITLE:...]`**, a non-JSON pseudo-event. `parseDuckDuckGoWebDataLine`
23
+ * returns `undefined` for it rather than throwing.
24
+ *
25
+ * ## What lives where
26
+ *
27
+ * Everything here is testable without a network: the cookie parsing, the catalog filter, the stream parsing,
28
+ * the payload builder, and every refusal. The handshake, the challenge and the request itself are the
29
+ * driver's, declared here and implemented in the gateway — the same boundary `gemini-web` uses, and required
30
+ * here too because the challenge solver needs `node:vm`.
31
+ */
32
+ export declare const DUCKDUCKGO_WEB: {
33
+ readonly origin: "https://duck.ai";
34
+ readonly modelsPath: "/duckchat/v1/models";
35
+ readonly statusPath: "/duckchat/v1/status";
36
+ readonly chatPath: "/duckchat/v1/chat";
37
+ readonly homePath: "/";
38
+ /**
39
+ * The model a request gets when it names none.
40
+ *
41
+ * `gpt-5.4-mini` because it is free **today** — verified against the live catalog — and because it is the
42
+ * one free id that has been free on every reading. It is not a promise: a catalog read that no longer lists
43
+ * it free is refused by name rather than routed to something else. See `freeDuckDuckGoWebModels`.
44
+ */
45
+ readonly defaultModel: "gpt-5.4-mini";
46
+ /** The service's own default effort, and the value the successful requests used. */
47
+ readonly defaultReasoningEffort: "none";
48
+ readonly statusTimeoutMs: 15000;
49
+ readonly catalogTimeoutMs: 15000;
50
+ readonly homeTimeoutMs: 15000;
51
+ readonly chatTimeoutMs: 60000;
52
+ readonly userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36";
53
+ };
54
+ export declare const duckduckgoWebProviderId = "duckduckgo-web";
55
+ /**
56
+ * A pasted cookie in any of the three shapes a browser hands over.
57
+ *
58
+ * Bare value, a full `Cookie:` header line, or a JSON export. Returns `undefined` when the paste carries
59
+ * nothing, so the caller can tell "the user pasted junk" from "there is no cookie", which are different
60
+ * answers: the first is a mistake to report, the second is this provider's normal case.
61
+ */
62
+ export declare function normalizeDuckDuckGoWebCookie(value: string): string | undefined;
63
+ /**
64
+ * The `Cookie` header for a credential, or `undefined` when there is none to send.
65
+ *
66
+ * `{ type: 'none' }` is the **normal** case for this provider, not a missing value: it is what the connect
67
+ * route stores when the user pastes nothing, and the request goes out without a `Cookie` header. Every other
68
+ * web-cookie adapter in this repository refuses that credential, so this is the one place the difference is
69
+ * visible.
70
+ */
71
+ export declare function duckDuckGoWebCookieHeader(credential: ProviderCredential | undefined): string | undefined;
72
+ export type DuckDuckGoWebModel = {
73
+ id: string;
74
+ displayName: string;
75
+ };
76
+ type UnknownRecord = Record<string, unknown>;
77
+ /**
78
+ * The models the live catalog offers on a **free** tier.
79
+ *
80
+ * **The field is `accessTier` on the model itself**, and that is a measurement rather than a reading. Against
81
+ * the live catalog on 2026-10-17:
82
+ *
83
+ * ```
84
+ * free by model.accessTier : 6 (gpt-6-luna, gpt-5.4-mini, claude-haiku-4-5,
85
+ * mistral-small-2603, tinfoil/gpt-oss-120b, tinfoil/gemma4-31b)
86
+ * free by reasoningEffortAccess[].accessTier : 3 (gpt-5.4-mini, claude-haiku-4-5, tinfoil/gpt-oss-120b)
87
+ * ```
88
+ *
89
+ * Reading the nested field looks more precise and is simply wrong: it describes which *effort levels* an
90
+ * account tier may select, not whether the model is served. A parser keyed on it silently drops half the
91
+ * free lineup, which is why the first test in `duckduckgo-web.test.js` asserts both numbers.
92
+ *
93
+ * The list is read per request rather than pinned, because the lineup rotates — `gpt-6-luna` had appeared and
94
+ * three of the source's hardcoded "free" ids no longer were, inside a week.
95
+ */
96
+ export declare function freeDuckDuckGoWebModels(catalog: unknown): DuckDuckGoWebModel[];
97
+ export declare function duckDuckGoWebModelIds(catalog: unknown): string[];
98
+ /**
99
+ * One `data:` line, or `undefined` for everything that is not answer text.
100
+ *
101
+ * Non-JSON lines are dropped rather than thrown on: the stream opens with `data: [CHAT_TITLE:...]` before any
102
+ * JSON arrives, and `[DONE]` terminates it. Both are normal, and a parser that threw on them would fail every
103
+ * turn at the first byte.
104
+ */
105
+ export declare function parseDuckDuckGoWebDataLine(line: string): UnknownRecord | undefined;
106
+ /**
107
+ * The text of one event, from `content` **or** `message`.
108
+ *
109
+ * This is trap 1, and it is the difference between an answer and a 200 with nothing in it. The live service
110
+ * sends `{"action":"success","message":"..."}` and then a run of `{"message":"..."}` frames; `content` never
111
+ * appears. A reader that looked only for `content` — which is what every other provider here sends — would
112
+ * return an empty string and report success.
113
+ */
114
+ export declare function duckDuckGoWebContent(event: UnknownRecord | undefined): string;
115
+ /** An RSA public key, as the service expects it inside `durableStream`. */
116
+ export type DuckDuckGoWebPublicKey = {
117
+ kty: string;
118
+ n?: string;
119
+ e?: string;
120
+ alg: string;
121
+ ext: boolean;
122
+ key_ops: string[];
123
+ use: string;
124
+ };
125
+ /**
126
+ * The request body.
127
+ *
128
+ * `publicKey` is a **parameter**, not generated here, and that is a hard boundary rather than a preference:
129
+ * generating an RSA key pair needs `node:crypto`, and this module is reachable from the SDK barrel, which the
130
+ * dashboard imports. `tests/sdk-browser-safety.test.js` fails if anything reachable from that barrel imports a
131
+ * Node builtin — the 1.42.0 blank page — so the gateway generates the key and passes it in.
132
+ *
133
+ * `reasoningEffort` is always sent. The working implementation records it as **required**, with a 400
134
+ * `ERR_BAD_REQUEST` when omitted, verified live on its side. Not re-measured here, because proving it costs a
135
+ * chat POST against a rate-limited endpoint, and the requests that succeeded all sent it.
136
+ */
137
+ export declare function buildDuckDuckGoWebPayload(input: {
138
+ model: string;
139
+ messages: readonly {
140
+ role: string;
141
+ content: string;
142
+ }[];
143
+ publicKey: DuckDuckGoWebPublicKey;
144
+ conversationId: string;
145
+ messageId: string;
146
+ reasoningEffort?: string;
147
+ }): UnknownRecord;
148
+ /**
149
+ * A refusal with a **kind**, so the adapter decides retryability and the driver only reports what happened.
150
+ *
151
+ * The alternative — throwing `ProviderError` from the driver — would put routing policy in the gateway, and
152
+ * the alternative this file avoids most deliberately is classifying by *message*: `ERR_CHALLENGE` and
153
+ * `ERR_BN_LIMIT` arrive in the same JSON shape and, per the working implementation's own measurement, the
154
+ * service maps both to the same user-facing message token. A regex over prose cannot tell them apart; a field
155
+ * can.
156
+ */
157
+ export declare class DuckDuckGoWebError extends Error {
158
+ readonly kind: 'challenge' | 'quota' | 'rate-limit' | 'unavailable' | 'bad-request';
159
+ readonly overrideCode?: string | undefined;
160
+ constructor(kind: 'challenge' | 'quota' | 'rate-limit' | 'unavailable' | 'bad-request', message: string, overrideCode?: string | undefined);
161
+ }
162
+ export type DuckDuckGoWebDriver = {
163
+ /** Whether this host can solve the challenge at all. A `vm` is always present on Node, so this is honest. */
164
+ available: () => Promise<{
165
+ ok: true;
166
+ } | {
167
+ ok: false;
168
+ reason: string;
169
+ }>;
170
+ /** The live catalog, unfiltered — the adapter decides what is free. */
171
+ catalog: (input: {
172
+ timeoutMs?: number;
173
+ signal?: AbortSignal;
174
+ }) => Promise<unknown>;
175
+ /** The handshake, reported as facts: whether a challenge was served and how long solving took. */
176
+ status: (input: {
177
+ timeoutMs?: number;
178
+ signal?: AbortSignal;
179
+ }) => Promise<{
180
+ challengeServed: boolean;
181
+ solvedMs: number;
182
+ }>;
183
+ /** One turn. `cookieHeader` is `undefined` when the connection holds `{ type: 'none' }`. */
184
+ ask: (input: {
185
+ cookieHeader?: string;
186
+ model: string;
187
+ messages: readonly {
188
+ role: string;
189
+ content: string;
190
+ }[];
191
+ timeoutMs: number;
192
+ signal?: AbortSignal;
193
+ }) => Promise<{
194
+ text: string;
195
+ model: string;
196
+ }>;
197
+ };
198
+ export type DuckDuckGoWebAdapterOptions = {
199
+ driver: DuckDuckGoWebDriver;
200
+ id?: string;
201
+ now?: () => number;
202
+ };
203
+ export declare class DuckDuckGoWebAdapter implements ProviderAdapter {
204
+ readonly id: string;
205
+ readonly name = "Duck.ai";
206
+ /**
207
+ * Streaming is not implemented and saying otherwise would be false: this adapter reads the service's SSE
208
+ * and hands back the assembled answer, so no token arrives "over time" as far as a caller is concerned.
209
+ */
210
+ readonly capabilities: {
211
+ readonly chat: true;
212
+ readonly streaming: false;
213
+ readonly models: true;
214
+ };
215
+ private readonly driver;
216
+ constructor(options: DuckDuckGoWebAdapterOptions);
217
+ listModels(): Promise<Model[]>;
218
+ /**
219
+ * A credential is valid when it is absent, or when it looks like a cookie header.
220
+ *
221
+ * `{ type: 'none' }` returning `valid` is the whole point of this provider — and it is why the *check* the
222
+ * dashboard runs is the handshake rather than this method. Nothing here contacts the service, so it cannot
223
+ * claim a cookie works; it can only refuse one that is malformed.
224
+ */
225
+ validateCredential(credential: ProviderCredential | undefined): Promise<CredentialValidation>;
226
+ /**
227
+ * The refusal worth making before a request is spent.
228
+ *
229
+ * An unknown or no-longer-free model is refused by name rather than sent: the service answers those with a
230
+ * 400 or a 429, and each attempt costs the address's anonymous-chat quota. This checks the *live* catalog,
231
+ * so a model that stopped being free fails here with the reason rather than at the service.
232
+ */
233
+ private refuseUnsupported;
234
+ chat(request: ChatRequest, context?: ProviderRequestContext): Promise<ChatResponse>;
235
+ /** There is no stream. The whole answer is yielded once, honestly, rather than pretending. */
236
+ streamChat(request: ChatRequest, context?: ProviderRequestContext): AsyncIterable<ChatChunk>;
237
+ /**
238
+ * A credential check, and it says so.
239
+ *
240
+ * `verified: 'credential'` rather than `'inference'`: this reads whether the *endpoint answers and serves a
241
+ * solvable challenge*, which is not the same claim as "a model can answer". Same distinction the whole
242
+ * repository settled on in 1.40.0, and the reason the card's label is "Credential check".
243
+ */
244
+ healthCheck(context?: ProviderRequestContext): Promise<ProviderHealth>;
245
+ }
246
+ /**
247
+ * A driver failure as a provider error.
248
+ *
249
+ * The classification is a **field**, not a pattern, because the service's own errors cannot be told apart by
250
+ * their text: `ERR_CHALLENGE` and `ERR_BN_LIMIT` share a message token, and reading that token as prose would
251
+ * classify a per-address quota as an expired challenge.
252
+ *
253
+ * - **`challenge`** — the challenge expired or was refused. Measured: a `418 ERR_CHALLENGE` appeared 43 s
254
+ * after a successful turn, and the byte-identical request succeeded 4.9 min later. So this is *not* retryable
255
+ * in a loop; it is "wait". The working implementation retried once with a fresh challenge, which spends a
256
+ * second call against the quota that just refused it.
257
+ * - **`quota`** — `ERR_BN_LIMIT`. Not retryable, not the credential's fault, and it is about this address.
258
+ * - **`rate-limit`** — `ERR_RATE_LIMIT`, the one the service marks retryable itself.
259
+ * - **`bad-request`** — the request was malformed (a model that is not free, a missing field). Not retryable.
260
+ * - **`unavailable`** — anything else, including a transport failure.
261
+ */
262
+ export declare function normalizeDuckDuckGoWebError(error: unknown, providerId: string): ProviderError;
263
+ export {};
264
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/providers/duckduckgo-web/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,438 @@
1
+ import { ProviderError } from '../../core/errors.js';
2
+ /**
3
+ * DuckDuckGo AI Chat (`duck.ai`), as a web-cookie provider whose cookie is **optional**.
4
+ *
5
+ * ## Why the cookie is optional, and why the card still offers one
6
+ *
7
+ * The service is anonymous: `/duckchat/v1/models` answers 200 with no credential at all, and a turn works
8
+ * without one — measured from this host, twice, before any of this was written. So a pasted cookie is
9
+ * hardening against rate limits rather than a requirement, and the card says so. Two things follow that the
10
+ * code has to get right: a credential of `{ type: 'none' }` must be accepted everywhere a cookie is, and a
11
+ * pasted cookie must never be *claimed* to be verified, because this service does not read it.
12
+ *
13
+ * ## The three traps, all measured rather than read
14
+ *
15
+ * 1. **The delta arrives in `message`, not `content`.** A reader that checks only `content` returns HTTP 200
16
+ * with an **empty answer** — the spike's own first attempt did exactly that, which is why
17
+ * `duckDuckGoWebContent` checks both and why the test pins the empty case.
18
+ * 2. **Free models are `accessTier` on the model itself.** The live catalog marks 6 models free there, and
19
+ * only 3 by `reasoningEffortAccess[].accessTier` — a parser keyed on the wrong field silently drops half
20
+ * the free lineup. See `freeDuckDuckGoWebModels`.
21
+ * 3. **The stream opens with `data: [CHAT_TITLE:...]`**, a non-JSON pseudo-event. `parseDuckDuckGoWebDataLine`
22
+ * returns `undefined` for it rather than throwing.
23
+ *
24
+ * ## What lives where
25
+ *
26
+ * Everything here is testable without a network: the cookie parsing, the catalog filter, the stream parsing,
27
+ * the payload builder, and every refusal. The handshake, the challenge and the request itself are the
28
+ * driver's, declared here and implemented in the gateway — the same boundary `gemini-web` uses, and required
29
+ * here too because the challenge solver needs `node:vm`.
30
+ */
31
+ export const DUCKDUCKGO_WEB = {
32
+ origin: 'https://duck.ai',
33
+ modelsPath: '/duckchat/v1/models',
34
+ statusPath: '/duckchat/v1/status',
35
+ chatPath: '/duckchat/v1/chat',
36
+ homePath: '/',
37
+ /**
38
+ * The model a request gets when it names none.
39
+ *
40
+ * `gpt-5.4-mini` because it is free **today** — verified against the live catalog — and because it is the
41
+ * one free id that has been free on every reading. It is not a promise: a catalog read that no longer lists
42
+ * it free is refused by name rather than routed to something else. See `freeDuckDuckGoWebModels`.
43
+ */
44
+ defaultModel: 'gpt-5.4-mini',
45
+ /** The service's own default effort, and the value the successful requests used. */
46
+ defaultReasoningEffort: 'none',
47
+ statusTimeoutMs: 15_000,
48
+ catalogTimeoutMs: 15_000,
49
+ homeTimeoutMs: 15_000,
50
+ chatTimeoutMs: 60_000,
51
+ userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36',
52
+ };
53
+ export const duckduckgoWebProviderId = 'duckduckgo-web';
54
+ /* ------------------------------------------------------------------ *
55
+ * The cookie, which may legitimately be absent
56
+ * ------------------------------------------------------------------ */
57
+ /**
58
+ * A pasted cookie in any of the three shapes a browser hands over.
59
+ *
60
+ * Bare value, a full `Cookie:` header line, or a JSON export. Returns `undefined` when the paste carries
61
+ * nothing, so the caller can tell "the user pasted junk" from "there is no cookie", which are different
62
+ * answers: the first is a mistake to report, the second is this provider's normal case.
63
+ */
64
+ export function normalizeDuckDuckGoWebCookie(value) {
65
+ const text = value.trim();
66
+ if (!text)
67
+ return undefined;
68
+ if (text.startsWith('{') || text.startsWith('[')) {
69
+ try {
70
+ const parsed = JSON.parse(text);
71
+ const entries = Array.isArray(parsed)
72
+ ? parsed
73
+ : typeof parsed === 'object' && parsed !== null && Array.isArray(parsed.cookies)
74
+ ? (parsed.cookies)
75
+ : [];
76
+ const pairs = entries
77
+ .map((entry) => {
78
+ const cookie = entry;
79
+ return typeof cookie?.name === 'string' && typeof cookie?.value === 'string' ? `${cookie.name}=${cookie.value}` : undefined;
80
+ })
81
+ .filter((pair) => pair !== undefined);
82
+ return pairs.length > 0 ? pairs.join('; ') : undefined;
83
+ }
84
+ catch {
85
+ return undefined;
86
+ }
87
+ }
88
+ const withoutPrefix = text.replace(/^cookie:\s*/i, '');
89
+ // A header line is `a=1; b=2`; a bare value is whatever was copied out of the devtools cell. Both are
90
+ // accepted, because both are what people paste, and neither can be validated beyond "looks like a token".
91
+ return withoutPrefix.includes('=') ? withoutPrefix : undefined;
92
+ }
93
+ /**
94
+ * The `Cookie` header for a credential, or `undefined` when there is none to send.
95
+ *
96
+ * `{ type: 'none' }` is the **normal** case for this provider, not a missing value: it is what the connect
97
+ * route stores when the user pastes nothing, and the request goes out without a `Cookie` header. Every other
98
+ * web-cookie adapter in this repository refuses that credential, so this is the one place the difference is
99
+ * visible.
100
+ */
101
+ export function duckDuckGoWebCookieHeader(credential) {
102
+ if (!credential || credential.type === 'none')
103
+ return undefined;
104
+ if (credential.type !== 'api-key')
105
+ return undefined;
106
+ return normalizeDuckDuckGoWebCookie(credential.value);
107
+ }
108
+ const isRecord = (value) => typeof value === 'object' && value !== null;
109
+ /**
110
+ * The models the live catalog offers on a **free** tier.
111
+ *
112
+ * **The field is `accessTier` on the model itself**, and that is a measurement rather than a reading. Against
113
+ * the live catalog on 2026-10-17:
114
+ *
115
+ * ```
116
+ * free by model.accessTier : 6 (gpt-6-luna, gpt-5.4-mini, claude-haiku-4-5,
117
+ * mistral-small-2603, tinfoil/gpt-oss-120b, tinfoil/gemma4-31b)
118
+ * free by reasoningEffortAccess[].accessTier : 3 (gpt-5.4-mini, claude-haiku-4-5, tinfoil/gpt-oss-120b)
119
+ * ```
120
+ *
121
+ * Reading the nested field looks more precise and is simply wrong: it describes which *effort levels* an
122
+ * account tier may select, not whether the model is served. A parser keyed on it silently drops half the
123
+ * free lineup, which is why the first test in `duckduckgo-web.test.js` asserts both numbers.
124
+ *
125
+ * The list is read per request rather than pinned, because the lineup rotates — `gpt-6-luna` had appeared and
126
+ * three of the source's hardcoded "free" ids no longer were, inside a week.
127
+ */
128
+ export function freeDuckDuckGoWebModels(catalog) {
129
+ if (!isRecord(catalog) || !Array.isArray(catalog.models))
130
+ return [];
131
+ return catalog.models
132
+ .filter((entry) => isRecord(entry))
133
+ .filter((entry) => {
134
+ const tiers = Array.isArray(entry.accessTier) ? entry.accessTier : [];
135
+ return tiers.some((tier) => typeof tier === 'string' && tier.toLowerCase() === 'free');
136
+ })
137
+ .map((entry) => ({
138
+ id: String(entry.id ?? ''),
139
+ displayName: String(entry.modelName ?? entry.name ?? entry.id ?? ''),
140
+ }))
141
+ .filter((model) => model.id.length > 0);
142
+ }
143
+ export function duckDuckGoWebModelIds(catalog) {
144
+ return freeDuckDuckGoWebModels(catalog).map((model) => model.id);
145
+ }
146
+ /* ------------------------------------------------------------------ *
147
+ * Reading the answer out of the stream
148
+ * ------------------------------------------------------------------ */
149
+ /**
150
+ * One `data:` line, or `undefined` for everything that is not answer text.
151
+ *
152
+ * Non-JSON lines are dropped rather than thrown on: the stream opens with `data: [CHAT_TITLE:...]` before any
153
+ * JSON arrives, and `[DONE]` terminates it. Both are normal, and a parser that threw on them would fail every
154
+ * turn at the first byte.
155
+ */
156
+ export function parseDuckDuckGoWebDataLine(line) {
157
+ if (!line.startsWith('data: '))
158
+ return undefined;
159
+ const data = line.slice('data: '.length).trim();
160
+ if (!data || data === '[DONE]')
161
+ return undefined;
162
+ try {
163
+ const parsed = JSON.parse(data);
164
+ return isRecord(parsed) ? parsed : undefined;
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ }
170
+ /**
171
+ * The text of one event, from `content` **or** `message`.
172
+ *
173
+ * This is trap 1, and it is the difference between an answer and a 200 with nothing in it. The live service
174
+ * sends `{"action":"success","message":"..."}` and then a run of `{"message":"..."}` frames; `content` never
175
+ * appears. A reader that looked only for `content` — which is what every other provider here sends — would
176
+ * return an empty string and report success.
177
+ */
178
+ export function duckDuckGoWebContent(event) {
179
+ if (!event)
180
+ return '';
181
+ if (typeof event.content === 'string')
182
+ return event.content;
183
+ if (typeof event.message === 'string')
184
+ return event.message;
185
+ return '';
186
+ }
187
+ /**
188
+ * The request body.
189
+ *
190
+ * `publicKey` is a **parameter**, not generated here, and that is a hard boundary rather than a preference:
191
+ * generating an RSA key pair needs `node:crypto`, and this module is reachable from the SDK barrel, which the
192
+ * dashboard imports. `tests/sdk-browser-safety.test.js` fails if anything reachable from that barrel imports a
193
+ * Node builtin — the 1.42.0 blank page — so the gateway generates the key and passes it in.
194
+ *
195
+ * `reasoningEffort` is always sent. The working implementation records it as **required**, with a 400
196
+ * `ERR_BAD_REQUEST` when omitted, verified live on its side. Not re-measured here, because proving it costs a
197
+ * chat POST against a rate-limited endpoint, and the requests that succeeded all sent it.
198
+ */
199
+ export function buildDuckDuckGoWebPayload(input) {
200
+ return {
201
+ model: input.model,
202
+ metadata: {
203
+ // Every tool off: this gateway is a chat surface, and a search tool that fired on its own would be a
204
+ // request the caller did not ask for.
205
+ toolChoice: { NewsSearch: false, VideosSearch: false, LocalSearch: false, WeatherForecast: false },
206
+ },
207
+ messages: input.messages.map((message) => ({ role: message.role, content: message.content })),
208
+ canUseTools: true,
209
+ reasoningEffort: input.reasoningEffort ?? DUCKDUCKGO_WEB.defaultReasoningEffort,
210
+ canUseApproxLocation: null,
211
+ canDelegateImageGeneration: null,
212
+ durableStream: { messageId: input.messageId, conversationId: input.conversationId, publicKey: input.publicKey },
213
+ };
214
+ }
215
+ /* ------------------------------------------------------------------ *
216
+ * The driver, as the adapter sees it
217
+ * ------------------------------------------------------------------ */
218
+ /**
219
+ * A refusal with a **kind**, so the adapter decides retryability and the driver only reports what happened.
220
+ *
221
+ * The alternative — throwing `ProviderError` from the driver — would put routing policy in the gateway, and
222
+ * the alternative this file avoids most deliberately is classifying by *message*: `ERR_CHALLENGE` and
223
+ * `ERR_BN_LIMIT` arrive in the same JSON shape and, per the working implementation's own measurement, the
224
+ * service maps both to the same user-facing message token. A regex over prose cannot tell them apart; a field
225
+ * can.
226
+ */
227
+ export class DuckDuckGoWebError extends Error {
228
+ kind;
229
+ overrideCode;
230
+ constructor(kind, message, overrideCode) {
231
+ super(message);
232
+ this.kind = kind;
233
+ this.overrideCode = overrideCode;
234
+ this.name = 'DuckDuckGoWebError';
235
+ }
236
+ }
237
+ export class DuckDuckGoWebAdapter {
238
+ id;
239
+ name = 'Duck.ai';
240
+ /**
241
+ * Streaming is not implemented and saying otherwise would be false: this adapter reads the service's SSE
242
+ * and hands back the assembled answer, so no token arrives "over time" as far as a caller is concerned.
243
+ */
244
+ capabilities = { chat: true, streaming: false, models: true };
245
+ driver;
246
+ constructor(options) {
247
+ this.driver = options.driver;
248
+ this.id = options.id ?? duckduckgoWebProviderId;
249
+ }
250
+ async listModels() {
251
+ const catalog = await this.driver.catalog({ timeoutMs: DUCKDUCKGO_WEB.catalogTimeoutMs });
252
+ return freeDuckDuckGoWebModels(catalog).map((model) => ({
253
+ id: model.id,
254
+ displayName: `${model.displayName} (free tier)`,
255
+ providerId: this.id,
256
+ }));
257
+ }
258
+ /**
259
+ * A credential is valid when it is absent, or when it looks like a cookie header.
260
+ *
261
+ * `{ type: 'none' }` returning `valid` is the whole point of this provider — and it is why the *check* the
262
+ * dashboard runs is the handshake rather than this method. Nothing here contacts the service, so it cannot
263
+ * claim a cookie works; it can only refuse one that is malformed.
264
+ */
265
+ async validateCredential(credential) {
266
+ if (!credential || credential.type === 'none')
267
+ return { status: 'valid', checkedAt: new Date().toISOString() };
268
+ if (credential.type !== 'api-key') {
269
+ throw new ProviderError('AUTHENTICATION_FAILED', 'Duck.ai takes no credential of that kind.', {
270
+ providerId: this.id,
271
+ publicMessage: 'Paste a duck.ai cookie, or leave it empty: this provider needs no credential.',
272
+ });
273
+ }
274
+ const parsed = normalizeDuckDuckGoWebCookie(credential.value);
275
+ if (!parsed) {
276
+ throw new ProviderError('AUTHENTICATION_FAILED', 'The pasted Duck.ai cookie could not be read.', {
277
+ providerId: this.id,
278
+ publicMessage: 'That does not look like a cookie. Paste the whole `Cookie:` line from duck.ai, or leave the field empty — no cookie is required.',
279
+ });
280
+ }
281
+ return { status: 'valid', checkedAt: new Date().toISOString() };
282
+ }
283
+ /**
284
+ * The refusal worth making before a request is spent.
285
+ *
286
+ * An unknown or no-longer-free model is refused by name rather than sent: the service answers those with a
287
+ * 400 or a 429, and each attempt costs the address's anonymous-chat quota. This checks the *live* catalog,
288
+ * so a model that stopped being free fails here with the reason rather than at the service.
289
+ */
290
+ async refuseUnsupported(request, signal) {
291
+ const requested = request.model || DUCKDUCKGO_WEB.defaultModel;
292
+ if (request.toolChoice === 'required') {
293
+ throw new ProviderError('NOT_SUPPORTED', 'Duck.ai cannot be told to force a tool call.', {
294
+ providerId: this.id,
295
+ publicMessage: 'Duck.ai answers in prose and has no way to force a tool call. Send `tool_choice: "auto"` or omit it.',
296
+ });
297
+ }
298
+ let free = [];
299
+ try {
300
+ free = freeDuckDuckGoWebModels(await this.driver.catalog({ timeoutMs: DUCKDUCKGO_WEB.catalogTimeoutMs, ...(signal ? { signal } : {}) }));
301
+ }
302
+ catch {
303
+ // A catalog that cannot be read is not a reason to refuse a turn: the service decides, and a named
304
+ // refusal here would be about this gateway rather than about the request.
305
+ return requested;
306
+ }
307
+ if (free.length === 0)
308
+ return requested;
309
+ if (free.some((model) => model.id === requested))
310
+ return requested;
311
+ const known = free.map((model) => model.id).join(', ');
312
+ throw new ProviderError('NOT_SUPPORTED', `Duck.ai does not offer ${requested} on its free tier.`, {
313
+ providerId: this.id,
314
+ publicMessage: `Duck.ai serves ${known} free. ${requested} is not free on this account, and a request for it would be refused by the service.`,
315
+ });
316
+ }
317
+ async chat(request, context = {}) {
318
+ const model = await this.refuseUnsupported(request, context.signal);
319
+ const cookieHeader = duckDuckGoWebCookieHeader(context.credential);
320
+ const createdAt = new Date().toISOString();
321
+ try {
322
+ const { text, model: answered } = await this.driver.ask({
323
+ ...(cookieHeader ? { cookieHeader } : {}),
324
+ model,
325
+ messages: request.messages.map((message) => ({ role: message.role, content: messageText(message.content) })),
326
+ timeoutMs: DUCKDUCKGO_WEB.chatTimeoutMs,
327
+ ...(context.signal ? { signal: context.signal } : {}),
328
+ });
329
+ return {
330
+ id: `duckduckgo-web-${Date.parse(createdAt)}`,
331
+ providerId: this.id,
332
+ model: answered || model,
333
+ createdAt,
334
+ message: { role: 'assistant', content: text },
335
+ finishReason: 'stop',
336
+ };
337
+ }
338
+ catch (error) {
339
+ throw normalizeDuckDuckGoWebError(error, this.id);
340
+ }
341
+ }
342
+ /** There is no stream. The whole answer is yielded once, honestly, rather than pretending. */
343
+ async *streamChat(request, context = {}) {
344
+ const response = await this.chat(request, context);
345
+ yield {
346
+ id: response.id,
347
+ providerId: response.providerId,
348
+ model: response.model,
349
+ delta: { role: 'assistant', content: messageText(response.message.content) },
350
+ finishReason: response.finishReason,
351
+ };
352
+ }
353
+ /**
354
+ * A credential check, and it says so.
355
+ *
356
+ * `verified: 'credential'` rather than `'inference'`: this reads whether the *endpoint answers and serves a
357
+ * solvable challenge*, which is not the same claim as "a model can answer". Same distinction the whole
358
+ * repository settled on in 1.40.0, and the reason the card's label is "Credential check".
359
+ */
360
+ async healthCheck(context = {}) {
361
+ const checkedAt = new Date().toISOString();
362
+ try {
363
+ await this.validateCredential(context.credential);
364
+ const driver = await this.driver.available();
365
+ if (!driver.ok)
366
+ return { status: 'unavailable', verified: 'credential', checkedAt };
367
+ const status = await this.driver.status({ timeoutMs: DUCKDUCKGO_WEB.statusTimeoutMs, ...(context.signal ? { signal: context.signal } : {}) });
368
+ if (!status.challengeServed) {
369
+ // `message` rather than an `errorCode`: `ProviderHealth` declares no code field, and inventing one
370
+ // would be a value no consumer can read. The sentence is the part a person needs.
371
+ return { status: 'degraded', verified: 'credential', checkedAt, message: 'Duck.ai answered without serving an anti-bot challenge.' };
372
+ }
373
+ return { status: 'healthy', verified: 'credential', checkedAt, latencyMs: status.solvedMs };
374
+ }
375
+ catch (error) {
376
+ return {
377
+ status: 'unavailable',
378
+ verified: 'credential',
379
+ checkedAt,
380
+ message: error instanceof Error ? error.message : 'Duck.ai could not be reached.',
381
+ };
382
+ }
383
+ }
384
+ }
385
+ function messageText(content) {
386
+ if (typeof content === 'string')
387
+ return content;
388
+ if (!Array.isArray(content))
389
+ return '';
390
+ return content
391
+ .map((part) => {
392
+ if (typeof part === 'string')
393
+ return part;
394
+ if (part && typeof part === 'object' && 'text' in part && typeof part.text === 'string')
395
+ return part.text;
396
+ return '';
397
+ })
398
+ .join('');
399
+ }
400
+ /**
401
+ * A driver failure as a provider error.
402
+ *
403
+ * The classification is a **field**, not a pattern, because the service's own errors cannot be told apart by
404
+ * their text: `ERR_CHALLENGE` and `ERR_BN_LIMIT` share a message token, and reading that token as prose would
405
+ * classify a per-address quota as an expired challenge.
406
+ *
407
+ * - **`challenge`** — the challenge expired or was refused. Measured: a `418 ERR_CHALLENGE` appeared 43 s
408
+ * after a successful turn, and the byte-identical request succeeded 4.9 min later. So this is *not* retryable
409
+ * in a loop; it is "wait". The working implementation retried once with a fresh challenge, which spends a
410
+ * second call against the quota that just refused it.
411
+ * - **`quota`** — `ERR_BN_LIMIT`. Not retryable, not the credential's fault, and it is about this address.
412
+ * - **`rate-limit`** — `ERR_RATE_LIMIT`, the one the service marks retryable itself.
413
+ * - **`bad-request`** — the request was malformed (a model that is not free, a missing field). Not retryable.
414
+ * - **`unavailable`** — anything else, including a transport failure.
415
+ */
416
+ export function normalizeDuckDuckGoWebError(error, providerId) {
417
+ if (error instanceof ProviderError)
418
+ return error;
419
+ const kind = error instanceof DuckDuckGoWebError ? error.kind : 'unavailable';
420
+ const detail = error instanceof Error ? error.message : String(error);
421
+ const override = error instanceof DuckDuckGoWebError && error.overrideCode ? ` (overrideCode ${error.overrideCode})` : '';
422
+ const publicByKind = {
423
+ challenge: 'Duck.ai refused the request at its anti-bot check. That usually means too many requests from this address in a short window — waiting a few minutes is the fix, not retrying, and not re-pasting a cookie.',
424
+ quota: 'Duck.ai is refusing this address rather than this request. A pasted cookie can help and is optional; a request from a different address may work immediately.',
425
+ 'rate-limit': 'Duck.ai is rate limiting this address. Retrying later is the fix.',
426
+ 'bad-request': 'Duck.ai refused the request as malformed. See the message for which field.',
427
+ unavailable: 'Duck.ai could not be reached. If this is a network problem on the gateway host it will work again on its own; if it keeps happening, the service may have changed.',
428
+ };
429
+ const code = kind === 'rate-limit' || kind === 'unavailable' ? 'PROVIDER_REQUEST_FAILED' : kind === 'bad-request' ? 'INVALID_REQUEST' : 'PROVIDER_UNAVAILABLE';
430
+ return new ProviderError(code, `Duck.ai could not complete the turn: ${detail}${override}`, {
431
+ providerId,
432
+ // Only the kind the service itself marks retryable is retried. Everything else would spend quota to be
433
+ // refused again, which is what turns a short wait into a longer one.
434
+ retryable: kind === 'rate-limit' || kind === 'unavailable',
435
+ publicMessage: publicByKind[kind],
436
+ });
437
+ }
438
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/providers/duckduckgo-web/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hilbras/omnihilbras",
3
- "version": "1.86.1",
3
+ "version": "1.87.1",
4
4
  "description": "Typed provider SDK for the OmniHilbras gateway: normalized chat, streaming, model listing, and capability contracts for OpenAI, Anthropic, Gemini, and OpenAI-compatible providers.",
5
5
  "license": "MIT",
6
6
  "author": "Hilbras",