rohitg00/agentmemory · error · Error

${this.name} returned unexpected response: ${JSON.stringify(

Error message

${this.name} returned unexpected response: ${JSON.stringify(data).slice(0, 200)}

What it means

OpenRouterProvider.call parses data.choices[0].message.content from the response and throws this if the content is missing or empty, including a 200-char JSON dump of the body. Since this.name is usually 'resilient(openrouter(...))' or the model name, the message identifies which provider returned the malformed payload. It catches 2xx responses whose payload doesn't match the Chat Completions shape.

Source

Thrown at src/providers/openrouter.ts:66

        messages: [
          { role: "system", content: systemPrompt },
          { role: "user", content: userPrompt },
        ],
      }),
    });

    if (!response.ok) {
      const text = await response.text();
      throw new Error(`${this.name} API error (${response.status}): ${text}`);
    }

    const data = (await response.json()) as Record<string, unknown>;
    const choices = data.choices as
      | Array<{ message: { content: string } }>
      | undefined;
    const content = choices?.[0]?.message?.content;
    if (!content) {
      throw new Error(
        `${this.name} returned unexpected response: ${JSON.stringify(data).slice(0, 200)}`,
      );
    }
    return content;
  }
}

View on GitHub (pinned to e04ba88819)

Solutions

  1. Read the JSON dump in the message — check for nested OpenRouter error metadata
  2. Switch config.model to a paid/stable OpenRouter model slug instead of a flaky free tier
  3. Retry — transient upstream failures often return empty choices once
  4. Reduce/adjust the prompt if the upstream model refuses or filters the content
  5. Use the resilient wrapper's fallback chain to fail over to another provider

Example fix

// before
model: 'meta-llama/llama-3-70b-instruct:free' // 200, empty choices, in-body error

// after
model: 'meta-llama/llama-3.1-70b-instruct'
Defensive patterns

Strategy: retry

Validate before calling

if (!config.model?.includes('/')) throw new Error(`Invalid OpenRouter model slug: ${config.model}`);

Type guard

interface OpenRouterResponse { choices?: Array<{ message?: { content?: string } }> }
function hasOpenRouterContent(r: OpenRouterResponse): boolean {
  return typeof r.choices?.[0]?.message?.content === 'string' && r.choices[0].message.content.length > 0;
}

Try / catch

try {
  return await provider.call(prompt);
} catch (e) {
  if ((e as Error).message.includes('returned unexpected response')) {
    return retryWithBackoff(() => provider.call(prompt), 2)
      .catch(() => fallbackProvider.call(prompt));
  }
  throw e;
}

Prevention

When it happens

Trigger: compress()/summarize() where OpenRouter returns 200 with choices absent, empty, or message.content empty — commonly when the upstream model returns only an error embedded in a 200 body, content is filtered, or the chosen slug routes to a non-chat endpoint.

Common situations: Free OpenRouter model tiers returning 200 with an in-body error and no choices; moderation/empty completion from the upstream model; OpenRouter routing a request to a backend that omits message.content; truncation settings producing empty content.

Related errors


AI-assisted analysis of rohitg00/agentmemory@e04ba88819 (2026-08-30). Data as JSON: /api/errors/6cd5e27650367bfc. Report an issue: GitHub.