jeopi-ai 16.2.24 → 16.2.26

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 CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.2.26] - 2026-07-05
6
+
7
+ ### Fixed
8
+
9
+ - `renderDemotedThinking`'s classifier-safe markdown-italic prose form (avoids Anthropic's `reasoning_extraction` refusal when replaying a prior turn's unsigned reasoning as plaintext) was keyed off a hand-rolled `claude-fable`-only regex, missing `claude-mythos-5` — a real first-party Anthropic model the rest of the catalog already treats as classifier-equivalent to Fable (`isAnthropicFableOrMythosModel`). Unsigned Mythos thinking replayed cross-model/branch-handoff was demoted to a raw `<thinking>...</thinking>` text block instead, which can trip the same reasoning-extraction classifier Fable's carve-out exists to avoid. Now uses the canonical `isAnthropicFableOrMythosModel` classifier for both.
10
+
11
+ ## [16.2.25] - 2026-07-05
12
+
13
+ ### Added
14
+
15
+ - Added the `tencent` provider registry def (`registry/tencent.ts`): API-key login validated against Tencent Cloud MaaS's Anthropic Messages endpoint (`tokenhub-intl.tencentcloudmaas.com/v1/messages`), registered in the `ALL` provider list. Env fallback: `TENCENT_API_KEY`. Verified live: full inference round-trip (GLM-5.2, Hunyuan MT2 Plus) succeeds end-to-end through `streamAnthropic`; DeepSeek/Kimi ids on the same test key correctly surfaced a clean `402` (quota exhausted) rather than an error.
16
+ - `createApiKeyLogin`'s `anthropic-messages` validation now accepts an optional `acceptableErrorStatuses` list: HTTP statuses that still prove the key is valid (e.g. a billing/quota gate only a real, authenticated key would reach) and should pass validation instead of being treated as an invalid key. Discovered live while validating the `tencent` login: a real, correctly-authenticated key with no active billing got HTTP 402, which the login flow was previously rejecting as "invalid key" — `tencent`'s validation now sets `acceptableErrorStatuses: [402]`; other providers are unaffected (opt-in, defaults to none).
17
+
5
18
  ## [16.2.24] - 2026-07-03
6
19
 
7
20
  ### Added
@@ -5,10 +5,10 @@
5
5
  * replayed unsigned `thought` part is schema-accepted but silently discarded —
6
6
  * neither recalled nor influencing generation).
7
7
  *
8
- * Fable is the exception: replaying prior reasoning inside `<thinking>` /
8
+ * Fable/Mythos are the exception: replaying prior reasoning inside `<thinking>` /
9
9
  * `antml:thinking`-style assistant text is treated as a reasoning-extraction
10
- * attempt and can train the next turn to leak thoughts, so Fable receives the
11
- * reasoning as markdown-italic assistant prose instead. Harmony and Gemma are
10
+ * attempt and can train the next turn to leak thoughts, so Fable/Mythos receive
11
+ * the reasoning as markdown-italic assistant prose instead. Harmony and Gemma are
12
12
  * also exceptions: their `renderThinking` emits chat-template control tokens
13
13
  * (`<|channel|>analysis`, `<|channel>thought`) that must not appear inside a
14
14
  * structured native message, so they fall back to a plain `<think>` block. Every
@@ -17,6 +17,10 @@ type AnthropicMessagesValidation = {
17
17
  provider: string;
18
18
  baseUrl: string;
19
19
  model: string;
20
+ /** HTTP error statuses that still prove the key is valid (e.g. a billing/quota
21
+ * gate that only a real, recognized key would reach) and should be treated as
22
+ * successful validation rather than an invalid-key failure. */
23
+ acceptableErrorStatuses?: readonly number[];
20
24
  };
