@gajae-code/ai 0.11.6 → 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.
@@ -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
- alibabaCodingPlanModelManagerOptions,
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-coding-plan",
134
- "qwen3.5-plus",
135
- config => alibabaCodingPlanModelManagerOptions(config),
136
- catalog("Alibaba Coding Plan", ["ALIBABA_CODING_PLAN_API_KEY"]),
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 Coding Plan
1127
+ // Alibaba Token Plan
1128
1128
  // ---------------------------------------------------------------------------
1129
1129
 
1130
- export interface AlibabaCodingPlanModelManagerConfig {
1130
+ export interface AlibabaTokenPlanModelManagerConfig {
1131
1131
  apiKey?: string;
1132
1132
  baseUrl?: string;
1133
1133
  }
1134
1134
 
1135
- export function alibabaCodingPlanModelManagerOptions(
1136
- config?: AlibabaCodingPlanModelManagerConfig,
1135
+ export function alibabaTokenPlanModelManagerOptions(
1136
+ config?: AlibabaTokenPlanModelManagerConfig,
1137
1137
  ): ModelManagerOptions<"openai-completions"> {
1138
1138
  const apiKey = config?.apiKey;
1139
- const baseUrl = config?.baseUrl ?? "https://coding-intl.dashscope.aliyuncs.com/v1";
1140
- const references = createBundledReferenceMap<"openai-completions">("alibaba-coding-plan");
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-coding-plan",
1142
+ providerId: "alibaba-token-plan",
1143
1143
  fetchDynamicModels: () =>
1144
1144
  fetchOpenAICompatibleModels({
1145
1145
  api: "openai-completions",
1146
- provider: "alibaba-coding-plan",
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 Coding Plan ---
2359
+ // --- Alibaba Token Plan ---
2360
2360
  openAiCompletionsDescriptor(
2361
- "alibaba-coding-plan",
2362
- "alibaba-coding-plan",
2363
- "https://coding-intl.dashscope.aliyuncs.com/v1",
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 = content.filter((block): block is ImageContent => block.type === "image");
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 = provider === "alibaba-coding-plan" || baseUrl.includes("dashscope");
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-coding-plan": "ALIBABA_CODING_PLAN_API_KEY",
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
@@ -114,7 +114,7 @@ export interface ThinkingConfig {
114
114
  }
115
115
 
116
116
  export type KnownProvider =
117
- | "alibaba-coding-plan"
117
+ | "alibaba-token-plan"
118
118
  | "amazon-bedrock"
119
119
  | "azure-openai"
120
120
  | "anthropic"
@@ -1,10 +1,11 @@
1
1
  /**
2
- * Alibaba Coding Plan login flow.
2
+ * Alibaba Token Plan login flow.
3
3
  *
4
- * Alibaba Coding Plan provides OpenAI-compatible models via https://coding-intl.dashscope.aliyuncs.com/v1.
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 DashScope API key settings
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://coding-intl.dashscope.aliyuncs.com/v1";
17
- const VALIDATION_MODEL = "qwen3.5-plus";
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 Coding Plan.
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 loginAlibabaCodingPlan(options: OAuthController): Promise<string> {
26
+ export async function loginAlibabaTokenPlan(options: OAuthController): Promise<string> {
26
27
  if (!options.onPrompt) {
27
- throw new Error("Alibaba Coding Plan login requires onPrompt callback");
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 DashScope console",
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 Coding Plan API key",
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 Coding Plan",
52
+ provider: "Alibaba Token Plan",
52
53
  apiKey: trimmed,
53
54
  baseUrl: API_BASE_URL,
54
55
  model: VALIDATION_MODEL,
@@ -16,8 +16,8 @@ const builtInOAuthProviders: OAuthProviderInfo[] = [
16
16
  available: true,
17
17
  },
18
18
  {
19
- id: "alibaba-coding-plan",
20
- name: "Alibaba Coding Plan",
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":
@@ -9,7 +9,7 @@ export type OAuthCredentials = {
9
9
  };
10
10
 
11
11
  export type OAuthProvider =
12
- | "alibaba-coding-plan"
12
+ | "alibaba-token-plan"
13
13
  | "anthropic"
14
14
  | "cerebras"
15
15
  | "cloudflare-ai-gateway"
@@ -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>;