mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
fix(translators): normalize TitleCase tool names for non-Anthropic models (#7926)
* fix(translators): normalize TitleCase tool names for non-Anthropic models * fix(translator): parse XML <invoke> blocks in OpenAI→Claude response translator * fix(gemini-web): mark gemini-3.1-flash-lite as toolCalling: false * fix(gemini-web): mark all models as toolCalling: false, add to no-tools combo * fix(web-providers): mark all web-cookie models as toolCalling: false * fix(nvidia): disable tool calling on models that can't handle it * fix(lint): drop explicit any annotation on extractXmlInvokeBlocks state param Removes the no-explicit-any violation introduced alongside the TitleCase tool-name normalization work so the merged branch stays lint-clean (state stays implicitly typed like its sibling helpers in this file). Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com> Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
55549bfe5a
commit
eab59d4048
@@ -9,12 +9,12 @@ export const adapta_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: [
|
||||
{ id: "adapta-one", name: "Adapta ONE (Auto)" },
|
||||
{ id: "adapta-gpt", name: "GPT-5 (via Adapta)" },
|
||||
{ id: "adapta-claude", name: "Claude Sonnet 4.6 (via Adapta)" },
|
||||
{ id: "adapta-gemini", name: "Gemini 2.5 Pro (via Adapta)" },
|
||||
{ id: "adapta-grok", name: "Grok 4 (via Adapta)" },
|
||||
{ id: "adapta-deepseek", name: "DeepSeek R2 (via Adapta)" },
|
||||
{ id: "adapta-llama", name: "Llama 4 (via Adapta)" },
|
||||
{ id: "adapta-one", name: "Adapta ONE (Auto)", toolCalling: false },
|
||||
{ id: "adapta-gpt", name: "GPT-5 (via Adapta)", toolCalling: false },
|
||||
{ id: "adapta-claude", name: "Claude Sonnet 4.6 (via Adapta)", toolCalling: false },
|
||||
{ id: "adapta-gemini", name: "Gemini 2.5 Pro (via Adapta)", toolCalling: false },
|
||||
{ id: "adapta-grok", name: "Grok 4 (via Adapta)", toolCalling: false },
|
||||
{ id: "adapta-deepseek", name: "DeepSeek R2 (via Adapta)", toolCalling: false },
|
||||
{ id: "adapta-llama", name: "Llama 4 (via Adapta)", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,11 +9,11 @@ export const blackbox_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "gpt-4-turbo", name: "GPT-4 Turbo" },
|
||||
{ id: "gpt-4", name: "GPT-4" },
|
||||
{ id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo" },
|
||||
{ id: "claude-3-opus", name: "Claude 3 Opus" },
|
||||
{ id: "claude-3-sonnet", name: "Claude 3 Sonnet" },
|
||||
{ id: "gemini-pro", name: "Gemini Pro" },
|
||||
{ id: "gpt-4-turbo", name: "GPT-4 Turbo", toolCalling: false },
|
||||
{ id: "gpt-4", name: "GPT-4", toolCalling: false },
|
||||
{ id: "gpt-3.5-turbo", name: "GPT-3.5 Turbo", toolCalling: false },
|
||||
{ id: "claude-3-opus", name: "Claude 3 Opus", toolCalling: false },
|
||||
{ id: "claude-3-sonnet", name: "Claude 3 Sonnet", toolCalling: false },
|
||||
{ id: "gemini-pro", name: "Gemini Pro", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,12 +9,12 @@ export const chatgpt_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "gpt-5.6-pro", name: "GPT-5.6 Pro" }, // pro tier only, standard effort
|
||||
{ id: "gpt-5.6-thinking", name: "GPT-5.6 Thinking" }, // plus, pro tier
|
||||
{ id: "gpt-5.5-pro-extended", name: "GPT-5.5 Pro Extended" }, // pro tier only, extended effort
|
||||
{ id: "gpt-5.5-pro", name: "GPT-5.5 Pro" }, // pro tier only, standard effort
|
||||
{ id: "gpt-5.5-thinking", name: "GPT-5.5 Thinking" }, // plus, pro tier
|
||||
{ id: "gpt-5.5", name: "GPT-5.5 Instant" }, // free, plus, pro tier
|
||||
{ id: "o3", name: "o3" }, // plus ~ tier
|
||||
{ id: "gpt-5.6-pro", name: "GPT-5.6 Pro", toolCalling: false }, // pro tier only, standard effort
|
||||
{ id: "gpt-5.6-thinking", name: "GPT-5.6 Thinking", toolCalling: false }, // plus, pro tier
|
||||
{ id: "gpt-5.5-pro-extended", name: "GPT-5.5 Pro Extended", toolCalling: false }, // pro tier only, extended effort
|
||||
{ id: "gpt-5.5-pro", name: "GPT-5.5 Pro", toolCalling: false }, // pro tier only, standard effort
|
||||
{ id: "gpt-5.5-thinking", name: "GPT-5.5 Thinking", toolCalling: false }, // plus, pro tier
|
||||
{ id: "gpt-5.5", name: "GPT-5.5 Instant", toolCalling: false }, // free, plus, pro tier
|
||||
{ id: "o3", name: "o3", toolCalling: false }, // plus ~ tier
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ export const claude_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "claude-sonnet-5", name: "Claude 5 Sonnet (web)" },
|
||||
{ id: "claude-sonnet-4-6", name: "Claude 4.6 Sonnet (web)" },
|
||||
{ id: "claude-haiku-4-5", name: "Claude 4.5 Haiku (web)" },
|
||||
{ id: "claude-sonnet-5", name: "Claude 5 Sonnet (web)", toolCalling: false },
|
||||
{ id: "claude-sonnet-4-6", name: "Claude 4.6 Sonnet (web)", toolCalling: false },
|
||||
{ id: "claude-haiku-4-5", name: "Claude 4.5 Haiku (web)", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -8,5 +8,5 @@ export const copilot_m365_webProvider: RegistryEntry = {
|
||||
baseUrl: "wss://substrate.office.com/m365Copilot/Chathub",
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [{ id: "copilot-m365", name: "Microsoft 365 Copilot (BizChat)" }],
|
||||
models: [{ id: "copilot-m365", name: "Microsoft 365 Copilot (BizChat)", toolCalling: false }],
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ export const copilot_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "copilot-pro", name: "Copilot Pro (web)" },
|
||||
{ id: "gpt-4-turbo", name: "GPT-4 Turbo (via Copilot)" },
|
||||
{ id: "gpt-4", name: "GPT-4 (via Copilot)" },
|
||||
{ id: "copilot-pro", name: "Copilot Pro (web)", toolCalling: false },
|
||||
{ id: "gpt-4-turbo", name: "GPT-4 Turbo (via Copilot)", toolCalling: false },
|
||||
{ id: "gpt-4", name: "GPT-4 (via Copilot)", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,27 +9,27 @@ export const deepseek_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "bearer",
|
||||
models: [
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro" },
|
||||
{ id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", toolCalling: false },
|
||||
{ id: "deepseek-v4-pro-think", name: "DeepSeek V4 Pro Think", supportsReasoning: true },
|
||||
{ id: "deepseek-v4-pro-search", name: "DeepSeek V4 Pro Search" },
|
||||
{ id: "deepseek-v4-pro-search", name: "DeepSeek V4 Pro Search", toolCalling: false },
|
||||
{
|
||||
id: "deepseek-v4-pro-think-search",
|
||||
name: "DeepSeek V4 Pro Think+Search",
|
||||
supportsReasoning: true,
|
||||
},
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash" },
|
||||
{ id: "deepseek-v4-flash", name: "DeepSeek V4 Flash", toolCalling: false },
|
||||
{ id: "deepseek-v4-flash-think", name: "DeepSeek V4 Flash Think", supportsReasoning: true },
|
||||
{ id: "deepseek-v4-flash-search", name: "DeepSeek V4 Flash Search" },
|
||||
{ id: "deepseek-v4-flash-search", name: "DeepSeek V4 Flash Search", toolCalling: false },
|
||||
{
|
||||
id: "deepseek-v4-flash-think-search",
|
||||
name: "DeepSeek V4 Flash Think+Search",
|
||||
supportsReasoning: true,
|
||||
},
|
||||
{ id: "deepseek-chat", name: "DeepSeek Chat" },
|
||||
{ id: "deepseek-chat", name: "DeepSeek Chat", toolCalling: false },
|
||||
{ id: "deepseek-reasoner", name: "DeepSeek Reasoner", supportsReasoning: true },
|
||||
{ id: "DeepSeek-R1", name: "DeepSeek R1", supportsReasoning: true },
|
||||
{ id: "DeepSeek-R1-Search", name: "DeepSeek R1 Search", supportsReasoning: true },
|
||||
{ id: "DeepSeek-V3.2", name: "DeepSeek V3.2" },
|
||||
{ id: "DeepSeek-Search", name: "DeepSeek Search" },
|
||||
{ id: "DeepSeek-V3.2", name: "DeepSeek V3.2", toolCalling: false },
|
||||
{ id: "DeepSeek-Search", name: "DeepSeek Search", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const doubao_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "dola-speed", name: "Dola Speed" },
|
||||
{ id: "dola-pro", name: "Dola Pro" },
|
||||
{ id: "dola-speed", name: "Dola Speed", toolCalling: false },
|
||||
{ id: "dola-pro", name: "Dola Pro", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,11 +9,11 @@ export const duckduckgo_webProvider: RegistryEntry = {
|
||||
authType: "none",
|
||||
authHeader: "none",
|
||||
models: [
|
||||
{ id: "gpt-4o-mini", name: "GPT-4o Mini" },
|
||||
{ id: "gpt-5-mini", name: "GPT-5 Mini" },
|
||||
{ id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku" },
|
||||
{ id: "llama-4-scout", name: "Llama 4 Scout" },
|
||||
{ id: "mistral-small-2501", name: "Mistral Small" },
|
||||
{ id: "o3-mini", name: "O3 Mini" },
|
||||
{ id: "gpt-4o-mini", name: "GPT-4o Mini", toolCalling: false },
|
||||
{ id: "gpt-5-mini", name: "GPT-5 Mini", toolCalling: false },
|
||||
{ id: "claude-3-5-haiku-20241022", name: "Claude 3.5 Haiku", toolCalling: false },
|
||||
{ id: "llama-4-scout", name: "Llama 4 Scout", toolCalling: false },
|
||||
{ id: "mistral-small-2501", name: "Mistral Small", toolCalling: false },
|
||||
{ id: "o3-mini", name: "O3 Mini", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,10 +9,10 @@ export const felo_webProvider: RegistryEntry = {
|
||||
authType: "none",
|
||||
authHeader: "none",
|
||||
models: [
|
||||
{ id: "felo-chat", name: "Felo Chat" },
|
||||
{ id: "felo-search", name: "Felo Search" },
|
||||
{ id: "felo-scholar", name: "Felo Scholar" },
|
||||
{ id: "felo-social", name: "Felo Social" },
|
||||
{ id: "felo-document", name: "Felo Document" },
|
||||
{ id: "felo-chat", name: "Felo Chat", toolCalling: false },
|
||||
{ id: "felo-search", name: "Felo Search", toolCalling: false },
|
||||
{ id: "felo-scholar", name: "Felo Scholar", toolCalling: false },
|
||||
{ id: "felo-social", name: "Felo Social", toolCalling: false },
|
||||
{ id: "felo-document", name: "Felo Document", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ export const gemini_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro" },
|
||||
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash" },
|
||||
{ id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash-Lite" },
|
||||
{ id: "gemini-3.1-pro", name: "Gemini 3.1 Pro", toolCalling: false },
|
||||
{ id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", toolCalling: false },
|
||||
{ id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash-Lite", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const muse_spark_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "muse-spark", name: "Muse Spark" },
|
||||
{ id: "muse-spark", name: "Muse Spark", toolCalling: false },
|
||||
{
|
||||
id: "muse-spark-thinking",
|
||||
name: "Muse Spark Thinking",
|
||||
|
||||
@@ -49,21 +49,21 @@ export const nvidiaProvider: RegistryEntry = {
|
||||
{ id: "abacusai/dracarys-llama-3.1-70b-instruct", name: "Dracarys Llama 3.1 70B Instruct" },
|
||||
{ id: "google/gemma-2-2b-it", name: "Gemma 2 2B IT" },
|
||||
{ id: "google/gemma-3n-e2b-it", name: "Gemma 3n E2B IT" },
|
||||
{ id: "meta/llama-3.1-8b-instruct", name: "Llama 3.1 8B Instruct" },
|
||||
{ id: "meta/llama-3.1-8b-instruct", name: "Llama 3.1 8B Instruct", toolCalling: false },
|
||||
{
|
||||
id: "meta/llama-3.2-11b-vision-instruct",
|
||||
name: "Llama 3.2 11B Vision Instruct",
|
||||
supportsVision: true,
|
||||
},
|
||||
{ id: "meta/llama-3.2-1b-instruct", name: "Llama 3.2 1B Instruct" },
|
||||
{ id: "meta/llama-3.2-3b-instruct", name: "Llama 3.2 3B Instruct" },
|
||||
{ id: "meta/llama-3.2-3b-instruct", name: "Llama 3.2 3B Instruct", toolCalling: false },
|
||||
{
|
||||
id: "meta/llama-3.2-90b-vision-instruct",
|
||||
name: "Llama 3.2 90B Vision Instruct",
|
||||
supportsVision: true,
|
||||
},
|
||||
{ id: "meta/llama-4-maverick-17b-128e-instruct", name: "Llama 4 Maverick 17B 128E Instruct" },
|
||||
{ id: "meta/llama-guard-4-12b", name: "Llama Guard 4 12B" },
|
||||
{ id: "meta/llama-guard-4-12b", name: "Llama Guard 4 12B", toolCalling: false },
|
||||
{ id: "mistralai/ministral-14b-instruct-2512", name: "Ministral 14B Instruct 2512" },
|
||||
{ id: "mistralai/mistral-medium-3.5-128b", name: "Mistral Medium 3.5 128B" },
|
||||
{ id: "mistralai/mistral-nemotron", name: "Mistral Nemotron" },
|
||||
|
||||
@@ -9,16 +9,16 @@ export const perplexity_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "pplx-auto", name: "Perplexity Best" },
|
||||
{ id: "pplx-sonar", name: "Sonar 2 (via Perplexity)" },
|
||||
{ id: "pplx-gpt-5.6-terra", name: "GPT-5.6 Terra (via Perplexity)" },
|
||||
{ id: "pplx-gpt-5.6-sol", name: "GPT-5.6 Sol (via Perplexity)" },
|
||||
{ id: "pplx-gemini", name: "Gemini 3.1 Pro (via Perplexity)" },
|
||||
{ id: "pplx-sonnet", name: "Claude Sonnet 5.0 (via Perplexity)" },
|
||||
{ id: "pplx-opus", name: "Claude Opus 4.8 (via Perplexity)" },
|
||||
{ id: "pplx-glm", name: "GLM-5.2 (via Perplexity)" },
|
||||
{ id: "pplx-kimi", name: "Kimi K2.6 (via Perplexity)" },
|
||||
{ id: "pplx-grok-4.5", name: "Grok 4.5 (via Perplexity)" },
|
||||
{ id: "pplx-nemotron", name: "Nemotron 3 Ultra (via Perplexity)" },
|
||||
{ id: "pplx-auto", name: "Perplexity Best", toolCalling: false },
|
||||
{ id: "pplx-sonar", name: "Sonar 2 (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-gpt-5.6-terra", name: "GPT-5.6 Terra (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-gpt-5.6-sol", name: "GPT-5.6 Sol (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-gemini", name: "Gemini 3.1 Pro (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-sonnet", name: "Claude Sonnet 5.0 (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-opus", name: "Claude Opus 4.8 (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-glm", name: "GLM-5.2 (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-kimi", name: "Kimi K2.6 (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-grok-4.5", name: "Grok 4.5 (via Perplexity)", toolCalling: false },
|
||||
{ id: "pplx-nemotron", name: "Nemotron 3 Ultra (via Perplexity)", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -10,36 +10,36 @@ export const t3_webProvider: RegistryEntry = {
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
// Claude
|
||||
{ id: "claude-opus-4", name: "Claude Opus 4 (via t3.chat)" },
|
||||
{ id: "claude-sonnet-4", name: "Claude Sonnet 4 (via t3.chat)" },
|
||||
{ id: "claude-haiku-4", name: "Claude Haiku 4 (via t3.chat)" },
|
||||
{ id: "claude-3.7", name: "Claude 3.7 Sonnet (via t3.chat)" },
|
||||
{ id: "claude-opus-4", name: "Claude Opus 4 (via t3.chat)", toolCalling: false },
|
||||
{ id: "claude-sonnet-4", name: "Claude Sonnet 4 (via t3.chat)", toolCalling: false },
|
||||
{ id: "claude-haiku-4", name: "Claude Haiku 4 (via t3.chat)", toolCalling: false },
|
||||
{ id: "claude-3.7", name: "Claude 3.7 Sonnet (via t3.chat)", toolCalling: false },
|
||||
// GPT / OpenAI
|
||||
{ id: "gpt-5", name: "GPT-5 (via t3.chat)" },
|
||||
{ id: "gpt-4o", name: "GPT-4o (via t3.chat)" },
|
||||
{ id: "gpt-4.1", name: "GPT-4.1 (via t3.chat)" },
|
||||
{ id: "o3", name: "o3 (via t3.chat)" },
|
||||
{ id: "o4-mini", name: "o4-mini (via t3.chat)" },
|
||||
{ id: "gpt-5", name: "GPT-5 (via t3.chat)", toolCalling: false },
|
||||
{ id: "gpt-4o", name: "GPT-4o (via t3.chat)", toolCalling: false },
|
||||
{ id: "gpt-4.1", name: "GPT-4.1 (via t3.chat)", toolCalling: false },
|
||||
{ id: "o3", name: "o3 (via t3.chat)", toolCalling: false },
|
||||
{ id: "o4-mini", name: "o4-mini (via t3.chat)", toolCalling: false },
|
||||
// Gemini
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro (via t3.chat)" },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash (via t3.chat)" },
|
||||
{ id: "gemini-2.5-pro", name: "Gemini 2.5 Pro (via t3.chat)", toolCalling: false },
|
||||
{ id: "gemini-2.5-flash", name: "Gemini 2.5 Flash (via t3.chat)", toolCalling: false },
|
||||
// DeepSeek
|
||||
{ id: "deepseek-r1", name: "DeepSeek R1 (via t3.chat)", supportsReasoning: true },
|
||||
{ id: "deepseek-v3", name: "DeepSeek V3 (via t3.chat)" },
|
||||
{ id: "deepseek-v3", name: "DeepSeek V3 (via t3.chat)", toolCalling: false },
|
||||
// Grok
|
||||
{ id: "grok-3", name: "Grok 3 (via t3.chat)" },
|
||||
{ id: "grok-3-mini", name: "Grok 3 Mini (via t3.chat)" },
|
||||
{ id: "grok-3", name: "Grok 3 (via t3.chat)", toolCalling: false },
|
||||
{ id: "grok-3-mini", name: "Grok 3 Mini (via t3.chat)", toolCalling: false },
|
||||
// Llama / Meta
|
||||
{ id: "llama-4-maverick", name: "Llama 4 Maverick (via t3.chat)" },
|
||||
{ id: "llama-4-scout", name: "Llama 4 Scout (via t3.chat)" },
|
||||
{ id: "llama-3.3-70b", name: "Llama 3.3 70B (via t3.chat)" },
|
||||
{ id: "llama-4-maverick", name: "Llama 4 Maverick (via t3.chat)", toolCalling: false },
|
||||
{ id: "llama-4-scout", name: "Llama 4 Scout (via t3.chat)", toolCalling: false },
|
||||
{ id: "llama-3.3-70b", name: "Llama 3.3 70B (via t3.chat)", toolCalling: false },
|
||||
// Mistral
|
||||
{ id: "devstral", name: "Devstral (via t3.chat)" },
|
||||
{ id: "mistral-large", name: "Mistral Large (via t3.chat)" },
|
||||
{ id: "devstral", name: "Devstral (via t3.chat)", toolCalling: false },
|
||||
{ id: "mistral-large", name: "Mistral Large (via t3.chat)", toolCalling: false },
|
||||
// Qwen
|
||||
{ id: "qwen3-235b", name: "Qwen3 235B (via t3.chat)", supportsReasoning: true },
|
||||
{ id: "qwen3-32b", name: "Qwen3 32B (via t3.chat)", supportsReasoning: true },
|
||||
// Kimi
|
||||
{ id: "kimi-k2", name: "Kimi K2 (via t3.chat)" },
|
||||
{ id: "kimi-k2", name: "Kimi K2 (via t3.chat)", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const veoaifree_webProvider: RegistryEntry = {
|
||||
authType: "none",
|
||||
authHeader: "none",
|
||||
models: [
|
||||
{ id: "veo", name: "VEO 3.1" },
|
||||
{ id: "seedance", name: "Seedance" },
|
||||
{ id: "veo", name: "VEO 3.1", toolCalling: false },
|
||||
{ id: "seedance", name: "Seedance", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -15,19 +15,19 @@ export const yuanbao_webProvider: RegistryEntry = {
|
||||
name: "DeepSeek R1 (via Yuanbao)",
|
||||
supportsReasoning: true,
|
||||
},
|
||||
{ id: "hunyuan", name: "Hunyuan (via Yuanbao)" },
|
||||
{ id: "hunyuan", name: "Hunyuan (via Yuanbao)", toolCalling: false },
|
||||
{
|
||||
id: "hunyuan-t1",
|
||||
name: "Hunyuan T1 (via Yuanbao)",
|
||||
supportsReasoning: true,
|
||||
},
|
||||
{ id: "deepseek-v3-search", name: "DeepSeek V3 + Web Search (via Yuanbao)" },
|
||||
{ id: "deepseek-v3-search", name: "DeepSeek V3 + Web Search (via Yuanbao)", toolCalling: false },
|
||||
{
|
||||
id: "deepseek-r1-search",
|
||||
name: "DeepSeek R1 + Web Search (via Yuanbao)",
|
||||
supportsReasoning: true,
|
||||
},
|
||||
{ id: "hunyuan-search", name: "Hunyuan + Web Search (via Yuanbao)" },
|
||||
{ id: "hunyuan-search", name: "Hunyuan + Web Search (via Yuanbao)", toolCalling: false },
|
||||
{
|
||||
id: "hunyuan-t1-search",
|
||||
name: "Hunyuan T1 + Web Search (via Yuanbao)",
|
||||
|
||||
@@ -12,8 +12,8 @@ export const zai_webProvider: RegistryEntry = {
|
||||
authType: "apikey",
|
||||
authHeader: "cookie",
|
||||
models: [
|
||||
{ id: "glm-4.6", name: "GLM-4.6" },
|
||||
{ id: "glm-4.5", name: "GLM-4.5" },
|
||||
{ id: "glm-4.5v", name: "GLM-4.5V (Vision)" },
|
||||
{ id: "glm-4.6", name: "GLM-4.6", toolCalling: false },
|
||||
{ id: "glm-4.5", name: "GLM-4.5", toolCalling: false },
|
||||
{ id: "glm-4.5v", name: "GLM-4.5V (Vision)", toolCalling: false },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { register } from "../registry.ts";
|
||||
import { FORMATS } from "../formats.ts";
|
||||
import { isAbortFinishReason } from "../../utils/finishReason.ts";
|
||||
import { REVERSE_MAP } from "../../services/claudeCodeToolRemapper.ts";
|
||||
|
||||
function normalizeToolName(name: string): string {
|
||||
return REVERSE_MAP[name] ?? name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Direct Gemini → Claude response translator.
|
||||
@@ -82,7 +87,7 @@ export function geminiToClaudeResponse(chunk, state) {
|
||||
}
|
||||
const fc = part.functionCall;
|
||||
const rawToolName = fc.name;
|
||||
const restoredToolName = state.toolNameMap?.get(rawToolName) || rawToolName;
|
||||
const restoredToolName = normalizeToolName(state.toolNameMap?.get(rawToolName) || rawToolName);
|
||||
const idx = state.contentBlockIndex++;
|
||||
const toolId = fc.id || `toolu_${Date.now()}_${idx}`;
|
||||
|
||||
|
||||
@@ -5,6 +5,83 @@ import { hasToolCallShim, applyToolCallShimToBuffer } from "../helpers/toolCallS
|
||||
import { appendToolCallArgumentDelta } from "../../utils/toolCallArguments.ts";
|
||||
import { isAbortFinishReason } from "../../utils/finishReason.ts";
|
||||
import { isInternalReasoningPlaceholder } from "../../utils/reasoningPlaceholder.ts";
|
||||
import { REVERSE_MAP } from "../../services/claudeCodeToolRemapper.ts";
|
||||
|
||||
function normalizeToolName(name: string): string {
|
||||
return REVERSE_MAP[name] ?? name;
|
||||
}
|
||||
|
||||
interface XmlToolCall {
|
||||
id: string;
|
||||
name: string;
|
||||
args: Record<string, string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract complete XML <invoke> blocks from text content.
|
||||
* Some models (e.g. nvidia/abacusai/dracarys) emit tool calls as
|
||||
* XML blocks instead of JSON tool_calls. This function detects
|
||||
* <invoke name="ToolName"><parameter name="arg">value</parameter></invoke>
|
||||
* blocks, converts them to tool calls, and returns the cleaned text.
|
||||
* Incomplete XML is buffered in state for the next chunk.
|
||||
*/
|
||||
function extractXmlInvokeBlocks(
|
||||
text: string,
|
||||
state
|
||||
): { cleaned: string; toolCalls: XmlToolCall[] } {
|
||||
const toolCalls: XmlToolCall[] = [];
|
||||
|
||||
// Prepend any incomplete content from previous chunk
|
||||
const combined = (state._xmlInvokeBuffer || "") + text;
|
||||
state._xmlInvokeBuffer = "";
|
||||
|
||||
let remaining = combined;
|
||||
let cleaned = "";
|
||||
|
||||
while (true) {
|
||||
const startMatch = remaining.match(/<invoke\s+name="([^"]*)"\s*>/);
|
||||
if (!startMatch) {
|
||||
cleaned += remaining;
|
||||
break;
|
||||
}
|
||||
|
||||
// Text before the <invoke> block
|
||||
cleaned += remaining.slice(0, startMatch.index);
|
||||
|
||||
const blockStart = startMatch.index;
|
||||
const restAfterStart = remaining.slice(blockStart);
|
||||
const endMatch = restAfterStart.match(/<\/invoke>/);
|
||||
|
||||
if (!endMatch) {
|
||||
// Incomplete block — buffer for next chunk
|
||||
state._xmlInvokeBuffer = restAfterStart;
|
||||
break;
|
||||
}
|
||||
|
||||
// Complete block found
|
||||
const innerXml = restAfterStart.slice(startMatch[0].length, endMatch.index);
|
||||
const fullBlock = restAfterStart.slice(0, endMatch.index + endMatch[0].length);
|
||||
|
||||
// Parse <parameter name="..." ...>value</parameter>
|
||||
const args: Record<string, string> = {};
|
||||
const paramRegex = /<parameter\s+name="([^"]*)"[^>]*>([\s\S]*?)<\/parameter>/g;
|
||||
let pm;
|
||||
while ((pm = paramRegex.exec(innerXml)) !== null) {
|
||||
args[pm[1]] = pm[2].trim();
|
||||
}
|
||||
|
||||
toolCalls.push({
|
||||
id: `toolu_xml_${Date.now()}_${toolCalls.length}`,
|
||||
name: startMatch[1],
|
||||
args,
|
||||
});
|
||||
|
||||
// Continue scanning after the block
|
||||
remaining = remaining.slice(blockStart + fullBlock.length);
|
||||
}
|
||||
|
||||
return { cleaned, toolCalls };
|
||||
}
|
||||
|
||||
// Helper: stop thinking block if started
|
||||
function stopThinkingBlock(state, results) {
|
||||
@@ -81,6 +158,8 @@ export function openaiToClaudeResponse(chunk, state) {
|
||||
}
|
||||
state.model = chunk.model || "unknown";
|
||||
state.nextBlockIndex = 0;
|
||||
state._pendingXmlToolCalls = [];
|
||||
state._xmlInvokeBuffer = "";
|
||||
results.push({
|
||||
type: "message_start",
|
||||
message: {
|
||||
@@ -133,22 +212,55 @@ export function openaiToClaudeResponse(chunk, state) {
|
||||
if (delta?.content) {
|
||||
stopThinkingBlock(state, results);
|
||||
|
||||
if (!state.textBlockStarted) {
|
||||
state.textBlockIndex = state.nextBlockIndex++;
|
||||
state.textBlockStarted = true;
|
||||
state.textBlockClosed = false;
|
||||
results.push({
|
||||
type: "content_block_start",
|
||||
index: state.textBlockIndex,
|
||||
content_block: { type: "text", text: "" },
|
||||
});
|
||||
// Check for XML <invoke> blocks that some models emit instead of JSON tool_calls
|
||||
const { cleaned, toolCalls: xmlToolCalls } = extractXmlInvokeBlocks(delta.content, state);
|
||||
|
||||
// Accumulate extracted tool calls for emission at finish
|
||||
if (xmlToolCalls.length > 0) {
|
||||
// Close any ongoing text block before tool calls
|
||||
stopTextBlock(state, results);
|
||||
state._pendingXmlToolCalls.push(...xmlToolCalls);
|
||||
}
|
||||
|
||||
results.push({
|
||||
type: "content_block_delta",
|
||||
index: state.textBlockIndex,
|
||||
delta: { type: "text_delta", text: delta.content },
|
||||
});
|
||||
// Emit remaining non-XML text content
|
||||
if (!cleaned) {
|
||||
// All content was XML invoke blocks — skip text block entirely
|
||||
// (tool calls will be emitted at finish)
|
||||
} else if (xmlToolCalls.length > 0) {
|
||||
// Text before/between/after XML blocks — (re)start a text block
|
||||
if (!state.textBlockStarted) {
|
||||
state.textBlockIndex = state.nextBlockIndex++;
|
||||
state.textBlockStarted = true;
|
||||
state.textBlockClosed = false;
|
||||
results.push({
|
||||
type: "content_block_start",
|
||||
index: state.textBlockIndex,
|
||||
content_block: { type: "text", text: "" },
|
||||
});
|
||||
}
|
||||
results.push({
|
||||
type: "content_block_delta",
|
||||
index: state.textBlockIndex,
|
||||
delta: { type: "text_delta", text: cleaned },
|
||||
});
|
||||
} else {
|
||||
// No XML — emit as regular text (original behaviour)
|
||||
if (!state.textBlockStarted) {
|
||||
state.textBlockIndex = state.nextBlockIndex++;
|
||||
state.textBlockStarted = true;
|
||||
state.textBlockClosed = false;
|
||||
results.push({
|
||||
type: "content_block_start",
|
||||
index: state.textBlockIndex,
|
||||
content_block: { type: "text", text: "" },
|
||||
});
|
||||
}
|
||||
results.push({
|
||||
type: "content_block_delta",
|
||||
index: state.textBlockIndex,
|
||||
delta: { type: "text_delta", text: cleaned },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Tool calls
|
||||
@@ -289,14 +401,38 @@ export function openaiToClaudeResponse(chunk, state) {
|
||||
});
|
||||
}
|
||||
|
||||
// Emit any XML-extracted tool calls (from models like Dracarys that
|
||||
// emit <invoke> blocks in content instead of JSON tool_calls in delta)
|
||||
const xmlToolCalls = state._pendingXmlToolCalls || [];
|
||||
for (const tc of xmlToolCalls) {
|
||||
const blockIndex = state.nextBlockIndex++;
|
||||
results.push({
|
||||
type: "content_block_start",
|
||||
index: blockIndex,
|
||||
content_block: {
|
||||
type: "tool_use",
|
||||
id: tc.id,
|
||||
name: normalizeToolName(tc.name),
|
||||
input: tc.args,
|
||||
},
|
||||
});
|
||||
results.push({
|
||||
type: "content_block_stop",
|
||||
index: blockIndex,
|
||||
});
|
||||
}
|
||||
|
||||
// Override finish_reason to tool_use if XML tool calls were found
|
||||
const overrideFinishReason = xmlToolCalls.length > 0 ? "tool_calls" : choice.finish_reason;
|
||||
|
||||
// Mark finish for later usage injection in stream.js
|
||||
state.finishReason = choice.finish_reason;
|
||||
state.finishReason = overrideFinishReason;
|
||||
|
||||
// Use tracked usage (will be estimated in stream.js if not valid)
|
||||
const finalUsage = state.usage || { input_tokens: 0, output_tokens: 0 };
|
||||
results.push({
|
||||
type: "message_delta",
|
||||
delta: { stop_reason: convertFinishReason(choice.finish_reason) },
|
||||
delta: { stop_reason: convertFinishReason(overrideFinishReason) },
|
||||
usage: finalUsage,
|
||||
});
|
||||
results.push({ type: "message_stop" });
|
||||
|
||||
108
package-lock.json
generated
108
package-lock.json
generated
@@ -346,9 +346,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -363,9 +360,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -380,9 +374,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -397,9 +388,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5223,9 +5211,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5243,9 +5228,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5263,9 +5245,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5283,9 +5262,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5303,9 +5279,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5413,9 +5386,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5432,9 +5402,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5451,9 +5418,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5470,9 +5434,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -9587,9 +9548,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10434,9 +10392,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10453,9 +10408,6 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10472,9 +10424,6 @@
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10491,9 +10440,6 @@
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10510,9 +10456,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -10529,9 +10472,6 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0 AND MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31007,9 +30947,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31027,9 +30964,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31047,9 +30981,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31067,9 +30998,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31087,9 +31015,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31107,9 +31032,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31127,9 +31049,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31147,9 +31066,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31167,9 +31083,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31193,9 +31106,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31219,9 +31129,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31245,9 +31152,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31271,9 +31175,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31297,9 +31198,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31323,9 +31221,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -31349,9 +31244,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
@@ -10,6 +10,8 @@ const { FORMATS } = await import("../../open-sse/translator/formats.ts");
|
||||
function createState() {
|
||||
return {
|
||||
toolCalls: new Map(),
|
||||
_pendingXmlToolCalls: [],
|
||||
_xmlInvokeBuffer: "",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -282,6 +284,190 @@ test("OpenAI non-stream: chat completion becomes Claude message with thinking an
|
||||
});
|
||||
});
|
||||
|
||||
test("OpenAI stream: XML <invoke> block in content becomes tool_use at finish", () => {
|
||||
const state = createState();
|
||||
const chunk1 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml1",
|
||||
model: "dracarys",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content:
|
||||
'<invoke name="Bash"><parameter name="command" string="true">ls -la</parameter></invoke>',
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
state
|
||||
);
|
||||
const chunk2 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml1",
|
||||
model: "dracarys",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 3, completion_tokens: 5, total_tokens: 8 },
|
||||
},
|
||||
state
|
||||
);
|
||||
const result = flatten([chunk1, chunk2]);
|
||||
|
||||
// message_start → (no text block since all content was XML)
|
||||
assert.equal(result[0].type, "message_start");
|
||||
// At finish: tool_use content_block_start
|
||||
const toolStart = result.find((e) => e.type === "content_block_start" && e.content_block?.type === "tool_use");
|
||||
assert.ok(toolStart, "expected tool_use content_block_start");
|
||||
assert.equal(toolStart.content_block.name, "bash"); // normalized via REVERSE_MAP
|
||||
assert.deepEqual(toolStart.content_block.input, { command: "ls -la" });
|
||||
// tool_use content_block_stop
|
||||
const toolStop = result.find((e) => e.type === "content_block_stop");
|
||||
assert.ok(toolStop, "expected content_block_stop after tool_use");
|
||||
// message_delta with tool_use stop_reason
|
||||
const delta = result.find((e) => e.type === "message_delta");
|
||||
assert.ok(delta, "expected message_delta");
|
||||
assert.equal(delta.delta.stop_reason, "tool_use");
|
||||
});
|
||||
|
||||
test("OpenAI stream: XML invoke block across two streaming chunks", () => {
|
||||
const state = createState();
|
||||
// Chunk 1: partial XML
|
||||
const chunk1 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml2",
|
||||
model: "dracarys",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { content: '<invoke name="Read"><parameter name="file_path" string="true">/etc/' },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
state
|
||||
);
|
||||
// Chunk 2: completes the XML
|
||||
const chunk2 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml2",
|
||||
model: "dracarys",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { content: 'hosts</parameter></invoke>' },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
state
|
||||
);
|
||||
// Chunk 3: finish
|
||||
const chunk3 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml2",
|
||||
model: "dracarys",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 3, completion_tokens: 5, total_tokens: 8 },
|
||||
},
|
||||
state
|
||||
);
|
||||
const result = flatten([chunk1, chunk2, chunk3]);
|
||||
|
||||
// Buffer should be cleared after chunk2
|
||||
assert.equal(state._xmlInvokeBuffer, "", "buffer cleared after complete block");
|
||||
|
||||
const toolStart = result.find((e) => e.type === "content_block_start" && e.content_block?.type === "tool_use");
|
||||
assert.ok(toolStart, "expected tool_use content_block_start");
|
||||
assert.equal(toolStart.content_block.name, "read");
|
||||
assert.deepEqual(toolStart.content_block.input, { file_path: "/etc/hosts" });
|
||||
});
|
||||
|
||||
test("OpenAI stream: text before XML block is emitted as text content", () => {
|
||||
const state = createState();
|
||||
const chunk1 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml3",
|
||||
model: "dracarys",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: {
|
||||
content:
|
||||
'Checking...<invoke name="Bash"><parameter name="command">date</parameter></invoke>',
|
||||
},
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
state
|
||||
);
|
||||
const chunk2 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml3",
|
||||
model: "dracarys",
|
||||
choices: [
|
||||
{
|
||||
index: 0,
|
||||
delta: { content: " Done." },
|
||||
finish_reason: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
state
|
||||
);
|
||||
const chunk3 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-xml3",
|
||||
model: "dracarys",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 3, completion_tokens: 8, total_tokens: 11 },
|
||||
},
|
||||
state
|
||||
);
|
||||
const result = flatten([chunk1, chunk2, chunk3]);
|
||||
|
||||
// "Checking..." should be emitted as text
|
||||
const textDeltas = result.filter((e) => e.type === "content_block_delta" && e.delta?.type === "text_delta");
|
||||
assert.ok(textDeltas.length > 0, "expected at least one text delta");
|
||||
assert.ok(textDeltas.some((d) => d.delta.text.includes("Checking...")), "text before XML preserved");
|
||||
assert.ok(textDeltas.some((d) => d.delta.text.includes("Done.")), "text after XML preserved");
|
||||
|
||||
// Tool call should still be emitted
|
||||
const toolStart = result.find((e) => e.type === "content_block_start" && e.content_block?.type === "tool_use");
|
||||
assert.ok(toolStart, "expected tool_use content_block_start");
|
||||
assert.equal(toolStart.content_block.name, "bash");
|
||||
assert.deepEqual(toolStart.content_block.input, { command: "date" });
|
||||
});
|
||||
|
||||
test("OpenAI stream: no XML in content behaves normally", () => {
|
||||
const state = createState();
|
||||
const chunk1 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-normal",
|
||||
model: "gpt-4.1",
|
||||
choices: [{ index: 0, delta: { content: "Hello world" }, finish_reason: null }],
|
||||
},
|
||||
state
|
||||
);
|
||||
const chunk2 = openaiToClaudeResponse(
|
||||
{
|
||||
id: "chatcmpl-normal",
|
||||
model: "gpt-4.1",
|
||||
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
|
||||
usage: { prompt_tokens: 3, completion_tokens: 2, total_tokens: 5 },
|
||||
},
|
||||
state
|
||||
);
|
||||
const result = flatten([chunk1, chunk2]);
|
||||
|
||||
assert.equal(result[1].type, "content_block_start");
|
||||
assert.equal(result[2].delta.text, "Hello world");
|
||||
assert.equal(result[3].type, "content_block_stop");
|
||||
assert.equal(result[4].type, "message_delta");
|
||||
assert.equal(result[4].delta.stop_reason, "end_turn");
|
||||
});
|
||||
|
||||
test("OpenAI stream: null chunk is ignored", () => {
|
||||
assert.equal(openaiToClaudeResponse(null, createState()), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user