@gajae-code/ai 0.15.4 → 0.15.5
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 +12 -1
- package/dist/types/auth-storage.d.ts +23 -9
- package/dist/types/provider-models/special.d.ts +2 -1
- package/dist/types/providers/kiro-api-key.d.ts +50 -0
- package/dist/types/providers/kiro-codewhisperer.d.ts +3 -0
- package/dist/types/types.d.ts +13 -22
- package/dist/types/utils/fallback-transport.d.ts +7 -0
- package/dist/types/utils/json-parse.d.ts +5 -3
- package/dist/types/utils/tool-call-healing.d.ts +4 -4
- package/package.json +3 -3
- package/src/auth-storage.ts +93 -12
- package/src/model-thinking.ts +16 -0
- package/src/models.json +31 -0
- package/src/models.ts +9 -1
- package/src/provider-models/special.ts +15 -3
- package/src/providers/kiro-api-key.d.ts +50 -0
- package/src/providers/kiro-api-key.ts +786 -0
- package/src/providers/kiro-codewhisperer.d.ts +3 -0
- package/src/providers/kiro-codewhisperer.ts +11 -2
- package/src/providers/openai-codex-responses.ts +4 -0
- package/src/providers/openai-responses-shared.ts +23 -2
- package/src/stream.ts +5 -3
- package/src/types.d.ts +13 -22
- package/src/types.ts +13 -22
- package/src/utils/fallback-transport.d.ts +7 -0
- package/src/utils/fallback-transport.ts +21 -4
- package/src/utils/json-parse.d.ts +5 -3
- package/src/utils/json-parse.ts +6 -6
- package/src/utils/tool-call-healing.d.ts +4 -4
- package/src/utils/tool-call-healing.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.15.5] - 2026-08-29
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- 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.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- 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)
|
|
14
|
+
- 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.
|
|
15
|
+
- 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
16
|
## [0.15.4] - 2026-08-29
|
|
6
17
|
|
|
7
18
|
## [0.15.3] - 2026-08-27
|
|
8
19
|
|
|
9
20
|
### Fixed
|
|
10
21
|
|
|
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
|
|
22
|
+
- 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
23
|
- Explicit positive `maxTokens` values declared for custom `models.yml` models
|
|
13
24
|
and model overrides now reach the provider request across the shared stream
|
|
14
25
|
mapping (`max_tokens`, `max_completion_tokens`, and `max_output_tokens`).
|
|
@@ -682,8 +682,17 @@ export declare class AuthStorage {
|
|
|
682
682
|
*
|
|
683
683
|
* Lower priority than {@link setRuntimeApiKey} so a CLI `--api-key`
|
|
684
684
|
* still wins for the duration of a single invocation.
|
|
685
|
+
*
|
|
686
|
+
* `options.envSourced` marks the value as resolved from a models.yml
|
|
687
|
+
* `apiKeyEnv` indirection. Unlike a literal pin, an env pointer only says
|
|
688
|
+
* where to look for a key; when the user has since run `auth login`, the
|
|
689
|
+
* stored api_key credential is the fresher, actively-managed secret and
|
|
690
|
+
* wins over the indirection (stored OAuth credentials still yield, so a
|
|
691
|
+
* custom-endpoint bearer is never replaced by an upstream OAuth token).
|
|
685
692
|
*/
|
|
686
|
-
setConfigApiKey(provider: string, apiKey: string
|
|
693
|
+
setConfigApiKey(provider: string, apiKey: string, options?: {
|
|
694
|
+
envSourced?: boolean;
|
|
695
|
+
}): void;
|
|
687
696
|
/**
|
|
688
697
|
* Remove a single config-sourced API key override.
|
|
689
698
|
*/
|
|
@@ -840,13 +849,16 @@ export declare class AuthStorage {
|
|
|
840
849
|
* Get API key for a provider.
|
|
841
850
|
* Priority:
|
|
842
851
|
* 1. Runtime override (CLI --api-key)
|
|
843
|
-
* 2. Config override (models.yml `providers.<name>.apiKey`)
|
|
844
|
-
* 3.
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
852
|
+
* 2. Config override (models.yml `providers.<name>.apiKey` literal pin)
|
|
853
|
+
* 3. Stored api_key credential from `auth login`, when the config override
|
|
854
|
+
* is only an `apiKeyEnv` indirection
|
|
855
|
+
* 4. Config override sourced from models.yml `providers.<name>.apiKeyEnv`
|
|
856
|
+
* 5. Session-selected OAuth credential, when present
|
|
857
|
+
* 6. Usable or unresolved API key from storage
|
|
858
|
+
* 7. OAuth token from storage (auto-refreshed)
|
|
859
|
+
* 8. Previously unusable command-backed API key retry
|
|
860
|
+
* 9. Environment variable
|
|
861
|
+
* 10. Fallback resolver (models.yml custom providers, last-resort)
|
|
850
862
|
*/
|
|
851
863
|
getApiKey(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<string | undefined>;
|
|
852
864
|
/**
|
|
@@ -915,7 +927,9 @@ export declare class AuthStorage {
|
|
|
915
927
|
*
|
|
916
928
|
* Surfaces four layers, highest precedence first:
|
|
917
929
|
* 1. Runtime override (`--api-key`).
|
|
918
|
-
* 2. Config override (`models.yml` `providers.<name>.apiKey`
|
|
930
|
+
* 2. Config override (`models.yml` `providers.<name>.apiKey` literal pin,
|
|
931
|
+
* or an `apiKeyEnv` indirection when no stored api_key credential
|
|
932
|
+
* outranks it).
|
|
919
933
|
* 3. Stored credential (the one this session is currently sticky to, or the
|
|
920
934
|
* one round-robin would pick next when no session id is supplied).
|
|
921
935
|
* 4. Env var / fallback resolver — when no stored credential exists.
|
|
@@ -24,5 +24,6 @@ export interface JetBrainsJunieModelManagerConfig {
|
|
|
24
24
|
}
|
|
25
25
|
export declare function jetbrainsJunieModelManagerOptions(_config?: JetBrainsJunieModelManagerConfig): ModelManagerOptions<"anthropic-messages">;
|
|
26
26
|
export interface KiroModelManagerConfig {
|
|
27
|
+
apiKey?: string;
|
|
27
28
|
}
|
|
28
|
-
export declare function kiroModelManagerOptions(
|
|
29
|
+
export declare function kiroModelManagerOptions(config?: KiroModelManagerConfig): ModelManagerOptions<"kiro-codewhisperer-stream">;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Model, StreamFunction } from "../types";
|
|
2
|
+
export declare function isKiroApiKey(value: string | undefined): value is string;
|
|
3
|
+
export declare function kiroApiRegion(options?: {
|
|
4
|
+
region?: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function kiroApiBaseUrl(region: string): string;
|
|
7
|
+
export declare function toKiroModelId(modelId: string): string;
|
|
8
|
+
export declare function kiroApiStaticModels(): Model<"kiro-codewhisperer-stream">[];
|
|
9
|
+
/** Discover models this API key can use. Returns null when the key is missing. */
|
|
10
|
+
export declare function fetchKiroApiModels(apiKey: string, region?: string): Promise<Model<"kiro-codewhisperer-stream">[]>;
|
|
11
|
+
type KiroStreamEvent = {
|
|
12
|
+
type: "content";
|
|
13
|
+
data: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "toolUse";
|
|
16
|
+
data: {
|
|
17
|
+
name: string;
|
|
18
|
+
toolUseId: string;
|
|
19
|
+
input: string;
|
|
20
|
+
stop?: boolean;
|
|
21
|
+
};
|
|
22
|
+
} | {
|
|
23
|
+
type: "toolUseInput";
|
|
24
|
+
data: {
|
|
25
|
+
input: string;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
type: "toolUseStop";
|
|
29
|
+
data: {
|
|
30
|
+
stop: boolean;
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
type: "usage";
|
|
34
|
+
data: {
|
|
35
|
+
inputTokens?: number;
|
|
36
|
+
outputTokens?: number;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
type: "error";
|
|
40
|
+
data: {
|
|
41
|
+
error: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export declare function parseKiroApiEvents(buffer: string): {
|
|
46
|
+
events: KiroStreamEvent[];
|
|
47
|
+
remaining: string;
|
|
48
|
+
};
|
|
49
|
+
export declare const streamKiroApiKey: StreamFunction<"kiro-codewhisperer-stream">;
|
|
50
|
+
export {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { Effort } from "../model-thinking";
|
|
1
2
|
import type { StreamFunction, StreamOptions } from "../types";
|
|
2
3
|
export interface KiroCodeWhispererOptions extends StreamOptions {
|
|
4
|
+
/** Effort level for Kiro API-key reasoning. */
|
|
5
|
+
reasoning?: Effort | boolean;
|
|
3
6
|
/** AWS region for the CodeWhisperer streaming endpoint. */
|
|
4
7
|
region?: string;
|
|
5
8
|
/** Profile ARN for enterprise IAM Identity Center accounts. */
|
package/dist/types/types.d.ts
CHANGED
|
@@ -399,31 +399,22 @@ export interface ToolCall {
|
|
|
399
399
|
*/
|
|
400
400
|
incompleteArgumentsReason?: "truncated" | "malformed" | "conflicting" | "ambiguous";
|
|
401
401
|
/**
|
|
402
|
-
* Set when
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* The agent loop
|
|
409
|
-
*
|
|
410
|
-
* is a tool that enumerated its display-only fields
|
|
411
|
-
* (`displaySafeEscapedArgFields`): when every escaped scalar corroborates
|
|
412
|
-
* a decoded non-ASCII character inside those fields, the call executes
|
|
413
|
-
* with a warning instead — rendered question text, never executable
|
|
414
|
-
* content, ids, or durable metadata.
|
|
415
|
-
* Escapes that are required (control characters) or unavoidable (lone
|
|
416
|
-
* surrogates) never set this.
|
|
402
|
+
* Set by current producers when raw argument JSON carries unsafe Unicode
|
|
403
|
+
* data, such as malformed escape evidence or a decoded unpaired surrogate.
|
|
404
|
+
* Valid JSON `\uXXXX` escapes are canonical spellings of the decoded string
|
|
405
|
+
* and current producers do not set this flag for them.
|
|
406
|
+
*
|
|
407
|
+
* Legacy producers may still set the flag for any escaped non-ASCII spelling.
|
|
408
|
+
* The agent loop keeps its bounded legacy resample/display-safe behavior for
|
|
409
|
+
* those calls while consuming the transient evidence below.
|
|
417
410
|
*/
|
|
418
411
|
escapedNonAsciiArguments?: boolean;
|
|
419
412
|
/**
|
|
420
|
-
* Bounded, payload-free evidence for
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* `escapedNonAsciiArguments`. The agent consumes and removes this transient
|
|
426
|
-
* field before the tool-call message can become durable.
|
|
413
|
+
* Bounded, payload-free evidence for raw Unicode argument data. Current
|
|
414
|
+
* producers attach it only for unsafe data; legacy producers may attach
|
|
415
|
+
* non-malformed positional evidence used by the display-safe compatibility
|
|
416
|
+
* path. The agent consumes and removes this transient field before the
|
|
417
|
+
* tool-call message can become durable.
|
|
427
418
|
*/
|
|
428
419
|
escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
|
|
429
420
|
}
|
|
@@ -23,6 +23,13 @@ export interface FallbackTrigger {
|
|
|
23
23
|
export declare const STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE = "stream_first_event_timeout";
|
|
24
24
|
/** Stable code for a nominally successful response with no content or token usage. */
|
|
25
25
|
export declare const EMPTY_RESPONSE_PROVIDER_CODE = "empty_response";
|
|
26
|
+
/**
|
|
27
|
+
* OpenAI's typed capacity-overload code. It arrives without an HTTP status —
|
|
28
|
+
* inside an HTTP 200 terminal Responses envelope or a Codex error event — so the
|
|
29
|
+
* code itself is the only structured evidence of the failure and must survive
|
|
30
|
+
* the existence gate below. It is always compared case-sensitively.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SERVER_OVERLOADED_PROVIDER_CODE = "server_is_overloaded";
|
|
26
33
|
export type TransportHeaders = Headers | Record<string, string | undefined>;
|
|
27
34
|
/**
|
|
28
35
|
* Structured facts from an upstream HTTP or transport failure. Retry decisions
|
|
@@ -67,9 +67,11 @@ export declare function collectUnicodeEscapeEvidence(json: string): UnicodeEscap
|
|
|
67
67
|
/**
|
|
68
68
|
* Return evidence only when decoded tool arguments are unsafe to execute.
|
|
69
69
|
*
|
|
70
|
-
* Valid JSON escapes and literal UTF-8 have the same canonical decoded value
|
|
71
|
-
*
|
|
72
|
-
*
|
|
70
|
+
* Valid JSON escapes and literal UTF-8 have the same canonical decoded value,
|
|
71
|
+
* including a valid scalar whose hex digits differ from what a caller intended:
|
|
72
|
+
* runtime syntax validation cannot infer author intent after decoding.
|
|
73
|
+
* Malformed escape-bearing JSON, duplicate/deep suspicious escape evidence, and
|
|
74
|
+
* unpaired UTF-16 surrogates keep the fail-closed path.
|
|
73
75
|
*/
|
|
74
76
|
export declare function collectUnsafeUnicodeEscapeEvidence(json: string): UnicodeEscapeEvidence | undefined;
|
|
75
77
|
/** Attach unsafe raw evidence while preserving the existing call-level guard flag. */
|
|
@@ -22,10 +22,10 @@ export interface HealedToolCall {
|
|
|
22
22
|
readonly name: string;
|
|
23
23
|
readonly arguments: string;
|
|
24
24
|
/**
|
|
25
|
-
* Whether the raw leaked payload
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* Whether the raw leaked payload carried unsafe Unicode argument data.
|
|
26
|
+
* Captured before the normalizing round-trip below, which would otherwise
|
|
27
|
+
* erase malformed escape evidence. Valid `\uXXXX` spellings decode
|
|
28
|
+
* canonically and leave this false.
|
|
29
29
|
*/
|
|
30
30
|
readonly escapedNonAsciiArguments: boolean;
|
|
31
31
|
readonly escapedUnicodeArgumentEvidence?: UnicodeEscapeEvidence;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/ai",
|
|
4
|
-
"version": "0.15.
|
|
4
|
+
"version": "0.15.5",
|
|
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,8 +40,8 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/sdk": "^0.94.0",
|
|
42
42
|
"@bufbuild/protobuf": "^2.12.0",
|
|
43
|
-
"@gajae-code/natives": "0.15.
|
|
44
|
-
"@gajae-code/utils": "0.15.
|
|
43
|
+
"@gajae-code/natives": "0.15.5",
|
|
44
|
+
"@gajae-code/utils": "0.15.5",
|
|
45
45
|
"openai": "^6.36.0",
|
|
46
46
|
"partial-json": "^0.1.7",
|
|
47
47
|
"zod": "4.4.3"
|
package/src/auth-storage.ts
CHANGED
|
@@ -1231,6 +1231,14 @@ export class AuthStorage {
|
|
|
1231
1231
|
#data: Map<string, StoredCredential[]> = new Map();
|
|
1232
1232
|
#runtimeOverrides: Map<string, string> = new Map();
|
|
1233
1233
|
#configOverrides: Map<string, string> = new Map();
|
|
1234
|
+
/**
|
|
1235
|
+
* Providers whose config override was resolved from a models.yml `apiKeyEnv`
|
|
1236
|
+
* indirection rather than a literal `apiKey` pin. An env pointer is not a
|
|
1237
|
+
* pinned secret: the pointed-to value (shell env, trusted env files) can go
|
|
1238
|
+
* stale silently, and the 401 rotation machinery cannot repair it. A stored
|
|
1239
|
+
* api_key credential from `auth login` therefore outranks it.
|
|
1240
|
+
*/
|
|
1241
|
+
#configOverrideEnvSourced: Set<string> = new Set();
|
|
1234
1242
|
#runtimeCredentialSelectors: Map<string, AuthCredentialSelector> = new Map();
|
|
1235
1243
|
/** Soft runtime credential preference per provider; quota failures may rotate away from it. */
|
|
1236
1244
|
#runtimePreferredCredentialSelectors: Map<string, AuthCredentialSelector> = new Map();
|
|
@@ -1926,10 +1934,22 @@ export class AuthStorage {
|
|
|
1926
1934
|
*
|
|
1927
1935
|
* Lower priority than {@link setRuntimeApiKey} so a CLI `--api-key`
|
|
1928
1936
|
* still wins for the duration of a single invocation.
|
|
1937
|
+
*
|
|
1938
|
+
* `options.envSourced` marks the value as resolved from a models.yml
|
|
1939
|
+
* `apiKeyEnv` indirection. Unlike a literal pin, an env pointer only says
|
|
1940
|
+
* where to look for a key; when the user has since run `auth login`, the
|
|
1941
|
+
* stored api_key credential is the fresher, actively-managed secret and
|
|
1942
|
+
* wins over the indirection (stored OAuth credentials still yield, so a
|
|
1943
|
+
* custom-endpoint bearer is never replaced by an upstream OAuth token).
|
|
1929
1944
|
*/
|
|
1930
|
-
setConfigApiKey(provider: string, apiKey: string): void {
|
|
1945
|
+
setConfigApiKey(provider: string, apiKey: string, options: { envSourced?: boolean } = {}): void {
|
|
1931
1946
|
const storageProvider = resolveOAuthStorageProvider(provider);
|
|
1932
1947
|
this.#configOverrides.set(storageProvider, apiKey);
|
|
1948
|
+
if (options.envSourced) {
|
|
1949
|
+
this.#configOverrideEnvSourced.add(storageProvider);
|
|
1950
|
+
} else {
|
|
1951
|
+
this.#configOverrideEnvSourced.delete(storageProvider);
|
|
1952
|
+
}
|
|
1933
1953
|
this.#bumpGeneration("set-config-api-key", storageProvider);
|
|
1934
1954
|
}
|
|
1935
1955
|
|
|
@@ -1938,6 +1958,7 @@ export class AuthStorage {
|
|
|
1938
1958
|
*/
|
|
1939
1959
|
removeConfigApiKey(provider: string): void {
|
|
1940
1960
|
const storageProvider = resolveOAuthStorageProvider(provider);
|
|
1961
|
+
this.#configOverrideEnvSourced.delete(storageProvider);
|
|
1941
1962
|
if (this.#configOverrides.delete(storageProvider)) this.#bumpGeneration("remove-config-api-key", storageProvider);
|
|
1942
1963
|
}
|
|
1943
1964
|
|
|
@@ -1947,6 +1968,7 @@ export class AuthStorage {
|
|
|
1947
1968
|
*/
|
|
1948
1969
|
clearConfigApiKeys(): void {
|
|
1949
1970
|
const providers = [...this.#configOverrides.keys()];
|
|
1971
|
+
this.#configOverrideEnvSourced.clear();
|
|
1950
1972
|
if (providers.length === 0) return;
|
|
1951
1973
|
this.#configOverrides.clear();
|
|
1952
1974
|
for (const provider of providers) this.#bumpGeneration("clear-config-api-keys", provider);
|
|
@@ -5165,9 +5187,16 @@ export class AuthStorage {
|
|
|
5165
5187
|
if (runtimeKey) return runtimeKey;
|
|
5166
5188
|
|
|
5167
5189
|
const configKey = this.#configOverrides.get(provider);
|
|
5168
|
-
if (configKey) return configKey;
|
|
5190
|
+
if (configKey && !this.#configOverrideEnvSourced.has(provider)) return configKey;
|
|
5169
5191
|
|
|
5170
5192
|
const selectedCredential = this.#resolveSelectedStoredCredential(provider, undefined, undefined);
|
|
5193
|
+
if (configKey) {
|
|
5194
|
+
// Env-sourced (`apiKeyEnv`) override: same precedence as getApiKey —
|
|
5195
|
+
// a stored api_key credential from `auth login` wins, stored OAuth
|
|
5196
|
+
// still yields to the indirection.
|
|
5197
|
+
const storedApiKey = await this.#resolveStoredApiKeyOverEnvConfig(provider, selectedCredential, undefined);
|
|
5198
|
+
return storedApiKey ?? configKey;
|
|
5199
|
+
}
|
|
5171
5200
|
if (selectedCredential?.credential.type === "api_key") {
|
|
5172
5201
|
return this.#resolveStoredApiKey(provider, selectedCredential.credential.key);
|
|
5173
5202
|
}
|
|
@@ -5217,13 +5246,16 @@ export class AuthStorage {
|
|
|
5217
5246
|
* Get API key for a provider.
|
|
5218
5247
|
* Priority:
|
|
5219
5248
|
* 1. Runtime override (CLI --api-key)
|
|
5220
|
-
* 2. Config override (models.yml `providers.<name>.apiKey`)
|
|
5221
|
-
* 3.
|
|
5222
|
-
*
|
|
5223
|
-
*
|
|
5224
|
-
*
|
|
5225
|
-
*
|
|
5226
|
-
*
|
|
5249
|
+
* 2. Config override (models.yml `providers.<name>.apiKey` literal pin)
|
|
5250
|
+
* 3. Stored api_key credential from `auth login`, when the config override
|
|
5251
|
+
* is only an `apiKeyEnv` indirection
|
|
5252
|
+
* 4. Config override sourced from models.yml `providers.<name>.apiKeyEnv`
|
|
5253
|
+
* 5. Session-selected OAuth credential, when present
|
|
5254
|
+
* 6. Usable or unresolved API key from storage
|
|
5255
|
+
* 7. OAuth token from storage (auto-refreshed)
|
|
5256
|
+
* 8. Previously unusable command-backed API key retry
|
|
5257
|
+
* 9. Environment variable
|
|
5258
|
+
* 10. Fallback resolver (models.yml custom providers, last-resort)
|
|
5227
5259
|
*/
|
|
5228
5260
|
async getApiKey(provider: string, sessionId?: string, options?: AuthApiKeyOptions): Promise<string | undefined> {
|
|
5229
5261
|
provider = resolveOAuthStorageProvider(provider);
|
|
@@ -5239,7 +5271,17 @@ export class AuthStorage {
|
|
|
5239
5271
|
// honor it instead of forwarding an upstream OAuth token that the proxy
|
|
5240
5272
|
// won't accept.
|
|
5241
5273
|
const configKey = this.#configOverrides.get(provider);
|
|
5242
|
-
if (configKey)
|
|
5274
|
+
if (configKey) {
|
|
5275
|
+
if (!this.#configOverrideEnvSourced.has(provider)) return configKey;
|
|
5276
|
+
// The override is an `apiKeyEnv` indirection, not a pinned value. A
|
|
5277
|
+
// stored api_key credential from `auth login` is actively managed
|
|
5278
|
+
// (validated at login, rotated on 401), while the pointed-to env
|
|
5279
|
+
// value can go stale with no recovery path — prefer the stored
|
|
5280
|
+
// credential. Stored OAuth credentials still yield to the override.
|
|
5281
|
+
const storedApiKey = await this.#resolveStoredApiKeyOverEnvConfig(provider, selectedCredential, sessionId);
|
|
5282
|
+
if (storedApiKey) return storedApiKey;
|
|
5283
|
+
return configKey;
|
|
5284
|
+
}
|
|
5243
5285
|
|
|
5244
5286
|
if (selectedCredential?.credential.type === "api_key") {
|
|
5245
5287
|
this.#recordSessionCredential(provider, sessionId, "api_key", selectedCredential.index);
|
|
@@ -5295,6 +5337,38 @@ export class AuthStorage {
|
|
|
5295
5337
|
return this.#fallbackResolver?.(provider) ?? undefined;
|
|
5296
5338
|
}
|
|
5297
5339
|
|
|
5340
|
+
/**
|
|
5341
|
+
* Resolve a stored api_key credential that outranks an env-sourced config
|
|
5342
|
+
* override (`apiKeyEnv`). Mirrors the api_key branches of {@link getApiKey}:
|
|
5343
|
+
* the selector-pinned credential first, then the round-robin/session pool.
|
|
5344
|
+
* Returns undefined when no stored api_key credential resolves, leaving the
|
|
5345
|
+
* env-sourced override in effect.
|
|
5346
|
+
*/
|
|
5347
|
+
async #resolveStoredApiKeyOverEnvConfig(
|
|
5348
|
+
provider: string,
|
|
5349
|
+
selectedCredential: ({ index: number } & StoredCredential) | undefined,
|
|
5350
|
+
sessionId?: string,
|
|
5351
|
+
): Promise<string | undefined> {
|
|
5352
|
+
if (selectedCredential?.credential.type === "api_key") {
|
|
5353
|
+
const resolved = await this.#resolveStoredApiKey(provider, selectedCredential.credential.key);
|
|
5354
|
+
if (resolved) {
|
|
5355
|
+
this.#recordSessionCredential(provider, sessionId, "api_key", selectedCredential.index);
|
|
5356
|
+
return resolved;
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
const attemptedApiKeyIndices = new Set<number>();
|
|
5360
|
+
for (;;) {
|
|
5361
|
+
const apiKeySelection = this.#selectApiKeyCredential(provider, sessionId, attemptedApiKeyIndices);
|
|
5362
|
+
if (!apiKeySelection) return undefined;
|
|
5363
|
+
attemptedApiKeyIndices.add(apiKeySelection.index);
|
|
5364
|
+
const resolved = await this.#resolveStoredApiKey(provider, apiKeySelection.credential.key);
|
|
5365
|
+
if (resolved) {
|
|
5366
|
+
this.#recordSessionCredential(provider, sessionId, "api_key", apiKeySelection.index);
|
|
5367
|
+
return resolved;
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
}
|
|
5371
|
+
|
|
5298
5372
|
/**
|
|
5299
5373
|
* Resolve the OAuth credential for `provider`, refreshing through the same
|
|
5300
5374
|
* pipeline as {@link AuthStorage.getApiKey} but returning the refreshed
|
|
@@ -5621,7 +5695,9 @@ export class AuthStorage {
|
|
|
5621
5695
|
*
|
|
5622
5696
|
* Surfaces four layers, highest precedence first:
|
|
5623
5697
|
* 1. Runtime override (`--api-key`).
|
|
5624
|
-
* 2. Config override (`models.yml` `providers.<name>.apiKey`
|
|
5698
|
+
* 2. Config override (`models.yml` `providers.<name>.apiKey` literal pin,
|
|
5699
|
+
* or an `apiKeyEnv` indirection when no stored api_key credential
|
|
5700
|
+
* outranks it).
|
|
5625
5701
|
* 3. Stored credential (the one this session is currently sticky to, or the
|
|
5626
5702
|
* one round-robin would pick next when no session id is supplied).
|
|
5627
5703
|
* 4. Env var / fallback resolver — when no stored credential exists.
|
|
@@ -5633,7 +5709,12 @@ export class AuthStorage {
|
|
|
5633
5709
|
return "runtime override (--api-key)";
|
|
5634
5710
|
}
|
|
5635
5711
|
if (this.#configOverrides.has(provider)) {
|
|
5636
|
-
|
|
5712
|
+
// An `apiKeyEnv` indirection loses to a stored api_key credential
|
|
5713
|
+
// (see getApiKey); describe the credential that actually wins.
|
|
5714
|
+
const shadowed = this.#getStoredCredentials(provider).some(entry => entry.credential.type === "api_key");
|
|
5715
|
+
if (!this.#configOverrideEnvSourced.has(provider) || !shadowed) {
|
|
5716
|
+
return "config override (models.yml)";
|
|
5717
|
+
}
|
|
5637
5718
|
}
|
|
5638
5719
|
|
|
5639
5720
|
const baseLabel = this.#sourceLabel ?? "local store";
|
package/src/model-thinking.ts
CHANGED
|
@@ -549,6 +549,22 @@ function applyGeneratedModelPolicy(model: ApiModel<Api>): void {
|
|
|
549
549
|
levels: [Effort.Low, Effort.High, Effort.Max],
|
|
550
550
|
};
|
|
551
551
|
}
|
|
552
|
+
// GLM-5.3-Flash keeps GLM-5.3's text contract: 1M context, 128K output, and
|
|
553
|
+
// the same always-on low/high/max reasoning_effort. models.dev currently
|
|
554
|
+
// lists it with null limits, so pin the contract the same way.
|
|
555
|
+
// https://docs.z.ai/guides/llm/glm-5.3-flash ("Text parameters are
|
|
556
|
+
// consistent with GLM-5.3, with support for a 1M-token context window.")
|
|
557
|
+
if (model.provider === "zai" && model.id === "glm-5.3-flash") {
|
|
558
|
+
model.contextWindow = 1_000_000;
|
|
559
|
+
model.maxTokens = 131_072;
|
|
560
|
+
model.thinking = {
|
|
561
|
+
mode: "effort",
|
|
562
|
+
minLevel: Effort.Low,
|
|
563
|
+
maxLevel: Effort.Max,
|
|
564
|
+
defaultLevel: Effort.Max,
|
|
565
|
+
levels: [Effort.Low, Effort.High, Effort.Max],
|
|
566
|
+
};
|
|
567
|
+
}
|
|
552
568
|
// Groq's agentic `compound` systems reject `reasoning_effort` outright
|
|
553
569
|
// (400 "`reasoning_effort` is not supported with this model", verified
|
|
554
570
|
// 2026-08-23), yet models.dev advertises them as reasoning models. Drop the
|
package/src/models.json
CHANGED
|
@@ -89307,6 +89307,37 @@
|
|
|
89307
89307
|
]
|
|
89308
89308
|
}
|
|
89309
89309
|
},
|
|
89310
|
+
"glm-5.3-flash": {
|
|
89311
|
+
"id": "glm-5.3-flash",
|
|
89312
|
+
"name": "GLM-5.3-Flash",
|
|
89313
|
+
"api": "anthropic-messages",
|
|
89314
|
+
"provider": "zai",
|
|
89315
|
+
"baseUrl": "https://api.z.ai/api/anthropic",
|
|
89316
|
+
"reasoning": true,
|
|
89317
|
+
"input": [
|
|
89318
|
+
"text",
|
|
89319
|
+
"image"
|
|
89320
|
+
],
|
|
89321
|
+
"cost": {
|
|
89322
|
+
"input": 0,
|
|
89323
|
+
"output": 0,
|
|
89324
|
+
"cacheRead": 0,
|
|
89325
|
+
"cacheWrite": 0
|
|
89326
|
+
},
|
|
89327
|
+
"contextWindow": 1000000,
|
|
89328
|
+
"maxTokens": 131072,
|
|
89329
|
+
"thinking": {
|
|
89330
|
+
"mode": "effort",
|
|
89331
|
+
"minLevel": "low",
|
|
89332
|
+
"maxLevel": "max",
|
|
89333
|
+
"defaultLevel": "max",
|
|
89334
|
+
"levels": [
|
|
89335
|
+
"low",
|
|
89336
|
+
"high",
|
|
89337
|
+
"max"
|
|
89338
|
+
]
|
|
89339
|
+
}
|
|
89340
|
+
},
|
|
89310
89341
|
"glm-5v-turbo": {
|
|
89311
89342
|
"id": "glm-5v-turbo",
|
|
89312
89343
|
"name": "GLM-5V-Turbo",
|
package/src/models.ts
CHANGED
|
@@ -29,7 +29,15 @@ const providerModelRegistry: Map<string, Map<string, Model<Api>>> = new Map();
|
|
|
29
29
|
function getBundledCatalog(): BundledCatalog {
|
|
30
30
|
// TS types a .json import as its contents; at runtime `with { type: "file" }`
|
|
31
31
|
// yields the file path (bunfs path in compiled binaries, disk path in dev).
|
|
32
|
-
|
|
32
|
+
if (bundledCatalog !== undefined) return bundledCatalog;
|
|
33
|
+
const resource = modelsJsonPath as unknown;
|
|
34
|
+
if (typeof resource === "string") {
|
|
35
|
+
bundledCatalog = JSON.parse(readFileSync(resource, "utf8")) as BundledCatalog;
|
|
36
|
+
} else if (resource !== null && typeof resource === "object") {
|
|
37
|
+
bundledCatalog = resource as BundledCatalog;
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error("Bundled models catalog resource is invalid.");
|
|
40
|
+
}
|
|
33
41
|
return bundledCatalog;
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -3,6 +3,7 @@ import { once, sanitizeText } from "@gajae-code/utils";
|
|
|
3
3
|
|
|
4
4
|
import type { ModelManagerOptions } from "../model-manager";
|
|
5
5
|
import { buildZCodeSourceHeaders, resolveGlmZcodeAnthropicBaseUrl } from "../providers/anthropic";
|
|
6
|
+
import { fetchKiroApiModels, isKiroApiKey, kiroApiStaticModels } from "../providers/kiro-api-key";
|
|
6
7
|
import { fetchOpenCodexModels, OPENCODEX_MODEL_CACHE_TTL_MS } from "../providers/openai-opencodex-responses";
|
|
7
8
|
import { fetchCodexModels } from "../utils/discovery/codex";
|
|
8
9
|
import { fetchOpenAICompatibleModels } from "../utils/discovery/openai-compatible";
|
|
@@ -164,10 +165,21 @@ export function jetbrainsJunieModelManagerOptions(
|
|
|
164
165
|
// Kiro (Amazon Q Developer / CodeWhisperer)
|
|
165
166
|
// ---------------------------------------------------------------------------
|
|
166
167
|
|
|
167
|
-
export interface KiroModelManagerConfig {
|
|
168
|
+
export interface KiroModelManagerConfig {
|
|
169
|
+
apiKey?: string;
|
|
170
|
+
}
|
|
168
171
|
|
|
169
172
|
export function kiroModelManagerOptions(
|
|
170
|
-
|
|
173
|
+
config: KiroModelManagerConfig = {},
|
|
171
174
|
): ModelManagerOptions<"kiro-codewhisperer-stream"> {
|
|
172
|
-
|
|
175
|
+
const apiKey = config.apiKey;
|
|
176
|
+
return {
|
|
177
|
+
providerId: "kiro",
|
|
178
|
+
...(isKiroApiKey(apiKey)
|
|
179
|
+
? {
|
|
180
|
+
staticModels: kiroApiStaticModels(),
|
|
181
|
+
fetchDynamicModels: () => fetchKiroApiModels(apiKey),
|
|
182
|
+
}
|
|
183
|
+
: undefined),
|
|
184
|
+
};
|
|
173
185
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Model, StreamFunction } from "../types";
|
|
2
|
+
export declare function isKiroApiKey(value: string | undefined): value is string;
|
|
3
|
+
export declare function kiroApiRegion(options?: {
|
|
4
|
+
region?: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function kiroApiBaseUrl(region: string): string;
|
|
7
|
+
export declare function toKiroModelId(modelId: string): string;
|
|
8
|
+
export declare function kiroApiStaticModels(): Model<"kiro-codewhisperer-stream">[];
|
|
9
|
+
/** Discover models this API key can use. Returns null when the key is missing. */
|
|
10
|
+
export declare function fetchKiroApiModels(apiKey: string, region?: string): Promise<Model<"kiro-codewhisperer-stream">[]>;
|
|
11
|
+
type KiroStreamEvent = {
|
|
12
|
+
type: "content";
|
|
13
|
+
data: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: "toolUse";
|
|
16
|
+
data: {
|
|
17
|
+
name: string;
|
|
18
|
+
toolUseId: string;
|
|
19
|
+
input: string;
|
|
20
|
+
stop?: boolean;
|
|
21
|
+
};
|
|
22
|
+
} | {
|
|
23
|
+
type: "toolUseInput";
|
|
24
|
+
data: {
|
|
25
|
+
input: string;
|
|
26
|
+
};
|
|
27
|
+
} | {
|
|
28
|
+
type: "toolUseStop";
|
|
29
|
+
data: {
|
|
30
|
+
stop: boolean;
|
|
31
|
+
};
|
|
32
|
+
} | {
|
|
33
|
+
type: "usage";
|
|
34
|
+
data: {
|
|
35
|
+
inputTokens?: number;
|
|
36
|
+
outputTokens?: number;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
type: "error";
|
|
40
|
+
data: {
|
|
41
|
+
error: string;
|
|
42
|
+
message?: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
export declare function parseKiroApiEvents(buffer: string): {
|
|
46
|
+
events: KiroStreamEvent[];
|
|
47
|
+
remaining: string;
|
|
48
|
+
};
|
|
49
|
+
export declare const streamKiroApiKey: StreamFunction<"kiro-codewhisperer-stream">;
|
|
50
|
+
export {};
|