@gajae-code/ai 0.11.7 → 0.11.9

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 (41) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -0
  3. package/dist/types/auth-broker/client.d.ts +2 -1
  4. package/dist/types/auth-broker/remote-store.d.ts +2 -1
  5. package/dist/types/auth-broker/types.d.ts +3 -1
  6. package/dist/types/auth-broker/wire-schemas.d.ts +68 -0
  7. package/dist/types/auth-storage.d.ts +21 -1
  8. package/dist/types/provider-models/openai-compat.d.ts +10 -0
  9. package/dist/types/providers/anthropic.d.ts +9 -0
  10. package/dist/types/providers/register-builtins.d.ts +8 -0
  11. package/dist/types/providers/transform-messages.d.ts +1 -0
  12. package/dist/types/types.d.ts +3 -1
  13. package/dist/types/utils/idle-iterator.d.ts +1 -0
  14. package/dist/types/utils/oauth/opengateway.d.ts +1 -0
  15. package/dist/types/utils/oauth/types.d.ts +1 -1
  16. package/package.json +2 -2
  17. package/src/auth-broker/client.ts +13 -0
  18. package/src/auth-broker/refresher.ts +1 -0
  19. package/src/auth-broker/remote-store.ts +25 -0
  20. package/src/auth-broker/server.ts +10 -2
  21. package/src/auth-broker/types.ts +4 -0
  22. package/src/auth-broker/wire-schemas.ts +17 -1
  23. package/src/auth-storage.ts +202 -27
  24. package/src/cli.ts +1 -0
  25. package/src/models.json +72 -0
  26. package/src/provider-models/descriptors.ts +7 -0
  27. package/src/provider-models/openai-compat.ts +20 -0
  28. package/src/providers/anthropic.ts +53 -23
  29. package/src/providers/openai-anthropic-shim.ts +4 -0
  30. package/src/providers/openai-completions.ts +8 -1
  31. package/src/providers/openai-responses.ts +4 -1
  32. package/src/providers/register-builtins.ts +21 -2
  33. package/src/providers/transform-messages.ts +25 -6
  34. package/src/stream.ts +1 -0
  35. package/src/types.ts +7 -1
  36. package/src/utils/idle-iterator.ts +5 -0
  37. package/src/utils/oauth/index.ts +6 -0
  38. package/src/utils/oauth/opengateway.ts +15 -0
  39. package/src/utils/oauth/types.ts +1 -0
  40. package/src/utils/validation.ts +17 -2
  41. package/src/utils.ts +41 -4
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.11.9] - 2026-07-24
6
+ ### Fixed
7
+
8
+ - Kimi Code now allows one continuous 300-second first-event wait before aborting, while preserving explicit caller and environment timeout overrides and the existing inter-event idle timeout.
9
+
10
+ ### Added
11
+
12
+ - Added first-class support for **OpenGateway by Sionic AI**, an OpenAI-compatible gateway. Registers the `opengateway` provider descriptor, `/login` OAuth entry (API-key paste validated against `https://apis.opengateway.ai/v1/models`), `OPENGATEWAY_API_KEY` environment resolution, and bundled `models.json` seed models. Models are discovered dynamically from the OpenAI-compatible `/v1/models` endpoint (base URL `https://apis.opengateway.ai/v1`).
13
+
14
+ ## [0.11.8] - 2026-07-23
15
+
16
+ ### Fixed
17
+
18
+ - OpenAI Responses / Codex native history replay no longer submits missing resident-image placeholders as `input_image.image_url`. Invalid values (including `[Session resident imageUrl blob missing: …]`) are dropped, or retained as `file_id`-only parts when a non-empty `file_id` is present, so a single unavailable historical image cannot brick `/retry` (#2924).
19
+ - Raised the first-event stream timeout floor to five minutes for `alibaba-token-plan` models at both the OpenAI provider and outer lazy-stream watchdogs, while preserving caller and environment overrides and the existing inter-event idle timeout.
20
+ - OAuth refresh peer-rotation recovery now runs before failure classification instead of only on the definitive-failure path, and the definitive matcher recognizes the "grant is invalid" phrasing. Providers whose invalid-grant response does not contain the literal `invalid_grant` (e.g. Kimi's 400 "The provided authorization grant is invalid") previously had rotation races misclassified as transient, temp-blocking a healthy credential for five minutes on every race; with Kimi's ~12-minute access tokens and multiple processes sharing the credential store this surfaced as repeated logouts. Genuine revocations are now disabled with a cause instead of looping temp-blocks.
21
+ - Anthropic 400 `Invalid \`signature\` in \`thinking\` block` responses now trigger the one-shot thinking replay repair instead of failing the turn. The existing repair matcher only recognized the "latest assistant message ... cannot be modified" wording, so the signature-validation variant — which can cite a `thinking`/`redacted_thinking` block anywhere in the replayed history (e.g. after compaction/pruning rewrote an earlier turn) — was treated as a fatal request error. The retry now rebuilds the request with thinking blocks dropped from every replayed assistant message (`repairAllAssistantThinking`), while the latest-message mutation variant keeps the targeted latest-only repair.
22
+
23
+ ### Changed
24
+
25
+ - Raw tool-argument rejection hooks can now select from bounded, authority-controlled correction codes. Unknown or extension-supplied values retain the byte-for-byte generic rejection instead of reaching model-visible validation errors.
26
+
5
27
  ## [0.11.7] - 2026-07-22
6
28
 
7
29
  ### Changed
package/README.md CHANGED
@@ -69,6 +69,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
69
69
  - **MiniMax Coding Plan** (requires `MINIMAX_CODE_API_KEY` or `MINIMAX_CODE_CN_API_KEY`)
70
70
  - **Xiaomi MiMo** (requires `XIAOMI_API_KEY`)
71
71
  - **ZenMux** (requires `ZENMUX_API_KEY`)
72
+ - **OpenGateway by Sionic AI** (requires `OPENGATEWAY_API_KEY`)
72
73
  - **Qwen Portal** (supports `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY`)
73
74
  - **Cloudflare AI Gateway** (requires `CLOUDFLARE_AI_GATEWAY_API_KEY` and provider-specific gateway base URL)
74
75
  - **Ollama** (local OpenAI-compatible runtime; optional `OLLAMA_API_KEY`)
@@ -954,6 +955,7 @@ In Node.js environments, you can set environment variables to avoid passing API
954
955
  | MiniMax Code | `MINIMAX_CODE_API_KEY` (international) or `MINIMAX_CODE_CN_API_KEY` (China) |
955
956
  | Xiaomi MiMo | `XIAOMI_API_KEY` |
956
957
  | ZenMux | `ZENMUX_API_KEY` |
958
+ | OpenGateway | `OPENGATEWAY_API_KEY` |
957
959
  | vLLM | `VLLM_API_KEY` |
958
960
  | Cloudflare AI Gateway | `CLOUDFLARE_AI_GATEWAY_API_KEY` |
959
961
  | GitHub Copilot | `COPILOT_GITHUB_TOKEN` or `GH_TOKEN` or `GITHUB_TOKEN` |
@@ -977,6 +979,7 @@ Provider endpoint defaults for the current OpenAI-compatible integrations:
977
979
  - Xiaomi MiMo: `https://api.xiaomimimo.com/anthropic`
978
980
  - ZenMux (OpenAI): `https://zenmux.ai/api/v1`
979
981
  - ZenMux (Anthropic models): `https://zenmux.ai/api/anthropic`
982
+ - OpenGateway by Sionic AI: `https://apis.opengateway.ai/v1`
980
983
  - vLLM: `http://127.0.0.1:8000/v1`
981
984
  - Ollama: local OpenAI-compatible runtime (`http://127.0.0.1:11434/v1`)
982
985
  - Ollama Cloud: native Ollama API host (`https://ollama.com/api`, configured here as base URL `https://ollama.com`)
@@ -1,5 +1,5 @@
1
1
  import type { AuthCredential } from "../auth-storage";
2
- import type { CredentialDisableResponse, CredentialIfAbsentUploadResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
2
+ import type { CredentialDisableResponse, CredentialIfAbsentUploadResponse, CredentialRefreshRequest, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageResponse } from "./types";
3
3
  export interface AuthBrokerClientOptions {
4
4
  /** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
5
5
  url: string;
@@ -61,6 +61,7 @@ export declare class AuthBrokerClient {
61
61
  }): AsyncGenerator<SnapshotStreamEvent>;
62
62
  fetchUsage(signal?: AbortSignal): Promise<UsageResponse>;
63
63
  refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
64
+ refreshMCPCredential(id: number, body: CredentialRefreshRequest, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
64
65
  disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
65
66
  uploadCredential(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialUploadResponse>;
66
67
  uploadCredentialIfAbsent(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialIfAbsentUploadResponse>;
@@ -1,4 +1,4 @@
1
- import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type OAuthCredential, type StoredAuthCredential } from "../auth-storage";
1
+ import { type AuthCredential, type AuthCredentialIfAbsentResult, type AuthCredentialStore, type MCPOAuthRefreshClient, type OAuthCredential, type StoredAuthCredential } from "../auth-storage";
2
2
  import type { Provider } from "../types";
3
3
  import type { UsageReport } from "../usage";
4
4
  import type { OAuthCredentials } from "../utils/oauth/types";
@@ -78,6 +78,7 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
78
78
  * the `refresh` slot.
79
79
  */
80
80
  refreshOAuthCredential(_provider: Provider, credentialId: number, _credential: OAuthCredential, signal?: AbortSignal): Promise<OAuthCredentials>;
81
+ refreshMCPOAuthCredential(credentialId: number, credential: OAuthCredential, client: MCPOAuthRefreshClient, signal?: AbortSignal): Promise<OAuthCredential>;
81
82
  /**
82
83
  * Store-level hook consumed by `AuthStorage.fetchUsageReports()` — proxies
83
84
  * to the broker's `/v1/usage` endpoint. The broker's egress IP isn't
@@ -5,7 +5,7 @@
5
5
  * clients use `access` tokens directly and call back to the broker when a
6
6
  * credential expires or a 401 surfaces on a supposedly-fresh credential.
7
7
  */
8
- import type { AuthCredential, AuthCredentialIfAbsentReason, AuthCredentialSnapshot, AuthCredentialSnapshotEntry } from "../auth-storage";
8
+ import type { AuthCredential, AuthCredentialIfAbsentReason, AuthCredentialSnapshot, AuthCredentialSnapshotEntry, MCPOAuthRefreshClient } from "../auth-storage";
9
9
  import type { UsageReport } from "../usage";
10
10
  /** GET /v1/healthz response body. */
11
11
  export interface HealthzResponse {
@@ -36,6 +36,8 @@ export interface UsageResponse {
36
36
  export interface CredentialRefreshResponse {
37
37
  entry: AuthCredentialSnapshotEntry;
38
38
  }
39
+ /** Optional MCP client metadata; the broker still selects the stored token endpoint. */
40
+ export type CredentialRefreshRequest = MCPOAuthRefreshClient;
39
41
  /** POST /v1/credential/:id/disable request body. */
40
42
  export interface CredentialDisableRequest {
41
43
  cause: string;
@@ -11,6 +11,10 @@
11
11
  * `hasOnlyFields` allowlist for the same effect.
12
12
  */
13
13
  import * as z from "zod/v4";
14
+ export declare const mcpOAuthBindingSchema: z.ZodObject<{
15
+ resourceOrigin: z.ZodString;
16
+ tokenEndpoint: z.ZodString;
17
+ }, z.core.$strict>;
14
18
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
15
19
  export declare const oauthCredentialSchema: z.ZodObject<{
16
20
  type: z.ZodLiteral<"oauth">;
@@ -21,6 +25,10 @@ export declare const oauthCredentialSchema: z.ZodObject<{
21
25
  projectId: z.ZodOptional<z.ZodString>;
22
26
  email: z.ZodOptional<z.ZodString>;
23
27
  accountId: z.ZodOptional<z.ZodString>;
28
+ mcpBinding: z.ZodOptional<z.ZodObject<{
29
+ resourceOrigin: z.ZodString;
30
+ tokenEndpoint: z.ZodString;
31
+ }, z.core.$strict>>;
24
32
  }, z.core.$strict>;
25
33
  /** OAuth credential as it appears in broker snapshots — refresh replaced with sentinel. */
26
34
  export declare const remoteOauthCredentialSchema: z.ZodObject<{
@@ -31,6 +39,10 @@ export declare const remoteOauthCredentialSchema: z.ZodObject<{
31
39
  projectId: z.ZodOptional<z.ZodString>;
32
40
  email: z.ZodOptional<z.ZodString>;
33
41
  accountId: z.ZodOptional<z.ZodString>;
42
+ mcpBinding: z.ZodOptional<z.ZodObject<{
43
+ resourceOrigin: z.ZodString;
44
+ tokenEndpoint: z.ZodString;
45
+ }, z.core.$strict>>;
34
46
  refresh: z.ZodLiteral<"__remote__">;
35
47
  }, z.core.$strict>;
36
48
  export declare const apiKeyCredentialSchema: z.ZodObject<{
@@ -47,6 +59,10 @@ export declare const writableAuthCredentialSchema: z.ZodDiscriminatedUnion<[z.Zo
47
59
  projectId: z.ZodOptional<z.ZodString>;
48
60
  email: z.ZodOptional<z.ZodString>;
49
61
  accountId: z.ZodOptional<z.ZodString>;
62
+ mcpBinding: z.ZodOptional<z.ZodObject<{
63
+ resourceOrigin: z.ZodString;
64
+ tokenEndpoint: z.ZodString;
65
+ }, z.core.$strict>>;
50
66
  }, z.core.$strict>, z.ZodObject<{
51
67
  type: z.ZodLiteral<"api_key">;
52
68
  key: z.ZodString;
@@ -60,6 +76,10 @@ export declare const snapshotCredentialSchema: z.ZodDiscriminatedUnion<[z.ZodObj
60
76
  projectId: z.ZodOptional<z.ZodString>;
61
77
  email: z.ZodOptional<z.ZodString>;
62
78
  accountId: z.ZodOptional<z.ZodString>;
79
+ mcpBinding: z.ZodOptional<z.ZodObject<{
80
+ resourceOrigin: z.ZodString;
81
+ tokenEndpoint: z.ZodString;
82
+ }, z.core.$strict>>;
63
83
  refresh: z.ZodLiteral<"__remote__">;
64
84
  }, z.core.$strict>, z.ZodObject<{
65
85
  type: z.ZodLiteral<"api_key">;
@@ -76,6 +96,10 @@ export declare const credentialSnapshotEntrySchema: z.ZodObject<{
76
96
  projectId: z.ZodOptional<z.ZodString>;
77
97
  email: z.ZodOptional<z.ZodString>;
78
98
  accountId: z.ZodOptional<z.ZodString>;
99
+ mcpBinding: z.ZodOptional<z.ZodObject<{
100
+ resourceOrigin: z.ZodString;
101
+ tokenEndpoint: z.ZodString;
102
+ }, z.core.$strict>>;
79
103
  refresh: z.ZodLiteral<"__remote__">;
80
104
  }, z.core.$strict>, z.ZodObject<{
81
105
  type: z.ZodLiteral<"api_key">;
@@ -94,6 +118,10 @@ export declare const snapshotEntrySchema: z.ZodObject<{
94
118
  projectId: z.ZodOptional<z.ZodString>;
95
119
  email: z.ZodOptional<z.ZodString>;
96
120
  accountId: z.ZodOptional<z.ZodString>;
121
+ mcpBinding: z.ZodOptional<z.ZodObject<{
122
+ resourceOrigin: z.ZodString;
123
+ tokenEndpoint: z.ZodString;
124
+ }, z.core.$strict>>;
97
125
  refresh: z.ZodLiteral<"__remote__">;
98
126
  }, z.core.$strict>, z.ZodObject<{
99
127
  type: z.ZodLiteral<"api_key">;
@@ -129,6 +157,10 @@ export declare const snapshotResponseSchema: z.ZodObject<{
129
157
  projectId: z.ZodOptional<z.ZodString>;
130
158
  email: z.ZodOptional<z.ZodString>;
131
159
  accountId: z.ZodOptional<z.ZodString>;
160
+ mcpBinding: z.ZodOptional<z.ZodObject<{
161
+ resourceOrigin: z.ZodString;
162
+ tokenEndpoint: z.ZodString;
163
+ }, z.core.$strict>>;
132
164
  refresh: z.ZodLiteral<"__remote__">;
133
165
  }, z.core.$strict>, z.ZodObject<{
134
166
  type: z.ZodLiteral<"api_key">;
@@ -160,6 +192,10 @@ export declare const snapshotStreamSnapshotEventSchema: z.ZodObject<{
160
192
  projectId: z.ZodOptional<z.ZodString>;
161
193
  email: z.ZodOptional<z.ZodString>;
162
194
  accountId: z.ZodOptional<z.ZodString>;
195
+ mcpBinding: z.ZodOptional<z.ZodObject<{
196
+ resourceOrigin: z.ZodString;
197
+ tokenEndpoint: z.ZodString;
198
+ }, z.core.$strict>>;
163
199
  refresh: z.ZodLiteral<"__remote__">;
164
200
  }, z.core.$strict>, z.ZodObject<{
165
201
  type: z.ZodLiteral<"api_key">;
@@ -192,6 +228,10 @@ export declare const snapshotStreamEntryEventSchema: z.ZodObject<{
192
228
  projectId: z.ZodOptional<z.ZodString>;
193
229
  email: z.ZodOptional<z.ZodString>;
194
230
  accountId: z.ZodOptional<z.ZodString>;
231
+ mcpBinding: z.ZodOptional<z.ZodObject<{
232
+ resourceOrigin: z.ZodString;
233
+ tokenEndpoint: z.ZodString;
234
+ }, z.core.$strict>>;
195
235
  refresh: z.ZodLiteral<"__remote__">;
196
236
  }, z.core.$strict>, z.ZodObject<{
197
237
  type: z.ZodLiteral<"api_key">;
@@ -236,6 +276,10 @@ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
236
276
  projectId: z.ZodOptional<z.ZodString>;
237
277
  email: z.ZodOptional<z.ZodString>;
238
278
  accountId: z.ZodOptional<z.ZodString>;
279
+ mcpBinding: z.ZodOptional<z.ZodObject<{
280
+ resourceOrigin: z.ZodString;
281
+ tokenEndpoint: z.ZodString;
282
+ }, z.core.$strict>>;
239
283
  refresh: z.ZodLiteral<"__remote__">;
240
284
  }, z.core.$strict>, z.ZodObject<{
241
285
  type: z.ZodLiteral<"api_key">;
@@ -266,6 +310,10 @@ export declare const snapshotStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodOb
266
310
  projectId: z.ZodOptional<z.ZodString>;
267
311
  email: z.ZodOptional<z.ZodString>;
268
312
  accountId: z.ZodOptional<z.ZodString>;
313
+ mcpBinding: z.ZodOptional<z.ZodObject<{
314
+ resourceOrigin: z.ZodString;
315
+ tokenEndpoint: z.ZodString;
316
+ }, z.core.$strict>>;
269
317
  refresh: z.ZodLiteral<"__remote__">;
270
318
  }, z.core.$strict>, z.ZodObject<{
271
319
  type: z.ZodLiteral<"api_key">;
@@ -348,6 +396,10 @@ export declare const usageResponseSchema: z.ZodObject<{
348
396
  raw: z.ZodOptional<z.ZodUnknown>;
349
397
  }, z.core.$strip>>;
350
398
  }, z.core.$strict>;
399
+ export declare const credentialRefreshRequestSchema: z.ZodObject<{
400
+ clientId: z.ZodOptional<z.ZodString>;
401
+ clientSecret: z.ZodOptional<z.ZodString>;
402
+ }, z.core.$strict>;
351
403
  export declare const credentialRefreshResponseSchema: z.ZodObject<{
352
404
  entry: z.ZodObject<{
353
405
  id: z.ZodNumber;
@@ -360,6 +412,10 @@ export declare const credentialRefreshResponseSchema: z.ZodObject<{
360
412
  projectId: z.ZodOptional<z.ZodString>;
361
413
  email: z.ZodOptional<z.ZodString>;
362
414
  accountId: z.ZodOptional<z.ZodString>;
415
+ mcpBinding: z.ZodOptional<z.ZodObject<{
416
+ resourceOrigin: z.ZodString;
417
+ tokenEndpoint: z.ZodString;
418
+ }, z.core.$strict>>;
363
419
  refresh: z.ZodLiteral<"__remote__">;
364
420
  }, z.core.$strict>, z.ZodObject<{
365
421
  type: z.ZodLiteral<"api_key">;
@@ -385,6 +441,10 @@ export declare const credentialUploadRequestSchema: z.ZodObject<{
385
441
  projectId: z.ZodOptional<z.ZodString>;
386
442
  email: z.ZodOptional<z.ZodString>;
387
443
  accountId: z.ZodOptional<z.ZodString>;
444
+ mcpBinding: z.ZodOptional<z.ZodObject<{
445
+ resourceOrigin: z.ZodString;
446
+ tokenEndpoint: z.ZodString;
447
+ }, z.core.$strict>>;
388
448
  }, z.core.$strict>, z.ZodObject<{
389
449
  type: z.ZodLiteral<"api_key">;
390
450
  key: z.ZodString;
@@ -402,6 +462,10 @@ export declare const credentialUploadResponseSchema: z.ZodObject<{
402
462
  projectId: z.ZodOptional<z.ZodString>;
403
463
  email: z.ZodOptional<z.ZodString>;
404
464
  accountId: z.ZodOptional<z.ZodString>;
465
+ mcpBinding: z.ZodOptional<z.ZodObject<{
466
+ resourceOrigin: z.ZodString;
467
+ tokenEndpoint: z.ZodString;
468
+ }, z.core.$strict>>;
405
469
  refresh: z.ZodLiteral<"__remote__">;
406
470
  }, z.core.$strict>, z.ZodObject<{
407
471
  type: z.ZodLiteral<"api_key">;
@@ -433,6 +497,10 @@ export declare const credentialIfAbsentUploadResponseSchema: z.ZodObject<{
433
497
  projectId: z.ZodOptional<z.ZodString>;
434
498
  email: z.ZodOptional<z.ZodString>;
435
499
  accountId: z.ZodOptional<z.ZodString>;
500
+ mcpBinding: z.ZodOptional<z.ZodObject<{
501
+ resourceOrigin: z.ZodString;
502
+ tokenEndpoint: z.ZodString;
503
+ }, z.core.$strict>>;
436
504
  refresh: z.ZodLiteral<"__remote__">;
437
505
  }, z.core.$strict>, z.ZodObject<{
438
506
  type: z.ZodLiteral<"api_key">;
@@ -15,10 +15,26 @@ export type ApiKeyCredential = {
15
15
  type: "api_key";
16
16
  key: string;
17
17
  };
18
+ export interface MCPOAuthBinding {
19
+ /** Exact HTTP(S) origin of the MCP resource endpoint. */
20
+ resourceOrigin: string;
21
+ /** Exact canonical HTTP(S) token endpoint used to create and refresh the credential. */
22
+ tokenEndpoint: string;
23
+ }
24
+ export declare function resolveMCPOAuthResourceOrigin(value: string): string | undefined;
25
+ export declare function resolveMCPOAuthTokenEndpoint(value: string): string | undefined;
26
+ export declare function isCanonicalMCPOAuthBinding(binding: MCPOAuthBinding): boolean;
27
+ export declare function assertCanonicalMCPOAuthBinding(binding: MCPOAuthBinding | undefined): asserts binding is MCPOAuthBinding;
18
28
  export type OAuthCredential = {
19
29
  type: "oauth";
30
+ /** Present only for credentials created by runtime MCP OAuth. */
31
+ mcpBinding?: MCPOAuthBinding;
20
32
  } & OAuthCredentials;
21
33
  export type AuthCredential = ApiKeyCredential | OAuthCredential;
34
+ export interface MCPOAuthRefreshClient {
35
+ clientId?: string;
36
+ clientSecret?: string;
37
+ }
22
38
  export type AuthCredentialEntry = AuthCredential | AuthCredential[];
23
39
  export type AuthStorageData = Record<string, AuthCredentialEntry>;
24
40
  /**
@@ -161,6 +177,8 @@ export interface AuthCredentialStore {
161
177
  * for `timeoutMs * (maxRetries + 1)`.
162
178
  */
163
179
  refreshOAuthCredential?(provider: Provider, credentialId: number, credential: OAuthCredential, signal?: AbortSignal): Promise<OAuthCredentials>;
180
+ /** Broker-backed MCP refresh using the broker's stored token endpoint and refresh secret. */
181
+ refreshMCPOAuthCredential?(credentialId: number, credential: OAuthCredential, client: MCPOAuthRefreshClient, signal?: AbortSignal): Promise<OAuthCredential>;
164
182
  /**
165
183
  * Optional async pre-read hook invoked after AuthStorage selects a stored
166
184
  * credential but before it returns that credential for an outbound request.
@@ -597,7 +615,7 @@ export declare class AuthStorage {
597
615
  * refresh attempt, which is required for providers that rotate refresh tokens
598
616
  * on every successful refresh.
599
617
  */
600
- refreshCredentialById(id: number, signal?: AbortSignal): Promise<AuthCredentialSnapshotEntry>;
618
+ refreshCredentialById(id: number, signal?: AbortSignal, mcpClient?: MCPOAuthRefreshClient): Promise<AuthCredentialSnapshotEntry>;
601
619
  /**
602
620
  * Force-refresh the OAuth credential with the given id, bypassing the
603
621
  * not-yet-expired guard. Used by the auth-broker server to honour
@@ -607,6 +625,8 @@ export declare class AuthStorage {
607
625
  * Throws when no OAuth credential with that id is loaded.
608
626
  */
609
627
  forceRefreshCredentialById(id: number, signal?: AbortSignal): Promise<AuthCredentialSnapshotEntry>;
628
+ /** Force-refresh the first OAuth credential stored for a provider. */
629
+ forceRefreshOAuthCredential(provider: string, expected: OAuthCredential, client?: MCPOAuthRefreshClient, signal?: AbortSignal): Promise<OAuthCredential>;
610
630
  /**
611
631
  * Disable the credential with the given id and emit a
612
632
  * {@link CredentialDisabledEvent}. Used by the auth-broker server to honour
@@ -111,6 +111,16 @@ export interface ZenMuxModelManagerConfig {
111
111
  baseUrl?: string;
112
112
  }
113
113
  export declare function zenmuxModelManagerOptions(config?: ZenMuxModelManagerConfig): ModelManagerOptions<Api>;
114
+ export interface OpenGatewayModelManagerConfig {
115
+ apiKey?: string;
116
+ baseUrl?: string;
117
+ }
118
+ /**
119
+ * OpenGateway by Sionic AI — an OpenAI-compatible gateway that fronts OpenAI,
120
+ * Anthropic, and Google models behind one API key. Models are discovered from
121
+ * the OpenAI-compatible `/v1/models` endpoint.
122
+ */
123
+ export declare function opengatewayModelManagerOptions(config?: OpenGatewayModelManagerConfig): ModelManagerOptions<"openai-completions">;
114
124
  export interface KiloModelManagerConfig {
115
125
  apiKey?: string;
116
126
  baseUrl?: string;
@@ -42,6 +42,14 @@ type AnthropicCacheControl = {
42
42
  export declare function clearAnthropicFastModeFallback(providerSessionState: Map<string, ProviderSessionState> | undefined): void;
43
43
  export declare function isAnthropicFastModeUnsupportedError(error: unknown): boolean;
44
44
  export declare function isAnthropicThinkingBlockMutationError(error: unknown): boolean;
45
+ /**
46
+ * 400 shape where a replayed `thinking`/`redacted_thinking` block fails signature
47
+ * validation, e.g. `messages.5.content.24: Invalid \`signature\` in \`thinking\` block`.
48
+ * Unlike the latest-assistant mutation error above, the cited block can sit anywhere
49
+ * in the replayed history, so recovery must repair every assistant message rather
50
+ * than only the latest one.
51
+ */
52
+ export declare function isAnthropicThinkingSignatureInvalidError(error: unknown): boolean;
45
53
  export declare const claudeCodeVersion = "2.1.63";
46
54
  export declare const claudeToolPrefix: string;
47
55
  export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
@@ -184,6 +192,7 @@ export declare function buildAnthropicClientOptions(args: AnthropicClientOptions
184
192
  export declare function normalizeCacheControlTtlOrdering(params: MessageCreateParamsStreaming): void;
185
193
  export declare function convertAnthropicMessages(messages: Message[], model: Model<"anthropic-messages">, isOAuthToken: boolean, options?: {
186
194
  repairLatestAssistantThinking?: boolean;
195
+ repairAllAssistantThinking?: boolean;
187
196
  }): MessageParam[];
188
197
  /**
189
198
  * Normalize a JSON Schema node for Anthropic tool `input_schema`.
@@ -17,6 +17,14 @@ interface BedrockProviderModule {
17
17
  streamBedrock: (model: Model<"bedrock-converse-stream">, context: Context, options: BedrockOptions) => AssistantMessageEventStream;
18
18
  }
19
19
  export declare function setBedrockProviderModule(module: BedrockProviderModule): void;
20
+ /**
21
+ * Resolves the first-event timeout fallback for the outer lazy-stream watchdog.
22
+ * A configured wrapper-specific fallback (from `LazyStreamLimits`) always wins;
23
+ * otherwise providers known to have slow first events get a five-minute floor
24
+ * matching their inner provider-level override. Returns `undefined` for
25
+ * providers that should use the shared default.
26
+ */
27
+ export declare function resolveLazyStreamFirstEventFallbackMs(provider: string, configuredFallbackMs?: number): number | undefined;
20
28
  export declare const streamAnthropic: (model: Model<"anthropic-messages">, context: Context, options: OptionsForApi<"anthropic-messages">) => EventStreamImpl;
21
29
  export declare const streamAzureOpenAIResponses: (model: Model<"azure-openai-responses">, context: Context, options: OptionsForApi<"azure-openai-responses">) => EventStreamImpl;
22
30
  export declare const streamGoogle: (model: Model<"google-generative-ai">, context: Context, options: OptionsForApi<"google-generative-ai">) => EventStreamImpl;
@@ -11,4 +11,5 @@ import type { Api, AssistantMessage, Message, Model } from "../types";
11
11
  */
12
12
  export declare function transformMessages<TApi extends Api>(messages: Message[], model: Model<TApi>, normalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string, options?: {
13
13
  repairLatestAssistantThinking?: boolean;
14
+ repairAllAssistantThinking?: boolean;
14
15
  }): Message[];
@@ -51,7 +51,7 @@ export interface ThinkingConfig {
51
51
  /** Provider-specific transport used to encode the selected effort. */
52
52
  mode: ThinkingControlMode;
53
53
  }
54
- export type KnownProvider = "alibaba-token-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "fugu" | "gitlab-duo" | "cursor" | "deepseek" | "deepinfra" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
54
+ export type KnownProvider = "alibaba-token-plan" | "amazon-bedrock" | "azure-openai" | "anthropic" | "google" | "google-gemini-cli" | "google-antigravity" | "google-vertex" | "openai" | "openai-codex" | "kimi-code" | "minimax-code" | "minimax-code-cn" | "github-copilot" | "fireworks" | "firepass" | "fugu" | "gitlab-duo" | "cursor" | "deepseek" | "deepinfra" | "xai" | "groq" | "cerebras" | "openrouter" | "kilo" | "vercel-ai-gateway" | "zai" | "glm-zcode" | "mistral" | "minimax" | "opencode-go" | "opencode-zen" | "opengateway" | "synthetic" | "cloudflare-ai-gateway" | "huggingface" | "litellm" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "qianfan" | "qwen-portal" | "together" | "venice" | "vllm" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "lm-studio";
55
55
  export type Provider = KnownProvider | string;
56
56
  import type { Effort } from "./model-thinking";
57
57
  /** Token budgets for each thinking level (token-based providers only) */
@@ -519,6 +519,7 @@ export type TSchema = ZodType | TJsonSchema;
519
519
  export type Static<S> = S extends ZodType ? z.infer<S> : S extends {
520
520
  static: infer T;
521
521
  } ? T : unknown;
522
+ export type RawArgumentRejectionCode = "ask-intent-review-requires-positive-round" | "ask-intent-contract-requires-non-empty-authority" | "ask-deep-interview-metadata-requires-deep-interview-gate";
522
523
  export type RawArgumentValidationResult = {
523
524
  outcome: "passthrough";
524
525
  } | {
@@ -526,6 +527,7 @@ export type RawArgumentValidationResult = {
526
527
  arguments: ToolCall["arguments"];
527
528
  } | {
528
529
  outcome: "reject";
530
+ code?: RawArgumentRejectionCode;
529
531
  };
530
532
  export interface Tool<TParameters extends TSchema = TSchema> {
531
533
  name: string;
@@ -1,3 +1,4 @@
1
+ export declare function getProviderFirstEventTimeoutFallbackMs(provider: string): number | undefined;
1
2
  /**
2
3
  * Returns the idle timeout used for provider streaming transports.
3
4
  *
@@ -0,0 +1 @@
1
+ export declare const loginOpenGateway: (options: import("./types").OAuthController) => Promise<string>;
@@ -7,7 +7,7 @@ export type OAuthCredentials = {
7
7
  email?: string;
8
8
  accountId?: string;
9
9
  };
10
- export type OAuthProvider = "alibaba-token-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
10
+ export type OAuthProvider = "alibaba-token-plan" | "anthropic" | "cerebras" | "cloudflare-ai-gateway" | "cursor" | "deepseek" | "deepinfra" | "fireworks" | "firepass" | "fugu" | "github-copilot" | "google-gemini-cli" | "google-antigravity" | "gitlab-duo" | "huggingface" | "kimi-code" | "kilo" | "kagi" | "litellm" | "lm-studio" | "minimax-code" | "minimax-code-cn" | "moonshot" | "nvidia" | "nanogpt" | "ollama" | "ollama-cloud" | "openai-codex" | "openai-codex-device" | "opencode-go" | "opencode-zen" | "opengateway" | "parallel" | "perplexity" | "qianfan" | "qwen-portal" | "synthetic" | "tavily" | "together" | "venice" | "vercel-ai-gateway" | "vllm" | "xai" | "glm-zcode" | "xiaomi" | "xiaomi-token-plan-sgp" | "xiaomi-token-plan-ams" | "xiaomi-token-plan-cn" | "zenmux" | "zai";
11
11
  export type OAuthProviderId = OAuthProvider | (string & {});
12
12
  export type OAuthPrompt = {
13
13
  message: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/ai",
4
- "version": "0.11.7",
4
+ "version": "0.11.9",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://gajae-code.com",
7
7
  "author": "Yeachan-Heo and Gajae Code Contributors",
@@ -40,7 +40,7 @@
40
40
  "dependencies": {
41
41
  "@anthropic-ai/sdk": "^0.94.0",
42
42
  "@bufbuild/protobuf": "^2.12.0",
43
- "@gajae-code/utils": "0.11.7",
43
+ "@gajae-code/utils": "0.11.9",
44
44
  "openai": "^6.36.0",
45
45
  "partial-json": "^0.1.7",
46
46
  "zod": "4.4.3"
@@ -12,6 +12,7 @@ import type {
12
12
  CredentialDisableRequest,
13
13
  CredentialDisableResponse,
14
14
  CredentialIfAbsentUploadResponse,
15
+ CredentialRefreshRequest,
15
16
  CredentialRefreshResponse,
16
17
  CredentialUploadRequest,
17
18
  CredentialUploadResponse,
@@ -242,6 +243,18 @@ export class AuthBrokerClient {
242
243
  }) as Promise<CredentialRefreshResponse>;
243
244
  }
244
245
 
246
+ async refreshMCPCredential(
247
+ id: number,
248
+ body: CredentialRefreshRequest,
249
+ signal?: AbortSignal,
250
+ ): Promise<CredentialRefreshResponse> {
251
+ return this.#request("POST", `/v1/credential/${id}/refresh`, {
252
+ body,
253
+ schema: credentialRefreshResponseSchema,
254
+ signal,
255
+ }) as Promise<CredentialRefreshResponse>;
256
+ }
257
+
245
258
  async disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse> {
246
259
  const body: CredentialDisableRequest = { cause };
247
260
  return this.#request("POST", `/v1/credential/${id}/disable`, {
@@ -96,6 +96,7 @@ export class AuthBrokerRefresher {
96
96
  const targets: number[] = [];
97
97
  for (const entry of snapshot.credentials) {
98
98
  if (entry.credential.type !== "oauth") continue;
99
+ if (entry.credential.mcpBinding) continue;
99
100
  const expires = entry.credential.expires;
100
101
  if (typeof expires !== "number" || !Number.isFinite(expires)) continue;
101
102
  if (expires > deadline) continue;
@@ -14,6 +14,8 @@ import {
14
14
  type AuthCredentialIfAbsentResult,
15
15
  type AuthCredentialSnapshotEntry,
16
16
  type AuthCredentialStore,
17
+ assertCanonicalMCPOAuthBinding,
18
+ type MCPOAuthRefreshClient,
17
19
  type OAuthCredential,
18
20
  REMOTE_REFRESH_SENTINEL,
19
21
  type StoredAuthCredential,
@@ -512,6 +514,29 @@ export class RemoteAuthCredentialStore implements AuthCredentialStore {
512
514
  };
513
515
  }
514
516
 
517
+ async refreshMCPOAuthCredential(
518
+ credentialId: number,
519
+ credential: OAuthCredential,
520
+ client: MCPOAuthRefreshClient,
521
+ signal?: AbortSignal,
522
+ ): Promise<OAuthCredential> {
523
+ const { entry } = await this.#client.refreshMCPCredential(credentialId, client, signal);
524
+ if (entry.credential.type !== "oauth") {
525
+ throw new Error(`Broker returned non-OAuth credential for id=${credentialId}`);
526
+ }
527
+ assertCanonicalMCPOAuthBinding(credential.mcpBinding);
528
+ assertCanonicalMCPOAuthBinding(entry.credential.mcpBinding);
529
+ if (
530
+ entry.credential.mcpBinding.resourceOrigin !== credential.mcpBinding.resourceOrigin ||
531
+ entry.credential.mcpBinding.tokenEndpoint !== credential.mcpBinding.tokenEndpoint
532
+ ) {
533
+ throw new Error("Broker returned mismatched MCP OAuth credential binding");
534
+ }
535
+ this.#applyCredentialEntry(entry);
536
+ this.#maybeRefreshSnapshot("MCP credential refresh");
537
+ return entry.credential;
538
+ }
539
+
515
540
  /**
516
541
  * Store-level hook consumed by `AuthStorage.fetchUsageReports()` — proxies
517
542
  * to the broker's `/v1/usage` endpoint. The broker's egress IP isn't
@@ -16,6 +16,7 @@ import { AuthBrokerRefresher, type AuthBrokerRefresherSchedule } from "./refresh
16
16
  import type {
17
17
  CredentialDisableResponse,
18
18
  CredentialIfAbsentUploadResponse,
19
+ CredentialRefreshRequest,
19
20
  CredentialRefreshResponse,
20
21
  CredentialUploadResponse,
21
22
  HealthzResponse,
@@ -33,7 +34,11 @@ import {
33
34
  DEFAULT_SERVER_IDLE_TIMEOUT_S,
34
35
  DEFAULT_STREAM_KEEPALIVE_MS,
35
36
  } from "./types";
36
- import { credentialDisableRequestSchema, credentialUploadRequestSchema } from "./wire-schemas";
37
+ import {
38
+ credentialDisableRequestSchema,
39
+ credentialRefreshRequestSchema,
40
+ credentialUploadRequestSchema,
41
+ } from "./wire-schemas";
37
42
 
38
43
  export interface AuthBrokerServerOptions {
39
44
  /** Underlying credential storage (wraps the local SQLite store on the broker). */
@@ -561,7 +566,10 @@ export function startAuthBroker(opts: AuthBrokerServerOptions): AuthBrokerServer
561
566
  if (refreshMatch) {
562
567
  const id = Number.parseInt(refreshMatch[1], 10);
563
568
  try {
564
- const entry = await opts.storage.refreshCredentialById(id, req.signal);
569
+ const parsed = await parseBody(req, credentialRefreshRequestSchema, { allowEmpty: true });
570
+ if (!parsed.ok) return parsed.response;
571
+ const refreshRequest: CredentialRefreshRequest = parsed.data;
572
+ const entry = await opts.storage.refreshCredentialById(id, req.signal, refreshRequest);
565
573
  const body: CredentialRefreshResponse = { entry };
566
574
  logger.info("auth-broker credential refreshed", {
567
575
  id,
@@ -11,6 +11,7 @@ import type {
11
11
  AuthCredentialIfAbsentReason,
12
12
  AuthCredentialSnapshot,
13
13
  AuthCredentialSnapshotEntry,
14
+ MCPOAuthRefreshClient,
14
15
  } from "../auth-storage";
15
16
  import type { UsageReport } from "../usage";
16
17
 
@@ -49,6 +50,9 @@ export interface CredentialRefreshResponse {
49
50
  entry: AuthCredentialSnapshotEntry;
50
51
  }
51
52
 
53
+ /** Optional MCP client metadata; the broker still selects the stored token endpoint. */
54
+ export type CredentialRefreshRequest = MCPOAuthRefreshClient;
55
+
52
56
  /** POST /v1/credential/:id/disable request body. */
53
57
  export interface CredentialDisableRequest {
54
58
  cause: string;
@@ -11,11 +11,19 @@
11
11
  * `hasOnlyFields` allowlist for the same effect.
12
12
  */
13
13
  import * as z from "zod/v4";
14
- import { REMOTE_REFRESH_SENTINEL } from "../auth-storage";
14
+ import { isCanonicalMCPOAuthBinding, REMOTE_REFRESH_SENTINEL } from "../auth-storage";
15
15
  import { usageReportSchema } from "../usage";
16
16
 
17
17
  // ─── Credential payloads ───────────────────────────────────────────────────
18
18
 
19
+ export const mcpOAuthBindingSchema = z
20
+ .object({
21
+ resourceOrigin: z.string().min(1),
22
+ tokenEndpoint: z.string().min(1),
23
+ })
24
+ .strict()
25
+ .refine(isCanonicalMCPOAuthBinding, { message: "MCP OAuth binding must use canonical HTTP(S) URLs" });
26
+
19
27
  /** Real OAuth credential (broker-side) — refresh token is the actual upstream value. */
20
28
  export const oauthCredentialSchema = z
21
29
  .object({
@@ -37,6 +45,7 @@ export const oauthCredentialSchema = z
37
45
  projectId: z.string().optional(),
38
46
  email: z.string().optional(),
39
47
  accountId: z.string().optional(),
48
+ mcpBinding: mcpOAuthBindingSchema.optional(),
40
49
  })
41
50
  .strict();
42
51
 
@@ -164,6 +173,13 @@ export const usageResponseSchema = z
164
173
 
165
174
  // ─── Refresh ───────────────────────────────────────────────────────────────
166
175
 
176
+ export const credentialRefreshRequestSchema = z
177
+ .object({
178
+ clientId: z.string().optional(),
179
+ clientSecret: z.string().optional(),
180
+ })
181
+ .strict();
182
+
167
183
  export const credentialRefreshResponseSchema = z
168
184
  .object({
169
185
  entry: credentialSnapshotEntrySchema,