@wenathlan/getry 2.0.164 → 2.0.166
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/accounts.ts +2087 -0
- package/antigravity.ts +2171 -0
- package/auth.ts +265 -0
- package/constants.ts +950 -0
- package/db.ts +810 -0
- package/docs/distributionlanes.md +1 -1
- package/docs/getry.md +33 -0
- package/engine.ts +1535 -0
- package/fingerprint.ts +1461 -0
- package/gateway.ts +1306 -0
- package/gatewayprobe.ts +61 -0
- package/gatewaysmoke.ts +64 -0
- package/intelligence.ts +169 -0
- package/json.ts +932 -0
- package/labels.ts +262 -0
- package/llm.ts +2709 -0
- package/models.ts +4017 -0
- package/nodeshim.ts +265 -0
- package/oauth.ts +2201 -0
- package/package.json +580 -215
- package/project.ts +1293 -0
- package/providers.ts +3238 -0
- package/quota.ts +1891 -0
- package/recovery.ts +2434 -0
- package/request.ts +4496 -0
- package/search.ts +1761 -0
- package/server.ts +766 -0
- package/streaming.ts +4398 -0
- package/thinking.ts +416 -0
- package/types.ts +809 -0
- package/utils.ts +263 -0
- package/validate.ts +61 -0
- package/versionregistry.ts +1325 -0
package/auth.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file auth.ts
|
|
3
|
+
* @module gateway/auth
|
|
4
|
+
* @description
|
|
5
|
+
* The gateway request auth and key resolution family of the ownership
|
|
6
|
+
* wave (wave/ownership.gateway): moved verbatim from devthink/server.ts
|
|
7
|
+
* section 1D (the grand-merge interned gateway request auth), so the
|
|
8
|
+
* gateway owns its auth surface — one owner, the family consumes, and
|
|
9
|
+
* devthink re-exports the public face for the compat consumers.
|
|
10
|
+
*
|
|
11
|
+
* Responsibilities isolated here:
|
|
12
|
+
* - Gateway client key resolution — db / env / inline / ephemeral
|
|
13
|
+
* sources walked in the configured order (resolvekeys and the
|
|
14
|
+
* private keysfrom* helpers, keys labeled and length-filtered)
|
|
15
|
+
* - Token extraction / validation / masking from client requests
|
|
16
|
+
* (extracttoken, extractchatid, extractsessionid, extractrequestid,
|
|
17
|
+
* validatetoken, masktoken)
|
|
18
|
+
* - Auth header and query param construction for upstream provider
|
|
19
|
+
* calls (buildauthheaders, buildauthqueryparams, appendqueryparams —
|
|
20
|
+
* the 12 auth methods plus the ride-along headers and params)
|
|
21
|
+
* - Round robin key helpers kept for backward compat (getkey,
|
|
22
|
+
* getkeycount)
|
|
23
|
+
*
|
|
24
|
+
* Architectural constraints:
|
|
25
|
+
* - Verbatim move: every export name and signature preserved — the
|
|
26
|
+
* devthink server keeps answering the family through one re-export
|
|
27
|
+
* shim, so no consumer edits with the move
|
|
28
|
+
* - The db seam stays dynamic: keysfromdb imports the os storage layer
|
|
29
|
+
* through the published "@wenathlan/devthink/storage" specifier (the
|
|
30
|
+
* prisma persistence owner) at call time, so this leaf never links
|
|
31
|
+
* the persistence stack statically and a source-less consumer still
|
|
32
|
+
* loads
|
|
33
|
+
* - Zero external dependencies: global fetch / Buffer beside the
|
|
34
|
+
* owner's types and utils faces
|
|
35
|
+
*
|
|
36
|
+
* Version: 2.1.7 — the ownership wave auth family extraction
|
|
37
|
+
*
|
|
38
|
+
* Related modules:
|
|
39
|
+
* - types.ts — authconfig, resolvedkey (the configuration surface)
|
|
40
|
+
* - utils.ts — genid (the generated identifier family)
|
|
41
|
+
* - engine.ts — the universal request pipeline riding these builders
|
|
42
|
+
* - devthink/server.ts — the compat re-export face of the moved family
|
|
43
|
+
*
|
|
44
|
+
* @author devthink
|
|
45
|
+
* @license MIT
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import type { authconfig, resolvedkey } from "@wenathlan/getry/types";
|
|
49
|
+
import { genid } from "@wenathlan/getry/utils";
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// inbound extractors — read credentials from client requests
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/** extract token — get user supplied token from headers or body */
|
|
56
|
+
export function extracttoken(req: Request, body?: Record<string, unknown>): string {
|
|
57
|
+
return (
|
|
58
|
+
req.headers.get("x-token") ||
|
|
59
|
+
req.headers.get("authorization")?.replace(/^bearer\s+/i, "") ||
|
|
60
|
+
req.headers.get("x-api-key") ||
|
|
61
|
+
req.headers.get("x-goog-api-key") ||
|
|
62
|
+
req.headers.get("api-key") ||
|
|
63
|
+
(body?.token as string) ||
|
|
64
|
+
(body?.apiKey as string) ||
|
|
65
|
+
""
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** extract chat id — get or generate chat id for zai api */
|
|
70
|
+
export function extractchatid(req: Request, body?: Record<string, unknown>): string {
|
|
71
|
+
return (
|
|
72
|
+
req.headers.get("x-chat-id") ||
|
|
73
|
+
req.headers.get("x-user-id") ||
|
|
74
|
+
(body?.chat_id as string) ||
|
|
75
|
+
(body?.chatId as string) ||
|
|
76
|
+
(body?.userId as string) ||
|
|
77
|
+
genid("chat")
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** extract session id — get or generate session id for context tracking */
|
|
82
|
+
export function extractsessionid(req: Request, body?: Record<string, unknown>): string {
|
|
83
|
+
return (
|
|
84
|
+
req.headers.get("x-session-id") || (body?.session_id as string) || (body?.sessionId as string) || genid("sess")
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** extract request id — get or generate request id */
|
|
89
|
+
export function extractrequestid(req: Request): string {
|
|
90
|
+
return req.headers.get("x-request-id") || genid("req");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** validate token — check if token format is valid */
|
|
94
|
+
export function validatetoken(token: string): boolean {
|
|
95
|
+
if (!token || token.length < 10) return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** mask token — mask token for logging showing only prefix and suffix */
|
|
100
|
+
export function masktoken(token: string): string {
|
|
101
|
+
if (!token || token.length < 12) return "invalid";
|
|
102
|
+
return `${token.slice(0, 8)}...${token.slice(-4)}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// universal key resolution — db env inline ephemeral
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
/** resolve keys from the db source — prisma model lookup */
|
|
110
|
+
async function keysfromdb(auth: authconfig): Promise<resolvedkey[]> {
|
|
111
|
+
const model = auth.dbmodel ?? "apiKey";
|
|
112
|
+
const take = auth.dbtake ?? 50;
|
|
113
|
+
try {
|
|
114
|
+
const { db } = await import("@wenathlan/devthink/storage");
|
|
115
|
+
const table = (
|
|
116
|
+
db as unknown as Record<
|
|
117
|
+
string,
|
|
118
|
+
{
|
|
119
|
+
findMany: (args: Record<string, unknown>) => Promise<Array<Record<string, unknown>>>;
|
|
120
|
+
}
|
|
121
|
+
>
|
|
122
|
+
)[model];
|
|
123
|
+
if (!table) return [];
|
|
124
|
+
const rows = await table.findMany({ where: { active: true, status: "active" }, take });
|
|
125
|
+
return rows
|
|
126
|
+
.map((row, idx) => ({
|
|
127
|
+
key: String(row.keyValue ?? row.key ?? ""),
|
|
128
|
+
label: String(row.label ?? (auth.keylabelformat ?? "key-{idx}").replace("{idx}", String(idx))),
|
|
129
|
+
source: "db" as const,
|
|
130
|
+
}))
|
|
131
|
+
.filter((k) => k.key.length > 0);
|
|
132
|
+
} catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** resolve keys from the env source — single or separator-joined */
|
|
138
|
+
function keysfromenv(auth: authconfig): resolvedkey[] {
|
|
139
|
+
if (!auth.envvar) return [];
|
|
140
|
+
const raw = process.env[auth.envvar] ?? "";
|
|
141
|
+
const sep = auth.envseparator ?? ",";
|
|
142
|
+
const keys = raw
|
|
143
|
+
.split(sep)
|
|
144
|
+
.map((k) => k.trim())
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
return keys.map((key, idx) => ({
|
|
147
|
+
key,
|
|
148
|
+
label: (auth.keylabelformat ?? "env-key-{idx}").replace("{idx}", String(idx)),
|
|
149
|
+
source: "env" as const,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** resolve keys from the inline source — config provided */
|
|
154
|
+
function keysfrominline(auth: authconfig): resolvedkey[] {
|
|
155
|
+
return (auth.inlinekeys ?? []).map((key, idx) => ({
|
|
156
|
+
key,
|
|
157
|
+
label: (auth.keylabelformat ?? "inline-key-{idx}").replace("{idx}", String(idx)),
|
|
158
|
+
source: "inline" as const,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** resolve keys from the ephemeral url source — babel town pattern */
|
|
163
|
+
async function keysfromephemeral(auth: authconfig): Promise<resolvedkey[]> {
|
|
164
|
+
if (!auth.keyurl) return [];
|
|
165
|
+
try {
|
|
166
|
+
const res = await fetch(auth.keyurl, { method: "GET" });
|
|
167
|
+
const data = (await res.json()) as Record<string, unknown>;
|
|
168
|
+
const key = String(data.key ?? data.api_key ?? data.token ?? "");
|
|
169
|
+
if (!key) return [];
|
|
170
|
+
return [{ key, label: "ephemeral", source: "ephemeralurl" as const }];
|
|
171
|
+
} catch {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** resolvekeys — resolve all available keys per the auth config source order */
|
|
177
|
+
export async function resolvekeys(auth: authconfig): Promise<resolvedkey[]> {
|
|
178
|
+
const sources = auth.keysources ?? ["env"];
|
|
179
|
+
const minlen = auth.minkeylength ?? 10;
|
|
180
|
+
const all: resolvedkey[] = [];
|
|
181
|
+
for (const source of sources) {
|
|
182
|
+
if (source === "db") all.push(...(await keysfromdb(auth)));
|
|
183
|
+
else if (source === "env") all.push(...keysfromenv(auth));
|
|
184
|
+
else if (source === "inline") all.push(...keysfrominline(auth));
|
|
185
|
+
else if (source === "ephemeralurl") all.push(...(await keysfromephemeral(auth)));
|
|
186
|
+
}
|
|
187
|
+
return all.filter((k) => k.key.length >= minlen);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// outbound header building — per auth method
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/** buildauthheaders — build upstream auth headers from the auth config and key */
|
|
195
|
+
export function buildauthheaders(auth: authconfig, key: string): Record<string, string> {
|
|
196
|
+
const headers: Record<string, string> = {};
|
|
197
|
+
switch (auth.mode) {
|
|
198
|
+
case "bearer":
|
|
199
|
+
case "oauth2clientcredentials":
|
|
200
|
+
case "jwtsign":
|
|
201
|
+
headers["authorization"] = `Bearer ${key}`;
|
|
202
|
+
break;
|
|
203
|
+
case "apikeyheader":
|
|
204
|
+
headers[auth.headername ?? "x-api-key"] = key;
|
|
205
|
+
break;
|
|
206
|
+
case "basic": {
|
|
207
|
+
const encoded = Buffer.from(key).toString("base64");
|
|
208
|
+
headers["authorization"] = `Basic ${encoded}`;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case "queryparam":
|
|
212
|
+
case "cookie":
|
|
213
|
+
case "mtls":
|
|
214
|
+
case "sigv4":
|
|
215
|
+
case "hmacsign":
|
|
216
|
+
case "keylesssdk":
|
|
217
|
+
case "anonymous":
|
|
218
|
+
case "none":
|
|
219
|
+
// these methods do not use simple headers — handled by transport or url
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
// ride-along headers — anthropic-version azure api-version http-referer x-title etc
|
|
223
|
+
for (const [k, v] of Object.entries(auth.extraheaders ?? {})) {
|
|
224
|
+
headers[k] = v;
|
|
225
|
+
}
|
|
226
|
+
return headers;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** buildauthqueryparams — build query params for queryparam auth mode */
|
|
230
|
+
export function buildauthqueryparams(auth: authconfig, key: string): Record<string, string> {
|
|
231
|
+
const params: Record<string, string> = {};
|
|
232
|
+
if (auth.mode === "queryparam") {
|
|
233
|
+
params[auth.queryparamname ?? "key"] = key;
|
|
234
|
+
}
|
|
235
|
+
for (const [k, v] of Object.entries(auth.extraparams ?? {})) {
|
|
236
|
+
params[k] = v;
|
|
237
|
+
}
|
|
238
|
+
return params;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** appendqueryparams — append query params to a url string */
|
|
242
|
+
export function appendqueryparams(url: string, params: Record<string, string>): string {
|
|
243
|
+
const entries = Object.entries(params);
|
|
244
|
+
if (entries.length === 0) return url;
|
|
245
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
246
|
+
const qs = entries.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
|
|
247
|
+
return `${url}${separator}${qs}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// legacy helpers — kept for backward compat with existing callers
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
/** get key by index — simple round robin helper for single key lookups */
|
|
255
|
+
export async function getkey(auth: authconfig, index = 0): Promise<string> {
|
|
256
|
+
const keys = await resolvekeys(auth);
|
|
257
|
+
if (keys.length === 0) return "";
|
|
258
|
+
return keys[index % keys.length].key;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** get key count — total resolvable keys */
|
|
262
|
+
export async function getkeycount(auth: authconfig): Promise<number> {
|
|
263
|
+
const keys = await resolvekeys(auth);
|
|
264
|
+
return keys.length;
|
|
265
|
+
}
|