@gajae-code/ai 0.15.4 → 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 (77) hide show
  1. package/CHANGELOG.md +27 -1
  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 +116 -34
  9. package/dist/types/provider-models/openai-compat.d.ts +1 -0
  10. package/dist/types/provider-models/special.d.ts +2 -1
  11. package/dist/types/providers/kiro-api-key.d.ts +50 -0
  12. package/dist/types/providers/kiro-codewhisperer.d.ts +3 -0
  13. package/dist/types/providers/register-builtins.d.ts +12 -12
  14. package/dist/types/stream.d.ts +2 -1
  15. package/dist/types/types.d.ts +35 -24
  16. package/dist/types/utils/fallback-transport.d.ts +7 -0
  17. package/dist/types/utils/json-parse.d.ts +5 -3
  18. package/dist/types/utils/oauth/api-key-login.d.ts +4 -1
  19. package/dist/types/utils/oauth/api-key-validation.d.ts +12 -6
  20. package/dist/types/utils/oauth/commandcode.d.ts +1 -0
  21. package/dist/types/utils/oauth/types.d.ts +1 -1
  22. package/dist/types/utils/retry.d.ts +2 -0
  23. package/dist/types/utils/tool-call-healing.d.ts +4 -4
  24. package/package.json +3 -3
  25. package/src/auth-broker/client.ts +41 -13
  26. package/src/auth-broker/redact.ts +25 -1
  27. package/src/auth-broker/remote-store.ts +374 -115
  28. package/src/auth-broker/server.ts +131 -91
  29. package/src/auth-broker/types.ts +6 -0
  30. package/src/auth-broker/wire-schemas.ts +6 -0
  31. package/src/auth-gateway/server.ts +447 -79
  32. package/src/auth-gateway/types.ts +28 -2
  33. package/src/auth-storage.ts +742 -157
  34. package/src/cli.ts +1 -0
  35. package/src/model-thinking.ts +16 -0
  36. package/src/models.json +1054 -0
  37. package/src/models.ts +9 -1
  38. package/src/provider-models/descriptors.ts +3 -1
  39. package/src/provider-models/openai-compat.ts +41 -1
  40. package/src/provider-models/special.ts +15 -3
  41. package/src/providers/anthropic.ts +7 -1
  42. package/src/providers/azure-openai-responses.ts +4 -1
  43. package/src/providers/cursor.ts +256 -101
  44. package/src/providers/gitlab-duo.ts +18 -1
  45. package/src/providers/google-gemini-cli.ts +3 -0
  46. package/src/providers/google-shared.ts +3 -0
  47. package/src/providers/kiro-api-key.d.ts +50 -0
  48. package/src/providers/kiro-api-key.ts +786 -0
  49. package/src/providers/kiro-codewhisperer.d.ts +3 -0
  50. package/src/providers/kiro-codewhisperer.ts +34 -9
  51. package/src/providers/ollama.ts +3 -0
  52. package/src/providers/openai-codex-responses.ts +24 -6
  53. package/src/providers/openai-completions.ts +11 -1
  54. package/src/providers/openai-responses-shared.ts +23 -2
  55. package/src/providers/openai-responses.ts +10 -1
  56. package/src/providers/pi-native-client.ts +1 -0
  57. package/src/providers/pi-native-server.ts +24 -0
  58. package/src/providers/register-builtins.d.ts +12 -12
  59. package/src/providers/register-builtins.ts +16 -3
  60. package/src/stream.d.ts +2 -1
  61. package/src/stream.ts +180 -70
  62. package/src/types.d.ts +35 -24
  63. package/src/types.ts +40 -23
  64. package/src/utils/fallback-transport.d.ts +7 -0
  65. package/src/utils/fallback-transport.ts +21 -4
  66. package/src/utils/json-parse.d.ts +5 -3
  67. package/src/utils/json-parse.ts +6 -6
  68. package/src/utils/oauth/api-key-login.ts +13 -2
  69. package/src/utils/oauth/api-key-validation.ts +242 -41
  70. package/src/utils/oauth/commandcode.ts +17 -0
  71. package/src/utils/oauth/index.ts +20 -5
  72. package/src/utils/oauth/types.d.ts +1 -1
  73. package/src/utils/oauth/types.ts +1 -0
  74. package/src/utils/retry.d.ts +2 -0
  75. package/src/utils/retry.ts +15 -2
  76. package/src/utils/tool-call-healing.d.ts +4 -4
  77. package/src/utils/tool-call-healing.ts +4 -4
