Skip to content

Commit 4b3dff4

Browse files
committed
init
0 parents  commit 4b3dff4

File tree

12 files changed

+2834
-0
lines changed

12 files changed

+2834
-0
lines changed

.eslintrc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"root": true,
3+
"parser": "@typescript-eslint/parser",
4+
"plugins": ["@typescript-eslint"],
5+
"extends": [
6+
"eslint:recommended",
7+
"plugin:@typescript-eslint/eslint-recommended",
8+
"plugin:@typescript-eslint/recommended",
9+
"prettier"
10+
],
11+
"parserOptions": {
12+
"sourceType": "module"
13+
},
14+
"rules": {
15+
"no-unused-vars": "off",
16+
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
17+
"@typescript-eslint/ban-ts-comment": "off",
18+
"no-prototype-builtins": "off",
19+
"@typescript-eslint/no-empty-function": "off"
20+
}
21+
}

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "npm"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Build and Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
inputs:
9+
releaseVersion:
10+
description: 'Release Version (e.g., v1.0.0)'
11+
required: true
12+
13+
jobs:
14+
release:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v3
19+
20+
- name: Setup Node.js
21+
uses: actions/setup-node@v3
22+
with:
23+
node-version: '21'
24+
25+
- name: Install dependencies
26+
run: npm ci
27+
28+
- name: Build
29+
run: npm run build
30+
31+
- name: Create Release
32+
id: create_release
33+
uses: ncipollo/release-action@v1
34+
with:
35+
token: ${{ secrets.GH_TOKEN }}
36+
tag: ${{ github.event.inputs.releaseVersion || github.ref_name }}
37+
name: Release ${{ github.event.inputs.releaseVersion || github.ref_name }}
38+
draft: false
39+
prerelease: false
40+
artifacts: 'build/*'

.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Dependency directories
2+
node_modules/
3+
4+
# Build output
5+
build/
6+
dist/
7+
8+
# Environment variables
9+
.env
10+
11+
# IDE specific files
12+
.idea/
13+
.vscode/
14+
*.swp
15+
*.swo
16+
17+
# Operating System Files
18+
.DS_Store
19+
Thumbs.db
20+
21+
# Debug logs
22+
npm-debug.log*
23+
yarn-debug.log*
24+
yarn-error.log*
25+
26+
# TypeScript cache
27+
*.tsbuildinfo

.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"trailingComma": "es5",
3+
"tabWidth": 2,
4+
"semi": true,
5+
"singleQuote": true
6+
}

