@gajae-code/ai 0.15.5 → 0.15.6

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.
Files changed (58) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/types/auth-broker/client.d.ts +6 -2
  3. package/dist/types/auth-broker/remote-store.d.ts +14 -2
  4. package/dist/types/auth-broker/types.d.ts +6 -0
  5. package/dist/types/auth-broker/wire-schemas.d.ts +19 -0
  6. package/dist/types/auth-gateway/server.d.ts +39 -5
  7. package/dist/types/auth-gateway/types.d.ts +16 -2
  8. package/dist/types/auth-storage.d.ts +92 -24
  9. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  10. package/dist/types/providers/register-builtins.d.ts +12 -12
  11. package/dist/types/stream.d.ts +2 -1
  12. package/dist/types/types.d.ts +22 -2
  13. package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
  14. package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
  15. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  16. package/dist/types/utils/oauth/types.d.ts +1 -1
  17. package/dist/types/utils/retry.d.ts +2 -0
  18. package/package.json +3 -3
  19. package/src/auth-broker/client.ts +41 -13
  20. package/src/auth-broker/redact.ts +25 -1
  21. package/src/auth-broker/remote-store.ts +374 -115
  22. package/src/auth-broker/server.ts +131 -91
  23. package/src/auth-broker/types.ts +6 -0
  24. package/src/auth-broker/wire-schemas.ts +6 -0
  25. package/src/auth-gateway/server.ts +447 -79
  26. package/src/auth-gateway/types.ts +28 -2
  27. package/src/auth-storage.ts +658 -154
  28. package/src/cli.ts +1 -0
  29. package/src/models.json +1023 -0
  30. package/src/provider-models/descriptors.ts +3 -1
  31. package/src/provider-models/openai-compat.ts +41 -1
  32. package/src/providers/anthropic.ts +7 -1
  33. package/src/providers/azure-openai-responses.ts +4 -1
  34. package/src/providers/cursor.ts +256 -101
  35. package/src/providers/gitlab-duo.ts +18 -1
  36. package/src/providers/google-gemini-cli.ts +3 -0
  37. package/src/providers/google-shared.ts +3 -0
  38. package/src/providers/kiro-codewhisperer.ts +24 -8
  39. package/src/providers/ollama.ts +3 -0
  40. package/src/providers/openai-codex-responses.ts +20 -6
  41. package/src/providers/openai-completions.ts +11 -1
  42. package/src/providers/openai-responses.ts +10 -1
  43. package/src/providers/pi-native-client.ts +1 -0
  44. package/src/providers/pi-native-server.ts +24 -0
  45. package/src/providers/register-builtins.d.ts +12 -12
  46. package/src/providers/register-builtins.ts +16 -3
  47. package/src/stream.d.ts +2 -1
  48. package/src/stream.ts +175 -67
  49. package/src/types.d.ts +22 -2
  50. package/src/types.ts +27 -1
  51. package/src/utils/oauth/api-key-login.ts +13 -2
  52. package/src/utils/oauth/api-key-validation.ts +242 -41
  53. package/src/utils/oauth/commandcode.ts +17 -0
  54. package/src/utils/oauth/index.ts +20 -5
  55. package/src/utils/oauth/types.d.ts +1 -1
  56. package/src/utils/oauth/types.ts +1 -0
  57. package/src/utils/retry.d.ts +2 -0
  58. package/src/utils/retry.ts +15 -2
@@ -8,6 +8,7 @@
8
8
  import { readSseEvents } from "@gajae-code/utils";
9
9
  import type { ZodType, infer as zInfer } from "zod/v4";
10
10
  import type { AuthCredential } from "../auth-storage";