package/CHANGELOG.md CHANGED
@@ -2,13 +2,39 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.15.6] - 2026-08-30
6
+
7
+ ### Added
8
+
9
+ - `AuthGatewayServerOptions` now requires `providerScope`; `AuthGatewayBootOptions` requires the provider-filtered `listModels` callback and an exact-catalog-object `resolveModel`. Direct `@gajae-code/ai` callers must construct one gateway instance per provider and pass a provider-filtered source catalog. This matches the mandatory `--provider=<id>` CLI migration and prevents cross-provider model and credential ambiguity.
10
+ - `AuthGatewayBootOptions` now requires live authority callbacks (`hasProviderCredential`, `reloadProviderCredentials`, and `validateProviderCredential`). Gateway dispatch holds the authority lease through `streamSimple()` admission and rejects credentials revoked during asynchronous selection instead of sending a stale key.
11
+ - `auth-broker serve` now requires an `AuthCredentialStore` with atomic `allocateMonotonicSequence` support for durable broker incarnation epochs; unsupported custom stores fail closed before binding and must migrate to the durable cache-backed contract.
12
+
13
+ ### Fixed
14
+
15
+ - Cursor HTTP/2 streams now drain admitted exec responses before normal teardown and centralize terminal failures, preventing delayed handlers and retained shell callbacks from writing after the request has ended.
16
+ - Auth-gateway boot and dispatch are now provider-scoped: model catalogs reject cross-provider id ambiguity, Codex rows retain `openai-codex-responses`, and requests cannot borrow credentials from another provider.
17
+ - Broker-backed gateway dispatch leases now remain held until the provider's transport-admission boundary, preventing lazy stream construction from releasing authority before outbound dispatch.
18
+ - Kiro (Amazon Q Developer / CodeWhisperer) OAuth is now fully wired end-to-end: `AuthStorage.login("kiro", ...)` previously had no dispatch case despite `kiro` being advertised in `getOAuthProviders()`, so every advertised login path (`gjc auth-broker login kiro`, the interactive `/login` provider picker, direct `@gajae-code/ai` CLI `login kiro`) failed with `Unknown OAuth provider: kiro`. `mapOptionsForApi()` also had no case for `kiro-codewhisperer-stream`, so any chat call on a Kiro model threw `Unhandled API in mapOptionsForApi` regardless of auth method. The bundled model catalog had no `kiro` entries at all, leaving OAuth-only users with an empty model list; the provider descriptor's default model id also did not match any catalog entry. The OAuth CodeWhisperer request now also carries `modelId` in every wire message, matching the sibling API-key transport's request shape (issue #5064).
19
+
20
+ ## [0.15.5] - 2026-08-29
21
+
22
+ ### Added
23
+
24
+ - The built-in `kiro` provider now accepts long-lived `KIRO_API_KEY` values (`ksk_…` from [app.kiro.dev](https://app.kiro.dev/settings/api-keys)) in addition to AWS Builder ID OAuth. API-key auth talks to the Kiro service root with `tokentype: API_KEY` and discovers the live model catalog via `ListAvailableModels`. Builder ID login (`gjc auth-broker login kiro` / `AWS_BEARER_TOKEN_KIRO`) is unchanged.
25
+
26
+ ### Fixed
27
+
28
+ - The generic OpenAI Responses transport now preserves the typed `server_is_overloaded` code from an HTTP 200 terminal envelope as transport facts (`openaiErrorCode` / `providerCode`), and that statusless code classifies as a `server` fallback trigger. Both terminal shapes that carry the structured failure are covered: `response.failed`, and `response.completed` whose response `status` is `failed` (including the nested `status_details.error` form). Previously the envelope became a plain `Error`, so the only structured evidence of a capacity rejection was lost and consumers had to read provider prose. The code is matched case-sensitively and exactly, so near misses (`server_is_overloaded_now`), case variants (`SERVER_IS_OVERLOADED`), padded variants, and a `cancelled` response carrying the same code all stay untyped and unclassified; the displayed error message is unchanged in every case. The shared Azure Responses path inherits the same parsing. (#5018)
29
+ - Kiro credential selection now rejects control-character injection and keeps non-`ksk_` values on the Builder ID bearer path. API-key failures redact the credential before reaching assistant error output, and the transport accepts provider header overrides without changing the documented endpoint contract.
30
+ - API-key model discovery also retains the contributor-supplied static catalog for offline print-mode resolution, while live `ListAvailableModels` results remain authoritative when available.
5
31
  ## [0.15.4] - 2026-08-29
6
32
 
7
33
  ## [0.15.3] - 2026-08-27
8
34
 
9
35
  ### Fixed
10
36
 
11
- - Valid JSON `\uXXXX` tool arguments now execute as their canonical decoded strings instead of entering the escaped-non-ASCII resample loop. Provider adapters retain guard metadata only for malformed JSON, duplicate/deep evidence, and unpaired UTF-16 surrogates, so those cases remain fail-closed while standard escaped Hangul, emoji surrogate pairs, and printable ASCII no longer consume retries or terminate managed runs.
37
+ - Valid JSON `\uXXXX` tool arguments now execute as their canonical decoded strings instead of entering the escaped-non-ASCII resample loop. This intentionally treats every syntactically valid decoded scalar as canonical; runtime validation cannot infer whether a valid hex digit differed from the model's intent. Provider adapters retain guard metadata for malformed escape-bearing JSON, duplicate/deep suspicious escape evidence, and unpaired UTF-16 surrogates, while standard escaped Hangul, emoji surrogate pairs, and printable ASCII no longer consume retries or terminate managed runs.
12
38
  - Explicit positive `maxTokens` values declared for custom `models.yml` models
13
39
  and model overrides now reach the provider request across the shared stream
14
40
  mapping (`max_tokens`, `max_completion_tokens`, and `max_output_tokens`).
@@ -1,5 +1,7 @@
1
1
  import type { AuthCredential } from "../auth-storage";
2
+ import type { Provider } from "../types";
2
3
  import type { CredentialDisableResponse, CredentialIfAbsentUploadResponse, CredentialMetadataResponse, CredentialRefreshRequest, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
4
+ export declare const AUTH_BROKER_EPOCH_HEADER = "X-GJC-Auth-Broker-Epoch";
3
5
  export interface AuthBrokerClientOptions {
4
6
  /** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
5
7
  url: string;
@@ -35,6 +37,7 @@ export declare class AuthBrokerCredentialMetadataUnsupportedError extends AuthBr
35
37
  }
36
38
  export interface FetchSnapshotOptions {
37
39
  ifGenerationGt?: number;
40
+ ifEpoch?: string;
38
41
  waitMs?: number;
39
42
  signal?: AbortSignal;
40
43
  }
@@ -45,6 +48,7 @@ export type FetchSnapshotResult = {
45
48
  } | {
46
49
  status: 304;
47
50
  generation: number;
51
+ epoch?: string;
48
52
  };
49
53
  export declare class AuthBrokerClient {
50
54
  #private;
@@ -67,10 +71,10 @@ export declare class AuthBrokerClient {
67
71
  openSnapshotStream(opts?: {
68
72
  signal?: AbortSignal;
69
73
  }): AsyncGenerator<SnapshotStreamEvent>;
70
- fetchUsage(signal?: AbortSignal): Promise<UsageResponse>;
74
+ fetchUsage(signal?: AbortSignal, provider?: Provider): Promise<UsageResponse>;
71
75
  refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
72
76
  refreshMCPCredential(id: number, body: CredentialRefreshRequest, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
73
- disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
77
+ disableCredential(id: number, cause: string, signal?: AbortSignal, expectedRevision?: number): Promise<CredentialDisableResponse>;
74
78
  uploadCredential(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialUploadResponse>;
75
79
  uploadCredentialIfAbsent(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialIfAbsentUploadResponse>;
76
80
  }
@@ -7,7 +7,7 @@
7
7
  * usage reports cache TTL is 5 minutes per credential, so durability across
8
8
  * runs isn't required.
9
9
  */
10
- import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type CachedCredentialHealth, type CachedUsagePresentation, type CredentialInventoryRecord, type MCPOAuthRefreshClient, type OAuthCredential, type SafeUsageReport, type StoredAuthCredential } from "../auth-storage";
10
+ import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type CachedCredentialHealth, type CachedUsagePresentation, type CredentialDispatchTicket, type CredentialInventoryRecord, type MCPOAuthRefreshClient, type OAuthCredential, type SafeUsageReport, type StoredAuthCredential } from "../auth-storage";
11
11
  import type { Provider } from "../types";
12
12
  import type { UsageReport } from "../usage";
13
13
  import type { OAuthCredentials } from "../utils/oauth/types";
@@ -50,6 +50,14 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
50
50
  flushPresentationPersistence(): Promise<void>;
51
51
  get snapshot(): SnapshotResponse;
52
52
  getInventoryMetadataState(): Readonly<CredentialInventoryMetadataState>;
53
+ onSnapshotChanged(listener: () => void): () => void;
54
+ /**
55
+ * Queue a provider-admission ticket behind all currently pending snapshot
56
+ * authority work. Snapshot applications that arrive after this ticket wait
57
+ * for its release, so a revocation is ordered either before admission or
58
+ * after it — never in the middle of the dispatch boundary.
59
+ */
60
+ acquireCredentialDispatchTicket(_provider: Provider, signal?: AbortSignal): Promise<CredentialDispatchTicket>;
53
61
  syncInventoryMetadata(): Promise<Readonly<CredentialInventoryMetadataState>>;
54
62
  /**
55
63
  * Payload-free inventory view. This method never performs network I/O; metadata
@@ -58,7 +66,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
58
66
  */
59
67
  listCredentialInventory(provider?: string): CredentialInventoryRecord[];
60
68
  /** Re-hydrate the in-memory snapshot from the broker. */
61
- refreshSnapshot(): Promise<SnapshotResponse>;
69
+ refreshSnapshot(signal?: AbortSignal): Promise<SnapshotResponse>;
62
70
  listAuthCredentials(provider?: string): StoredAuthCredential[];
63
71
  /**
64
72
  * In-memory update from a successful refresh through the broker. AuthStorage
@@ -67,7 +75,10 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
67
75
  */
68
76
  updateAuthCredential(id: number, credential: AuthCredential): void;
69
77
  deleteAuthCredential(_id: number, _disabledCause: string): void;
78
+ allocateMonotonicSequence(_key: string, _expiresAtSec: number): number;
70
79
  tryDisableAuthCredentialIfMatches(_id: number, _expectedData: string, _disabledCause: string): boolean;
80
+ /** Disable one credential through the authenticated broker mutation endpoint. */
81
+ disableAuthCredentialRemote(credentialId: number, disabledCause: string, signal?: AbortSignal, expectedRevision?: number): Promise<boolean>;
71
82
  waitForFreshSnapshot(maxWaitMs: number, opts?: {
72
83
  signal?: AbortSignal;
73
84
  }): Promise<boolean>;
@@ -117,6 +128,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
117
128
  * residential laptop is, so all credentials surface every cycle.
118
129
  */
119
130
  fetchUsageReports(signal?: AbortSignal): Promise<UsageReport[] | null>;
131
+ fetchUsageReportsForProvider(provider: Provider, signal?: AbortSignal): Promise<UsageReport[] | null>;
120
132
  /** Synchronous, zero-network usage presentation read. */
121
133
  peekCachedUsagePresentation(provider: Provider, credentialId: number): CachedUsagePresentation | undefined;
122
134
  /** Synchronous, zero-network health presentation read backed by the redacted sidecar. */
@@ -17,6 +17,7 @@ export interface CredentialMetadataRecord {
17
17
  }
18
18
  /** GET /v1/credentials/metadata response body. */
19
19
  export interface CredentialMetadataResponse {
20
+ epoch?: string;
20
21
  generation: number;
21
22
  generatedAt: number;
22
23
  credentials: CredentialMetadataRecord[];
@@ -37,6 +38,8 @@ export type SnapshotEntry = AuthCredentialSnapshotEntry & {
37
38
  };
38
39
  /** GET /v1/snapshot response body. */
39
40
  export interface SnapshotResponse extends Omit<AuthCredentialSnapshot, "credentials"> {
41
+ /** Stable for one broker process; changes when the broker restarts. */
42
+ epoch?: string;
40
43
  serverNowMs: number;
41
44
  refresher: RefresherSchedule;
42
45
  credentials: SnapshotEntry[];
@@ -55,6 +58,7 @@ export type CredentialRefreshRequest = MCPOAuthRefreshClient;
55
58
  /** POST /v1/credential/:id/disable request body. */
56
59
  export interface CredentialDisableRequest {
57
60
  cause: string;
61
+ expectedRevision?: number;
58
62
  }
59
63
  /** POST /v1/credential/:id/disable response body. */
60
64
  export interface CredentialDisableResponse {
@@ -91,6 +95,7 @@ export interface SnapshotStreamSnapshotEvent extends SnapshotResponse {
91
95
  /** Single credential added/changed (upsert or refresh). */
92
96
  export interface SnapshotStreamEntryEvent {
93
97
  kind: "entry";
98
+ epoch?: string;
94
99
  generation: number;
95
100
  serverNowMs: number;
96
101
  refresher: RefresherSchedule;
@@ -99,6 +104,7 @@ export interface SnapshotStreamEntryEvent {
99
104
  /** Single credential disabled/deleted. */
100
105
  export interface SnapshotStreamRemovedEvent {
101
106
  kind: "removed";
107
+ epoch?: string;
102
108
  generation: number;
103
109
  serverNowMs: number;
104
110
  refresher: RefresherSchedule;
@@ -106,6 +106,7 @@ export declare const credentialSnapshotEntrySchema: z.ZodObject<{
106
106
  key: z.ZodString;
107
107
  }, z.core.$strict>], "type">;
108
108
  identityKey: z.ZodNullable<z.ZodString>;
109
+ revision: z.ZodOptional<z.ZodNumber>;
109
110
  }, z.core.$strict>;
110
111
  export declare const snapshotEntrySchema: z.ZodObject<{
111
112
  id: z.ZodNumber;
@@ -128,6 +129,7 @@ export declare const snapshotEntrySchema: z.ZodObject<{
128
129
  key: z.ZodString;
129
130
  }, z.core.$strict>], "type">;
130
131
  identityKey: z.ZodNullable<z.ZodString>;
132
+ revision: z.ZodOptional<z.ZodNumber>;
131
133
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
132
134
  }, z.core.$strict>;
133
135
  export declare const refresherScheduleSchema: z.ZodObject<{
@@ -137,6 +139,7 @@ export declare const refresherScheduleSchema: z.ZodObject<{
137
139
  nextSweepInMs: z.ZodNumber;
138
140
  }, z.core.$strict>;
139
141
  export declare const snapshotResponseSchema: z.ZodObject<{
142
+ epoch: z.ZodOptional<z.ZodString>;
140
143
  generation: z.ZodNumber;
141
144
  generatedAt: z.ZodNumber;
142
145
  serverNowMs: z.ZodNumber;
@@ -167,6 +170,7 @@ export declare const snapshotResponseSchema: z.ZodObject<{
167
170
  key: z.ZodString;
168
171
  }, z.core.$strict>], "type">;
169
172
  identityKey: z.ZodNullable<z.ZodString>;
173
+ revision: z.ZodOptional<z.ZodNumber>;
170
174
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
171
175
  }, z.core.$strict>>;
172
176
  }, z.core.$strict>;
@@ -182,6 +186,7 @@ export declare const credentialMetadataRecordSchema: z.ZodObject<{
182
186
  disabledCause: z.ZodNullable<z.ZodString>;
183
187
  }, z.core.$strict>;
184
188
  export declare const credentialMetadataResponseSchema: z.ZodObject<{
189
+ epoch: z.ZodOptional<z.ZodString>;
185
190
  generation: z.ZodNumber;
186
191
  generatedAt: z.ZodNumber;
187
192
  credentials: z.ZodArray<z.ZodObject<{
@@ -197,6 +202,7 @@ export declare const credentialMetadataResponseSchema: z.ZodObject<{
197
202
  }, z.core.$strict>;
198
203
  /** First frame on connect — full snapshot embedded inline with a `kind` tag. */
199
204
  export declare const snapshotStreamSnapshotEventSchema: z.ZodObject<{
205
+ epoch: z.ZodOptional<z.ZodString>;
200
206
  generation: z.ZodNumber;
201
207
  generatedAt: z.ZodNumber;
202
208
  serverNowMs: z.ZodNumber;
@@ -227,6 +233,7 @@ export declare const snapshotStreamSnapshotEventSchema: z.ZodObject<{
227
233
  key: z.ZodString;
228
234
  }, z.core.$strict>], "type">;
229
235
  identityKey: z.ZodNullable<z.ZodString>;
236
+ revision: z.ZodOptional<z.ZodNumber>;
230
237
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
231
238
  }, z.core.$strict>>;
232
239
  kind: z.ZodLiteral<"snapshot">;
@@ -234,6 +241,7 @@ export declare const snapshotStreamSnapshotEventSchema: z.ZodObject<{
234
241
  /** Per-credential upsert/refresh delta. */
235
242
  export declare const snapshotStreamEntryEventSchema: z.ZodObject<{
236
243
  kind: z.ZodLiteral<"entry">;
244
+ epoch: z.ZodOptional<z.ZodString>;
237
245
  generation: z.ZodNumber;
238
246
  serverNowMs: z.ZodNumber;
239
247
  refresher: z.ZodObject<{
@@ -263,12 +271,14 @@ export declare const snapshotStreamEntryEventSchema: z.ZodObject<{
263
271
  key: z.ZodString;
264
272
  }, z.core.$strict>], "type">;
265
273
  identityKey: z.ZodNullable<z.ZodString>;
274
+ revision: z.ZodOptional<z.ZodNumber>;
266
275
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
267
276
  }, z.core.$strict>;
268
277
  }, z.core.$strict>;
269
278
  /** Per-credential delete delta. */
270
279
  export declare const snapshotStreamRemovedEventSchema: z.ZodObject<{
271
280
  kind: z.ZodLiteral<"removed">;
281
+ epoch: z.ZodOptional<z.ZodString>;
272
282
  generation: z.ZodNumber;
273
283
  serverNowMs: z.ZodNumber;
274
284
  refresher: z.ZodObject<{
@@ -281,6 +291,7 @@ export declare const snapshotStreamRemovedEventSchema: z.ZodObject<{
281
291
  }, z.core.$strict>;
282
292
  /** Discriminated union over every event frame the snapshot stream emits. */
283
293
  export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
294
+ epoch: z.ZodOptional<z.ZodString>;
284
295
  generation: z.ZodNumber;
285
296
  generatedAt: z.ZodNumber;
286
297
  serverNowMs: z.ZodNumber;
@@ -311,11 +322,13 @@ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
311
322
  key: z.ZodString;
312
323
  }, z.core.$strict>], "type">;
313
324
  identityKey: z.ZodNullable<z.ZodString>;
325
+ revision: z.ZodOptional<z.ZodNumber>;
314
326
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
315
327
  }, z.core.$strict>>;
316
328
  kind: z.ZodLiteral<"snapshot">;
317
329
  }, z.core.$strict>, z.ZodObject<{
318
330
  kind: z.ZodLiteral<"entry">;
331
+ epoch: z.ZodOptional<z.ZodString>;
319
332
  generation: z.ZodNumber;
320
333
  serverNowMs: z.ZodNumber;
321
334
  refresher: z.ZodObject<{
@@ -345,10 +358,12 @@ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
345
358
  key: z.ZodString;
346
359
  }, z.core.$strict>], "type">;
347
360
  identityKey: z.ZodNullable<z.ZodString>;
361
+ revision: z.ZodOptional<z.ZodNumber>;
348
362
  rotatesInMs: z.ZodNullable<z.ZodNumber>;
349
363
  }, z.core.$strict>;
350
364
  }, z.core.$strict>, z.ZodObject<{
351
365
  kind: z.ZodLiteral<"removed">;
366
+ epoch: z.ZodOptional<z.ZodString>;
352
367
  generation: z.ZodNumber;
353
368
  serverNowMs: z.ZodNumber;
354
369
  refresher: z.ZodObject<{
@@ -447,10 +462,12 @@ export declare const credentialRefreshResponseSchema: z.ZodObject<{
447
462
  key: z.ZodString;
448
463
  }, z.core.$strict>], "type">;
449
464
  identityKey: z.ZodNullable<z.ZodString>;
465
+ revision: z.ZodOptional<z.ZodNumber>;
450
466
  }, z.core.$strict>;
451
467
  }, z.core.$strict>;
452
468
  export declare const credentialDisableRequestSchema: z.ZodObject<{
453
469
  cause: z.ZodOptional<z.ZodString>;
470
+ expectedRevision: z.ZodOptional<z.ZodNumber>;
454
471
  }, z.core.$strict>;
455
472
  export declare const credentialDisableResponseSchema: z.ZodObject<{
456
473
  ok: z.ZodBoolean;
@@ -497,6 +514,7 @@ export declare const credentialUploadResponseSchema: z.ZodObject<{
497
514
  key: z.ZodString;
498
515
  }, z.core.$strict>], "type">;
499
516
  identityKey: z.ZodNullable<z.ZodString>;
517
+ revision: z.ZodOptional<z.ZodNumber>;
500
518
  }, z.core.$strict>>;
501
519
  }, z.core.$strict>;
502
520
  export declare const credentialIfAbsentUploadResponseSchema: z.ZodObject<{
@@ -532,5 +550,6 @@ export declare const credentialIfAbsentUploadResponseSchema: z.ZodObject<{
532
550
  key: z.ZodString;
533
551
  }, z.core.$strict>], "type">;
534
552
  identityKey: z.ZodNullable<z.ZodString>;
553
+ revision: z.ZodOptional<z.ZodNumber>;
535
554
  }, z.core.$strict>>;