config.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
export const config = {
2+
connectorName: 'OpenRouter',
3+
connectorVersion: '1.0.0',
4+
models: [
5+
'openrouter/auto',
6+
'deepseek/deepseek-chat',
7+
'sao10k/l3.1-70b-hanami-x1',
8+
'qwen/qvq-72b-preview',
9+
'meta-llama/llama-3.3-70b-instruct'
10+
],
11+
description:
12+
'OpenRouter Connector for Prompt Mixer - Access multiple AI models through a single API',
13+
author: 'Prompt Mixer',
14+
properties: [
15+
{
16+
id: 'prompt',
17+
name: 'System Prompt',
18+
value: 'You are a helpful assistant.',
19+
type: 'string',
20+
},
21+
{
22+
id: 'max_tokens',
23+
name: 'Max Tokens',
24+
value: 4096,
25+
type: 'number',
26+
},
27+
{
28+
id: 'temperature',
29+
name: 'Temperature',
30+
value: 0.7,
31+
type: 'number',
32+
},
33+
{
34+
id: 'top_p',
35+
name: 'Top P',
36+
value: 1,
37+
type: 'number',
38+
},
39+
{
40+
id: 'frequency_penalty',
41+
name: 'Frequency Penalty',
42+
value: 0.5,
43+
type: 'number',
44+
},
45+
{
46+
id: 'presence_penalty',
47+
name: 'Presence Penalty',
48+
value: 0.5,
49+
type: 'number',
50+
},
51+
{
52+
id: 'stop',
53+
name: 'Stop Sequences',
54+
value: ['\n'],
55+
type: 'array',
56+
}
57+
],
58+
settings: [
59+
{
60+
id: 'OPENROUTER_API_KEY',
61+
name: 'OpenRouter API Key',
62+
value: '',
63+
type: 'string',
64+
},
65+
{
66+
id: 'SITE_URL',
67+
name: 'Site URL',
68+
value: '',
69+
type: 'string',
70+
description: 'Optional. Site URL for rankings on openrouter.ai'
71+
},
72+
{
73+
id: 'SITE_NAME',
74+
name: 'Site Name',
75+
value: '',
76+
type: 'string',
77+
description: 'Optional. Site title for rankings on openrouter.ai'
78+
}
79+
],
80+
iconBase64:
81+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFyWlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNy4yLWMwMDAgNzkuMWI2NWE3OWI0LCAyMDIyLzA2LzEzLTIyOjAxOjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjQuMCAoTWFjaW50b3NoKSIgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDMtMjBUMTY6NDc6NDctMDc6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQtMDMtMjBUMTY6NDc6NDctMDc6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDI0LTAzLTIwVDE2OjQ3OjQ3LTA3OjAwIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjY5ZDY4ZjE5LTJlZDAtNDI4ZC1hMjI5LTNlMjVjOWZkYmFiZiIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjY5ZDY4ZjE5LTJlZDAtNDI4ZC1hMjI5LTNlMjVjOWZkYmFiZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjY5ZDY4ZjE5LTJlZDAtNDI4ZC1hMjI5LTNlMjVjOWZkYmFiZiIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjY5ZDY4ZjE5LTJlZDAtNDI4ZC1hMjI5LTNlMjVjOWZkYmFiZiIgc3RFdnQ6d2hlbj0iMjAyNC0wMy0yMFQxNjo0Nzo0Ny0wNzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI0LjAgKE1hY2ludG9zaCkiLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+YcP6RAAAAZtJREFUOI210j9IVWEYx/HPuYp6L4hdCCKIoH9QENQUNERj0BA0NUWLSwQtgYvg0tQUDUVENDgJQdDQFg0NDQ0RQUNDQzREgxQ0RBd6G855j+fcs3C88MDL+z7P7/t9n/d5n5TneZqJlNIQZnEfPdjHJt7gQ57nP5oSQwixB6/wEDdxFT24jXF8xEwI4VLDhBDiKN5jBBcqXH7iGaZDCMdVhPcwj0s1yQp0YwKLIYTOEuEIXqKjQfI/9GMphHC+IGzDHK43SZZwC29DCG2FcBo3zkCW0Iu5ojKPWpQl9OBTqXIXL85IlnARkyGEVMg+4EqLZAnXsF0WfsalFskSurBXFr7H5XMgS7iC92VhB17/B1nCIFbLwm48wdEZyYRxrFXsw1m0t0iWsIOhEMJJpTDP8z28aYEsYQvD1bfFNKawc0ayhG8YCSEcVE56CCHhEb6ekSxhHWMhhJPaJ+p3q9qxgIEWyBLWMFwrg4YXJaXUiaeYwIUmyRI+Yz6EsNvwbTdDlFLqwz3cQRf+YA+f8A4rjSr6F/5J06pykU6HAAAAAElFTkSuQmCC'
82+
};

esbuild.config.mjs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import esbuild from 'esbuild';
2+
import process from 'process';
3+
4+
const prod = process.argv[2] === 'production';
5+
6+
const context = await esbuild.context({
7+
entryPoints: ['main.ts'],
8+
bundle: true,
9+
platform: 'node',
10+
target: 'es2022',
11+
outfile: './build/main.js',
12+
});
13+
14+
if (prod) {
15+
await context.rebuild();
16+
process.exit(0);
17+
} else {
18+
await context.watch();
19+
}

