@gajae-code/ai 0.13.2 → 0.14.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/CHANGELOG.md +61 -2
- package/dist/types/auth-broker/client.d.ts +9 -1
- package/dist/types/auth-broker/redact.d.ts +7 -0
- package/dist/types/auth-broker/remote-store.d.ts +50 -9
- package/dist/types/auth-broker/types.d.ts +14 -0
- package/dist/types/auth-broker/wire-schemas.d.ts +25 -0
- package/dist/types/auth-storage.d.ts +200 -6
- package/dist/types/core.d.ts +1 -0
- package/dist/types/model-cache.d.ts +4 -1
- package/dist/types/model-manager.d.ts +11 -0
- package/dist/types/provider-models/openai-compat.d.ts +5 -0
- package/dist/types/provider-models/special.d.ts +3 -0
- package/dist/types/providers/anthropic.d.ts +31 -0
- package/dist/types/providers/cursor.d.ts +9 -1
- package/dist/types/providers/kiro-codewhisperer.d.ts +8 -0
- package/dist/types/providers/mock.d.ts +8 -0
- package/dist/types/providers/register-builtins.d.ts +1 -0
- package/dist/types/providers/transform-messages.d.ts +18 -0
- package/dist/types/types.d.ts +34 -8
- package/dist/types/usage/grok-cli.d.ts +5 -0
- package/dist/types/usage.d.ts +6 -0
- package/dist/types/utils/discovery/openai-compatible.d.ts +5 -0
- package/dist/types/utils/event-stream.d.ts +4 -2
- package/dist/types/utils/fallback-transport.d.ts +10 -0
- package/dist/types/utils/http-inspector.d.ts +1 -0
- package/dist/types/utils/idle-iterator.d.ts +13 -1
- package/dist/types/utils/json-parse.d.ts +19 -0
- package/dist/types/utils/oauth/callback-server.d.ts +13 -0
- package/dist/types/utils/oauth/kiro.d.ts +71 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/dist/types/utils/parse-bind.d.ts +8 -5
- package/dist/types/utils/tool-call-healing.d.ts +7 -0
- package/dist/types/utils/tool-choice-capability.d.ts +11 -0
- package/package.json +3 -2
- package/src/auth-broker/client.ts +30 -0
- package/src/auth-broker/redact.ts +15 -0
- package/src/auth-broker/refresher.ts +4 -2
- package/src/auth-broker/remote-store.ts +693 -70
- package/src/auth-broker/server.ts +57 -12
- package/src/auth-broker/types.ts +16 -0
- package/src/auth-broker/wire-schemas.ts +21 -0
- package/src/auth-gateway/server.ts +84 -19
- package/src/auth-storage.ts +985 -41
- package/src/core.ts +1 -0
- package/src/model-cache.ts +23 -4
- package/src/model-manager.ts +70 -11
- package/src/model-thinking.ts +45 -1
- package/src/models.json +9604 -1932
- package/src/openai-completions-compat.ts +2 -1
- package/src/provider-models/descriptors.ts +7 -1
- package/src/provider-models/openai-compat.ts +52 -28
- package/src/provider-models/special.ts +12 -0
- package/src/providers/amazon-bedrock.ts +2 -1
- package/src/providers/anthropic.ts +831 -27
- package/src/providers/cursor.ts +83 -3
- package/src/providers/kiro-codewhisperer.ts +572 -0
- package/src/providers/mock.ts +15 -2
- package/src/providers/ollama.ts +9 -2
- package/src/providers/openai-codex-responses.ts +16 -9
- package/src/providers/openai-completions.ts +6 -1
- package/src/providers/openai-responses-shared.ts +180 -18
- package/src/providers/register-builtins.ts +24 -2
- package/src/providers/transform-messages.ts +64 -1
- package/src/stream.ts +25 -2
- package/src/types.ts +36 -7
- package/src/usage/grok-cli.ts +86 -1
- package/src/usage.ts +7 -0
- package/src/utils/discovery/openai-compatible.ts +89 -4
- package/src/utils/event-stream.ts +11 -2
- package/src/utils/fallback-transport.ts +44 -2
- package/src/utils/http-inspector.ts +1 -0
- package/src/utils/idle-iterator.ts +29 -6
- package/src/utils/json-parse.ts +80 -0
- package/src/utils/oauth/callback-server.ts +31 -1
- package/src/utils/oauth/index.ts +14 -1
- package/src/utils/oauth/kiro.ts +448 -0
- package/src/utils/oauth/synthetic.ts +2 -3
- package/src/utils/oauth/types.ts +1 -0
- package/src/utils/parse-bind.ts +27 -0
- package/src/utils/tool-call-healing.ts +13 -2
- package/src/utils/tool-choice-capability.ts +386 -6
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
* the server only checks a bearer token against an allow-list per request.
|
|
11
11
|
*/
|
|
12
12
|
import { logger } from "@gajae-code/utils";
|
|
13
|
+
import { timingSafeEqual } from "../auth-gateway/http";
|
|
13
14
|
import type { AuthStorage } from "../auth-storage";
|
|
14
|
-
import { parseBind } from "../utils/parse-bind";
|
|
15
|
+
import { assertAuthenticatedOrLoopback, parseBind } from "../utils/parse-bind";
|
|
16
|
+
import { cleanReason } from "./redact";
|
|
15
17
|
import { AuthBrokerRefresher, type AuthBrokerRefresherSchedule } from "./refresher";
|
|
16
18
|
import type {
|
|
17
19
|
CredentialDisableResponse,
|
|
18
20
|
CredentialIfAbsentUploadResponse,
|
|
21
|
+
CredentialMetadataResponse,
|
|
19
22
|
CredentialRefreshRequest,
|
|
20
23
|
CredentialRefreshResponse,
|
|
21
24
|
CredentialUploadResponse,
|
|
@@ -88,7 +91,13 @@ function isAuthorized(req: Request, tokens: ReadonlySet<string>): boolean {
|
|
|
88
91
|
if (!header) return false;
|
|
89
92
|
const match = header.match(/^Bearer\s+(.+)$/i);
|
|
90
93
|
if (!match) return false;
|
|
91
|
-
|
|
94
|
+
const presented = new TextEncoder().encode(match[1].trim());
|
|
95
|
+
let ok = false;
|
|
96
|
+
for (const token of tokens) {
|
|
97
|
+
const expected = new TextEncoder().encode(token);
|
|
98
|
+
if (timingSafeEqual(presented, expected)) ok = true;
|
|
99
|
+
}
|
|
100
|
+
return ok;
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
/**
|
|
@@ -105,7 +114,12 @@ async function parseBody<T>(
|
|
|
105
114
|
try {
|
|
106
115
|
raw = await req.text();
|
|
107
116
|
} catch (error) {
|
|
108
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
response: json(400, {
|
|
120
|
+
error: `Invalid request body: ${cleanReason(error) ?? "request body could not be read"}`,
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
109
123
|
}
|
|
110
124
|
if (raw.length === 0 && !options.allowEmpty) {
|
|
111
125
|
return { ok: false, response: json(400, { error: "Request body required" }) };
|
|
@@ -114,7 +128,12 @@ async function parseBody<T>(
|
|
|
114
128
|
try {
|
|
115
129
|
parsed = raw.length === 0 ? {} : JSON.parse(raw);
|
|
116
130
|
} catch (error) {
|
|
117
|
-
return {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
response: json(400, {
|
|
134
|
+
error: `Invalid JSON body: ${cleanReason(error) ?? "request body could not be parsed"}`,
|
|
135
|
+
}),
|
|
136
|
+
};
|
|
118
137
|
}
|
|
119
138
|
const result = schema.safeParse(parsed);
|
|
120
139
|
if (!result.success) {
|
|
@@ -284,6 +303,22 @@ function buildSnapshot(storage: AuthStorage, refresher: AuthBrokerRefresher | un
|
|
|
284
303
|
};
|
|
285
304
|
}
|
|
286
305
|
|
|
306
|
+
/** Build the payload-free credential inventory projection for the metadata route. */
|
|
307
|
+
function buildCredentialMetadata(storage: AuthStorage): CredentialMetadataResponse {
|
|
308
|
+
const credentials = storage.listCredentialInventory().map(record => ({
|
|
309
|
+
id: record.id,
|
|
310
|
+
provider: record.provider,
|
|
311
|
+
type: record.credentialKind,
|
|
312
|
+
identity: record.identityLabel,
|
|
313
|
+
disabledCause: record.disabledCause,
|
|
314
|
+
}));
|
|
315
|
+
return {
|
|
316
|
+
generation: storage.getGeneration(),
|
|
317
|
+
generatedAt: Date.now(),
|
|
318
|
+
credentials,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
287
322
|
async function serveSnapshot(
|
|
288
323
|
req: Request,
|
|
289
324
|
url: URL,
|
|
@@ -505,6 +540,7 @@ function serveSnapshotStream(
|
|
|
505
540
|
export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServerHandle {
|
|
506
541
|
const bind = parseBind(opts.bind ?? DEFAULT_AUTH_BROKER_BIND);
|
|
507
542
|
const tokens = new Set<string>(opts.bearerTokens);
|
|
543
|
+
assertAuthenticatedOrLoopback(bind, tokens.size, "auth-broker");
|
|
508
544
|
const version = opts.version;
|
|
509
545
|
const streamKeepaliveMs = opts.streamKeepaliveMs ?? DEFAULT_STREAM_KEEPALIVE_MS;
|
|
510
546
|
|
|
@@ -536,6 +572,15 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
536
572
|
logger.info("auth-broker request unauthorized", { method: req.method, path: pathname, peer });
|
|
537
573
|
return json(401, { error: "unauthorized" });
|
|
538
574
|
}
|
|
575
|
+
if (req.method === "GET" && pathname === "/v1/credentials/metadata") {
|
|
576
|
+
await opts.storage.reload();
|
|
577
|
+
const body = buildCredentialMetadata(opts.storage);
|
|
578
|
+
logger.info("auth-broker credential metadata served", {
|
|
579
|
+
generation: body.generation,
|
|
580
|
+
status: "ok",
|
|
581
|
+
});
|
|
582
|
+
return json(200, body);
|
|
583
|
+
}
|
|
539
584
|
if (req.method === "GET" && pathname === "/v1/snapshot/stream") {
|
|
540
585
|
return serveSnapshotStream(req, opts.storage, refresher, peer, streamKeepaliveMs);
|
|
541
586
|
}
|
|
@@ -557,7 +602,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
557
602
|
logger.info("auth-broker usage served", { peer, reports: trimmed.length });
|
|
558
603
|
return json(200, { generatedAt: Date.now(), reports: trimmed });
|
|
559
604
|
} catch (error) {
|
|
560
|
-
const message = error
|
|
605
|
+
const message = cleanReason(error) ?? "Usage fetch failed";
|
|
561
606
|
logger.warn("auth-broker usage fetch failed", { peer, error: message });
|
|
562
607
|
return json(502, { error: message });
|
|
563
608
|
}
|
|
@@ -579,9 +624,9 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
579
624
|
});
|
|
580
625
|
return json(200, body);
|
|
581
626
|
} catch (error) {
|
|
582
|
-
const message = error
|
|
627
|
+
const message = cleanReason(error) ?? "Credential refresh failed";
|
|
583
628
|
logger.warn("auth-broker refresh failed", { id, peer, error: message });
|
|
584
|
-
const status = message.
|
|
629
|
+
const status = message.startsWith("No credential with id") ? 404 : 500;
|
|
585
630
|
return json(status, { error: message });
|
|
586
631
|
}
|
|
587
632
|
}
|
|
@@ -594,10 +639,10 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
594
639
|
parsed.data.cause && parsed.data.cause.length > 0 ? parsed.data.cause : "disabled via auth-broker";
|
|
595
640
|
const ok = opts.storage.disableCredentialById(id, cause);
|
|
596
641
|
if (!ok) {
|
|
597
|
-
logger.info("auth-broker disable miss", { id, peer
|
|
642
|
+
logger.info("auth-broker disable miss", { id, peer });
|
|
598
643
|
return json(404, { error: `No credential with id=${id}` });
|
|
599
644
|
}
|
|
600
|
-
logger.info("auth-broker credential disabled", { id, peer
|
|
645
|
+
logger.info("auth-broker credential disabled", { id, peer });
|
|
601
646
|
const response: CredentialDisableResponse = { ok: true };
|
|
602
647
|
return json(200, response);
|
|
603
648
|
}
|
|
@@ -622,7 +667,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
622
667
|
};
|
|
623
668
|
return json(200, response);
|
|
624
669
|
} catch (error) {
|
|
625
|
-
const message = error
|
|
670
|
+
const message = cleanReason(error) ?? "Credential import failed";
|
|
626
671
|
logger.warn("auth-broker upload-if-absent failed", { provider, type: credential.type, peer });
|
|
627
672
|
return json(500, { error: message });
|
|
628
673
|
}
|
|
@@ -647,7 +692,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
647
692
|
const response: CredentialUploadResponse = { entries };
|
|
648
693
|
return json(200, response);
|
|
649
694
|
} catch (error) {
|
|
650
|
-
const message = error
|
|
695
|
+
const message = cleanReason(error) ?? "Credential upload failed";
|
|
651
696
|
logger.warn("auth-broker upload failed", { provider, peer, error: message });
|
|
652
697
|
return json(500, { error: message });
|
|
653
698
|
}
|
|
@@ -657,7 +702,7 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
|
|
|
657
702
|
logger.error("auth-broker handler crashed", {
|
|
658
703
|
method: req.method,
|
|
659
704
|
path: pathname,
|
|
660
|
-
error:
|
|
705
|
+
error: cleanReason(error) ?? "internal error",
|
|
661
706
|
});
|
|
662
707
|
return json(500, { error: "internal error" });
|
|
663
708
|
}
|
package/src/auth-broker/types.ts
CHANGED
|
@@ -15,6 +15,22 @@ import type {
|
|
|
15
15
|
} from "../auth-storage";
|
|
16
16
|
import type { UsageReport } from "../usage";
|
|
17
17
|
|
|
18
|
+
/** A single credential metadata projection; secret-bearing fields are intentionally absent. */
|
|
19
|
+
export interface CredentialMetadataRecord {
|
|
20
|
+
id: number;
|
|
21
|
+
provider: string;
|
|
22
|
+
type: "oauth" | "api_key";
|
|
23
|
+
identity: string | null;
|
|
24
|
+
disabledCause: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** GET /v1/credentials/metadata response body. */
|
|
28
|
+
export interface CredentialMetadataResponse {
|
|
29
|
+
generation: number;
|
|
30
|
+
generatedAt: number;
|
|
31
|
+
credentials: CredentialMetadataRecord[];
|
|
32
|
+
}
|
|
33
|
+
|
|
18
34
|
/** GET /v1/healthz response body. */
|
|
19
35
|
export interface HealthzResponse {
|
|
20
36
|
ok: boolean;
|
|
@@ -109,6 +109,27 @@ export const snapshotResponseSchema = z
|
|
|
109
109
|
})
|
|
110
110
|
.strict();
|
|
111
111
|
|
|
112
|
+
// ─── Credential metadata ───────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/** Closed redacted projection: no token, key, identity object, or extension fields. */
|
|
115
|
+
export const credentialMetadataRecordSchema = z
|
|
116
|
+
.object({
|
|
117
|
+
id: z.number().int(),
|
|
118
|
+
provider: z.string().min(1),
|
|
119
|
+
type: z.enum(["oauth", "api_key"]),
|
|
120
|
+
identity: z.string().nullable(),
|
|
121
|
+
disabledCause: z.string().nullable(),
|
|
122
|
+
})
|
|
123
|
+
.strict();
|
|
124
|
+
|
|
125
|
+
export const credentialMetadataResponseSchema = z
|
|
126
|
+
.object({
|
|
127
|
+
generation: z.number().int().nonnegative(),
|
|
128
|
+
generatedAt: z.number().finite().nonnegative(),
|
|
129
|
+
credentials: z.array(credentialMetadataRecordSchema),
|
|
130
|
+
})
|
|
131
|
+
.strict();
|
|
132
|
+
|
|
112
133
|
// ─── Snapshot stream (SSE) ────────────────────────────────────────────────
|
|
113
134
|
|
|
114
135
|
/** First frame on connect — full snapshot embedded inline with a `kind` tag. */
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { logger } from "@gajae-code/utils";
|
|
22
|
+
import { cleanReason } from "../auth-broker/redact";
|
|
22
23
|
import type { AuthStorage } from "../auth-storage";
|
|
23
24
|
import { Effort } from "../model-thinking";
|
|
24
25
|
import * as anthropicMessages from "../providers/anthropic-messages-server";
|
|
@@ -26,9 +27,17 @@ import * as openaiChat from "../providers/openai-chat-server";
|
|
|
26
27
|
import * as openaiResponses from "../providers/openai-responses-server";
|
|
27
28
|
import * as piNative from "../providers/pi-native-server";
|
|
28
29
|
import { streamSimple } from "../stream";
|
|
29
|
-
import type {
|
|
30
|
+
import type {
|
|
31
|
+
Api,
|
|
32
|
+
AssistantMessage,
|
|
33
|
+
AssistantMessageEvent,
|
|
34
|
+
AssistantMessageEventStream,
|
|
35
|
+
Context,
|
|
36
|
+
Model,
|
|
37
|
+
SimpleStreamOptions,
|
|
38
|
+
} from "../types";
|
|
30
39
|
import { beginAttempt, classifyFallbackTrigger } from "../utils/fallback-transport";
|
|
31
|
-
import { parseBind } from "../utils/parse-bind";
|
|
40
|
+
import { assertAuthenticatedOrLoopback, parseBind } from "../utils/parse-bind";
|
|
32
41
|
import {
|
|
33
42
|
captureRequestHeaders,
|
|
34
43
|
corsHeaders,
|
|
@@ -204,8 +213,9 @@ function buildStreamOptions(parsed: ParsedFormatRequest, api: Api, signal: Abort
|
|
|
204
213
|
* format emits its native envelope shape.
|
|
205
214
|
*/
|
|
206
215
|
function classifyGatewayError(err: unknown): { status: number; type: string; message: string } {
|
|
207
|
-
const
|
|
208
|
-
const
|
|
216
|
+
const rawMessage = err instanceof Error ? err.message : String(err);
|
|
217
|
+
const message = cleanReason(rawMessage) ?? "Upstream request failed";
|
|
218
|
+
const lower = rawMessage.toLowerCase();
|
|
209
219
|
|
|
210
220
|
// Custom pi-ai errors may attach a numeric `status` property; honor it
|
|
211
221
|
// when present and pick the matching tag.
|
|
@@ -242,6 +252,48 @@ function classifyGatewayError(err: unknown): { status: number; type: string; mes
|
|
|
242
252
|
return { status: 502, type: "upstream_error", message };
|
|
243
253
|
}
|
|
244
254
|
|
|
255
|
+
function redactGatewayError(error: unknown): Error {
|
|
256
|
+
const redacted = new Error(cleanReason(error) ?? "Upstream request failed");
|
|
257
|
+
if (error instanceof Error) {
|
|
258
|
+
redacted.name = error.name;
|
|
259
|
+
const status = (error as { status?: unknown }).status;
|
|
260
|
+
if (typeof status === "number") (redacted as Error & { status: number }).status = status;
|
|
261
|
+
}
|
|
262
|
+
return redacted;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function redactGatewayMessage(message: AssistantMessage): AssistantMessage {
|
|
266
|
+
if (message.errorMessage === undefined) return message;
|
|
267
|
+
return { ...message, errorMessage: cleanReason(message.errorMessage) ?? "Upstream request failed" };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Protect all gateway SSE encoders from upstream error text. Provider wire
|
|
272
|
+
* modules format `error` events themselves, so sanitize at this boundary and
|
|
273
|
+
* also convert iterator failures to bounded errors before their catch blocks.
|
|
274
|
+
*/
|
|
275
|
+
function redactGatewayStream(events: AssistantMessageEventStream): AssistantMessageEventStream {
|
|
276
|
+
async function* redactedEvents(): AsyncGenerator<AssistantMessageEvent> {
|
|
277
|
+
try {
|
|
278
|
+
for await (const event of events) {
|
|
279
|
+
if (event.type === "error") yield { ...event, error: redactGatewayMessage(event.error) };
|
|
280
|
+
else yield event;
|
|
281
|
+
}
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw redactGatewayError(error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const result = redactedEvents() as unknown as AssistantMessageEventStream;
|
|
287
|
+
result.result = async () => {
|
|
288
|
+
try {
|
|
289
|
+
return redactGatewayMessage(await events.result());
|
|
290
|
+
} catch (error) {
|
|
291
|
+
throw redactGatewayError(error);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
245
297
|
async function refreshGatewayApiKeyAfterAuthError(
|
|
246
298
|
storage: AuthStorage,
|
|
247
299
|
model: Model<Api>,
|
|
@@ -257,7 +309,7 @@ async function refreshGatewayApiKeyAfterAuthError(
|
|
|
257
309
|
format,
|
|
258
310
|
provider,
|
|
259
311
|
peer,
|
|
260
|
-
error: error
|
|
312
|
+
error: cleanReason(error) ?? "Upstream request failed",
|
|
261
313
|
});
|
|
262
314
|
return storage.getApiKey(provider, undefined, { modelId: model.id, signal });
|
|
263
315
|
}
|
|
@@ -308,7 +360,7 @@ async function markManagedGatewayCredentialFailure(
|
|
|
308
360
|
format,
|
|
309
361
|
provider: model.provider,
|
|
310
362
|
peer,
|
|
311
|
-
error: markError
|
|
363
|
+
error: cleanReason(markError) ?? "Credential bookkeeping failed",
|
|
312
364
|
});
|
|
313
365
|
}
|
|
314
366
|
}
|
|
@@ -382,7 +434,11 @@ async function handleFormatEndpoint(
|
|
|
382
434
|
body = await req.json();
|
|
383
435
|
} catch (error) {
|
|
384
436
|
if (controller.signal.aborted) return clientClosedResponse(route);
|
|
385
|
-
return route.module.formatError(
|
|
437
|
+
return route.module.formatError(
|
|
438
|
+
400,
|
|
439
|
+
"invalid_request_error",
|
|
440
|
+
`Invalid JSON body: ${cleanReason(error) ?? "request body could not be parsed"}`,
|
|
441
|
+
);
|
|
386
442
|
}
|
|
387
443
|
if (controller.signal.aborted) return clientClosedResponse(route);
|
|
388
444
|
|
|
@@ -438,7 +494,7 @@ async function handleFormatEndpoint(
|
|
|
438
494
|
parsed = route.module.parseRequest(body, req.headers);
|
|
439
495
|
} catch (error) {
|
|
440
496
|
if (controller.signal.aborted) return clientClosedResponse(route);
|
|
441
|
-
const message = error
|
|
497
|
+
const message = cleanReason(error) ?? "Request validation failed";
|
|
442
498
|
return route.module.formatError(400, "invalid_request_error", message);
|
|
443
499
|
}
|
|
444
500
|
// Merge gateway-captured passthrough headers under the parser's own
|
|
@@ -511,6 +567,7 @@ async function handleFormatEndpoint(
|
|
|
511
567
|
),
|
|
512
568
|
);
|
|
513
569
|
}
|
|
570
|
+
events = redactGatewayStream(events);
|
|
514
571
|
|
|
515
572
|
if (!parsed.stream) {
|
|
516
573
|
try {
|
|
@@ -520,17 +577,18 @@ async function handleFormatEndpoint(
|
|
|
520
577
|
const errorMessage =
|
|
521
578
|
message.errorMessage ??
|
|
522
579
|
(message.stopReason === "aborted" ? "Request was aborted" : "Upstream request failed");
|
|
580
|
+
const safeErrorMessage = cleanReason(errorMessage) ?? "Upstream request failed";
|
|
523
581
|
logger.warn("auth-gateway non-streaming failed", {
|
|
524
582
|
format: route.label,
|
|
525
583
|
reason: message.stopReason,
|
|
526
|
-
error:
|
|
584
|
+
error: safeErrorMessage,
|
|
527
585
|
peer,
|
|
528
586
|
});
|
|
529
587
|
if (message.stopReason === "aborted") {
|
|
530
|
-
return route.module.formatError(499, "request_aborted",
|
|
588
|
+
return route.module.formatError(499, "request_aborted", safeErrorMessage);
|
|
531
589
|
}
|
|
532
|
-
const classified = classifyGatewayError(new Error(
|
|
533
|
-
return route.module.formatError(classified.status, classified.type,
|
|
590
|
+
const classified = classifyGatewayError(new Error(safeErrorMessage));
|
|
591
|
+
return route.module.formatError(classified.status, classified.type, classified.message);
|
|
534
592
|
}
|
|
535
593
|
return json(200, route.module.encodeResponse(message, parsed.modelId));
|
|
536
594
|
} catch (error) {
|
|
@@ -585,7 +643,11 @@ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, pe
|
|
|
585
643
|
body = await req.json();
|
|
586
644
|
} catch (error) {
|
|
587
645
|
if (controller.signal.aborted) return aborted();
|
|
588
|
-
return piNative.formatError(
|
|
646
|
+
return piNative.formatError(
|
|
647
|
+
400,
|
|
648
|
+
"invalid_request_error",
|
|
649
|
+
`Invalid JSON body: ${cleanReason(error) ?? "request body could not be parsed"}`,
|
|
650
|
+
);
|
|
589
651
|
}
|
|
590
652
|
if (controller.signal.aborted) return aborted();
|
|
591
653
|
|
|
@@ -594,7 +656,7 @@ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, pe
|
|
|
594
656
|
parsed = piNative.parseRequest(body, req.headers);
|
|
595
657
|
} catch (error) {
|
|
596
658
|
if (controller.signal.aborted) return aborted();
|
|
597
|
-
const message = error
|
|
659
|
+
const message = cleanReason(error) ?? "Request validation failed";
|
|
598
660
|
return piNative.formatError(400, "invalid_request_error", message);
|
|
599
661
|
}
|
|
600
662
|
|
|
@@ -699,6 +761,7 @@ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, pe
|
|
|
699
761
|
),
|
|
700
762
|
);
|
|
701
763
|
}
|
|
764
|
+
events = redactGatewayStream(events);
|
|
702
765
|
|
|
703
766
|
if (!parsed.stream) {
|
|
704
767
|
try {
|
|
@@ -708,17 +771,18 @@ async function handlePiNative(bootOpts: AuthGatewayBootOptions, req: Request, pe
|
|
|
708
771
|
const errorMessage =
|
|
709
772
|
message.errorMessage ??
|
|
710
773
|
(message.stopReason === "aborted" ? "Request was aborted" : "Upstream request failed");
|
|
774
|
+
const safeErrorMessage = cleanReason(errorMessage) ?? "Upstream request failed";
|
|
711
775
|
logger.warn("auth-gateway non-streaming failed", {
|
|
712
776
|
format: "pi-native",
|
|
713
777
|
reason: message.stopReason,
|
|
714
|
-
error:
|
|
778
|
+
error: safeErrorMessage,
|
|
715
779
|
peer,
|
|
716
780
|
});
|
|
717
781
|
if (message.stopReason === "aborted") {
|
|
718
|
-
return piNative.formatError(499, "request_aborted",
|
|
782
|
+
return piNative.formatError(499, "request_aborted", safeErrorMessage);
|
|
719
783
|
}
|
|
720
|
-
const classified = classifyGatewayError(new Error(
|
|
721
|
-
return piNative.formatError(classified.status, classified.type,
|
|
784
|
+
const classified = classifyGatewayError(new Error(safeErrorMessage));
|
|
785
|
+
return piNative.formatError(classified.status, classified.type, classified.message);
|
|
722
786
|
}
|
|
723
787
|
return json(200, { message });
|
|
724
788
|
} catch (error) {
|
|
@@ -787,6 +851,7 @@ function handleModelsList(opts: AuthGatewayBootOptions): Response {
|
|
|
787
851
|
export function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServerHandle {
|
|
788
852
|
const bind = parseBind(opts.bind ?? DEFAULT_AUTH_GATEWAY_BIND);
|
|
789
853
|
const tokens = new Set<string>(opts.bearerTokens);
|
|
854
|
+
assertAuthenticatedOrLoopback(bind, tokens.size, "auth-gateway");
|
|
790
855
|
const version = opts.version;
|
|
791
856
|
|
|
792
857
|
const server = Bun.serve({
|
|
@@ -859,7 +924,7 @@ export function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServe
|
|
|
859
924
|
method: req.method,
|
|
860
925
|
path: pathname,
|
|
861
926
|
peer,
|
|
862
|
-
error:
|
|
927
|
+
error: cleanReason(error) ?? "internal error",
|
|
863
928
|
});
|
|
864
929
|
return withCors(json(500, { error: "internal error" }), req);
|
|
865
930
|
}
|