536
555
  }, z.core.$strict>;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * gjc auth-gateway HTTP server.
3
3
  *
4
- * Accepts any provider-format request (OpenAI chat-completions, Anthropic
4
+ * Accepts a provider-scoped provider-format request (OpenAI chat-completions, Anthropic
5
5
  * messages, OpenAI Responses) and dispatches through pi-ai's `streamSimple()`
6
6
  * — which handles credential injection, anthropic-beta headers, OpenAI code backend
7
7
  * websocket transport, and all the per-provider intricacies. The gateway is
@@ -12,25 +12,59 @@
12
12
  * GET /healthz → unauth; ok + version
13
13
  * GET /v1/usage → aggregated provider usage (5-min per-credential cache via AuthStorage)
14
14
  * GET /v1/credentials/check → per-credential auth probe (diagnose 401s in a multi-account pool)
15
- * GET /v1/models → list known models from the registry
15
+ * GET /v1/models → list models from the selected provider scope
16
16
  * POST /v1/chat/completions → OpenAI chat-completions in/out
17
17
  * POST /v1/messages → Anthropic messages in/out
18
18
  * POST /v1/responses → OpenAI Responses in/out
19
19
  */
20
20
  import type { AuthStorage } from "../auth-storage";
21
- import type { Api, Model } from "../types";
21
+ import type { Api, AssistantMessageEventStream, Model, Provider } from "../types";
22
22
  import type { AuthGatewayServerHandle, AuthGatewayServerOptions } from "./types";
23
23
  export type ModelResolver = (modelId: string) => Model<Api> | undefined;
24
24
  export interface AuthGatewayBootOptions extends AuthGatewayServerOptions {
25
25
  /** Source of credentials. Caller wires this to a broker-backed AuthStorage. */
26
26
  storage: AuthStorage;
27
+ /**
28
+ * Current broker-backed scope authority. When supplied, this is checked on
29
+ * every request so a live broker snapshot removal immediately fails closed.
30
+ */
31
+ hasProviderCredential: () => boolean;
32
+ /** Refresh the dispatch cache from the current broker snapshot before use. */
33
+ reloadProviderCredentials: (signal?: AbortSignal) => Promise<void>;
34
+ /** Confirm that the selected key is still present in the current authority snapshot. */
35
+ validateProviderCredential: (provider: string, apiKey: string) => boolean;
27
36
  /**
28
37
  * Resolve a client-requested model id to a pi-ai Model. Caller supplies
29
38
  * this from a ModelRegistry (lives in `coding-agent` to avoid an inverse
30
39
  * dependency in `pi-ai`).
31
40
  */
32
41
  resolveModel: ModelResolver;
33
- /** Optional supplier for `/v1/models` listing. Returns the full model array. */
34
- listModels?: () => Iterable<Model<Api>>;
42
+ /** Supplier for the source-backed model catalog used by `/v1/models`. */
43
+ listModels: () => Iterable<Model<Api>>;
44
+ }
45
+ export interface AuthGatewayModelCatalog {
46
+ readonly models: readonly Model<Api>[];
47
+ resolve(modelId: string): Model<Api> | undefined;
35
48
  }
49
+ /**
50
+ * Whether a model can be served through the broker-backed auth gateway.
51
+ *
52
+ * Bedrock's credential chain is process-local AWS authority, not a broker
53
+ * credential. Advertising a native Bedrock model from this gateway would let
54
+ * direct callers bypass the broker boundary (and make readiness lie about a
55
+ * model the gateway cannot authenticate). Keep this predicate shared with the
56
+ * CLI readiness checks so every entry point applies the same fence.
57
+ */
58
+ export declare function isAuthGatewayModelBrokerConsumable(model: Pick<Model<Api>, "api" | "transport">): boolean;
59
+ /**
60
+ * Build an unambiguous, provider-scoped catalog.
61
+ *
62
+ * Models from other providers are intentionally ignored rather than allowed
63
+ * to compete for the same id. Duplicate ids within the selected provider are
64
+ * rejected because choosing either one would make request dispatch
65
+ * order-dependent.
66
+ */
67
+ export declare function createAuthGatewayModelCatalog(provider: Provider, models: Iterable<Model<Api>>): AuthGatewayModelCatalog;
68
+ export declare function releaseGatewayCredentialLeaseOnAdmission(events: Pick<AssistantMessageEventStream, "result">, release: () => void, signal?: AbortSignal): void;
36
69
  export declare function startAuthGateway(opts: AuthGatewayBootOptions): AuthGatewayServerHandle;
70
+ export declare function isSafeProviderScope(provider: unknown): provider is string;
@@ -1,17 +1,29 @@
1
1
  import type { Effort } from "../model-thinking";
2
- import type { AssistantMessage, AssistantMessageEventStream, CacheRetention, Context, ServiceTier } from "../types";
2
+ import type { Api, AssistantMessage, AssistantMessageEventStream, CacheRetention, Context, Provider, ServiceTier } from "../types";
3
3
  /**
4
4
  * Wire types for the gjc auth-gateway.
5
5
  *
6
6
  * The gateway sits between unauthenticated clients (containerized gjc,
7
7
  * llm-git, …) and the broker. It accepts provider-format HTTP requests
8
- * (OpenAI chat-completions / Anthropic messages / OpenAI Responses),
8
+ * (OpenAI chat-completions / Anthropic messages / OpenAI Responses) within
9
+ * one explicit provider scope,
9
10
  * dispatches them through pi-ai's `streamSimple()`, and translates the
10
11
  * canonical event stream back to the matching wire format. The gateway
11
12
  * injects `Authorization` server-side so clients never see access tokens.
12
13
  */
13
14
  /** Default bind. Loopback-only — front with reverse proxy for remote access. */
14
15
  export declare const DEFAULT_AUTH_GATEWAY_BIND = "127.0.0.1:4000";
16
+ /**
17
+ * Provider APIs whose gateway identity is part of the provider contract.
18
+ *
19
+ * OpenAI Codex is backed by the Codex OAuth authority and must never be
20
+ * projected as a generic OpenAI Responses model. Other providers may expose
21
+ * more than one API and therefore remain catalog-defined.
22
+ */
23
+ export declare const AUTH_GATEWAY_PROVIDER_APIS: Readonly<Record<string, Api>>;
24
+ export interface AuthGatewayProviderScope {
25
+ provider: Provider;
26
+ }
15
27
  export type AuthGatewayToolChoice = "auto" | "none" | "required" | {
16
28
  name: string;
17
29
  };
@@ -102,6 +114,8 @@ export interface AuthGatewayFormatModule {
102
114
  export interface AuthGatewayServerOptions {
103
115
  /** Listen address. Default `127.0.0.1:4000`. */
104
116
  bind?: string;
117
+ /** Required provider scope. An unscoped gateway is not supported. */
118
+ providerScope: AuthGatewayProviderScope;
105
119
  /** Accept any of these bearer tokens. Empty allows unauthenticated calls. */
106
120
  bearerTokens: string[];
107
121
  /** Version surfaced on `/healthz`. */