@hilbras/omnihilbras 1.86.0 → 1.87.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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/cline/index.js +1 -1
- package/dist/providers/duckduckgo-web/index.d.ts +264 -0
- package/dist/providers/duckduckgo-web/index.d.ts.map +1 -0
- package/dist/providers/duckduckgo-web/index.js +438 -0
- package/dist/providers/duckduckgo-web/index.js.map +1 -0
- package/package.json +1 -1
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
47
|
+
const omnihilbrasVersion = '1.87.0';
|
|
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":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,oBAAoB,EAEpB,KAAK,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,sBAAsB,EACvB,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,eAAO,MAAM,cAAc;;;;;;IAMzB;;;;;;OAMG;;IAEH,oFAAoF;;;;;;;CAO5E,CAAC;AAEX,eAAO,MAAM,uBAAuB,mBAAmB,CAAC;AAMxD;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA4B9E;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,kBAAkB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIxG;AAMD,MAAM,MAAM,kBAAkB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAI7C;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,EAAE,CAa9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAEhE;AAMD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAUlF;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,CAK7E;AAMD,2EAA2E;AAC3E,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,SAAS,EAAE,sBAAsB,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,aAAa,CAehB;AAMD;;;;;;;;GAQG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAEzC,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,YAAY,GAAG,aAAa,GAAG,aAAa;IAEnF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM;gBAFrB,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,YAAY,GAAG,aAAa,GAAG,aAAa,EACnF,OAAO,EAAE,MAAM,EACN,YAAY,CAAC,EAAE,MAAM,YAAA;CAKjC;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,6GAA6G;IAC7G,SAAS,EAAE,MAAM,OAAO,CAAC;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,uEAAuE;IACvE,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACnF,kGAAkG;IAClG,MAAM,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,KAAK,OAAO,CAAC;QAAE,eAAe,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzH,4FAA4F;IAC5F,GAAG,EAAE,CAAC,KAAK,EAAE;QACX,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,SAAS;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACvD,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD,CAAC;AAMF,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAEF,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,aAAa;IAC1B;;;OAGG;IACH,QAAQ,CAAC,YAAY;;;;MAA2D;IAEhF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsB;gBAEjC,OAAO,EAAE,2BAA2B;IAK1C,UAAU,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;IASpC;;;;;;OAMG;IACG,kBAAkB,CAAC,UAAU,EAAE,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAkBnG;;;;;;OAMG;YACW,iBAAiB;IA4BzB,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,YAAY,CAAC;IA0B7F,8FAA8F;IACvF,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,GAAE,sBAA2B,GAAG,aAAa,CAAC,SAAS,CAAC;IAWvG;;;;;;OAMG;IACG,WAAW,CAAC,OAAO,GAAE,sBAA2B,GAAG,OAAO,CAAC,cAAc,CAAC;CAsBjF;AAcD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,CA2B7F"}
|
|
@@ -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":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAcrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,iBAAiB;IACzB,UAAU,EAAE,qBAAqB;IACjC,UAAU,EAAE,qBAAqB;IACjC,QAAQ,EAAE,mBAAmB;IAC7B,QAAQ,EAAE,GAAG;IACb;;;;;;OAMG;IACH,YAAY,EAAE,cAAc;IAC5B,oFAAoF;IACpF,sBAAsB,EAAE,MAAM;IAC9B,eAAe,EAAE,MAAM;IACvB,gBAAgB,EAAE,MAAM;IACxB,aAAa,EAAE,MAAM;IACrB,aAAa,EAAE,MAAM;IACrB,SAAS,EAAE,uGAAuG;CAC1G,CAAC;AAEX,MAAM,CAAC,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AAExD;;wEAEwE;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAAa;IACxD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;YAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBACnC,CAAC,CAAC,MAAM;gBACR,CAAC,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAE,MAAgC,CAAC,OAAO,CAAC;oBACzG,CAAC,CAAC,CAAE,MAAiC,CAAC,OAAO,CAAC;oBAC9C,CAAC,CAAC,EAAE,CAAC;YACT,MAAM,KAAK,GAAG,OAAO;iBAClB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;gBACb,MAAM,MAAM,GAAG,KAA4C,CAAC;gBAC5D,OAAO,OAAO,MAAM,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;YAC9H,CAAC,CAAC;iBACD,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;YACxD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;IACvD,sGAAsG;IACtG,0GAA0G;IAC1G,OAAO,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;AACjE,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CAAC,UAA0C;IAClF,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IAChE,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,4BAA4B,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAUD,MAAM,QAAQ,GAAG,CAAC,KAAc,EAA0B,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AAEzG;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,CAAC;IACpE,OAAO,OAAO,CAAC,MAAM;SAClB,MAAM,CAAC,CAAC,KAAK,EAA0B,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SAC1D,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;IACzF,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,EAAE,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QAC1B,WAAW,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;KACrE,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;wEAEwE;AAExE;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAY;IACrD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;QAC3C,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAgC;IACnE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IAC5D,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IAC5D,OAAO,EAAE,CAAC;AACZ,CAAC;AAiBD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAOzC;IACC,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,QAAQ,EAAE;YACR,qGAAqG;YACrG,sCAAsC;YACtC,UAAU,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE;SACnG;QACD,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7F,WAAW,EAAE,IAAI;QACjB,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,cAAc,CAAC,sBAAsB;QAC/E,oBAAoB,EAAE,IAAI;QAC1B,0BAA0B,EAAE,IAAI;QAChC,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE;KAChH,CAAC;AACJ,CAAC;AAED;;wEAEwE;AAExE;;;;;;;;GAQG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC;IAEA;IAHX,YACW,IAA0E,EACnF,OAAe,EACN,YAAqB;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJN,SAAI,GAAJ,IAAI,CAAsE;QAE1E,iBAAY,GAAZ,YAAY,CAAS;QAG9B,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AA6BD,MAAM,OAAO,oBAAoB;IACtB,EAAE,CAAS;IACX,IAAI,GAAG,SAAS,CAAC;IAC1B;;;OAGG;IACM,YAAY,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAW,CAAC;IAE/D,MAAM,CAAsB;IAE7C,YAAY,OAAoC;QAC9C,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,IAAI,uBAAuB,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,UAAU;QACd,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,gBAAgB,EAAE,CAAC,CAAC;QAC1F,OAAO,uBAAuB,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACtD,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,WAAW,EAAE,GAAG,KAAK,CAAC,WAAW,cAAc;YAC/C,UAAU,EAAE,IAAI,CAAC,EAAE;SACpB,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,kBAAkB,CAAC,UAA0C;QACjE,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QAC/G,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAClC,MAAM,IAAI,aAAa,CAAC,uBAAuB,EAAE,2CAA2C,EAAE;gBAC5F,UAAU,EAAE,IAAI,CAAC,EAAE;gBACnB,aAAa,EAAE,+EAA+E;aAC/F,CAAC,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAG,4BAA4B,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC9D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,aAAa,CAAC,uBAAuB,EAAE,8CAA8C,EAAE;gBAC/F,UAAU,EAAE,IAAI,CAAC,EAAE;gBACnB,aAAa,EAAE,kIAAkI;aAClJ,CAAC,CAAC;QACL,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAClE,CAAC;IAED;;;;;;OAMG;IACK,KAAK,CAAC,iBAAiB,CAAC,OAAoB,EAAE,MAAoB;QACxE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC,YAAY,CAAC;QAC/D,IAAI,OAAO,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;YACtC,MAAM,IAAI,aAAa,CAAC,eAAe,EAAE,8CAA8C,EAAE;gBACvF,UAAU,EAAE,IAAI,CAAC,EAAE;gBACnB,aAAa,EAAE,sGAAsG;aACtH,CAAC,CAAC;QACL,CAAC;QAED,IAAI,IAAI,GAAyB,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,IAAI,GAAG,uBAAuB,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3I,CAAC;QAAC,MAAM,CAAC;YACP,mGAAmG;YACnG,0EAA0E;YAC1E,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACxC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;QAEnE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,IAAI,aAAa,CAAC,eAAe,EAAE,0BAA0B,SAAS,oBAAoB,EAAE;YAChG,UAAU,EAAE,IAAI,CAAC,EAAE;YACnB,aAAa,EAAE,kBAAkB,KAAK,UAAU,SAAS,qFAAqF;SAC/I,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAoB,EAAE,UAAkC,EAAE;QACnE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACpE,MAAM,YAAY,GAAG,yBAAyB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACnE,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAE3C,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;gBACtD,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzC,KAAK;gBACL,QAAQ,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC5G,SAAS,EAAE,cAAc,CAAC,aAAa;gBACvC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACtD,CAAC,CAAC;YACH,OAAO;gBACL,EAAE,EAAE,kBAAkB,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;gBAC7C,UAAU,EAAE,IAAI,CAAC,EAAE;gBACnB,KAAK,EAAE,QAAQ,IAAI,KAAK;gBACxB,SAAS;gBACT,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE;gBAC7C,YAAY,EAAE,MAAM;aACrB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,2BAA2B,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,8FAA8F;IAC9F,KAAK,CAAC,CAAC,UAAU,CAAC,OAAoB,EAAE,UAAkC,EAAE;QAC1E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACnD,MAAM;YACJ,EAAE,EAAE,QAAQ,CAAC,EAAE;YACf,UAAU,EAAE,QAAQ,CAAC,UAAU;YAC/B,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE;YAC5E,YAAY,EAAE,QAAQ,CAAC,YAAY;SACpC,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,WAAW,CAAC,UAAkC,EAAE;QACpD,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;YACpF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,eAAe,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9I,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC;gBAC5B,mGAAmG;gBACnG,kFAAkF;gBAClF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,yDAAyD,EAAE,CAAC;YACvI,CAAC;YACD,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC9F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,MAAM,EAAE,aAAa;gBACrB,QAAQ,EAAE,YAAY;gBACtB,SAAS;gBACT,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B;aAClF,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAED,SAAS,WAAW,CAAC,OAAuB;IAC1C,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACZ,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1C,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,MAAM,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,IAAI,CAAC;QAC1G,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;SACD,IAAI,CAAC,EAAE,CAAC,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,2BAA2B,CAAC,KAAc,EAAE,UAAkB;IAC5E,IAAI,KAAK,YAAY,aAAa;QAAE,OAAO,KAAK,CAAC;IAEjD,MAAM,IAAI,GAAG,KAAK,YAAY,kBAAkB,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC;IAC9E,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,MAAM,QAAQ,GAAG,KAAK,YAAY,kBAAkB,IAAI,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,kBAAkB,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAE1H,MAAM,YAAY,GAA+C;QAC/D,SAAS,EACP,4MAA4M;QAC9M,KAAK,EACH,+JAA+J;QACjK,YAAY,EAAE,mEAAmE;QACjF,aAAa,EAAE,4EAA4E;QAC3F,WAAW,EACT,oKAAoK;KACvK,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,sBAAsB,CAAC;IAE/J,OAAO,IAAI,aAAa,CAAC,IAAI,EAAE,wCAAwC,MAAM,GAAG,QAAQ,EAAE,EAAE;QAC1F,UAAU;QACV,uGAAuG;QACvG,qEAAqE;QACrE,SAAS,EAAE,IAAI,KAAK,YAAY,IAAI,IAAI,KAAK,aAAa;QAC1D,aAAa,EAAE,YAAY,CAAC,IAAI,CAAC;KAClC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hilbras/omnihilbras",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.87.0",
|
|
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",
|