main.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import OpenAI from 'openai';
2+
import { config } from './config';
3+
import { ChatCompletion } from 'openai/resources';
4+
5+
const OPENROUTER_API_KEY = 'OPENROUTER_API_KEY';
6+
const SITE_URL = 'SITE_URL';
7+
const SITE_NAME = 'SITE_NAME';
8+
9+
interface Message {
10+
role: string;
11+
content: string;
12+
}
13+
14+
interface Completion {
15+
Content: string | null;
16+
Error?: string | undefined;
17+
TokenUsage: number | undefined;
18+
}
19+
20+
interface ConnectorResponse {
21+
Completions: Completion[];
22+
ModelType: string;
23+
}
24+
25+
interface ErrorCompletion {
26+
choices: Array<{
27+
message: {
28+
content: string;
29+
};
30+
}>;
31+
error: string;
32+
model: string;
33+
usage: undefined;
34+
}
35+
36+
const mapToResponse = (
37+
outputs: Array<ChatCompletion | ErrorCompletion>,
38+
model: string,
39+
): ConnectorResponse => {
40+
return {
41+
Completions: outputs.map((output) => {
42+
if ('error' in output) {
43+
return {
44+
Content: null,
45+
TokenUsage: undefined,
46+
Error: output.error,
47+
};
48+
} else {
49+
return {
50+
Content: output.choices[0]?.message?.content || null,
51+
TokenUsage: output.usage?.total_tokens,
52+
};
53+
}
54+
}),
55+
ModelType: outputs[0].model || model,
56+
};
57+
};
58+
59+
const mapErrorToCompletion = (error: any, model: string): ErrorCompletion => {
60+
const errorMessage = error.message || JSON.stringify(error);
61+
return {
62+
choices: [],
63+
error: errorMessage,
64+
model,
65+
usage: undefined,
66+
};
67+
};
68+
69+
async function main(
70+
model: string,
71+
prompts: string[],
72+
properties: Record<string, unknown>,
73+
settings: Record<string, unknown>,
74+
): Promise<ConnectorResponse> {
75+
const openai = new OpenAI({
76+
baseURL: 'https://openrouter.ai/api/v1',
77+
apiKey: settings?.[OPENROUTER_API_KEY] as string,
78+
defaultHeaders: {
79+
'HTTP-Referer': settings?.[SITE_URL] as string || 'https://github.com/prompt-mixer',
80+
'X-Title': settings?.[SITE_NAME] as string || 'Prompt Mixer',
81+
}
82+
});
83+
84+
const { prompt, ...restProperties } = properties;
85+
const systemPrompt = (prompt || config.properties.find((prop) => prop.id === 'prompt')?.value) as string;
86+
const messageHistory: Message[] = [{ role: 'system', content: systemPrompt }];
87+
const outputs: Array<ChatCompletion | ErrorCompletion> = [];
88+
89+
try {
90+
for (let index = 0; index < prompts.length; index++) {
91+
try {
92+
messageHistory.push({ role: 'user', content: prompts[index] });
93+
const chatCompletion = await openai.chat.completions.create({
94+
messages: messageHistory as unknown as [],
95+
model: model,
96+
...restProperties,
97+
});
98+
99+
const assistantResponse = chatCompletion.choices[0].message.content || 'No response.';
100+
messageHistory.push({ role: 'assistant', content: assistantResponse });
101+
outputs.push(chatCompletion);
102+
} catch (error) {
103+
console.error('Error in completion:', error);
104+
const completionWithError = mapErrorToCompletion(error, model);
105+
outputs.push(completionWithError);
106+
}
107+
}
108+
109+
return mapToResponse(outputs, model);
110+
} catch (error) {
111+
console.error('Error in main function:', error);
112+
const completionWithError = mapErrorToCompletion(error, model);
113+
return mapToResponse([completionWithError], model);
114+
}
115+
}
116+
117+
export { main, config };

manifest.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"id": "prompt-mixer-openrouter-connector",
3+
"name": "OpenRouter Connector",
4+
"version": "1.0.0",
5+
"description": "OpenRouter Connector for Prompt Mixer - Access multiple AI models through a single API",
6+
"author": "Prompt Mixer",
7+
"authorUrl": "https://github.com/prompt-mixer",
8+
"isDesktopOnly": false
9+
}

0 commit comments

Comments
 (0)