@agentgrader/agent-openrouter 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ import { AgentAdapter, StepEvent, AgentResult } from '@agentgrader/core';
2
+
3
+ declare class OpenRouterAgentAdapter implements AgentAdapter {
4
+ readonly name = "openrouter";
5
+ solve(input: {
6
+ prompt: string;
7
+ sandbox: any;
8
+ config: any;
9
+ onStep: (step: StepEvent) => void;
10
+ }): Promise<AgentResult>;
11
+ }
12
+
13
+ export { OpenRouterAgentAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,186 @@
1
+ import { tool, experimental_createMCPClient, generateText } from 'ai';
2
+ import { Experimental_StdioMCPTransport } from 'ai/mcp-stdio';
3
+ import { createOpenAI } from '@ai-sdk/openai';
4
+ import { z } from 'zod';
5
+
6
+ // src/index.ts
7
+ var OpenRouterAgentAdapter = class {
8
+ name = "openrouter";
9
+ async solve(input) {
10
+ const { prompt, sandbox, config, onStep } = input;
11
+ const apiKey = process.env.OPENROUTER_API_KEY || process.env.OPENAI_API_KEY || "mock-key";
12
+ const baseURL = process.env.OPENROUTER_API_KEY ? "https://openrouter.ai/api/v1" : void 0;
13
+ const client = createOpenAI({
14
+ baseURL,
15
+ apiKey,
16
+ headers: {
17
+ "HTTP-Referer": "https://github.com/agentgrader/agr",
18
+ "X-Title": "Agentgrader"
19
+ }
20
+ });
21
+ const modelName = config.model || "gpt-4o-mini";
22
+ const model = client(modelName);
23
+ let submitted = false;
24
+ let stepIndex = 0;
25
+ const localTools = {
26
+ executeCommand: tool({
27
+ description: "Execute a terminal bash command inside the sandbox container.",
28
+ parameters: z.object({
29
+ command: z.string()
30
+ }),
31
+ execute: async ({ command }) => {
32
+ const res = await sandbox.exec(command);
33
+ return {
34
+ stdout: res.stdout,
35
+ stderr: res.stderr,
36
+ exitCode: res.exitCode
37
+ };
38
+ }
39
+ }),
40
+ readFile: tool({
41
+ description: "Read the content of a file in the sandbox workspace.",
42
+ parameters: z.object({
43
+ path: z.string()
44
+ }),
45
+ execute: async ({ path }) => {
46
+ try {
47
+ const content = await sandbox.readFile(path);
48
+ return { content };
49
+ } catch (err) {
50
+ return { error: err.message };
51
+ }
52
+ }
53
+ }),
54
+ writeFile: tool({
55
+ description: "Write content to a file in the sandbox workspace.",
56
+ parameters: z.object({
57
+ path: z.string(),
58
+ content: z.string()
59
+ }),
60
+ execute: async ({ path, content }) => {
61
+ try {
62
+ await sandbox.writeFile(path, content);
63
+ return { success: true };
64
+ } catch (err) {
65
+ return { error: err.message };
66
+ }
67
+ }
68
+ }),
69
+ submit: tool({
70
+ description: "Submit the solution when the task is fully completed and verified.",
71
+ parameters: z.object({
72
+ summary: z.string().describe("A summary of changes made.")
73
+ }),
74
+ execute: async ({ summary }) => {
75
+ submitted = true;
76
+ return { message: "Solution submitted successfully." };
77
+ }
78
+ })
79
+ };
80
+ const mcpClients = [];
81
+ const mcpTools = {};
82
+ const mcpServers = config.mcp_servers ?? {};
83
+ for (const [serverName, serverConfig] of Object.entries(mcpServers)) {
84
+ try {
85
+ const transport = "command" in serverConfig ? new Experimental_StdioMCPTransport({
86
+ command: serverConfig.command,
87
+ args: serverConfig.args,
88
+ env: serverConfig.env
89
+ }) : (
90
+ // note: the installed AI SDK version only supports "sse" as a
91
+ // remote transport type, regardless of the configured `type`
92
+ { type: "sse", url: serverConfig.url, headers: serverConfig.headers }
93
+ );
94
+ const client2 = await experimental_createMCPClient({ transport });
95
+ mcpClients.push(client2);
96
+ const serverTools = await client2.tools();
97
+ for (const [toolName, toolDef] of Object.entries(serverTools)) {
98
+ mcpTools[`${serverName}_${toolName}`] = toolDef;
99
+ }
100
+ } catch (err) {
101
+ console.error(`Failed to connect to MCP server "${serverName}": ${err.message}`);
102
+ }
103
+ }
104
+ const tools = { ...localTools, ...mcpTools };
105
+ const getPricing = (model2) => {
106
+ const name = model2.toLowerCase();
107
+ if (name.includes("claude-3-5-sonnet")) {
108
+ return { input: 3 / 1e6, output: 15 / 1e6 };
109
+ }
110
+ if (name.includes("gpt-4o")) {
111
+ return { input: 5 / 1e6, output: 15 / 1e6 };
112
+ }
113
+ if (name.includes("mini") || name.includes("flash")) {
114
+ return { input: 0.15 / 1e6, output: 0.6 / 1e6 };
115
+ }
116
+ return { input: 2 / 1e6, output: 10 / 1e6 };
117
+ };
118
+ const pricing = getPricing(modelName);
119
+ try {
120
+ await generateText({
121
+ model,
122
+ maxSteps: config.max_steps || 30,
123
+ tools,
124
+ system: config.system_prompt || "You are an expert software engineering agent. Solve the coding task in the sandbox. Use tools to inspect, modify, and run tests. Call 'submit' when done.",
125
+ prompt,
126
+ experimental_telemetry: { isEnabled: true },
127
+ onStepFinish: ({ text, toolCalls, toolResults, usage }) => {
128
+ const promptTokens = usage?.promptTokens || 0;
129
+ const completionTokens = usage?.completionTokens || 0;
130
+ const stepCost = promptTokens * pricing.input + completionTokens * pricing.output;
131
+ if (text) {
132
+ onStep({
133
+ index: stepIndex++,
134
+ kind: "message",
135
+ tokensIn: promptTokens,
136
+ tokensOut: completionTokens,
137
+ costUsd: stepCost,
138
+ timestamp: Date.now(),
139
+ content: text
140
+ });
141
+ }
142
+ for (const tc of toolCalls) {
143
+ onStep({
144
+ index: stepIndex++,
145
+ kind: "tool_call",
146
+ tool: tc.toolName,
147
+ tokensIn: 0,
148
+ tokensOut: 0,
149
+ costUsd: 0,
150
+ timestamp: Date.now(),
151
+ content: JSON.stringify(tc.args)
152
+ });
153
+ }
154
+ for (const tr of toolResults) {
155
+ onStep({
156
+ index: stepIndex++,
157
+ kind: "tool_result",
158
+ tool: tr.toolName,
159
+ tokensIn: 0,
160
+ tokensOut: 0,
161
+ costUsd: 0,
162
+ timestamp: Date.now(),
163
+ content: JSON.stringify(tr.result)
164
+ });
165
+ }
166
+ }
167
+ });
168
+ } catch (err) {
169
+ console.error(`Error in generateText agent loop: ${err.message}`);
170
+ } finally {
171
+ for (const client2 of mcpClients) {
172
+ try {
173
+ await client2.close();
174
+ } catch (e) {
175
+ }
176
+ }
177
+ }
178
+ const finalDiff = await sandbox.gitDiff();
179
+ return {
180
+ finished: submitted,
181
+ finalDiff
182
+ };
183
+ }
184
+ };
185
+
186
+ export { OpenRouterAgentAdapter };
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@agentgrader/agent-openrouter",
3
+ "version": "1.0.0",
4
+ "description": "OpenRouter/AI SDK ReAct agent adapter for the Agentgrader framework",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format esm --dts --clean --treeshake",
21
+ "build:watch": "tsup src/index.ts --format esm --dts --watch"
22
+ },
23
+ "dependencies": {
24
+ "@ai-sdk/openai": "^1.1.0",
25
+ "@agentgrader/core": "workspace:*",
26
+ "ai": "^4.1.0",
27
+ "zod": "^3.23.8"
28
+ },
29
+ "devDependencies": {
30
+ "tsup": "^8.5.1"
31
+ },
32
+ "peerDependencies": {
33
+ "@agentgrader/core": "^1.0.0"
34
+ }
35
+ }