diff --git a/README-JA.md b/README-JA.md
index 25353f4..227c362 100644
--- a/README-JA.md
+++ b/README-JA.md
@@ -4,7 +4,7 @@
# AIClient-2-API 🚀
-**複数のクライアント専用大規模言語モデルAPI(Gemini CLI、Antigravity、Qwen Code、Kiro ...)を模擬リクエストし、ローカルのOpenAI互換インターフェースに統一的にラッピングする強力なプロキシ。**
+**複数のクライアント専用大規模言語モデルAPI(Gemini CLI、Antigravity、Codex, Grok、Kiro ...)を模擬リクエストし、ローカルのOpenAI互換インターフェースに統一的にラッピングする強力なプロキシ。**
@@ -62,7 +62,7 @@
## 🚀 概要
-`AIClient2API` はクライアント制限を突破するAPIプロキシサービスで、Gemini、Antigravity、Qwen Code、Kiroなど、元々クライアント内でのみ使用可能な無料大規模モデルを、あらゆるアプリケーションから呼び出せる標準OpenAI互換インターフェースに変換します。Node.jsをベースに構築され、OpenAI、Claude、Geminiの3大プロトコル間のインテリジェント変換をサポートし、Cherry-Studio、NextChat、Clineなどのツールで、Claude Opus 4.5、Gemini 3.0 Pro、Qwen3 Coder Plusなどの高度なモデルを大規模に無料で使用できるようにします。プロジェクトはストラテジーパターンとアダプターパターンに基づくモジュラーアーキテクチャを採用し、アカウントプール管理、インテリジェントポーリング、自動フェイルオーバー、ヘルスチェック機構を内蔵し、99.9%のサービス可用性を保証します。
+`AIClient2API` はクライアント制限を突破するAPIプロキシサービスで、Gemini、Antigravity、Codex, Grok、Kiroなど、元々クライアント内でのみ使用可能な無料大規模モデルを、あらゆるアプリケーションから呼び出せる標準OpenAI互換インターフェースに変換します。Node.jsをベースに構築され、OpenAI、Claude、Geminiの3大プロトコル間のインテリジェント変換をサポートし、Cherry-Studio、NextChat、Clineなどのツールで、Claude Opus 4.5、Gemini 3.0 Pro、Qwen3 Coder Plusなどの高度なモデルを大規模に無料で使用できるようにします。プロジェクトはストラテジーパターンとアダプターパターンに基づくモジュラーアーキテクチャを採用し、アカウントプール管理、インテリジェントポーリング、自動フェイルオーバー、ヘルスチェック機構を内蔵し、99.9%のサービス可用性を保証します。
> [!NOTE]
> **🎉 重要なマイルストーン**
@@ -100,7 +100,7 @@
## 💡 コアアドバンテージ
### 🎯 統一アクセス、ワンストップ管理
-* **マルチモデル統一インターフェース**:標準OpenAI互換プロトコルを通じて、一度の設定でGemini、Claude、Grok、Qwen Code、Kimi K2、MiniMax M2などの主流大規模モデルにアクセス
+* **マルチモデル統一インターフェース**:標準OpenAI互換プロトコルを通じて、一度の設定でGemini、Claude、Grok、Codex、 K2、MiniMax M2などの主流大規模モデルにアクセス
* **柔軟な切り替えメカニズム**:Pathルーティング、起動パラメータ、環境変数の3つの方法で動的にモデルを切り替え、異なるシナリオのニーズに対応
* **ゼロコスト移行**:OpenAI API仕様と完全互換、Cherry-Studio、NextChat、Clineなどのツールを変更なしで使用可能
* **マルチプロトコルインテリジェント変換**:OpenAI、Claude、Geminiの3大プロトコル間のインテリジェント変換をサポートし、クロスプロトコルモデル呼び出しを実現
diff --git a/README-ZH.md b/README-ZH.md
index 56b3d6e..e7e2ddf 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -4,7 +4,7 @@
# AIClient-2-API 🚀
-**一个能将多种仅客户端内使用的大模型 API(Gemini CLI, Antigravity, Qwen Code, Kiro ...),模拟请求,统一封装为本地 OpenAI 兼容接口的强大代理。**
+**一个能将多种仅客户端内使用的大模型 API(Gemini CLI, Antigravity, Codex, Grok, Kiro ...),模拟请求,统一封装为本地 OpenAI 兼容接口的强大代理。**
@@ -61,7 +61,7 @@
## 🚀 项目概览
-`AIClient2API` 是一个突破客户端限制的 API 代理服务,将 Gemini、Antigravity、Qwen Code、Kiro 等原本仅限客户端内使用的免费大模型,转换为可供任何应用调用的标准 OpenAI 兼容接口。基于 Node.js 构建,支持 OpenAI、Claude、Gemini 三大协议的智能互转,让 Cherry-Studio、NextChat、Cline 等工具能够免费大量使用 Claude Opus 4.5、Gemini 3.0 Pro、Qwen3 Coder Plus 等高级模型。项目采用策略模式和适配器模式的模块化架构,内置账号池管理、智能轮询、自动故障转移和健康检查机制,确保 99.9% 的服务可用性。
+`AIClient2API` 是一个突破客户端限制的 API 代理服务,将 Gemini、Antigravity、Codex, Grok、Kiro 等原本仅限客户端内使用的免费大模型,转换为可供任何应用调用的标准 OpenAI 兼容接口。基于 Node.js 构建,支持 OpenAI、Claude、Gemini 三大协议的智能互转,让 Cherry-Studio、NextChat、Cline 等工具能够免费大量使用 Claude Opus 4.5、Gemini 3.0 Pro、Qwen3 Coder Plus 等高级模型。项目采用策略模式和适配器模式的模块化架构,内置账号池管理、智能轮询、自动故障转移和健康检查机制,确保 99.9% 的服务可用性。
> [!NOTE]
> **🎉 重要里程碑**
@@ -98,7 +98,7 @@
## 💡 核心优势
### 🎯 统一接入,一站式管理
-* **多模型统一接口**:通过标准 OpenAI 兼容协议,一次配置即可接入 Gemini、Claude、Grok、Qwen Code、Kimi K2、MiniMax M2 等主流大模型
+* **多模型统一接口**:通过标准 OpenAI 兼容协议,一次配置即可接入 Gemini、Claude、Grok、Codex、Kimi K2、MiniMax M2 等主流大模型
* **灵活切换机制**:Path 路由、支持通过启动参数、环境变量三种方式动态切换模型,满足不同场景需求
* **零成本迁移**:完全兼容 OpenAI API 规范,Cherry-Studio、NextChat、Cline 等工具无需修改即可使用
* **多协议智能转换**:支持 OpenAI、Claude、Gemini 三大协议间的智能转换,实现跨协议模型调用
diff --git a/README.md b/README.md
index 9bc84ed..8c0895e 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
# AIClient-2-API 🚀
-**A powerful proxy that can unify the requests of various client-only large model APIs (Gemini CLI, Antigravity, Qwen Code, Kiro ...), simulate requests, and encapsulate them into a local OpenAI-compatible interface.**
+**A powerful proxy that can unify the requests of various client-only large model APIs (Gemini CLI, Antigravity, Codex, Grok, Kiro ...), simulate requests, and encapsulate them into a local OpenAI-compatible interface.**
@@ -62,7 +62,7 @@
## 🚀 Overview
-`AIClient2API` is an API proxy service that breaks through client limitations, converting free large models originally restricted to client use only (such as Gemini, Antigravity, Qwen Code, Kiro) into standard OpenAI-compatible interfaces that can be called by any application. Built on Node.js, it supports intelligent conversion between OpenAI, Claude, and Gemini protocols, enabling tools like Cherry-Studio, NextChat, and Cline to freely use advanced models such as Claude Opus 4.5, Gemini 3.0 Pro, and Qwen3 Coder Plus at scale. The project adopts a modular architecture based on strategy and adapter patterns, with built-in account pool management, intelligent polling, automatic failover, and health check mechanisms, ensuring 99.9% service availability.
+`AIClient2API` is an API proxy service that breaks through client limitations, converting free large models originally restricted to client use only (such as Gemini, Antigravity, Codex, Grok, Kiro) into standard OpenAI-compatible interfaces that can be called by any application. Built on Node.js, it supports intelligent conversion between OpenAI, Claude, and Gemini protocols, enabling tools like Cherry-Studio, NextChat, and Cline to freely use advanced models such as Claude Opus 4.5, Gemini 3.0 Pro, and Qwen3 Coder Plus at scale. The project adopts a modular architecture based on strategy and adapter patterns, with built-in account pool management, intelligent polling, automatic failover, and health check mechanisms, ensuring 99.9% service availability.
> [!NOTE]
> **🎉 Important Milestone**
@@ -100,7 +100,7 @@
## 💡 Core Advantages
### 🎯 Unified Access, One-Stop Management
-* **Multi-Model Unified Interface**: Through standard OpenAI-compatible protocol, configure once to access mainstream large models including Gemini, Claude, Grok, Qwen Code, Kimi K2, MiniMax M2
+* **Multi-Model Unified Interface**: Through standard OpenAI-compatible protocol, configure once to access mainstream large models including Gemini, Claude, Grok, Codex, Kimi K2, MiniMax M2
* **Flexible Switching Mechanism**: Path routing, support dynamic model switching via startup parameters or environment variables to meet different scenario requirements
* **Zero-Cost Migration**: Fully compatible with OpenAI API specifications, tools like Cherry-Studio, NextChat, Cline can be used without modification
* **Multi-Protocol Intelligent Conversion**: Support intelligent conversion between OpenAI, Claude, and Gemini protocols for cross-protocol model invocation
diff --git a/VERSION b/VERSION
index 371a952..500b629 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.12.2
+2.12.2.1
diff --git a/src/converters/strategies/GrokConverter.js b/src/converters/strategies/GrokConverter.js
index 7fb48f4..38ebfb5 100644
--- a/src/converters/strategies/GrokConverter.js
+++ b/src/converters/strategies/GrokConverter.js
@@ -104,6 +104,7 @@ export class GrokConverter extends BaseConverter {
has_tool_call: false,
rollout_id: "",
in_tool_call: false, // 是否处于 块内
+ content_started: false, // 是否已经开始输出正式内容
requestBaseUrl: "",
uuid: null,
pending_text_buffer: "" // 用于处理流式输出中被截断的 URL
@@ -352,6 +353,17 @@ export class GrokConverter extends BaseConverter {
}
continue;
}
+ if (key === "cardAttachmentsJson" && Array.isArray(item)) {
+ item.forEach(jsonStr => {
+ if (typeof jsonStr !== 'string') return;
+ try {
+ const card = JSON.parse(jsonStr);
+ const url = card.image?.original;
+ if (url) add(url);
+ } catch (e) {}
+ });
+ continue;
+ }
walk(item);
}
}
@@ -484,13 +496,55 @@ export class GrokConverter extends BaseConverter {
content = this._filterToken(content, responseId);
content = this._processGrokAssetsInText(content, state);
- // 收集图片并追加
+ // 处理 cardAttachmentsJson 中的图片,将其映射到卡片 ID
+ const cardMap = new Map();
+ const modelResponse = grokResponse.modelResponse || {};
+
+ // 收集所有的卡片原始数据(可能是 cardAttachmentsJson 中的,或者是单独收集的 cardAttachments 数组)
+ const allCardSources = [];
+ if (Array.isArray(modelResponse.cardAttachmentsJson)) allCardSources.push(...modelResponse.cardAttachmentsJson);
+ if (Array.isArray(grokResponse.cardAttachments)) {
+ grokResponse.cardAttachments.forEach(card => card.jsonData && allCardSources.push(card.jsonData));
+ } else if (grokResponse.cardAttachment?.jsonData) {
+ allCardSources.push(grokResponse.cardAttachment.jsonData);
+ }
+
+ for (const raw of allCardSources) {
+ try {
+ const cardData = JSON.parse(raw);
+ const cardId = cardData.id;
+ const image = cardData.image || {};
+ const original = image.original;
+ const title = image.title || "image";
+ if (cardId && original) {
+ cardMap.set(cardId, { title, original });
+ }
+ } catch (e) {}
+ }
+
+ // 替换正文中的 标签为 Markdown 图片
+ if (content && cardMap.size > 0) {
+ content = content.replace(/]*card_id="([^"]+)"[^>]*>.*?<\/grok:render>/gs, (match, cardId) => {
+ const item = cardMap.get(cardId);
+ if (!item) return "";
+ return this._renderImage(item.original, item.title || "image", state);
+ });
+ }
+
+ // 收集未在正文中渲染的其他图片并追加
const imageUrls = this._collectImages(grokResponse);
if (imageUrls.length > 0) {
- content += "\n";
+ // 已通过卡片 ID 渲染过的 URL 记录
+ const handledUrls = new Set();
+ for (const item of cardMap.values()) handledUrls.add(item.original);
+
+ let appendContent = "";
for (const url of imageUrls) {
- content += this._renderImage(url, "image", state) + "\n";
+ if (!handledUrls.has(url)) {
+ appendContent += this._renderImage(url, "image", state) + "\n";
+ }
}
+ if (appendContent) content += "\n" + appendContent;
}
// 处理视频 (非流式模式)
@@ -585,6 +639,13 @@ export class GrokConverter extends BaseConverter {
// 处理结束标志
if (resp.isDone) {
let finalContent = "";
+
+ // 如果思考块未关闭,在此关闭
+ if (state.think_opened) {
+ finalContent += "\n\n";
+ state.think_opened = false;
+ }
+
// 处理剩余的缓冲区
if (state.pending_text_buffer) {
finalContent += this._processGrokAssetsInText(state.pending_text_buffer, state);
@@ -722,11 +783,26 @@ export class GrokConverter extends BaseConverter {
const token = resp.token;
const filtered = this._filterToken(token, responseId);
const isThinking = !!resp.isThinking;
- const inThink = isThinking || state.image_think_active || state.video_think_active;
+ const hasStepId = !!resp.messageStepId;
+ const inThink = isThinking || hasStepId || state.image_think_active || state.video_think_active;
- if (inThink) {
+ // 正式内容已开始后,丢弃中途插入的 Agent 思考(1-2 句内部注释,无用户价值)
+ if (state.content_started && inThink && !state.image_think_active && !state.video_think_active) {
+ // 跳过不展示
+ } else if (inThink) {
+ if (!state.think_opened) {
+ deltaContent += "\n";
+ state.think_opened = true;
+ }
deltaReasoning += filtered;
+ deltaContent += filtered;
} else {
+ if (state.think_opened) {
+ deltaContent += "\n\n";
+ state.think_opened = false;
+ state.content_started = true;
+ }
+
// 将新 token 加入待处理缓冲区,解决 URL 被截断的问题
state.pending_text_buffer += filtered;
diff --git a/src/providers/grok/grok-core.js b/src/providers/grok/grok-core.js
index ab9f92c..815e07b 100644
--- a/src/providers/grok/grok-core.js
+++ b/src/providers/grok/grok-core.js
@@ -439,7 +439,20 @@ export class GrokApiService {
async generateContent(model, requestBody) {
logger.info(`[Grok] Starting generateContent (unified processing)`);
const stream = this.generateContentStream(model, requestBody);
- const collected = { message: "", responseId: "", postId: "", llmInfo: {}, rolloutId: "", modelResponse: null, cardAttachment: null, streamingImageGenerationResponse: null, streamingVideoGenerationResponse: null, finalVideoUrl: null, finalThumbnailUrl: null };
+ const collected = {
+ message: "",
+ responseId: "",
+ postId: "",
+ llmInfo: {},
+ rolloutId: "",
+ modelResponse: null,
+ cardAttachment: null,
+ cardAttachments: [], // 收集所有的卡片附件
+ streamingImageGenerationResponse: null,
+ streamingVideoGenerationResponse: null,
+ finalVideoUrl: null,
+ finalThumbnailUrl: null
+ };
for await (const chunk of stream) {
const resp = chunk.result?.response;
@@ -450,8 +463,41 @@ export class GrokApiService {
if (resp.rolloutId) collected.rolloutId = resp.rolloutId;
if (resp._requestBaseUrl) collected._requestBaseUrl = resp._requestBaseUrl;
if (resp._uuid) collected._uuid = resp._uuid;
- if (resp.modelResponse) collected.modelResponse = resp.modelResponse;
- if (resp.cardAttachment) collected.cardAttachment = resp.cardAttachment;
+
+ if (resp.modelResponse) {
+ if (!collected.modelResponse) {
+ collected.modelResponse = resp.modelResponse;
+ } else {
+ // 合并 modelResponse 中的数据
+ if (resp.modelResponse.message) collected.modelResponse.message = resp.modelResponse.message;
+ if (Array.isArray(resp.modelResponse.cardAttachmentsJson)) {
+ if (!collected.modelResponse.cardAttachmentsJson) {
+ collected.modelResponse.cardAttachmentsJson = resp.modelResponse.cardAttachmentsJson;
+ } else {
+ const currentIds = new Set(collected.modelResponse.cardAttachmentsJson.map(raw => {
+ try { return JSON.parse(raw).id; } catch (e) { return null; }
+ }).filter(id => id));
+
+ for (const raw of resp.modelResponse.cardAttachmentsJson) {
+ try {
+ const id = JSON.parse(raw).id;
+ if (!id || !currentIds.has(id)) {
+ collected.modelResponse.cardAttachmentsJson.push(raw);
+ if (id) currentIds.add(id);
+ }
+ } catch (e) {
+ collected.modelResponse.cardAttachmentsJson.push(raw);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (resp.cardAttachment) {
+ collected.cardAttachment = resp.cardAttachment;
+ collected.cardAttachments.push(resp.cardAttachment);
+ }
if (resp.streamingImageGenerationResponse) {
collected.streamingImageGenerationResponse = resp.streamingImageGenerationResponse;
}