@gajae-code/ai 0.11.6 → 0.11.8
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 +22 -0
- package/dist/types/auth-broker/client.d.ts +2 -1
- package/dist/types/auth-broker/remote-store.d.ts +2 -1
- package/dist/types/auth-broker/types.d.ts +3 -1
- package/dist/types/auth-broker/wire-schemas.d.ts +68 -0
- package/dist/types/auth-storage.d.ts +21 -1
- package/dist/types/provider-models/openai-compat.d.ts +2 -2
- package/dist/types/providers/anthropic.d.ts +9 -0
- package/dist/types/providers/transform-messages.d.ts +1 -0
- package/dist/types/types.d.ts +3 -1
- package/dist/types/utils/oauth/alibaba-token-plan.d.ts +19 -0
- package/dist/types/utils/oauth/types.d.ts +1 -1
- package/package.json +2 -2
- package/src/auth-broker/client.ts +13 -0
- package/src/auth-broker/refresher.ts +1 -0
- package/src/auth-broker/remote-store.ts +25 -0
- package/src/auth-broker/server.ts +10 -2
- package/src/auth-broker/types.ts +4 -0
- package/src/auth-broker/wire-schemas.ts +17 -1
- package/src/auth-storage.ts +199 -30
- package/src/model-thinking.ts +11 -3
- package/src/models.json +5362 -1219
- package/src/provider-models/descriptors.ts +5 -6
- package/src/provider-models/openai-compat.ts +12 -12
- package/src/providers/amazon-bedrock.ts +4 -0
- package/src/providers/anthropic.ts +78 -22
- package/src/providers/openai-completions-compat.ts +2 -2
- package/src/providers/openai-completions.ts +4 -1
- package/src/providers/openai-responses.ts +4 -1
- package/src/providers/transform-messages.ts +25 -6
- package/src/stream.ts +1 -1
- package/src/types.ts +7 -2
- package/src/utils/oauth/{alibaba-coding-plan.ts → alibaba-token-plan.ts} +12 -11
- package/src/utils/oauth/index.ts +3 -2
- package/src/utils/oauth/types.ts +1 -1
- package/src/utils/validation.ts +17 -2
- package/src/utils.ts +41 -4
- package/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
package/src/auth-storage.ts
CHANGED
|
@@ -46,12 +46,113 @@ export type ApiKeyCredential = {
|
|
|
46
46
|
key: string;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
export interface MCPOAuthBinding {
|
|
50
|
+
/** Exact HTTP(S) origin of the MCP resource endpoint. */
|
|
51
|
+
resourceOrigin: string;
|
|
52
|
+
/** Exact canonical HTTP(S) token endpoint used to create and refresh the credential. */
|
|
53
|
+
tokenEndpoint: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function resolveCanonicalHttpUrl(value: string): URL | undefined {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = new URL(value);
|
|
59
|
+
if (
|
|
60
|
+
(parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
|
|
61
|
+
parsed.username !== "" ||
|
|
62
|
+
parsed.password !== "" ||
|
|
63
|
+
parsed.hash !== ""
|
|
64
|
+
) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
} catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolveMCPOAuthResourceOrigin(value: string): string | undefined {
|
|
74
|
+
return resolveCanonicalHttpUrl(value)?.origin;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function resolveMCPOAuthTokenEndpoint(value: string): string | undefined {
|
|
78
|
+
return resolveCanonicalHttpUrl(value)?.href;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isCanonicalMCPOAuthBinding(binding: MCPOAuthBinding): boolean {
|
|
82
|
+
return (
|
|
83
|
+
resolveMCPOAuthResourceOrigin(binding.resourceOrigin) === binding.resourceOrigin &&
|
|
84
|
+
resolveMCPOAuthTokenEndpoint(binding.tokenEndpoint) === binding.tokenEndpoint
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function assertCanonicalMCPOAuthBinding(
|
|
89
|
+
binding: MCPOAuthBinding | undefined,
|
|
90
|
+
): asserts binding is MCPOAuthBinding {
|
|
91
|
+
if (!binding || !isCanonicalMCPOAuthBinding(binding)) {
|
|
92
|
+
throw new Error("Invalid MCP OAuth credential binding");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
49
96
|
export type OAuthCredential = {
|
|
50
97
|
type: "oauth";
|
|
98
|
+
/** Present only for credentials created by runtime MCP OAuth. */
|
|
99
|
+
mcpBinding?: MCPOAuthBinding;
|
|
51
100
|
} & OAuthCredentials;
|
|
52
101
|
|
|
53
102
|
export type AuthCredential = ApiKeyCredential | OAuthCredential;
|
|
54
103
|
|
|
104
|
+
export interface MCPOAuthRefreshClient {
|
|
105
|
+
clientId?: string;
|
|
106
|
+
clientSecret?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function refreshBoundMCPOAuthCredential(
|
|
110
|
+
credential: OAuthCredential,
|
|
111
|
+
client: MCPOAuthRefreshClient = {},
|
|
112
|
+
signal?: AbortSignal,
|
|
113
|
+
): Promise<OAuthCredentials> {
|
|
114
|
+
const binding = credential.mcpBinding;
|
|
115
|
+
assertCanonicalMCPOAuthBinding(binding);
|
|
116
|
+
const params = new URLSearchParams({
|
|
117
|
+
grant_type: "refresh_token",
|
|
118
|
+
refresh_token: credential.refresh,
|
|
119
|
+
});
|
|
120
|
+
if (client.clientId) params.set("client_id", client.clientId);
|
|
121
|
+
if (client.clientSecret) params.set("client_secret", client.clientSecret);
|
|
122
|
+
|
|
123
|
+
const response = await fetch(binding.tokenEndpoint, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
126
|
+
body: params.toString(),
|
|
127
|
+
redirect: "manual",
|
|
128
|
+
signal,
|
|
129
|
+
});
|
|
130
|
+
if (response.status >= 300 && response.status < 400) {
|
|
131
|
+
throw new Error(`MCP OAuth refresh rejected redirect response (${response.status})`);
|
|
132
|
+
}
|
|
133
|
+
if (!response.ok) throw new Error(`MCP OAuth refresh failed (${response.status})`);
|
|
134
|
+
const payload: unknown = await response.json();
|
|
135
|
+
if (!payload || typeof payload !== "object") throw new Error("MCP OAuth refresh returned an invalid payload");
|
|
136
|
+
const data = payload as { access_token?: unknown; refresh_token?: unknown; expires_in?: unknown };
|
|
137
|
+
if (typeof data.access_token !== "string" || data.access_token.length === 0) {
|
|
138
|
+
throw new Error("MCP OAuth refresh returned an invalid access token");
|
|
139
|
+
}
|
|
140
|
+
if (data.refresh_token !== undefined && typeof data.refresh_token !== "string") {
|
|
141
|
+
throw new Error("MCP OAuth refresh returned an invalid refresh token");
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
data.expires_in !== undefined &&
|
|
145
|
+
(typeof data.expires_in !== "number" || !Number.isFinite(data.expires_in) || data.expires_in < 0)
|
|
146
|
+
) {
|
|
147
|
+
throw new Error("MCP OAuth refresh returned an invalid expiry");
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
access: data.access_token,
|
|
151
|
+
refresh: data.refresh_token || credential.refresh,
|
|
152
|
+
expires: Date.now() + (data.expires_in ?? 3600) * 1000,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
55
156
|
export type AuthCredentialEntry = AuthCredential | AuthCredential[];
|
|
56
157
|
|
|
57
158
|
export type AuthStorageData = Record<string, AuthCredentialEntry>;
|
|
@@ -230,6 +331,13 @@ export interface AuthCredentialStore {
|
|
|
230
331
|
credential: OAuthCredential,
|
|
231
332
|
signal?: AbortSignal,
|
|
232
333
|
): Promise<OAuthCredentials>;
|
|
334
|
+
/** Broker-backed MCP refresh using the broker's stored token endpoint and refresh secret. */
|
|
335
|
+
refreshMCPOAuthCredential?(
|
|
336
|
+
credentialId: number,
|
|
337
|
+
credential: OAuthCredential,
|
|
338
|
+
client: MCPOAuthRefreshClient,
|
|
339
|
+
signal?: AbortSignal,
|
|
340
|
+
): Promise<OAuthCredential>;
|
|
233
341
|
/**
|
|
234
342
|
* Optional async pre-read hook invoked after AuthStorage selects a stored
|
|
235
343
|
* credential but before it returns that credential for an outbound request.
|
|
@@ -632,7 +740,9 @@ function authCredentialEquals(left: AuthCredential, right: AuthCredential): bool
|
|
|
632
740
|
left.accountId === right.accountId &&
|
|
633
741
|
left.email === right.email &&
|
|
634
742
|
left.projectId === right.projectId &&
|
|
635
|
-
left.enterpriseUrl === right.enterpriseUrl
|
|
743
|
+
left.enterpriseUrl === right.enterpriseUrl &&
|
|
744
|
+
left.mcpBinding?.resourceOrigin === right.mcpBinding?.resourceOrigin &&
|
|
745
|
+
left.mcpBinding?.tokenEndpoint === right.mcpBinding?.tokenEndpoint
|
|
636
746
|
);
|
|
637
747
|
}
|
|
638
748
|
|
|
@@ -1587,9 +1697,9 @@ export class AuthStorage {
|
|
|
1587
1697
|
});
|
|
1588
1698
|
break;
|
|
1589
1699
|
}
|
|
1590
|
-
case "alibaba-
|
|
1591
|
-
const {
|
|
1592
|
-
const apiKey = await
|
|
1700
|
+
case "alibaba-token-plan": {
|
|
1701
|
+
const { loginAlibabaTokenPlan } = await import("./utils/oauth/alibaba-token-plan");
|
|
1702
|
+
const apiKey = await loginAlibabaTokenPlan(ctrl);
|
|
1593
1703
|
await saveApiKeyCredential(apiKey);
|
|
1594
1704
|
return;
|
|
1595
1705
|
}
|
|
@@ -3009,6 +3119,8 @@ export class AuthStorage {
|
|
|
3009
3119
|
const overrideRefresh = this.#refreshOAuthCredentialOverride ?? storeRefresh;
|
|
3010
3120
|
if (overrideRefresh && credentialId !== undefined) {
|
|
3011
3121
|
refreshPromise = overrideRefresh(provider, credentialId, credential, signal);
|
|
3122
|
+
} else if (credential.mcpBinding) {
|
|
3123
|
+
refreshPromise = refreshBoundMCPOAuthCredential(credential, {}, signal);
|
|
3012
3124
|
} else {
|
|
3013
3125
|
const customProvider = getOAuthProvider(provider);
|
|
3014
3126
|
if (customProvider) {
|
|
@@ -3202,10 +3314,38 @@ export class AuthStorage {
|
|
|
3202
3314
|
return { apiKey: result.apiKey, credential: updated };
|
|
3203
3315
|
} catch (error) {
|
|
3204
3316
|
const errorMsg = String(error);
|
|
3317
|
+
// Peer-rotation recovery runs before ANY failure classification: a
|
|
3318
|
+
// concurrent process may have rotated the refresh token, which
|
|
3319
|
+
// invalidates the snapshot token we just attempted. Re-read the row —
|
|
3320
|
+
// if the persisted refresh token changed, the peer's rotation succeeded
|
|
3321
|
+
// and we pick up the fresh credential instead of disabling (definitive
|
|
3322
|
+
// path) or temp-blocking (transient path) a row that is actually
|
|
3323
|
+
// healthy. This matters for providers whose invalid-grant response does
|
|
3324
|
+
// not match the definitive regex below (e.g. Kimi's 400 "The provided
|
|
3325
|
+
// authorization grant is invalid"): with short-lived access tokens and
|
|
3326
|
+
// multiple gjc processes sharing the store, the stale-snapshot failure
|
|
3327
|
+
// would otherwise be misclassified as transient and the credential
|
|
3328
|
+
// temp-blocked on every rotation race.
|
|
3329
|
+
const attemptedCredentialId = this.#getStoredCredentials(provider)[selection.index]?.id;
|
|
3330
|
+
if (attemptedCredentialId !== undefined) {
|
|
3331
|
+
const latestRow = this.#store.listAuthCredentials(provider).find(row => row.id === attemptedCredentialId);
|
|
3332
|
+
const latestCredential = latestRow?.credential;
|
|
3333
|
+
if (latestCredential?.type === "oauth" && latestCredential.refresh !== selection.credential.refresh) {
|
|
3334
|
+
logger.debug("OAuth refresh race detected; another process rotated token first", {
|
|
3335
|
+
provider,
|
|
3336
|
+
index: selection.index,
|
|
3337
|
+
credentialId: attemptedCredentialId,
|
|
3338
|
+
});
|
|
3339
|
+
await this.reload();
|
|
3340
|
+
return this.#resolveOAuthSelection(provider, sessionId, options);
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3205
3343
|
// Only remove credentials for definitive auth failures
|
|
3206
3344
|
// Keep credentials for transient errors (network, 5xx) and block temporarily
|
|
3207
3345
|
const isDefinitiveFailure =
|
|
3208
|
-
/invalid_grant|invalid_token|revoked|unauthorized|expired.*refresh|refresh.*expired/i.test(
|
|
3346
|
+
/invalid_grant|grant is invalid|invalid_token|revoked|unauthorized|expired.*refresh|refresh.*expired/i.test(
|
|
3347
|
+
errorMsg,
|
|
3348
|
+
) ||
|
|
3209
3349
|
(/\b(401|403)\b/.test(errorMsg) && !/timeout|network|fetch failed|ECONNREFUSED/i.test(errorMsg));
|
|
3210
3350
|
|
|
3211
3351
|
logger.warn("OAuth token refresh failed", {
|
|
@@ -3216,27 +3356,6 @@ export class AuthStorage {
|
|
|
3216
3356
|
});
|
|
3217
3357
|
|
|
3218
3358
|
if (isDefinitiveFailure) {
|
|
3219
|
-
// The credential at this index may have been rotated by another process between
|
|
3220
|
-
// our in-memory snapshot and the refresh attempt: Anthropic rotates refresh
|
|
3221
|
-
// tokens on every use, so the peer's success leaves our stored token invalid.
|
|
3222
|
-
// Re-read the row from disk before marking it disabled — if the persisted
|
|
3223
|
-
// refresh token has changed, the peer rotation succeeded and we should pick
|
|
3224
|
-
// up the new credential instead of soft-deleting the row that the peer just
|
|
3225
|
-
// updated.
|
|
3226
|
-
const credentialId = this.#getStoredCredentials(provider)[selection.index]?.id;
|
|
3227
|
-
if (credentialId !== undefined) {
|
|
3228
|
-
const latestRow = this.#store.listAuthCredentials(provider).find(row => row.id === credentialId);
|
|
3229
|
-
const latestCredential = latestRow?.credential;
|
|
3230
|
-
if (latestCredential?.type === "oauth" && latestCredential.refresh !== selection.credential.refresh) {
|
|
3231
|
-
logger.debug("OAuth refresh race detected; another process rotated token first", {
|
|
3232
|
-
provider,
|
|
3233
|
-
index: selection.index,
|
|
3234
|
-
credentialId,
|
|
3235
|
-
});
|
|
3236
|
-
await this.reload();
|
|
3237
|
-
return this.#resolveOAuthSelection(provider, sessionId, options);
|
|
3238
|
-
}
|
|
3239
|
-
}
|
|
3240
3359
|
// Permanently disable invalid credentials with an explicit cause for inspection/debugging.
|
|
3241
3360
|
// Use a CAS-style disable conditioned on the row still containing the stale credential
|
|
3242
3361
|
// we tried to refresh, so a peer rotation that lands between the pre-check above and
|
|
@@ -3518,14 +3637,18 @@ export class AuthStorage {
|
|
|
3518
3637
|
* refresh attempt, which is required for providers that rotate refresh tokens
|
|
3519
3638
|
* on every successful refresh.
|
|
3520
3639
|
*/
|
|
3521
|
-
async refreshCredentialById(
|
|
3640
|
+
async refreshCredentialById(
|
|
3641
|
+
id: number,
|
|
3642
|
+
signal?: AbortSignal,
|
|
3643
|
+
mcpClient: MCPOAuthRefreshClient = {},
|
|
3644
|
+
): Promise<AuthCredentialSnapshotEntry> {
|
|
3522
3645
|
const existing = this.#oauthRefreshInFlight.get(id);
|
|
3523
3646
|
if (existing) return raceCredentialRefreshWithSignal(existing, signal);
|
|
3524
3647
|
|
|
3525
3648
|
const promise = (async () => {
|
|
3526
3649
|
this.#bumpGeneration("credential-refresh-start");
|
|
3527
3650
|
try {
|
|
3528
|
-
return await this.#forceRefreshCredentialByIdUnshared(id, signal);
|
|
3651
|
+
return await this.#forceRefreshCredentialByIdUnshared(id, signal, mcpClient);
|
|
3529
3652
|
} catch (error) {
|
|
3530
3653
|
this.#bumpGeneration("credential-refresh-failure");
|
|
3531
3654
|
throw error;
|
|
@@ -3549,7 +3672,32 @@ export class AuthStorage {
|
|
|
3549
3672
|
return this.refreshCredentialById(id, signal);
|
|
3550
3673
|
}
|
|
3551
3674
|
|
|
3552
|
-
|
|
3675
|
+
/** Force-refresh the first OAuth credential stored for a provider. */
|
|
3676
|
+
async forceRefreshOAuthCredential(
|
|
3677
|
+
provider: string,
|
|
3678
|
+
expected: OAuthCredential,
|
|
3679
|
+
client: MCPOAuthRefreshClient = {},
|
|
3680
|
+
signal?: AbortSignal,
|
|
3681
|
+
): Promise<OAuthCredential> {
|
|
3682
|
+
const storageProvider = resolveOAuthStorageProvider(provider);
|
|
3683
|
+
const target = this.#getStoredCredentials(storageProvider).find(
|
|
3684
|
+
entry => entry.credential === expected || authCredentialEquals(entry.credential, expected),
|
|
3685
|
+
);
|
|
3686
|
+
if (target?.credential.type !== "oauth") {
|
|
3687
|
+
throw new Error(`No OAuth credential found for provider=${storageProvider}`);
|
|
3688
|
+
}
|
|
3689
|
+
const entry = await this.refreshCredentialById(target.id, signal, client);
|
|
3690
|
+
if (entry.credential.type !== "oauth") {
|
|
3691
|
+
throw new Error(`Credential ${target.id} is not OAuth`);
|
|
3692
|
+
}
|
|
3693
|
+
return entry.credential;
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
async #forceRefreshCredentialByIdUnshared(
|
|
3697
|
+
id: number,
|
|
3698
|
+
signal?: AbortSignal,
|
|
3699
|
+
mcpClient: MCPOAuthRefreshClient = {},
|
|
3700
|
+
): Promise<AuthCredentialSnapshotEntry> {
|
|
3553
3701
|
for (const [provider, entries] of this.#data) {
|
|
3554
3702
|
const index = entries.findIndex(entry => entry.id === id);
|
|
3555
3703
|
if (index === -1) continue;
|
|
@@ -3560,7 +3708,27 @@ export class AuthStorage {
|
|
|
3560
3708
|
// Pass a clone with expires=0 so the cached not-yet-expired short-circuit
|
|
3561
3709
|
// in #refreshOAuthCredential doesn't suppress the requested refresh.
|
|
3562
3710
|
const stale: OAuthCredential = { ...target.credential, expires: 0 };
|
|
3563
|
-
|
|
3711
|
+
let refreshed: OAuthCredentials;
|
|
3712
|
+
if (target.credential.mcpBinding) {
|
|
3713
|
+
assertCanonicalMCPOAuthBinding(target.credential.mcpBinding);
|
|
3714
|
+
const remoteRefresh = this.#store.refreshMCPOAuthCredential?.bind(this.#store);
|
|
3715
|
+
const refreshedCredential = remoteRefresh
|
|
3716
|
+
? await remoteRefresh(id, stale, mcpClient, signal)
|
|
3717
|
+
: {
|
|
3718
|
+
type: "oauth" as const,
|
|
3719
|
+
...(await refreshBoundMCPOAuthCredential(stale, mcpClient, signal)),
|
|
3720
|
+
mcpBinding: target.credential.mcpBinding,
|
|
3721
|
+
};
|
|
3722
|
+
if (
|
|
3723
|
+
refreshedCredential.mcpBinding?.resourceOrigin !== target.credential.mcpBinding.resourceOrigin ||
|
|
3724
|
+
refreshedCredential.mcpBinding.tokenEndpoint !== target.credential.mcpBinding.tokenEndpoint
|
|
3725
|
+
) {
|
|
3726
|
+
throw new Error("Refreshed MCP OAuth credential binding mismatch");
|
|
3727
|
+
}
|
|
3728
|
+
refreshed = refreshedCredential;
|
|
3729
|
+
} else {
|
|
3730
|
+
refreshed = await this.#refreshOAuthCredential(provider as Provider, stale, id, signal);
|
|
3731
|
+
}
|
|
3564
3732
|
const updated: OAuthCredential = {
|
|
3565
3733
|
type: "oauth",
|
|
3566
3734
|
access: refreshed.access,
|
|
@@ -3570,6 +3738,7 @@ export class AuthStorage {
|
|
|
3570
3738
|
email: refreshed.email ?? target.credential.email,
|
|
3571
3739
|
projectId: refreshed.projectId ?? target.credential.projectId,
|
|
3572
3740
|
enterpriseUrl: refreshed.enterpriseUrl ?? target.credential.enterpriseUrl,
|
|
3741
|
+
mcpBinding: target.credential.mcpBinding,
|
|
3573
3742
|
};
|
|
3574
3743
|
this.#replaceCredentialAt(provider, index, updated);
|
|
3575
3744
|
return {
|
package/src/model-thinking.ts
CHANGED
|
@@ -62,7 +62,7 @@ type SemVer = {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
type GeminiKind = "pro" | "flash";
|
|
65
|
-
type AnthropicKind = "opus" | "sonnet";
|
|
65
|
+
type AnthropicKind = "opus" | "sonnet" | "fable";
|
|
66
66
|
type OpenAIVariant =
|
|
67
67
|
| "base"
|
|
68
68
|
| "codex"
|
|
@@ -638,6 +638,11 @@ function inferAnthropicSupportedEfforts<TApi extends Api>(
|
|
|
638
638
|
(model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") &&
|
|
639
639
|
semverGte(parsedModel.version, "4.6")
|
|
640
640
|
) {
|
|
641
|
+
if (parsedModel.kind === "fable") {
|
|
642
|
+
// Fable exposes Anthropic's Messages-only xhigh preset; Bedrock
|
|
643
|
+
// Converse lacks it (same split as Opus 4.7+ below).
|
|
644
|
+
return model.api === "anthropic-messages" ? DEFAULT_REASONING_EFFORTS_WITH_XHIGH : DEFAULT_REASONING_EFFORTS;
|
|
645
|
+
}
|
|
641
646
|
if (parsedModel.kind !== "opus") return DEFAULT_REASONING_EFFORTS;
|
|
642
647
|
return anthropicModelHasRealXHighEffort(model)
|
|
643
648
|
? DEFAULT_REASONING_EFFORTS_WITH_XHIGH_AND_MAX
|
|
@@ -697,7 +702,10 @@ function inferThinkingControlMode<TApi extends Api>(
|
|
|
697
702
|
|
|
698
703
|
case "bedrock-converse-stream":
|
|
699
704
|
if (parsedModel.family === "anthropic") {
|
|
700
|
-
if (
|
|
705
|
+
if (
|
|
706
|
+
semverGte(parsedModel.version, "4.6") &&
|
|
707
|
+
(parsedModel.kind === "opus" || parsedModel.kind === "fable")
|
|
708
|
+
) {
|
|
701
709
|
return "anthropic-adaptive";
|
|
702
710
|
}
|
|
703
711
|
if (semverGte(parsedModel.version, "4.5")) {
|
|
@@ -737,7 +745,7 @@ function parseGeminiModel(modelId: string): GeminiModel | null {
|
|
|
737
745
|
}
|
|
738
746
|
|
|
739
747
|
function parseAnthropicModel(modelId: string): AnthropicModel | null {
|
|
740
|
-
const match = /claude-(opus|sonnet)-(\d{1,2}(?:[.-]\d{1,2}){0,2})\b/.exec(modelId);
|
|
748
|
+
const match = /claude-(opus|sonnet|fable)-(\d{1,2}(?:[.-]\d{1,2}){0,2})\b/.exec(modelId);
|
|
741
749
|
if (!match) {
|
|
742
750
|
return null;
|
|
743
751
|
}
|