Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# API Keys for AI providers
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
OPENROUTER_API_KEY=sk-or-...
GOOGLE_GENAI_API_KEY=...

# Sentry Configuration (optional)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/daily-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ jobs:
continue-on-error: true
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
GOOGLE_GENAI_API_KEY: ${{ secrets.GOOGLE_GENAI_API_KEY }}
CO_API_KEY: ${{ secrets.CO_API_KEY }}
run: npm run test -- -j=12
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
continue-on-error: true
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
GOOGLE_GENAI_API_KEY: ${{ secrets.GOOGLE_GENAI_API_KEY }}
CO_API_KEY: ${{ secrets.CO_API_KEY }}
run: npm run test -- -j=12
Expand Down Expand Up @@ -201,7 +201,7 @@ jobs:
continue-on-error: true
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
GOOGLE_GENAI_API_KEY: ${{ secrets.GOOGLE_GENAI_API_KEY }}
CO_API_KEY: ${{ secrets.CO_API_KEY }}
run: npm run test -- -j=12
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ Create a `.env` file with your API keys:

```bash
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
OPENROUTER_API_KEY=sk-or-...
GOOGLE_GENAI_API_KEY=...
```

Expand Down Expand Up @@ -814,7 +814,7 @@ jobs:
with:
platform: python # or 'node', or leave empty for both
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
openrouter-api-key: ${{ secrets.OPENROUTER_API_KEY }}
google-genai-api-key: ${{ secrets.GOOGLE_GENAI_API_KEY }}
```

Expand All @@ -829,7 +829,7 @@ jobs:
| `sentry-python-path` | No | `""` | Path to local sentry-python for editable install |
| `sentry-javascript-path` | No | `""` | Path to local sentry-javascript for linking |
| `openai-api-key` | Yes | - | OpenAI API key |
| `anthropic-api-key` | Yes | - | Anthropic API key |
| `openrouter-api-key` | Yes | - | OpenRouter API key for Anthropic-compatible requests |
| `google-genai-api-key` | Yes | - | Google GenAI API key |

### Action Outputs
Expand All @@ -854,7 +854,7 @@ jobs:
parallel: 8
sentry-python-path: ${{ github.workspace }}
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
openrouter-api-key: ${{ secrets.OPENROUTER_API_KEY }}
google-genai-api-key: ${{ secrets.GOOGLE_GENAI_API_KEY }}

- name: Check results
Expand Down
6 changes: 3 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ inputs:
openai-api-key:
description: "OpenAI API key"
required: true
anthropic-api-key:
description: "Anthropic API key"
openrouter-api-key:
description: "OpenRouter API key used through the Anthropic-compatible endpoint"
required: true
google-genai-api-key:
description: "Google GenAI API key"
Expand Down Expand Up @@ -100,7 +100,7 @@ runs:
working-directory: ${{ github.action_path }}
env:
OPENAI_API_KEY: ${{ inputs.openai-api-key }}
ANTHROPIC_API_KEY: ${{ inputs.anthropic-api-key }}
OPENROUTER_API_KEY: ${{ inputs.openrouter-api-key }}
GOOGLE_GENAI_API_KEY: ${{ inputs.google-genai-api-key }}
run: |
set +e
Expand Down
4 changes: 2 additions & 2 deletions src/runner/browser-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ export default defineConfig({
window.SENTRY_DSN = ${JSON.stringify(sentryDsn)};
window.RUN_ID = ${JSON.stringify(runId)};
window.OPENAI_API_KEY = ${JSON.stringify(process.env.OPENAI_API_KEY || "")};
window.ANTHROPIC_API_KEY = ${JSON.stringify(process.env.ANTHROPIC_API_KEY || "")};
window.OPENROUTER_API_KEY = ${JSON.stringify(process.env.OPENROUTER_API_KEY || "")};
window.GOOGLE_GENAI_API_KEY = ${JSON.stringify(process.env.GOOGLE_GENAI_API_KEY || "")};
window.testComplete = false;
`);
Expand Down Expand Up @@ -430,7 +430,7 @@ declare global {
SENTRY_DSN: string;
RUN_ID: string;
OPENAI_API_KEY: string;
ANTHROPIC_API_KEY: string;
OPENROUTER_API_KEY: string;
GOOGLE_GENAI_API_KEY: string;
testComplete: boolean;
}
Expand Down
2 changes: 1 addition & 1 deletion src/runner/cloudflare-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export class CloudflareRunner {
// Always regenerate .dev.vars with current API keys (setup may be cached)
const devVars = [
`OPENAI_API_KEY=${process.env.OPENAI_API_KEY || ""}`,
`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY || ""}`,
`OPENROUTER_API_KEY=${process.env.OPENROUTER_API_KEY || ""}`,
`GOOGLE_GENAI_API_KEY=${process.env.GOOGLE_GENAI_API_KEY || ""}`,
].join("\n");
await fs.writeFile(path.join(workDir, ".dev.vars"), devVars);
Expand Down
2 changes: 1 addition & 1 deletion src/runner/javascript-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ export class JavaScriptRunner {
SENTRY_DSN: sentryDsn,
RUN_ID: runId,
OPENAI_API_KEY: process.env.OPENAI_API_KEY || "",
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "",
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY || "",
GOOGLE_GENAI_API_KEY: process.env.GOOGLE_GENAI_API_KEY || "",
};

Expand Down
2 changes: 1 addition & 1 deletion src/runner/php-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ return [
SENTRY_TRACES_SAMPLE_RATE: "1.0",
RUN_ID: runId,
OPENAI_API_KEY: process.env.OPENAI_API_KEY || "",
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || "",
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY || "",
GOOGLE_GENAI_API_KEY: process.env.GOOGLE_GENAI_API_KEY || "",
};

Expand Down
2 changes: 1 addition & 1 deletion src/runner/python-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ ${dependencies.map(d => ` ${d},`).join('\n')}
SENTRY_DSN: sentryDsn,
RUN_ID: runId,
OPENAI_API_KEY: process.env.OPENAI_API_KEY || '',
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',
OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY || '',
GOOGLE_GENAI_API_KEY: process.env.GOOGLE_GENAI_API_KEY || '',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ import { z } from "zod";
{% if provider == "anthropic" %}
const llm = new ChatAnthropic({
modelName: {% if causeAPIError %}"invalid-model"{% else %}"{{ inputs[0].model }}"{% endif %},
anthropicApiKey: env.ANTHROPIC_API_KEY,
anthropicApiKey: env.OPENROUTER_API_KEY,
anthropicApiUrl: "https://openrouter.ai/api",
});
{% else %}
const llm = new ChatOpenAI({
Expand Down
5 changes: 4 additions & 1 deletion src/runner/templates/agents/cloudflare/vercel/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ import { createOpenAI } from "@ai-sdk/openai";

{% block dynamic_imports %}
{% if provider == "anthropic" %}
const anthropic = createAnthropic({ apiKey: env.ANTHROPIC_API_KEY });
const anthropic = createAnthropic({
apiKey: env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
});
{% else %}
const openai = createOpenAI({ apiKey: env.OPENAI_API_KEY });
{% endif %}
Expand Down
6 changes: 5 additions & 1 deletion src/runner/templates/agents/nextjs/vercel/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ const { ToolLoopAgent, tool, jsonSchema, stepCountIs } = await import("ai");
const { generateText, streamText, tool, jsonSchema, stepCountIs } = await import("ai");
{% endif %}
{% if provider == "anthropic" %}
const { anthropic } = await import("@ai-sdk/anthropic");
const { createAnthropic } = await import("@ai-sdk/anthropic");
const anthropic = createAnthropic({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
});
{% else %}
const { openai } = await import("@ai-sdk/openai");
{% endif %}
Expand Down
2 changes: 2 additions & 0 deletions src/runner/templates/agents/node/langgraph/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const tools = [

{% if provider == "anthropic" %}
const llm = new ChatAnthropic({
anthropicApiKey: process.env.OPENROUTER_API_KEY,
anthropicApiUrl: "https://openrouter.ai/api",
modelName: {% if causeAPIError %}"invalid-model"{% else %}"{{ inputs[0].model }}"{% endif %},
});
{% else %}
Expand Down
6 changes: 5 additions & 1 deletion src/runner/templates/agents/node/vercel/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ const { streamText, tool, jsonSchema, stepCountIs } = await import("ai");
const { generateText, tool, jsonSchema, stepCountIs } = await import("ai");
{% endif %}
{% if provider == "anthropic" %}
const { anthropic } = await import("@ai-sdk/anthropic");
const { createAnthropic } = await import("@ai-sdk/anthropic");
const anthropic = createAnthropic({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong Vercel OpenRouter base URL

High Severity

@ai-sdk/anthropic treats baseURL as already versioned (default https://api.anthropic.com/v1) and appends /messages directly. Using https://openrouter.ai/api sends requests to /api/messages instead of /api/v1/messages, so Anthropic Vercel AI SDK tests fail against OpenRouter.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fd9d21f. Configure here.

{% else %}
const { openai } = await import("@ai-sdk/openai");
{% endif %}
Expand Down
3 changes: 2 additions & 1 deletion src/runner/templates/agents/python/langgraph/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ from langchain_core.messages import HumanMessage, SystemMessage
# Initialize ChatAnthropic for LangGraph
llm = ChatAnthropic(
model="{{ inputs[0].model }}",
api_key=os.environ.get("ANTHROPIC_API_KEY"),
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api",
)
{% else %}
# Initialize ChatOpenAI for LangGraph
Expand Down
11 changes: 6 additions & 5 deletions src/runner/templates/llm/browser/anthropic/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
{% block dynamic_imports %}
const { default: Anthropic } = await import('@anthropic-ai/sdk');

const ANTHROPIC_API_KEY = window.ANTHROPIC_API_KEY;
if (!ANTHROPIC_API_KEY) {
log('ERROR: ANTHROPIC_API_KEY not found in window');
throw new Error('ANTHROPIC_API_KEY not provided');
const OPENROUTER_API_KEY = window.OPENROUTER_API_KEY;
if (!OPENROUTER_API_KEY) {
log('ERROR: OPENROUTER_API_KEY not found in window');
throw new Error('OPENROUTER_API_KEY not provided');
}

// WARNING: API key is exposed in browser - this is for testing only!
const anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
apiKey: OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
dangerouslyAllowBrowser: true
});

Expand Down
11 changes: 6 additions & 5 deletions src/runner/templates/llm/browser/langchain/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
const { HumanMessage, SystemMessage, AIMessage } = await import("@langchain/core/messages");

{% if provider == "anthropic" %}
const ANTHROPIC_API_KEY = window.ANTHROPIC_API_KEY;
if (!ANTHROPIC_API_KEY) {
log('ERROR: ANTHROPIC_API_KEY not found in window');
throw new Error('ANTHROPIC_API_KEY not provided');
const OPENROUTER_API_KEY = window.OPENROUTER_API_KEY;
if (!OPENROUTER_API_KEY) {
log('ERROR: OPENROUTER_API_KEY not found in window');
throw new Error('OPENROUTER_API_KEY not provided');
}
{% else %}
const OPENAI_API_KEY = window.OPENAI_API_KEY;
Expand All @@ -45,7 +45,8 @@
{% if provider == "anthropic" %}
const chat = new ChatAnthropic({
modelName: {% if causeAPIError %}"invalid-model"{% else %}"{{ inputs[0].model }}"{% endif %},
anthropicApiKey: ANTHROPIC_API_KEY,
anthropicApiKey: OPENROUTER_API_KEY,
anthropicApiUrl: "https://openrouter.ai/api",
});

log('LangChain ChatAnthropic initialized with createLangChainCallbackHandler');
Expand Down
5 changes: 4 additions & 1 deletion src/runner/templates/llm/cloudflare/anthropic/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import Anthropic from "@anthropic-ai/sdk";
{% endblock %}

{% block dynamic_imports %}
const rawClient = new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
const rawClient = new Anthropic({
apiKey: env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
});
const client = Sentry.instrumentAnthropicAiClient(rawClient);
{% endblock %}

Expand Down
3 changes: 2 additions & 1 deletion src/runner/templates/llm/cloudflare/langchain/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ import { HumanMessage, SystemMessage, AIMessage } from "@langchain/core/messages
{% if provider == "anthropic" %}
const chat = new ChatAnthropic({
modelName: {% if causeAPIError %}"invalid-model"{% else %}"{{ inputs[0].model }}"{% endif %},
anthropicApiKey: env.ANTHROPIC_API_KEY,
anthropicApiKey: env.OPENROUTER_API_KEY,
anthropicApiUrl: "https://openrouter.ai/api",
});
{% else %}
const chat = new ChatOpenAI({
Expand Down
13 changes: 8 additions & 5 deletions src/runner/templates/llm/nextjs/anthropic/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
{% block dynamic_imports %}
const { default: Anthropic } = await import('@anthropic-ai/sdk');

const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
if (!ANTHROPIC_API_KEY) {
console.error('ERROR: ANTHROPIC_API_KEY not found in environment');
throw new Error('ANTHROPIC_API_KEY not provided');
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
if (!OPENROUTER_API_KEY) {
console.error('ERROR: OPENROUTER_API_KEY not found in environment');
throw new Error('OPENROUTER_API_KEY not provided');
}

const anthropic = new Anthropic({ apiKey: ANTHROPIC_API_KEY });
const anthropic = new Anthropic({
apiKey: OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
});
const client = Sentry.instrumentAnthropicAiClient(anthropic);
console.log('Anthropic client initialized and instrumented');
{% endblock %}
Expand Down
11 changes: 6 additions & 5 deletions src/runner/templates/llm/nextjs/langchain/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ const { ChatOpenAI } = await import("@langchain/openai");
const { HumanMessage, SystemMessage, AIMessage } = await import("@langchain/core/messages");

{% if provider == "anthropic" %}
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
if (!ANTHROPIC_API_KEY) {
console.error('ERROR: ANTHROPIC_API_KEY not found in environment');
throw new Error('ANTHROPIC_API_KEY not provided');
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
if (!OPENROUTER_API_KEY) {
console.error('ERROR: OPENROUTER_API_KEY not found in environment');
throw new Error('OPENROUTER_API_KEY not provided');
}
{% else %}
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
Expand All @@ -43,7 +43,8 @@ const callbackHandler = Sentry.createLangChainCallbackHandler();
{% if provider == "anthropic" %}
const chat = new ChatAnthropic({
modelName: {% if causeAPIError %}"invalid-model"{% else %}"{{ inputs[0].model }}"{% endif %},
anthropicApiKey: ANTHROPIC_API_KEY,
anthropicApiKey: OPENROUTER_API_KEY,
anthropicApiUrl: "https://openrouter.ai/api",
});
console.log('LangChain ChatAnthropic initialized with createLangChainCallbackHandler');
{% else %}
Expand Down
5 changes: 4 additions & 1 deletion src/runner/templates/llm/node/anthropic/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@

{% block dynamic_imports %}
const { default: Anthropic } = await import("@anthropic-ai/sdk");
const client = new Anthropic();
const client = new Anthropic({
apiKey: process.env.OPENROUTER_API_KEY,
baseURL: "https://openrouter.ai/api",
});
{% endblock %}

{% block test %}
Expand Down
2 changes: 2 additions & 0 deletions src/runner/templates/llm/node/langchain/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const { ChatOpenAI } = await import("@langchain/openai");
const { HumanMessage, SystemMessage, AIMessage } = await import("@langchain/core/messages");
{% if provider == "anthropic" %}
const chat = new ChatAnthropic({
anthropicApiKey: process.env.OPENROUTER_API_KEY,
anthropicApiUrl: "https://openrouter.ai/api",
modelName: {% if causeAPIError %}"invalid-model"{% else %}"{{ inputs[0].model }}"{% endif %},
});
{% else %}
Expand Down
12 changes: 9 additions & 3 deletions src/runner/templates/llm/python/anthropic/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@ import httpx

{% block inject_api_error %}
{% if causeAPIError %}
respx.route(host="api.anthropic.com").mock(return_value=httpx.Response(500, json={"error": {"message": "Server error", "type": "server_error"}}))
respx.route(host="openrouter.ai").mock(return_value=httpx.Response(500, json={"error": {"message": "Server error", "type": "server_error"}}))
respx.mock.start()
{% endif %}
{% endblock %}

{% block setup %}
{% if isAsync %}
client = AsyncAnthropic()
client = AsyncAnthropic(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api",
)
{% else %}
client = Anthropic()
client = Anthropic(
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api",
)
{% endif %}
{% endblock %}

Expand Down
8 changes: 6 additions & 2 deletions src/runner/templates/llm/python/langchain/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import httpx
{% block inject_api_error %}
{% if causeAPIError %}
{% if provider == "anthropic" %}
respx.route(host="api.anthropic.com").mock(return_value=httpx.Response(500, json={"error": {"message": "Server error", "type": "server_error"}}))
respx.route(host="openrouter.ai").mock(return_value=httpx.Response(500, json={"error": {"message": "Server error", "type": "server_error"}}))
{% else %}
respx.route(host="api.openai.com").mock(return_value=httpx.Response(500, json={"error": {"message": "Server error", "type": "server_error"}}))
{% endif %}
Expand All @@ -45,7 +45,11 @@ respx.mock.start()
{% block setup %}
{% set input = inputs[0] %}
{% if provider == "anthropic" %}
chat = ChatAnthropic(model="{{ input.model }}")
chat = ChatAnthropic(
model="{{ input.model }}",
api_key=os.environ["OPENROUTER_API_KEY"],
base_url="https://openrouter.ai/api",
)
{% else %}
chat = ChatOpenAI(model="{{ input.model }}")
{% endif %}
Expand Down
Loading
Loading