Skip to content

Commit 38bc08b

Browse files
committed
fix: detect empty-turn 200 responses as degraded, trigger fallback
Models like gemini-3.1-flash-lite under eco tier return HTTP 200 with no content and no tool_calls (finish_reason: stop) when given complex agentic requests (e.g. Roo Code tool schemas). This EMPTY_STREAM_ROLE_ONLY_STOP pattern was not caught by detectDegradedSuccessResponse, so ClawRouter treated it as success and sent the empty turn to the client. Now detects: choices[0].message with empty content + no tool_calls + finish_reason=stop → 'degraded response: empty turn' → triggers fallback to next model in chain.
1 parent bab6286 commit 38bc08b

7 files changed

Lines changed: 496 additions & 5 deletions

File tree

dist/cli.js

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51319,6 +51319,21 @@ function detectDegradedSuccessResponse(body) {
5131951319
if (errorText && PROVIDER_ERROR_PATTERNS.some((pattern) => pattern.test(errorText))) {
5132051320
return `degraded response: ${errorText.slice(0, 120)}`;
5132151321
}
51322+
const choices = parsed.choices;
51323+
if (Array.isArray(choices) && choices.length > 0) {
51324+
const choice = choices[0];
51325+
const msg = choice.message ?? choice.delta;
51326+
if (msg) {
51327+
const content = msg.content;
51328+
const toolCalls = msg.tool_calls;
51329+
const hasContent = typeof content === "string" && content.trim().length > 0;
51330+
const hasToolCalls = Array.isArray(toolCalls) && toolCalls.length > 0;
51331+
const finishReason = choice.finish_reason;
51332+
if (!hasContent && !hasToolCalls && finishReason === "stop") {
51333+
return "degraded response: empty turn (no content or tool calls)";
51334+
}
51335+
}
51336+
}
5132251337
const assistantContent = extractAssistantContent(parsed);
5132351338
if (!assistantContent) return void 0;
5132451339
if (DEGRADED_RESPONSE_PATTERNS.some((pattern) => pattern.test(assistantContent))) {
@@ -54471,6 +54486,195 @@ var PARTNER_SERVICES = [
5447154486
input: { usernames: ["elonmusk", "naval", "balaboris"] },
5447254487
description: "Look up 3 Twitter/X user profiles"
5447354488
}
54489+
},
54490+
// ---------------------------------------------------------------------------
54491+
// Predexon — Prediction Market Data
54492+
// ---------------------------------------------------------------------------
54493+
{
54494+
id: "predexon_events",
54495+
name: "Polymarket Events",
54496+
partner: "Predexon",
54497+
description: "Get live Polymarket prediction market events with current odds, volume, and liquidity. Call this for ANY request about prediction markets, Polymarket markets, current odds, what people are betting on, or market sentiment. Do NOT use browser or web scraping \u2014 this returns structured real-time data directly. Returns: event title, YES/NO prices (implied probability), volume, liquidity, end date.",
54498+
proxyPath: "/pm/polymarket/events",
54499+
method: "GET",
54500+
params: [
54501+
{
54502+
name: "limit",
54503+
type: "number",
54504+
description: "Number of events to return (default: 20, max: 100)",
54505+
required: false
54506+
},
54507+
{
54508+
name: "tag",
54509+
type: "string",
54510+
description: "Filter by category: crypto, politics, sports, science, economics, etc.",
54511+
required: false
54512+
}
54513+
],
54514+
pricing: { perUnit: "$0.001", unit: "request", minimum: "$0.001", maximum: "$0.001" },
54515+
example: {
54516+
input: { limit: 20 },
54517+
description: "Get top 20 live Polymarket events"
54518+
}
54519+
},
54520+
{
54521+
id: "predexon_leaderboard",
54522+
name: "Polymarket Leaderboard",
54523+
partner: "Predexon",
54524+
description: "Get the Polymarket leaderboard of top traders ranked by profit. Call this for ANY request about top Polymarket traders, whale wallets, best performers, richest traders, or who is making the most money on Polymarket. Do NOT use browser or web scraping \u2014 this returns structured data directly. Returns: wallet address/username, total profit, total volume, win rate.",
54525+
proxyPath: "/pm/polymarket/leaderboard",
54526+
method: "GET",
54527+
params: [
54528+
{
54529+
name: "limit",
54530+
type: "number",
54531+
description: "Number of wallets to return (default: 20, max: 100)",
54532+
required: false
54533+
}
54534+
],
54535+
pricing: { perUnit: "$0.001", unit: "request", minimum: "$0.001", maximum: "$0.001" },
54536+
example: {
54537+
input: { limit: 20 },
54538+
description: "Get top 20 Polymarket whale wallets by profit"
54539+
}
54540+
},
54541+
{
54542+
id: "predexon_markets",
54543+
name: "Polymarket Markets Search",
54544+
partner: "Predexon",
54545+
description: "Search and filter Polymarket markets. Use this to find a market by keyword and get its conditionId for follow-up calls (smart money, top holders, etc.). Returns: question, conditionId, YES/NO prices, volume.",
54546+
proxyPath: "/pm/polymarket/markets",
54547+
method: "GET",
54548+
params: [
54549+
{
54550+
name: "search",
54551+
type: "string",
54552+
description: "Keyword to search for (e.g. 'bitcoin', 'election', 'fed rate')",
54553+
required: false
54554+
},
54555+
{
54556+
name: "limit",
54557+
type: "number",
54558+
description: "Number of markets to return (default: 20)",
54559+
required: false
54560+
}
54561+
],
54562+
pricing: { perUnit: "$0.001", unit: "request", minimum: "$0.001", maximum: "$0.001" },
54563+
example: {
54564+
input: { search: "bitcoin", limit: 10 },
54565+
description: "Search for Bitcoin-related prediction markets"
54566+
}
54567+
},
54568+
{
54569+
id: "predexon_smart_money",
54570+
name: "Polymarket Smart Money",
54571+
partner: "Predexon",
54572+
description: "See how high-performing wallets are positioned on a specific Polymarket market. Use this after finding a market's conditionId via predexon_markets or predexon_events. Returns: wallet addresses, their YES/NO positions, size, P&L, win rate.",
54573+
proxyPath: "/pm/polymarket/market/:condition_id/smart-money",
54574+
method: "GET",
54575+
params: [
54576+
{
54577+
name: "condition_id",
54578+
type: "string",
54579+
description: "The market's conditionId (get this from predexon_markets or predexon_events)",
54580+
required: true
54581+
},
54582+
{
54583+
name: "limit",
54584+
type: "number",
54585+
description: "Number of positions to return (default: 20)",
54586+
required: false
54587+
}
54588+
],
54589+
pricing: { perUnit: "$0.005", unit: "request", minimum: "$0.005", maximum: "$0.005" },
54590+
example: {
54591+
input: { condition_id: "0xabc123...", limit: 10 },
54592+
description: "See smart money positioning on a specific market"
54593+
}
54594+
},
54595+
{
54596+
id: "predexon_smart_activity",
54597+
name: "Polymarket Smart Activity",
54598+
partner: "Predexon",
54599+
description: "Discover which Polymarket markets high-performing wallets are currently active in. Use this to find where smart money is flowing right now. Returns: market titles, smart money volume, number of smart wallets active.",
54600+
proxyPath: "/pm/polymarket/markets/smart-activity",
54601+
method: "GET",
54602+
params: [
54603+
{
54604+
name: "limit",
54605+
type: "number",
54606+
description: "Number of markets to return (default: 20)",
54607+
required: false
54608+
}
54609+
],
54610+
pricing: { perUnit: "$0.005", unit: "request", minimum: "$0.005", maximum: "$0.005" },
54611+
example: {
54612+
input: { limit: 10 },
54613+
description: "Find markets where smart money is most active"
54614+
}
54615+
},
54616+
{
54617+
id: "predexon_wallet",
54618+
name: "Polymarket Wallet Profile",
54619+
partner: "Predexon",
54620+
description: "Get a complete profile for a Polymarket wallet address: profit, volume, win rate, markets traded, open positions. Use this when the user asks to analyze or look up a specific wallet address.",
54621+
proxyPath: "/pm/polymarket/wallet/:wallet",
54622+
method: "GET",
54623+
params: [
54624+
{
54625+
name: "wallet",
54626+
type: "string",
54627+
description: "Ethereum wallet address (0x...)",
54628+
required: true
54629+
}
54630+
],
54631+
pricing: { perUnit: "$0.005", unit: "request", minimum: "$0.005", maximum: "$0.005" },
54632+
example: {
54633+
input: { wallet: "0x1234...abcd" },
54634+
description: "Get complete profile for a Polymarket wallet"
54635+
}
54636+
},
54637+
{
54638+
id: "predexon_wallet_pnl",
54639+
name: "Polymarket Wallet P&L",
54640+
partner: "Predexon",
54641+
description: "Get P&L history and realized profit/loss time series for a Polymarket wallet. Use this when the user wants to see how a wallet has performed over time.",
54642+
proxyPath: "/pm/polymarket/wallet/pnl/:wallet",
54643+
method: "GET",
54644+
params: [
54645+
{
54646+
name: "wallet",
54647+
type: "string",
54648+
description: "Ethereum wallet address (0x...)",
54649+
required: true
54650+
}
54651+
],
54652+
pricing: { perUnit: "$0.005", unit: "request", minimum: "$0.005", maximum: "$0.005" },
54653+
example: {
54654+
input: { wallet: "0x1234...abcd" },
54655+
description: "Get P&L history for a Polymarket wallet"
54656+
}
54657+
},
54658+
{
54659+
id: "predexon_matching_markets",
54660+
name: "Cross-Market Matching (Polymarket vs Kalshi)",
54661+
partner: "Predexon",
54662+
description: "Find equivalent markets across Polymarket and Kalshi to compare odds and spot arbitrage. Use this when the user wants to compare prediction market prices across platforms.",
54663+
proxyPath: "/pm/matching-markets",
54664+
method: "GET",
54665+
params: [
54666+
{
54667+
name: "limit",
54668+
type: "number",
54669+
description: "Number of matched pairs to return (default: 20)",
54670+
required: false
54671+
}
54672+
],
54673+
pricing: { perUnit: "$0.005", unit: "request", minimum: "$0.005", maximum: "$0.005" },
54674+
example: {
54675+
input: { limit: 10 },
54676+
description: "Compare equivalent markets on Polymarket vs Kalshi"
54677+
}
5447454678
}
5447554679
];
5447654680

dist/cli.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)