21
25
  type ModelsEndpointValidation = {
22
26
  kind: "models-endpoint";
@@ -14,6 +14,10 @@ type AnthropicCompatibleValidationOptions = {
14
14
  model: string;
15
15
  signal?: AbortSignal;
16
16
  fetch?: FetchImpl;
17
+ /** HTTP error statuses that still prove the key is valid (e.g. a billing/quota
18
+ * gate that only a real, recognized key would reach) and should be treated as
19
+ * successful validation rather than an invalid-key failure. */
20
+ acceptableErrorStatuses?: readonly number[];
17
21
  };
18
22
  type ModelListValidationOptions = {
19
23
  provider: string;
@@ -232,6 +232,10 @@ declare const ALL: ({
232
232
  readonly name: "Tavily";
233
233
  readonly envKeys: "TAVILY_API_KEY";
234
234
  readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
235
+ } | {
236
+ readonly id: "tencent";
237
+ readonly name: "Tencent Cloud MaaS";
238
+ readonly login: (cb: import("./oauth").OAuthLoginCallbacks) => Promise<string>;
235
239
  } | {
236
240
  readonly id: "together";
237
241
  readonly name: "Together";
@@ -0,0 +1,7 @@
1
+ import type { OAuthLoginCallbacks } from "./oauth/types";
2
+ export declare const loginTencent: (options: import("./oauth").OAuthController) => Promise<string>;
3
+ export declare const tencentProvider: {
4
+ readonly id: "tencent";
5
+ readonly name: "Tencent Cloud MaaS";
6
+ readonly login: (cb: OAuthLoginCallbacks) => Promise<string>;
7
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-ai",
4
- "version": "16.2.24",
4
+ "version": "16.2.26",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/akillness/jeopi",
7
7
  "author": "Can Boluk",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bufbuild/protobuf": "^2.12.0",
41
- "jeopi-catalog": "16.2.24",
42
- "jeopi-utils": "16.2.24",
43
- "jeopi-wire": "16.2.24",
41
+ "jeopi-catalog": "16.2.26",
42
+ "jeopi-utils": "16.2.26",
43
+ "jeopi-wire": "16.2.26",
44
44
  "arktype": "^2.2.0",
45
45
  "zod": "^4"
46
46
  },
@@ -1,8 +1,6 @@
1
- import { bareModelId, preferredDialect } from "jeopi-catalog/identity";
1
+ import { isAnthropicFableOrMythosModel, preferredDialect } from "jeopi-catalog/identity";
2
2
  import { getDialectDefinition } from "./factory";
3
3
 
4
- const CLAUDE_FABLE_ID = /(?:^|[./])claude[-.]fable(?:[-.]|$)/i;
5
-
6
4
  /**
7
5
  * Wrap a prior-turn reasoning string for demotion into native conversation
8
6
  * history — the cross-provider / cross-model case where the target cannot replay
@@ -10,10 +8,10 @@ const CLAUDE_FABLE_ID = /(?:^|[./])claude[-.]fable(?:[-.]|$)/i;
10
8
  * replayed unsigned `thought` part is schema-accepted but silently discarded —
11
9
  * neither recalled nor influencing generation).
12
10
  *
13
- * Fable is the exception: replaying prior reasoning inside `<thinking>` /
11
+ * Fable/Mythos are the exception: replaying prior reasoning inside `<thinking>` /
14
12
  * `antml:thinking`-style assistant text is treated as a reasoning-extraction
15
- * attempt and can train the next turn to leak thoughts, so Fable receives the
16
- * reasoning as markdown-italic assistant prose instead. Harmony and Gemma are
13
+ * attempt and can train the next turn to leak thoughts, so Fable/Mythos receive
14
+ * the reasoning as markdown-italic assistant prose instead. Harmony and Gemma are
17
15
  * also exceptions: their `renderThinking` emits chat-template control tokens
18
16
  * (`<|channel|>analysis`, `<|channel>thought`) that must not appear inside a
19
17
  * structured native message, so they fall back to a plain `<think>` block. Every
@@ -28,9 +26,8 @@ const CLAUDE_FABLE_ID = /(?:^|[./])claude[-.]fable(?:[-.]|$)/i;
28
26
  export function renderDemotedThinking(modelId: string, text: string): string {
29
27
  if (!text) return "";
30
28
  text = text.toWellFormed();
31
- const canonicalId = bareModelId(modelId);
32
29
  const dialect = preferredDialect(modelId);
33
- if (CLAUDE_FABLE_ID.test(canonicalId)) return `_Hmm. ${text}_\n`;
30
+ if (isAnthropicFableOrMythosModel(modelId)) return `_Hmm. ${text}_\n`;
34
31
  if (dialect === "harmony" || dialect === "gemma") return `<think>\n${text}\n</think>\n`;
35
32
  return `${getDialectDefinition(dialect).renderThinking(text)}\n`;
36
33
  }
@@ -26,6 +26,10 @@ type AnthropicMessagesValidation = {
26
26
  provider: string;
27
27
  baseUrl: string;
28
28
  model: string;
29
+ /** HTTP error statuses that still prove the key is valid (e.g. a billing/quota
30
+ * gate that only a real, recognized key would reach) and should be treated as
31
+ * successful validation rather than an invalid-key failure. */
32
+ acceptableErrorStatuses?: readonly number[];
29
33
  };
30
34
 
31
35
  type ModelsEndpointValidation = {
@@ -92,6 +96,7 @@ export function createApiKeyLogin(config: ApiKeyLoginConfig): (options: OAuthCon
92
96
  apiKey: trimmed,
93
97
  baseUrl: config.validation.baseUrl,
94
98
  model: config.validation.model,
99
+ acceptableErrorStatuses: config.validation.acceptableErrorStatuses,
95
100
  signal: options.signal,
96
101
  fetch: options.fetch,
97
102
  });
@@ -16,6 +16,10 @@ type AnthropicCompatibleValidationOptions = {
16
16
  model: string;
17
17
  signal?: AbortSignal;
18
18
  fetch?: FetchImpl;
19
+ /** HTTP error statuses that still prove the key is valid (e.g. a billing/quota
20
+ * gate that only a real, recognized key would reach) and should be treated as
21
+ * successful validation rather than an invalid-key failure. */
22
+ acceptableErrorStatuses?: readonly number[];
19
23
  };
20
24
 
21
25
  type ModelListValidationOptions = {
@@ -106,7 +110,7 @@ export async function validateAnthropicCompatibleApiKey(options: AnthropicCompat
106
110
  signal,
107
111
  });
108
112
 
109
- if (response.ok) {
113
+ if (response.ok || options.acceptableErrorStatuses?.includes(response.status)) {
110
114
  return;
111
115
  }
112
116
 
@@ -49,6 +49,7 @@ import { qwenPortalProvider } from "./qwen-portal";
49
49
  import { sakanaProvider } from "./sakana";
50
50
  import { syntheticProvider } from "./synthetic";
51
51
  import { tavilyProvider } from "./tavily";
52
+ import { tencentProvider } from "./tencent";
52
53
  import { togetherProvider } from "./together";
53
54
  import type { ProviderDefinition } from "./types";
54
55
  import { umansProvider } from "./umans";
@@ -139,6 +140,7 @@ const ALL = [
139
140
  mistralProvider,
140
141
  minimaxProvider,
141
142
  amazonBedrockProvider,
143
+ tencentProvider,
142
144
  ];
143
145
 
144
146
  export type RegistryDef = (typeof ALL)[number];
@@ -0,0 +1,33 @@
1
+ import { createApiKeyLogin } from "./api-key-login";
2
+ import type { OAuthLoginCallbacks } from "./oauth/types";
3
+ import type { ProviderDefinition } from "./types";
4
+
5
+ const AUTH_URL = "https://cloud.tencent.com/product/lke";
6
+ const API_BASE_URL = "https://tokenhub-intl.tencentcloudmaas.com";
7
+ const VALIDATION_MODEL = "deepseek-v4-pro";
8
+
9
+ export const loginTencent = createApiKeyLogin({
10
+ providerLabel: "Tencent Cloud MaaS",
11
+ authUrl: AUTH_URL,
12
+ instructions: "Create or copy your Tencent Cloud MaaS API key from the console.",
13
+ promptMessage: "Paste your Tencent Cloud MaaS API key",
14
+ placeholder: "sk-...",
15
+ validation: {
16
+ kind: "anthropic-messages",
17
+ provider: "Tencent Cloud MaaS",
18
+ baseUrl: API_BASE_URL,
19
+ model: VALIDATION_MODEL,
20
+ // Verified live (2026-07): a recognized-but-unbilled key gets HTTP 402
21
+ // ("free trial quota exhausted ... postpaid billing not enabled"), not a
22
+ // 401/403 — the gateway only returns 402 after successfully authenticating
23
+ // the key. Treat it as a valid credential so a real key isn't rejected as
24
+ // "invalid" just because the account has no active billing yet.
25
+ acceptableErrorStatuses: [402],
26
+ },
27
+ });
28
+
29
+ export const tencentProvider = {
30
+ id: "tencent",
31
+ name: "Tencent Cloud MaaS",
32
+ login: (cb: OAuthLoginCallbacks) => loginTencent(cb),
33
+ } as const satisfies ProviderDefinition;