@gajae-code/ai 0.11.5 → 0.11.7
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 +9 -0
- package/dist/types/provider-models/openai-compat.d.ts +2 -2
- package/dist/types/types.d.ts +1 -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-storage.ts +3 -3
- 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 +34 -1
- package/src/providers/openai-completions-compat.ts +2 -2
- package/src/stream.ts +1 -1
- package/src/types.ts +1 -1
- 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/dist/types/utils/oauth/alibaba-coding-plan.d.ts +0 -18
|
@@ -9,7 +9,7 @@ import type { OAuthProvider } from "../utils/oauth/types";
|
|
|
9
9
|
import { googleModelManagerOptions } from "./google";
|
|
10
10
|
import { ollamaCloudModelManagerOptions } from "./ollama";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
alibabaTokenPlanModelManagerOptions,
|
|
13
13
|
anthropicModelManagerOptions,
|
|
14
14
|
cerebrasModelManagerOptions,
|
|
15
15
|
cloudflareAiGatewayModelManagerOptions,
|
|
@@ -130,10 +130,10 @@ function catalogDescriptor(
|
|
|
130
130
|
export const PROVIDER_DESCRIPTORS: readonly ProviderDescriptor[] = [
|
|
131
131
|
descriptor("anthropic", "claude-sonnet-5", config => anthropicModelManagerOptions(config)),
|
|
132
132
|
catalogDescriptor(
|
|
133
|
-
"alibaba-
|
|
134
|
-
"
|
|
135
|
-
config =>
|
|
136
|
-
catalog("Alibaba
|
|
133
|
+
"alibaba-token-plan",
|
|
134
|
+
"deepseek-v4-pro",
|
|
135
|
+
config => alibabaTokenPlanModelManagerOptions(config),
|
|
136
|
+
catalog("Alibaba Token Plan", ["ALIBABA_TOKEN_PLAN_API_KEY"], { oauthProvider: "alibaba-token-plan" }),
|
|
137
137
|
),
|
|
138
138
|
descriptor("openai", "gpt-5.4", config => openaiModelManagerOptions(config)),
|
|
139
139
|
descriptor("groq", "openai/gpt-oss-120b", config => groqModelManagerOptions(config)),
|
|
@@ -334,7 +334,6 @@ export const DEFAULT_MODEL_PER_PROVIDER: Record<KnownProvider, string> = {
|
|
|
334
334
|
...Object.fromEntries(PROVIDER_DESCRIPTORS.map(d => [d.providerId, d.defaultModel])),
|
|
335
335
|
// Providers not in PROVIDER_DESCRIPTORS (special auth or no standard discovery)
|
|
336
336
|
"azure-openai": "gpt-4.1",
|
|
337
|
-
"alibaba-coding-plan": "qwen3.5-plus",
|
|
338
337
|
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
339
338
|
"google-antigravity": "gemini-3-pro-high",
|
|
340
339
|
"google-gemini-cli": "gemini-2.5-pro",
|
|
@@ -1124,26 +1124,26 @@ export function kiloModelManagerOptions(config?: KiloModelManagerConfig): ModelM
|
|
|
1124
1124
|
}
|
|
1125
1125
|
|
|
1126
1126
|
// ---------------------------------------------------------------------------
|
|
1127
|
-
// Alibaba
|
|
1127
|
+
// Alibaba Token Plan
|
|
1128
1128
|
// ---------------------------------------------------------------------------
|
|
1129
1129
|
|
|
1130
|
-
export interface
|
|
1130
|
+
export interface AlibabaTokenPlanModelManagerConfig {
|
|
1131
1131
|
apiKey?: string;
|
|
1132
1132
|
baseUrl?: string;
|
|
1133
1133
|
}
|
|
1134
1134
|
|
|
1135
|
-
export function
|
|
1136
|
-
config?:
|
|
1135
|
+
export function alibabaTokenPlanModelManagerOptions(
|
|
1136
|
+
config?: AlibabaTokenPlanModelManagerConfig,
|
|
1137
1137
|
): ModelManagerOptions<"openai-completions"> {
|
|
1138
1138
|
const apiKey = config?.apiKey;
|
|
1139
|
-
const baseUrl = config?.baseUrl ?? "https://
|
|
1140
|
-
const references = createBundledReferenceMap<"openai-completions">("alibaba-
|
|
1139
|
+
const baseUrl = config?.baseUrl ?? "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
|
|
1140
|
+
const references = createBundledReferenceMap<"openai-completions">("alibaba-token-plan");
|
|
1141
1141
|
return {
|
|
1142
|
-
providerId: "alibaba-
|
|
1142
|
+
providerId: "alibaba-token-plan",
|
|
1143
1143
|
fetchDynamicModels: () =>
|
|
1144
1144
|
fetchOpenAICompatibleModels({
|
|
1145
1145
|
api: "openai-completions",
|
|
1146
|
-
provider: "alibaba-
|
|
1146
|
+
provider: "alibaba-token-plan",
|
|
1147
1147
|
baseUrl,
|
|
1148
1148
|
apiKey,
|
|
1149
1149
|
mapModel: (entry, defaults) => {
|
|
@@ -2356,11 +2356,11 @@ const MODELS_DEV_PROVIDER_DESCRIPTORS_CODING_PLANS: readonly ModelsDevProviderDe
|
|
|
2356
2356
|
reasoningContentField: "reasoning_content",
|
|
2357
2357
|
},
|
|
2358
2358
|
}),
|
|
2359
|
-
// --- Alibaba
|
|
2359
|
+
// --- Alibaba Token Plan ---
|
|
2360
2360
|
openAiCompletionsDescriptor(
|
|
2361
|
-
"alibaba-
|
|
2362
|
-
"alibaba-
|
|
2363
|
-
"https://
|
|
2361
|
+
"alibaba-token-plan",
|
|
2362
|
+
"alibaba-token-plan",
|
|
2363
|
+
"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1",
|
|
2364
2364
|
{
|
|
2365
2365
|
compat: {
|
|
2366
2366
|
supportsDeveloperRole: false,
|
|
@@ -910,11 +910,15 @@ function buildAdditionalModelRequestFields(
|
|
|
910
910
|
/**
|
|
911
911
|
* Adaptive thinking `display` is supported starting with Anthropic model Opus 4.7.
|
|
912
912
|
* Older adaptive-thinking models (Opus 4.6, Sonnet 4.6+) reject the field.
|
|
913
|
+
* Fable (5+) postdates Opus 4.7, accepts `display`, and defaults it to
|
|
914
|
+
* "omitted" — thinking tokens are billed but no content streams back — so it
|
|
915
|
+
* must opt in like Opus 4.7+ (issue #2791).
|
|
913
916
|
* Bedrock model ids are prefixed with region/inference-profile slugs (e.g.
|
|
914
917
|
* `eu.anthropic.Anthropic model-opus-4-7-...`); the regex matches the `Anthropic model-opus-X-Y`
|
|
915
918
|
* fragment regardless of prefix.
|
|
916
919
|
*/
|
|
917
920
|
function supportsAdaptiveThinkingDisplay(modelId: string): boolean {
|
|
921
|
+
if (/claude-fable-\d/.test(modelId)) return true;
|
|
918
922
|
const match = /claude-opus-(\d+)-(\d+)/.exec(modelId);
|
|
919
923
|
if (!match) return false;
|
|
920
924
|
const major = Number(match[1]);
|
|
@@ -306,8 +306,12 @@ let warnedStopSequencesTrim = false;
|
|
|
306
306
|
/**
|
|
307
307
|
* Adaptive thinking `display` is supported starting with Anthropic model Opus 4.7.
|
|
308
308
|
* Older adaptive-thinking models (Opus 4.6, Sonnet 4.6+) reject the field.
|
|
309
|
+
* Fable (5+) postdates Opus 4.7, accepts `display`, and defaults it to
|
|
310
|
+
* "omitted" — thinking tokens are billed but no content streams back — so it
|
|
311
|
+
* must opt in like Opus 4.7+ (issue #2791).
|
|
309
312
|
*/
|
|
310
313
|
function supportsAdaptiveThinkingDisplay(modelId: string): boolean {
|
|
314
|
+
if (/claude-fable-\d/.test(modelId)) return true;
|
|
311
315
|
const match = /claude-opus-(\d+)-(\d+)/.exec(modelId);
|
|
312
316
|
if (!match) return false;
|
|
313
317
|
const major = Number(match[1]);
|
|
@@ -600,6 +604,24 @@ export const stripClaudeToolPrefix = (name: string, prefixOverride: string = cla
|
|
|
600
604
|
return name.slice(prefixOverride.length);
|
|
601
605
|
};
|
|
602
606
|
|
|
607
|
+
// Anthropic requires image `data` to be standard (RFC 4648) base64: the standard
|
|
608
|
+
// alphabet only, correct quartet grouping, and padding (when present) confined to
|
|
609
|
+
// a trailing `=`/`==`. A resident image whose blob went missing bakes a
|
|
610
|
+
// human-readable placeholder into `data` (e.g. "[Session resident imageData blob
|
|
611
|
+
// missing: …]"), and other callers can pass whitespace, data URLs, or URL-safe
|
|
612
|
+
// variants — all of which the API rejects with a 400 `invalid base64 data` that
|
|
613
|
+
// fails the *entire* request and bricks the session. Validate the wire format
|
|
614
|
+
// strictly and degrade anything that is not standard base64 to text.
|
|
615
|
+
//
|
|
616
|
+
// Accepts canonical padded forms and their unpadded equivalents; rejects
|
|
617
|
+
// length % 4 === 1, misplaced/overlong padding, whitespace, data URLs, URL-safe
|
|
618
|
+
// (`-`/`_`) alphabets, prose, and empty input. The pattern has no nested
|
|
619
|
+
// quantifier, so even oversized inputs are rejected in linear time.
|
|
620
|
+
const ANTHROPIC_BASE64_IMAGE_DATA = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}(?:==)?|[A-Za-z0-9+/]{3}=?)?$/;
|
|
621
|
+
function isAnthropicBase64ImageData(data: string): boolean {
|
|
622
|
+
return data.length > 0 && data.length % 4 !== 1 && ANTHROPIC_BASE64_IMAGE_DATA.test(data);
|
|
623
|
+
}
|
|
624
|
+
|
|
603
625
|
/**
|
|
604
626
|
* Convert content blocks to Anthropic API format
|
|
605
627
|
*/
|
|
@@ -623,7 +645,18 @@ function convertContentBlocks(
|
|
|
623
645
|
.filter((block): block is TextContent => block.type === "text")
|
|
624
646
|
.map(block => block.text.toWellFormed())
|
|
625
647
|
.filter(text => text.trim().length > 0);
|
|
626
|
-
const imageBlocks
|
|
648
|
+
const imageBlocks: ImageContent[] = [];
|
|
649
|
+
for (const block of content) {
|
|
650
|
+
if (block.type !== "image") continue;
|
|
651
|
+
if (isAnthropicBase64ImageData(block.data)) {
|
|
652
|
+
imageBlocks.push(block);
|
|
653
|
+
continue;
|
|
654
|
+
}
|
|
655
|
+
// Non-base64 image payload (e.g. a missing-blob placeholder): degrade to
|
|
656
|
+
// text so one lost image cannot invalidate the entire request.
|
|
657
|
+
const text = block.data.toWellFormed().trim();
|
|
658
|
+
if (text.length > 0) textBlocks.push(text);
|
|
659
|
+
}
|
|
627
660
|
const omittedImages = !supportsImages && imageBlocks.length > 0;
|
|
628
661
|
if (imageBlocks.length === 0 || !supportsImages) {
|
|
629
662
|
if (omittedImages) {
|
|
@@ -69,7 +69,7 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
69
69
|
baseUrl.includes("api.anthropic.com") ||
|
|
70
70
|
/(^|\/)claude[-.]/i.test(model.id) ||
|
|
71
71
|
/(^|\/)anthropic\//i.test(model.id);
|
|
72
|
-
const isAlibaba =
|
|
72
|
+
const isAlibaba = baseUrl.includes("dashscope");
|
|
73
73
|
const isQwen = model.id.toLowerCase().includes("qwen");
|
|
74
74
|
// DeepSeek V4 (and other reasoning-capable DeepSeek models) reject follow-up requests in
|
|
75
75
|
// thinking mode unless prior assistant tool-call turns include `reasoning_content`. The
|
|
@@ -244,7 +244,7 @@ export function detectOpenAICompat(model: Model<"openai-completions">, resolvedB
|
|
|
244
244
|
requiresAssistantContentForToolCalls: isKimiModel || isDirectDeepseekReasoning,
|
|
245
245
|
openRouterRouting: undefined,
|
|
246
246
|
vercelGatewayRouting: undefined,
|
|
247
|
-
supportsStrictMode: detectStrictModeSupport(provider, baseUrl),
|
|
247
|
+
supportsStrictMode: detectStrictModeSupport(provider, baseUrl) && !(isDeepseekFamily && isOpenRouter),
|
|
248
248
|
extraBody: isDirectDeepseekReasoning ? { thinking: { type: "enabled" } } : undefined,
|
|
249
249
|
toolStrictMode: isCerebras ? "all_strict" : "mixed",
|
|
250
250
|
};
|
package/src/stream.ts
CHANGED
|
@@ -85,7 +85,7 @@ function hasVertexAdcCredentials(): boolean {
|
|
|
85
85
|
type KeyResolver = string | (() => string | undefined);
|
|
86
86
|
|
|
87
87
|
const serviceProviderMap: Record<string, KeyResolver> = {
|
|
88
|
-
"alibaba-
|
|
88
|
+
"alibaba-token-plan": "ALIBABA_TOKEN_PLAN_API_KEY",
|
|
89
89
|
openai: () => $credentialEnv("OPENAI_API_KEY"),
|
|
90
90
|
google: "GEMINI_API_KEY",
|
|
91
91
|
groq: "GROQ_API_KEY",
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Alibaba
|
|
2
|
+
* Alibaba Token Plan login flow.
|
|
3
3
|
*
|
|
4
|
-
* Alibaba
|
|
4
|
+
* Alibaba Token Plan provides OpenAI-compatible models via
|
|
5
|
+
* https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1.
|
|
5
6
|
*
|
|
6
7
|
* This is not OAuth - it's a simple API key flow:
|
|
7
|
-
* 1. Open browser to Alibaba Cloud
|
|
8
|
+
* 1. Open browser to Alibaba Cloud Model Studio console
|
|
8
9
|
* 2. User copies their API key
|
|
9
10
|
* 3. User pastes the API key into the CLI
|
|
10
11
|
*/
|
|
@@ -13,27 +14,27 @@ import { validateOpenAICompatibleApiKey } from "./api-key-validation";
|
|
|
13
14
|
import type { OAuthController } from "./types";
|
|
14
15
|
|
|
15
16
|
const AUTH_URL = "https://modelstudio.console.alibabacloud.com/";
|
|
16
|
-
const API_BASE_URL = "https://
|
|
17
|
-
const VALIDATION_MODEL = "
|
|
17
|
+
const API_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
|
|
18
|
+
const VALIDATION_MODEL = "deepseek-v4-pro";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
* Login to Alibaba
|
|
21
|
+
* Login to Alibaba Token Plan.
|
|
21
22
|
*
|
|
22
23
|
* Opens browser to API keys page, prompts user to paste their API key.
|
|
23
24
|
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
24
25
|
*/
|
|
25
|
-
export async function
|
|
26
|
+
export async function loginAlibabaTokenPlan(options: OAuthController): Promise<string> {
|
|
26
27
|
if (!options.onPrompt) {
|
|
27
|
-
throw new Error("Alibaba
|
|
28
|
+
throw new Error("Alibaba Token Plan login requires onPrompt callback");
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
options.onAuth?.({
|
|
31
32
|
url: AUTH_URL,
|
|
32
|
-
instructions: "Copy your API key from the Alibaba Cloud
|
|
33
|
+
instructions: "Copy your API key from the Alibaba Cloud Model Studio console",
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
const apiKey = await options.onPrompt({
|
|
36
|
-
message: "Paste your Alibaba
|
|
37
|
+
message: "Paste your Alibaba Token Plan API key",
|
|
37
38
|
placeholder: "sk-...",
|
|
38
39
|
});
|
|
39
40
|
|
|
@@ -48,7 +49,7 @@ export async function loginAlibabaCodingPlan(options: OAuthController): Promise<
|
|
|
48
49
|
|
|
49
50
|
options.onProgress?.("Validating API key...");
|
|
50
51
|
await validateOpenAICompatibleApiKey({
|
|
51
|
-
provider: "Alibaba
|
|
52
|
+
provider: "Alibaba Token Plan",
|
|
52
53
|
apiKey: trimmed,
|
|
53
54
|
baseUrl: API_BASE_URL,
|
|
54
55
|
model: VALIDATION_MODEL,
|
package/src/utils/oauth/index.ts
CHANGED
|
@@ -16,8 +16,8 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
|
|
|
16
16
|
available: true,
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
|
-
id: "alibaba-
|
|
20
|
-
name: "Alibaba
|
|
19
|
+
id: "alibaba-token-plan",
|
|
20
|
+
name: "Alibaba Token Plan",
|
|
21
21
|
available: true,
|
|
22
22
|
},
|
|
23
23
|
{
|
|
@@ -371,6 +371,7 @@ export async function refreshOAuthToken(
|
|
|
371
371
|
case "together":
|
|
372
372
|
case "litellm":
|
|
373
373
|
case "lm-studio":
|
|
374
|
+
case "alibaba-token-plan":
|
|
374
375
|
case "ollama":
|
|
375
376
|
case "ollama-cloud":
|
|
376
377
|
case "xiaomi":
|
package/src/utils/oauth/types.ts
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Alibaba Coding Plan login flow.
|
|
3
|
-
*
|
|
4
|
-
* Alibaba Coding Plan provides OpenAI-compatible models via https://coding-intl.dashscope.aliyuncs.com/v1.
|
|
5
|
-
*
|
|
6
|
-
* This is not OAuth - it's a simple API key flow:
|
|
7
|
-
* 1. Open browser to Alibaba Cloud DashScope API key settings
|
|
8
|
-
* 2. User copies their API key
|
|
9
|
-
* 3. User pastes the API key into the CLI
|
|
10
|
-
*/
|
|
11
|
-
import type { OAuthController } from "./types";
|
|
12
|
-
/**
|
|
13
|
-
* Login to Alibaba Coding Plan.
|
|
14
|
-
*
|
|
15
|
-
* Opens browser to API keys page, prompts user to paste their API key.
|
|
16
|
-
* Returns the API key directly (not OAuthCredentials - this isn't OAuth).
|
|
17
|
-
*/
|
|
18
|
-
export declare function loginAlibabaCodingPlan(options: OAuthController): Promise<string>;
|