11
+ import type { Provider } from "../types";
11
12
  import type {
12
13
  CredentialDisableRequest,
13
14
  CredentialDisableResponse,
@@ -34,6 +35,8 @@ import {
34
35
  usageResponseSchema,
35
36
  } from "./wire-schemas";
36
37
 
38
+ export const AUTH_BROKER_EPOCH_HEADER = "X-GJC-Auth-Broker-Epoch";
39
+
37
40
  export interface AuthBrokerClientOptions {
38
41
  /** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
39
42
  url: string;
@@ -80,24 +83,27 @@ export class AuthBrokerCredentialMetadataUnsupportedError extends AuthBrokerErro
80
83
 
81
84
  export interface FetchSnapshotOptions {
82
85
  ifGenerationGt?: number;
86
+ ifEpoch?: string;
83
87
  waitMs?: number;
84
88
  signal?: AbortSignal;
85
89
  }
86
90
 
87
91
  export type FetchSnapshotResult =
88
92
  | { status: 200; snapshot: SnapshotResponse; generation: number }
89
- | { status: 304; generation: number };
93
+ | { status: 304; generation: number; epoch?: string };
90
94
 
91
- function parseGenerationTag(header: string | null): number | undefined {
95
+ function parseSnapshotTag(header: string | null): { epoch?: string; generation: number } | undefined {
92
96
  if (!header) return undefined;
93
97
  let value = header.trim();
94
98
  if (value.startsWith("W/")) value = value.slice(2).trim();
95
99
  if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
96
100
  value = value.slice(1, -1);
97
101
  }
98
- const generation = Number(value);
102
+ const separator = value.lastIndexOf(":");
103
+ const epoch = separator > 0 ? value.slice(0, separator) : undefined;
104
+ const generation = Number(separator > 0 ? value.slice(separator + 1) : value);
99
105
  if (!Number.isInteger(generation) || generation < 0) return undefined;
100
- return generation;
106
+ return { epoch, generation };
101
107
  }
102
108
 
103
109
  const DEFAULT_TIMEOUT_MS = 10_000;
@@ -136,6 +142,7 @@ export class AuthBrokerClient {
136
142
  try {
137
143
  return (await this.#request("GET", "/v1/credentials/metadata", {
138
144
  schema: credentialMetadataResponseSchema,
145
+ headers: { [AUTH_BROKER_EPOCH_HEADER]: "1" },
139
146
  signal,
140
147
  })) as CredentialMetadataResponse;
141
148
  } catch (error) {
@@ -150,7 +157,11 @@ export class AuthBrokerClient {
150
157
  if (opts.waitMs !== undefined) query.set("wait", String(opts.waitMs));
151
158
  const path = `/v1/snapshot${query.size > 0 ? `?${query.toString()}` : ""}`;
152
159
  const headers: Record<string, string> = {};
153
- if (opts.ifGenerationGt !== undefined) headers["If-None-Match"] = `"${opts.ifGenerationGt}"`;
160
+ headers[AUTH_BROKER_EPOCH_HEADER] = "1";
161
+ if (opts.ifGenerationGt !== undefined) {
162
+ const validator = opts.ifEpoch ? `${opts.ifEpoch}:${opts.ifGenerationGt}` : String(opts.ifGenerationGt);
163
+ headers["If-None-Match"] = `"${validator}"`;
164
+ }
154
165
  const timeoutMs =
155
166
  opts.waitMs !== undefined && opts.waitMs > 0 ? Math.max(this.#timeoutMs, opts.waitMs + 1000) : undefined;
156
167
  const response = await this.#fetchRaw("GET", path, {
@@ -159,9 +170,13 @@ export class AuthBrokerClient {
159
170
  signal: opts.signal,
160
171
  timeoutMs,
161
172
  });
162
- const etagGeneration = parseGenerationTag(response.headers.get("etag"));
173
+ const etag = parseSnapshotTag(response.headers.get("etag"));
163
174
  if (response.status === 304) {
164
- return { status: 304, generation: etagGeneration ?? opts.ifGenerationGt ?? 0 };
175
+ return {
176
+ status: 304,
177
+ generation: etag?.generation ?? opts.ifGenerationGt ?? 0,
178
+ ...(etag?.epoch ? { epoch: etag.epoch } : {}),
179
+ };
165
180
  }
166
181
  const text = await response.text();
167
182
  const raw = this.#parseJson(text, response.status);
@@ -173,7 +188,7 @@ export class AuthBrokerClient {
173
188
  });
174
189
  }
175
190
  const snapshot = validated.data as SnapshotResponse;
176
- return { status: 200, snapshot, generation: etagGeneration ?? snapshot.generation };
191
+ return { status: 200, snapshot, generation: etag?.generation ?? snapshot.generation };
177
192
  }
178
193
 
179
194
  /**
@@ -190,6 +205,7 @@ export class AuthBrokerClient {
190
205
  const headers: Record<string, string> = {
191
206
  Accept: "text/event-stream",
192
207
  Authorization: `Bearer ${this.#token}`,
208
+ [AUTH_BROKER_EPOCH_HEADER]: "1",
193
209
  };
194
210
  if (opts.signal?.aborted) {
195
211
  throw new AuthBrokerError("Auth broker request aborted", { cause: opts.signal.reason });
@@ -258,12 +274,13 @@ export class AuthBrokerClient {
258
274
  }
259
275
  }
260
276
 
261
- fetchUsage(signal?: AbortSignal): Promise<UsageResponse> {
277
+ fetchUsage(signal?: AbortSignal, provider?: Provider): Promise<UsageResponse> {
262
278
  // Validates the envelope (`generatedAt`, `reports[].provider`, `limits`,
263
279
  // `metadata`) but leaves provider-specific extension fields permissive so
264
280
  // the broker can ship new shapes ahead of the client. `raw` is accepted
265
281
  // but normally stripped by the broker before send.
266
- return this.#request("GET", "/v1/usage", { schema: usageResponseSchema, signal }) as Promise<UsageResponse>;
282
+ const path = provider ? `/v1/usage/scoped?provider=${encodeURIComponent(provider)}` : "/v1/usage";
283
+ return this.#request("GET", path, { schema: usageResponseSchema, signal }) as Promise<UsageResponse>;
267
284
  }
268
285
 
269
286
  async refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse> {
@@ -285,8 +302,13 @@ export class AuthBrokerClient {
285
302
  }) as Promise<CredentialRefreshResponse>;
286
303
  }
287
304
 
288
- async disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse> {
289
- const body: CredentialDisableRequest = { cause };
305
+ async disableCredential(
306
+ id: number,
307
+ cause: string,
308
+ signal?: AbortSignal,
309
+ expectedRevision?: number,
310
+ ): Promise<CredentialDisableResponse> {
311
+ const body: CredentialDisableRequest = { cause, ...(expectedRevision === undefined ? {} : { expectedRevision }) };
290
312
  return this.#request("POST", `/v1/credential/${id}/disable`, {
291
313
  body,
292
314
  schema: credentialDisableResponseSchema,
@@ -323,7 +345,13 @@ export class AuthBrokerClient {
323
345
  async #request<TSchema extends ZodType>(
324
346
  method: "GET" | "POST",
325
347
  path: string,
326
- opts: { schema: TSchema; auth?: boolean; body?: unknown; signal?: AbortSignal },
348
+ opts: {
349
+ schema: TSchema;
350
+ auth?: boolean;
351
+ body?: unknown;
352
+ signal?: AbortSignal;
353
+ headers?: Record<string, string>;
354
+ },
327
355
  ): Promise<zInfer<TSchema>> {
328
356
  const response = await this.#fetchRaw(method, path, opts);
329
357
  const text = await response.text();
@@ -7,8 +7,32 @@
7
7
  export function cleanReason(value: unknown): string | undefined {
8
8
  if (value === undefined || value === null) return undefined;
9
9
  let reason = value instanceof Error ? value.message : String(value);
10
+ if (/[\u0000-\u001f\u007f-\u009f]/.test(reason)) return "Credential diagnostic unavailable.";
11
+ if (reason.includes("\\")) return "Credential diagnostic unavailable.";
12
+ if (/["']?authorization(?:[_-]header)?["']?\s*[:=]/i.test(reason)) return "Credential diagnostic unavailable.";
13
+ if (
14
+ /\b["']?(?:key|api[_-]?key|client[_-]?secret|clientSecret|token|secret|password|access|refresh|cookie|credential)(?:[_-](?:token|key|secret|header|headers))?["']?\s*[:=]/i.test(
15
+ reason,
16
+ )
17
+ ) {
18
+ return "Credential diagnostic unavailable.";
19
+ }
20
+ if (/\b(?:bearer|basic)\s+["']/i.test(reason)) return "Credential diagnostic unavailable.";
21
+ if (/^No credential with id=\d+$/i.test(reason)) return reason;
22
+ if (/\b(?:api\s*[-_]?key|access\s*[-_]?token|refresh\s*[-_]?token|credential)\b/i.test(reason))
23
+ return "Credential diagnostic unavailable.";
10
24
  reason = reason.replace(/bearer\s+[^\s,;]+/gi, "Bearer [redacted]");
11
- reason = reason.replace(/(api[_-]?key|token|secret|authorization)[=:]\s*[^\s,;]+/gi, "$1=[redacted]");
25
+ reason = reason.replace(/basic\s+[^\s,;]+/gi, "Basic [redacted]");
26
+ reason = reason.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1[redacted]@");
27
+ reason = reason.replace(/\b([a-z][a-z0-9+.-]*:\/\/[^\s<>"']*?)(?:[?#][^\s<>"']*)/gi, "$1");
28
+ reason = reason.replace(
29
+ /((?:\\?["']?(?:key|api[_-]?key|client[_-]?secret|clientSecret|token|secret|authorization|password|access|refresh|cookie|credential)(?:[_-](?:token|key|secret|header|headers))?\\?["']?)\s*:\s*)\\?(["'])(?:\\.|(?!\2)[^\\])*\2/gi,
30
+ "$1$2[redacted]$2",
31
+ );
32
+ reason = reason.replace(
33
+ /((?:key|api[_-]?key|client[_-]?secret|clientSecret|token|secret|authorization|password|access|refresh|cookie|credential)(?:[_-](?:token|key|secret|header|headers))?)\s*[:=]\s*[^\s,;]+/gi,
34
+ "$1=[redacted]",
35
+ );
12
36
  reason = reason.replace(/[\r\n\t ]+/g, " ").trim();
13
37
  if (reason.length > 256) reason = `${reason.slice(0, 253)}...`;
14
38
  return reason || undefined;