n8n-nodes-openrouter-custom 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.
- package/README.md +140 -0
- package/dist/credentials/OpenRouterCustomApi.credentials.d.ts +9 -0
- package/dist/credentials/OpenRouterCustomApi.credentials.js +54 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +19 -0
- package/dist/nodes/LmChatOpenRouterCustom/LmChatOpenRouterCustom.node.d.ts +5 -0
- package/dist/nodes/LmChatOpenRouterCustom/LmChatOpenRouterCustom.node.js +371 -0
- package/dist/nodes/LmChatOpenRouterCustom/openrouter.dark.svg +1 -0
- package/dist/nodes/LmChatOpenRouterCustom/openrouter.svg +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# n8n-nodes-openrouter-custom
|
|
2
|
+
|
|
3
|
+
Custom OpenRouter Chat Model node for n8n with built-in usage tracking and extended options.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Usage Tracking**: Automatically track token usage and costs via webhook
|
|
8
|
+
- **User ID Support**: Pass custom user identifiers to OpenRouter for tracking
|
|
9
|
+
- **Session Keys**: Group usage by custom session keys (e.g., `article_123`, `batch_xyz`)
|
|
10
|
+
- **Reasoning Models**: Built-in support for reasoning effort settings (o1, etc.)
|
|
11
|
+
- **Custom Model Kwargs**: Pass any additional parameters to the OpenRouter API
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Via n8n Community Nodes
|
|
16
|
+
|
|
17
|
+
1. Go to **Settings** → **Community Nodes**
|
|
18
|
+
2. Click **Install**
|
|
19
|
+
3. Enter `n8n-nodes-openrouter-custom`
|
|
20
|
+
4. Click **Install**
|
|
21
|
+
|
|
22
|
+
### Manual Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
cd ~/.n8n
|
|
26
|
+
npm install n8n-nodes-openrouter-custom
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Then restart n8n.
|
|
30
|
+
|
|
31
|
+
### From Source
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/yourusername/n8n-nodes-openrouter-custom
|
|
35
|
+
cd n8n-nodes-openrouter-custom
|
|
36
|
+
npm install
|
|
37
|
+
npm run build
|
|
38
|
+
npm link
|
|
39
|
+
|
|
40
|
+
cd ~/.n8n
|
|
41
|
+
npm link n8n-nodes-openrouter-custom
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
### Credentials
|
|
47
|
+
|
|
48
|
+
1. Create new credentials of type **OpenRouter Custom API**
|
|
49
|
+
2. Enter your OpenRouter API key
|
|
50
|
+
3. Optionally set a default **Usage Webhook URL**
|
|
51
|
+
|
|
52
|
+
### Node Options
|
|
53
|
+
|
|
54
|
+
#### Basic Options
|
|
55
|
+
- **Model**: Select from available OpenRouter models
|
|
56
|
+
- **Temperature**: Controls randomness (0-2)
|
|
57
|
+
- **Max Tokens**: Maximum tokens to generate
|
|
58
|
+
- **Top P**: Nucleus sampling parameter
|
|
59
|
+
- **Frequency/Presence Penalty**: Token repetition controls
|
|
60
|
+
- **Response Format**: Text or JSON mode
|
|
61
|
+
|
|
62
|
+
#### Usage Tracking
|
|
63
|
+
- **Enable Usage Tracking**: Toggle tracking on/off
|
|
64
|
+
- **User ID**: Identifier sent to OpenRouter and logged
|
|
65
|
+
- **Session Key**: Custom key for grouping usage data
|
|
66
|
+
- **Webhook URL Override**: Override credentials webhook for this node
|
|
67
|
+
- **Include Cost**: Include cost data in tracking
|
|
68
|
+
|
|
69
|
+
#### Advanced Options
|
|
70
|
+
- **Model Kwargs**: Additional JSON parameters for the API
|
|
71
|
+
- **Reasoning Effort**: For reasoning models (low/medium/high)
|
|
72
|
+
|
|
73
|
+
## Usage Tracking Webhook
|
|
74
|
+
|
|
75
|
+
When enabled, the node sends a POST request to your webhook URL after each LLM call:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"timestamp": "2024-01-15T10:30:00.000Z",
|
|
80
|
+
"run_id": "abc123",
|
|
81
|
+
"parent_run_id": "xyz789",
|
|
82
|
+
"model": "anthropic/claude-sonnet-4",
|
|
83
|
+
"user": "user_123",
|
|
84
|
+
"session_key": "article_456",
|
|
85
|
+
"input_tokens": 150,
|
|
86
|
+
"output_tokens": 500,
|
|
87
|
+
"total_tokens": 650,
|
|
88
|
+
"cost": 0.00325,
|
|
89
|
+
"cached_tokens": 50,
|
|
90
|
+
"reasoning_tokens": 0,
|
|
91
|
+
"finish_reason": "stop"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Example Webhook Workflow
|
|
96
|
+
|
|
97
|
+
Create an n8n workflow with:
|
|
98
|
+
1. **Webhook** trigger node (POST)
|
|
99
|
+
2. **Google Sheets** or **Database** node to store the data
|
|
100
|
+
|
|
101
|
+
## Example Usage
|
|
102
|
+
|
|
103
|
+
### Basic Usage
|
|
104
|
+
|
|
105
|
+
1. Add the **OpenRouter Custom** node to your AI Agent
|
|
106
|
+
2. Select your model
|
|
107
|
+
3. Configure options as needed
|
|
108
|
+
|
|
109
|
+
### With Usage Tracking
|
|
110
|
+
|
|
111
|
+
1. Set up a webhook workflow to receive usage data
|
|
112
|
+
2. In credentials, set the **Usage Webhook URL**
|
|
113
|
+
3. In the node, enable **Usage Tracking**
|
|
114
|
+
4. Optionally set **User ID** and **Session Key**
|
|
115
|
+
|
|
116
|
+
### With Reasoning Models
|
|
117
|
+
|
|
118
|
+
1. Select a reasoning model (e.g., `openai/o1-preview`)
|
|
119
|
+
2. In **Advanced Options**, set **Reasoning Effort** to `high`
|
|
120
|
+
3. Set **Temperature** to `1` (required for some reasoning models)
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# Install dependencies
|
|
126
|
+
npm install
|
|
127
|
+
|
|
128
|
+
# Build
|
|
129
|
+
npm run build
|
|
130
|
+
|
|
131
|
+
# Watch mode
|
|
132
|
+
npm run dev
|
|
133
|
+
|
|
134
|
+
# Clean build
|
|
135
|
+
npm run clean && npm run build
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class OpenRouterCustomApi implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
documentationUrl: string;
|
|
6
|
+
properties: INodeProperties[];
|
|
7
|
+
authenticate: IAuthenticateGeneric;
|
|
8
|
+
test: ICredentialTestRequest;
|
|
9
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenRouterCustomApi = void 0;
|
|
4
|
+
class OpenRouterCustomApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'openRouterCustomApi';
|
|
7
|
+
this.displayName = 'OpenRouter Custom API';
|
|
8
|
+
this.documentationUrl = 'https://openrouter.ai/docs';
|
|
9
|
+
this.properties = [
|
|
10
|
+
{
|
|
11
|
+
displayName: 'API Key',
|
|
12
|
+
name: 'apiKey',
|
|
13
|
+
type: 'string',
|
|
14
|
+
typeOptions: {
|
|
15
|
+
password: true,
|
|
16
|
+
},
|
|
17
|
+
required: true,
|
|
18
|
+
default: '',
|
|
19
|
+
description: 'Your OpenRouter API key',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
displayName: 'Base URL',
|
|
23
|
+
name: 'url',
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: 'https://openrouter.ai/api/v1',
|
|
26
|
+
description: 'OpenRouter API base URL',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
displayName: 'Usage Webhook URL',
|
|
30
|
+
name: 'usageWebhookUrl',
|
|
31
|
+
type: 'string',
|
|
32
|
+
default: '',
|
|
33
|
+
description: 'Optional webhook URL to send usage data to (leave empty to disable)',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
this.authenticate = {
|
|
37
|
+
type: 'generic',
|
|
38
|
+
properties: {
|
|
39
|
+
headers: {
|
|
40
|
+
Authorization: '=Bearer {{$credentials.apiKey}}',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
this.test = {
|
|
45
|
+
request: {
|
|
46
|
+
baseURL: '={{$credentials.url}}',
|
|
47
|
+
url: '/models',
|
|
48
|
+
method: 'GET',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.OpenRouterCustomApi = OpenRouterCustomApi;
|
|
54
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiT3BlblJvdXRlckN1c3RvbUFwaS5jcmVkZW50aWFscy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jcmVkZW50aWFscy9PcGVuUm91dGVyQ3VzdG9tQXBpLmNyZWRlbnRpYWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQU9BLE1BQWEsbUJBQW1CO0lBQWhDO1FBQ0MsU0FBSSxHQUFHLHFCQUFxQixDQUFDO1FBQzdCLGdCQUFXLEdBQUcsdUJBQXVCLENBQUM7UUFDdEMscUJBQWdCLEdBQUcsNEJBQTRCLENBQUM7UUFDaEQsZUFBVSxHQUFzQjtZQUMvQjtnQkFDQyxXQUFXLEVBQUUsU0FBUztnQkFDdEIsSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsV0FBVyxFQUFFO29CQUNaLFFBQVEsRUFBRSxJQUFJO2lCQUNkO2dCQUNELFFBQVEsRUFBRSxJQUFJO2dCQUNkLE9BQU8sRUFBRSxFQUFFO2dCQUNYLFdBQVcsRUFBRSx5QkFBeUI7YUFDdEM7WUFDRDtnQkFDQyxXQUFXLEVBQUUsVUFBVTtnQkFDdkIsSUFBSSxFQUFFLEtBQUs7Z0JBQ1gsSUFBSSxFQUFFLFFBQVE7Z0JBQ2QsT0FBTyxFQUFFLDhCQUE4QjtnQkFDdkMsV0FBVyxFQUFFLHlCQUF5QjthQUN0QztZQUNEO2dCQUNDLFdBQVcsRUFBRSxtQkFBbUI7Z0JBQ2hDLElBQUksRUFBRSxpQkFBaUI7Z0JBQ3ZCLElBQUksRUFBRSxRQUFRO2dCQUNkLE9BQU8sRUFBRSxFQUFFO2dCQUNYLFdBQVcsRUFBRSxxRUFBcUU7YUFDbEY7U0FDRCxDQUFDO1FBRUYsaUJBQVksR0FBeUI7WUFDcEMsSUFBSSxFQUFFLFNBQVM7WUFDZixVQUFVLEVBQUU7Z0JBQ1gsT0FBTyxFQUFFO29CQUNSLGFBQWEsRUFBRSxpQ0FBaUM7aUJBQ2hEO2FBQ0Q7U0FDRCxDQUFDO1FBRUYsU0FBSSxHQUEyQjtZQUM5QixPQUFPLEVBQUU7Z0JBQ1IsT0FBTyxFQUFFLHVCQUF1QjtnQkFDaEMsR0FBRyxFQUFFLFNBQVM7Z0JBQ2QsTUFBTSxFQUFFLEtBQUs7YUFDYjtTQUNELENBQUM7SUFDSCxDQUFDO0NBQUE7QUFoREQsa0RBZ0RDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHR5cGUge1xuXHRJQXV0aGVudGljYXRlR2VuZXJpYyxcblx0SUNyZWRlbnRpYWxUZXN0UmVxdWVzdCxcblx0SUNyZWRlbnRpYWxUeXBlLFxuXHRJTm9kZVByb3BlcnRpZXMsXG59IGZyb20gJ244bi13b3JrZmxvdyc7XG5cbmV4cG9ydCBjbGFzcyBPcGVuUm91dGVyQ3VzdG9tQXBpIGltcGxlbWVudHMgSUNyZWRlbnRpYWxUeXBlIHtcblx0bmFtZSA9ICdvcGVuUm91dGVyQ3VzdG9tQXBpJztcblx0ZGlzcGxheU5hbWUgPSAnT3BlblJvdXRlciBDdXN0b20gQVBJJztcblx0ZG9jdW1lbnRhdGlvblVybCA9ICdodHRwczovL29wZW5yb3V0ZXIuYWkvZG9jcyc7XG5cdHByb3BlcnRpZXM6IElOb2RlUHJvcGVydGllc1tdID0gW1xuXHRcdHtcblx0XHRcdGRpc3BsYXlOYW1lOiAnQVBJIEtleScsXG5cdFx0XHRuYW1lOiAnYXBpS2V5Jyxcblx0XHRcdHR5cGU6ICdzdHJpbmcnLFxuXHRcdFx0dHlwZU9wdGlvbnM6IHtcblx0XHRcdFx0cGFzc3dvcmQ6IHRydWUsXG5cdFx0XHR9LFxuXHRcdFx0cmVxdWlyZWQ6IHRydWUsXG5cdFx0XHRkZWZhdWx0OiAnJyxcblx0XHRcdGRlc2NyaXB0aW9uOiAnWW91ciBPcGVuUm91dGVyIEFQSSBrZXknLFxuXHRcdH0sXG5cdFx0e1xuXHRcdFx0ZGlzcGxheU5hbWU6ICdCYXNlIFVSTCcsXG5cdFx0XHRuYW1lOiAndXJsJyxcblx0XHRcdHR5cGU6ICdzdHJpbmcnLFxuXHRcdFx0ZGVmYXVsdDogJ2h0dHBzOi8vb3BlbnJvdXRlci5haS9hcGkvdjEnLFxuXHRcdFx0ZGVzY3JpcHRpb246ICdPcGVuUm91dGVyIEFQSSBiYXNlIFVSTCcsXG5cdFx0fSxcblx0XHR7XG5cdFx0XHRkaXNwbGF5TmFtZTogJ1VzYWdlIFdlYmhvb2sgVVJMJyxcblx0XHRcdG5hbWU6ICd1c2FnZVdlYmhvb2tVcmwnLFxuXHRcdFx0dHlwZTogJ3N0cmluZycsXG5cdFx0XHRkZWZhdWx0OiAnJyxcblx0XHRcdGRlc2NyaXB0aW9uOiAnT3B0aW9uYWwgd2ViaG9vayBVUkwgdG8gc2VuZCB1c2FnZSBkYXRhIHRvIChsZWF2ZSBlbXB0eSB0byBkaXNhYmxlKScsXG5cdFx0fSxcblx0XTtcblxuXHRhdXRoZW50aWNhdGU6IElBdXRoZW50aWNhdGVHZW5lcmljID0ge1xuXHRcdHR5cGU6ICdnZW5lcmljJyxcblx0XHRwcm9wZXJ0aWVzOiB7XG5cdFx0XHRoZWFkZXJzOiB7XG5cdFx0XHRcdEF1dGhvcml6YXRpb246ICc9QmVhcmVyIHt7JGNyZWRlbnRpYWxzLmFwaUtleX19Jyxcblx0XHRcdH0sXG5cdFx0fSxcblx0fTtcblxuXHR0ZXN0OiBJQ3JlZGVudGlhbFRlc3RSZXF1ZXN0ID0ge1xuXHRcdHJlcXVlc3Q6IHtcblx0XHRcdGJhc2VVUkw6ICc9e3skY3JlZGVudGlhbHMudXJsfX0nLFxuXHRcdFx0dXJsOiAnL21vZGVscycsXG5cdFx0XHRtZXRob2Q6ICdHRVQnLFxuXHRcdH0sXG5cdH07XG59XG4iXX0=
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./nodes/LmChatOpenRouterCustom/LmChatOpenRouterCustom.node"), exports);
|
|
18
|
+
__exportStar(require("./credentials/OpenRouterCustomApi.credentials"), exports);
|
|
19
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDZGQUEyRTtBQUMzRSxnRkFBOEQiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgKiBmcm9tICcuL25vZGVzL0xtQ2hhdE9wZW5Sb3V0ZXJDdXN0b20vTG1DaGF0T3BlblJvdXRlckN1c3RvbS5ub2RlJztcbmV4cG9ydCAqIGZyb20gJy4vY3JlZGVudGlhbHMvT3BlblJvdXRlckN1c3RvbUFwaS5jcmVkZW50aWFscyc7XG4iXX0=
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { INodeType, INodeTypeDescription, ISupplyDataFunctions, SupplyData } from 'n8n-workflow';
|
|
2
|
+
export declare class LmChatOpenRouterCustom implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
supplyData(this: ISupplyDataFunctions, itemIndex: number): Promise<SupplyData>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LmChatOpenRouterCustom = void 0;
|
|
4
|
+
const openai_1 = require("@langchain/openai");
|
|
5
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
|
+
class LmChatOpenRouterCustom {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.description = {
|
|
9
|
+
displayName: 'OpenRouter Chat Model (Custom)',
|
|
10
|
+
name: 'lmChatOpenRouterCustom',
|
|
11
|
+
icon: 'file:openrouter.svg',
|
|
12
|
+
group: ['transform'],
|
|
13
|
+
version: [1],
|
|
14
|
+
description: 'Custom OpenRouter Chat Model with usage tracking and extended options',
|
|
15
|
+
defaults: {
|
|
16
|
+
name: 'OpenRouter Custom',
|
|
17
|
+
},
|
|
18
|
+
codex: {
|
|
19
|
+
categories: ['AI'],
|
|
20
|
+
subcategories: {
|
|
21
|
+
AI: ['Language Models', 'Root Nodes'],
|
|
22
|
+
'Language Models': ['Chat Models (Recommended)'],
|
|
23
|
+
},
|
|
24
|
+
resources: {
|
|
25
|
+
primaryDocumentation: [
|
|
26
|
+
{
|
|
27
|
+
url: 'https://openrouter.ai/docs',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
inputs: [],
|
|
33
|
+
outputs: [n8n_workflow_1.NodeConnectionTypes.AiLanguageModel],
|
|
34
|
+
outputNames: ['Model'],
|
|
35
|
+
credentials: [
|
|
36
|
+
{
|
|
37
|
+
name: 'openRouterCustomApi',
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
requestDefaults: {
|
|
42
|
+
ignoreHttpStatusErrors: true,
|
|
43
|
+
baseURL: '={{ $credentials?.url }}',
|
|
44
|
+
},
|
|
45
|
+
properties: [
|
|
46
|
+
{
|
|
47
|
+
displayName: 'Model',
|
|
48
|
+
name: 'model',
|
|
49
|
+
type: 'options',
|
|
50
|
+
description: 'The model which will generate the completion. <a href="https://openrouter.ai/docs/models">Learn more</a>.',
|
|
51
|
+
typeOptions: {
|
|
52
|
+
loadOptions: {
|
|
53
|
+
routing: {
|
|
54
|
+
request: {
|
|
55
|
+
method: 'GET',
|
|
56
|
+
url: '/models',
|
|
57
|
+
},
|
|
58
|
+
output: {
|
|
59
|
+
postReceive: [
|
|
60
|
+
{
|
|
61
|
+
type: 'rootProperty',
|
|
62
|
+
properties: {
|
|
63
|
+
property: 'data',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
type: 'setKeyValue',
|
|
68
|
+
properties: {
|
|
69
|
+
name: '={{$responseItem.id}}',
|
|
70
|
+
value: '={{$responseItem.id}}',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'sort',
|
|
75
|
+
properties: {
|
|
76
|
+
key: 'name',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
routing: {
|
|
85
|
+
send: {
|
|
86
|
+
type: 'body',
|
|
87
|
+
property: 'model',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
default: 'openai/gpt-4.1-mini',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
displayName: 'Options',
|
|
94
|
+
name: 'options',
|
|
95
|
+
placeholder: 'Add Option',
|
|
96
|
+
description: 'Additional options to add',
|
|
97
|
+
type: 'collection',
|
|
98
|
+
default: {},
|
|
99
|
+
options: [
|
|
100
|
+
{
|
|
101
|
+
displayName: 'Frequency Penalty',
|
|
102
|
+
name: 'frequencyPenalty',
|
|
103
|
+
default: 0,
|
|
104
|
+
typeOptions: { maxValue: 2, minValue: -2, numberPrecision: 1 },
|
|
105
|
+
description: "Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim",
|
|
106
|
+
type: 'number',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
displayName: 'Maximum Number of Tokens',
|
|
110
|
+
name: 'maxTokens',
|
|
111
|
+
default: -1,
|
|
112
|
+
description: 'The maximum number of tokens to generate in the completion. Most models have a context length of 2048 tokens (except for the newest models, which support 32,768).',
|
|
113
|
+
type: 'number',
|
|
114
|
+
typeOptions: {
|
|
115
|
+
maxValue: 128000,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
displayName: 'Response Format',
|
|
120
|
+
name: 'responseFormat',
|
|
121
|
+
default: 'text',
|
|
122
|
+
type: 'options',
|
|
123
|
+
options: [
|
|
124
|
+
{
|
|
125
|
+
name: 'Text',
|
|
126
|
+
value: 'text',
|
|
127
|
+
description: 'Regular text response',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'JSON',
|
|
131
|
+
value: 'json_object',
|
|
132
|
+
description: 'Enables JSON mode, which should guarantee the message the model generates is valid JSON',
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
displayName: 'Presence Penalty',
|
|
138
|
+
name: 'presencePenalty',
|
|
139
|
+
default: 0,
|
|
140
|
+
typeOptions: { maxValue: 2, minValue: -2, numberPrecision: 1 },
|
|
141
|
+
description: "Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics",
|
|
142
|
+
type: 'number',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
displayName: 'Sampling Temperature',
|
|
146
|
+
name: 'temperature',
|
|
147
|
+
default: 0.7,
|
|
148
|
+
typeOptions: { maxValue: 2, minValue: 0, numberPrecision: 1 },
|
|
149
|
+
description: 'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
|
|
150
|
+
type: 'number',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
displayName: 'Timeout',
|
|
154
|
+
name: 'timeout',
|
|
155
|
+
default: 360000,
|
|
156
|
+
description: 'Maximum amount of time a request is allowed to take in milliseconds',
|
|
157
|
+
type: 'number',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
displayName: 'Max Retries',
|
|
161
|
+
name: 'maxRetries',
|
|
162
|
+
default: 2,
|
|
163
|
+
description: 'Maximum number of retries to attempt',
|
|
164
|
+
type: 'number',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
displayName: 'Top P',
|
|
168
|
+
name: 'topP',
|
|
169
|
+
default: 1,
|
|
170
|
+
typeOptions: { maxValue: 1, minValue: 0, numberPrecision: 1 },
|
|
171
|
+
description: 'Controls diversity via nucleus sampling: 0.5 means half of all likelihood-weighted options are considered. We generally recommend altering this or temperature but not both.',
|
|
172
|
+
type: 'number',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
displayName: 'Usage Tracking',
|
|
178
|
+
name: 'usageTracking',
|
|
179
|
+
placeholder: 'Add Usage Tracking Options',
|
|
180
|
+
description: 'Options for tracking token usage and costs',
|
|
181
|
+
type: 'collection',
|
|
182
|
+
default: {},
|
|
183
|
+
options: [
|
|
184
|
+
{
|
|
185
|
+
displayName: 'Enable Usage Tracking',
|
|
186
|
+
name: 'enableTracking',
|
|
187
|
+
type: 'boolean',
|
|
188
|
+
default: true,
|
|
189
|
+
description: 'Whether to track and log usage data',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
displayName: 'User ID',
|
|
193
|
+
name: 'userId',
|
|
194
|
+
type: 'string',
|
|
195
|
+
default: '',
|
|
196
|
+
description: 'User identifier for usage tracking (sent to OpenRouter and logged)',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
displayName: 'Session Key',
|
|
200
|
+
name: 'sessionKey',
|
|
201
|
+
type: 'string',
|
|
202
|
+
default: '',
|
|
203
|
+
description: 'Custom session key for grouping usage (e.g., article_123, batch_xyz)',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
displayName: 'Webhook URL Override',
|
|
207
|
+
name: 'webhookUrlOverride',
|
|
208
|
+
type: 'string',
|
|
209
|
+
default: '',
|
|
210
|
+
description: 'Override the webhook URL from credentials for this specific node',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
displayName: 'Include Cost',
|
|
214
|
+
name: 'includeCost',
|
|
215
|
+
type: 'boolean',
|
|
216
|
+
default: true,
|
|
217
|
+
description: 'Whether to include cost data in usage tracking (requires OpenRouter to return it)',
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
displayName: 'Advanced Options',
|
|
223
|
+
name: 'advancedOptions',
|
|
224
|
+
placeholder: 'Add Advanced Option',
|
|
225
|
+
description: 'Advanced model configuration options',
|
|
226
|
+
type: 'collection',
|
|
227
|
+
default: {},
|
|
228
|
+
options: [
|
|
229
|
+
{
|
|
230
|
+
displayName: 'Model Kwargs (JSON)',
|
|
231
|
+
name: 'modelKwargs',
|
|
232
|
+
type: 'json',
|
|
233
|
+
default: '{}',
|
|
234
|
+
description: 'Additional model kwargs to pass to OpenRouter API as JSON',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
displayName: 'Reasoning Effort',
|
|
238
|
+
name: 'reasoningEffort',
|
|
239
|
+
type: 'options',
|
|
240
|
+
default: '',
|
|
241
|
+
description: 'For reasoning models (o1, etc.), set the reasoning effort level',
|
|
242
|
+
options: [
|
|
243
|
+
{
|
|
244
|
+
name: 'None',
|
|
245
|
+
value: '',
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'Low',
|
|
249
|
+
value: 'low',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'Medium',
|
|
253
|
+
value: 'medium',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'High',
|
|
257
|
+
value: 'high',
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
async supplyData(itemIndex) {
|
|
267
|
+
const credentials = await this.getCredentials('openRouterCustomApi');
|
|
268
|
+
const modelName = this.getNodeParameter('model', itemIndex);
|
|
269
|
+
const options = this.getNodeParameter('options', itemIndex, {});
|
|
270
|
+
const usageTracking = this.getNodeParameter('usageTracking', itemIndex, {});
|
|
271
|
+
const advancedOptions = this.getNodeParameter('advancedOptions', itemIndex, {});
|
|
272
|
+
// Build configuration
|
|
273
|
+
const configuration = {
|
|
274
|
+
baseURL: credentials.url,
|
|
275
|
+
};
|
|
276
|
+
// Build model kwargs
|
|
277
|
+
let modelKwargs = {};
|
|
278
|
+
// Add response format if specified
|
|
279
|
+
if (options.responseFormat && options.responseFormat !== 'text') {
|
|
280
|
+
modelKwargs.response_format = { type: options.responseFormat };
|
|
281
|
+
}
|
|
282
|
+
// Add user ID if specified
|
|
283
|
+
if (usageTracking.userId) {
|
|
284
|
+
modelKwargs.user = usageTracking.userId;
|
|
285
|
+
}
|
|
286
|
+
// Always request usage data from OpenRouter
|
|
287
|
+
modelKwargs.usage = { include: true };
|
|
288
|
+
// Add reasoning effort if specified
|
|
289
|
+
if (advancedOptions.reasoningEffort) {
|
|
290
|
+
modelKwargs.reasoning_effort = advancedOptions.reasoningEffort;
|
|
291
|
+
}
|
|
292
|
+
// Merge custom model kwargs
|
|
293
|
+
if (advancedOptions.modelKwargs) {
|
|
294
|
+
try {
|
|
295
|
+
const customKwargs = typeof advancedOptions.modelKwargs === 'string'
|
|
296
|
+
? JSON.parse(advancedOptions.modelKwargs)
|
|
297
|
+
: advancedOptions.modelKwargs;
|
|
298
|
+
modelKwargs = { ...modelKwargs, ...customKwargs };
|
|
299
|
+
}
|
|
300
|
+
catch (error) {
|
|
301
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid JSON in Model Kwargs', { description: 'Please provide valid JSON for Model Kwargs' });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Determine webhook URL
|
|
305
|
+
const webhookUrl = usageTracking.webhookUrlOverride || credentials.usageWebhookUrl || '';
|
|
306
|
+
const enableTracking = usageTracking.enableTracking !== false;
|
|
307
|
+
const sessionKey = usageTracking.sessionKey || '';
|
|
308
|
+
const userId = usageTracking.userId || '';
|
|
309
|
+
// Build callbacks
|
|
310
|
+
const callbacks = [];
|
|
311
|
+
if (enableTracking && webhookUrl) {
|
|
312
|
+
callbacks.push({
|
|
313
|
+
handleLLMEnd: async (output, runId, parentRunId) => {
|
|
314
|
+
try {
|
|
315
|
+
const generation = output.generations?.[0]?.[0];
|
|
316
|
+
if (!generation)
|
|
317
|
+
return;
|
|
318
|
+
const message = generation.message;
|
|
319
|
+
const usageMetadata = message?.usage_metadata;
|
|
320
|
+
const responseMetadata = message?.response_metadata;
|
|
321
|
+
const usageData = {
|
|
322
|
+
timestamp: new Date().toISOString(),
|
|
323
|
+
run_id: runId,
|
|
324
|
+
parent_run_id: parentRunId,
|
|
325
|
+
model: modelName,
|
|
326
|
+
user: userId || undefined,
|
|
327
|
+
session_key: sessionKey || undefined,
|
|
328
|
+
input_tokens: usageMetadata?.input_tokens,
|
|
329
|
+
output_tokens: usageMetadata?.output_tokens,
|
|
330
|
+
total_tokens: usageMetadata?.total_tokens,
|
|
331
|
+
cost: responseMetadata?.usage?.cost,
|
|
332
|
+
cached_tokens: responseMetadata?.usage?.prompt_tokens_details?.cached_tokens,
|
|
333
|
+
reasoning_tokens: responseMetadata?.usage?.completion_tokens_details?.reasoning_tokens,
|
|
334
|
+
finish_reason: responseMetadata?.finish_reason,
|
|
335
|
+
};
|
|
336
|
+
// Send to webhook
|
|
337
|
+
await fetch(webhookUrl, {
|
|
338
|
+
method: 'POST',
|
|
339
|
+
headers: { 'Content-Type': 'application/json' },
|
|
340
|
+
body: JSON.stringify(usageData),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
// Log error but don't fail the main request
|
|
345
|
+
console.error('Usage tracking webhook failed:', err);
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
// Create the model
|
|
351
|
+
const model = new openai_1.ChatOpenAI({
|
|
352
|
+
apiKey: credentials.apiKey,
|
|
353
|
+
model: modelName,
|
|
354
|
+
configuration,
|
|
355
|
+
timeout: options.timeout ?? 360000,
|
|
356
|
+
maxRetries: options.maxRetries ?? 2,
|
|
357
|
+
temperature: options.temperature,
|
|
358
|
+
maxTokens: options.maxTokens > 0 ? options.maxTokens : undefined,
|
|
359
|
+
topP: options.topP,
|
|
360
|
+
frequencyPenalty: options.frequencyPenalty,
|
|
361
|
+
presencePenalty: options.presencePenalty,
|
|
362
|
+
modelKwargs,
|
|
363
|
+
callbacks,
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
response: model,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
exports.LmChatOpenRouterCustom = LmChatOpenRouterCustom;
|
|
371
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTG1DaGF0T3BlblJvdXRlckN1c3RvbS5ub2RlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL25vZGVzL0xtQ2hhdE9wZW5Sb3V0ZXJDdXN0b20vTG1DaGF0T3BlblJvdXRlckN1c3RvbS5ub2RlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDhDQUErQztBQVMvQywrQ0FHc0I7QUF3QnRCLE1BQWEsc0JBQXNCO0lBQW5DO1FBQ0MsZ0JBQVcsR0FBeUI7WUFDbkMsV0FBVyxFQUFFLGdDQUFnQztZQUM3QyxJQUFJLEVBQUUsd0JBQXdCO1lBQzlCLElBQUksRUFBRSxxQkFBcUI7WUFDM0IsS0FBSyxFQUFFLENBQUMsV0FBVyxDQUFDO1lBQ3BCLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztZQUNaLFdBQVcsRUFBRSx1RUFBdUU7WUFDcEYsUUFBUSxFQUFFO2dCQUNULElBQUksRUFBRSxtQkFBbUI7YUFDekI7WUFDRCxLQUFLLEVBQUU7Z0JBQ04sVUFBVSxFQUFFLENBQUMsSUFBSSxDQUFDO2dCQUNsQixhQUFhLEVBQUU7b0JBQ2QsRUFBRSxFQUFFLENBQUMsaUJBQWlCLEVBQUUsWUFBWSxDQUFDO29CQUNyQyxpQkFBaUIsRUFBRSxDQUFDLDJCQUEyQixDQUFDO2lCQUNoRDtnQkFDRCxTQUFTLEVBQUU7b0JBQ1Ysb0JBQW9CLEVBQUU7d0JBQ3JCOzRCQUNDLEdBQUcsRUFBRSw0QkFBNEI7eUJBQ2pDO3FCQUNEO2lCQUNEO2FBQ0Q7WUFFRCxNQUFNLEVBQUUsRUFBRTtZQUNWLE9BQU8sRUFBRSxDQUFDLGtDQUFtQixDQUFDLGVBQWUsQ0FBQztZQUM5QyxXQUFXLEVBQUUsQ0FBQyxPQUFPLENBQUM7WUFDdEIsV0FBVyxFQUFFO2dCQUNaO29CQUNDLElBQUksRUFBRSxxQkFBcUI7b0JBQzNCLFFBQVEsRUFBRSxJQUFJO2lCQUNkO2FBQ0Q7WUFDRCxlQUFlLEVBQUU7Z0JBQ2hCLHNCQUFzQixFQUFFLElBQUk7Z0JBQzVC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QUFPO1lBQ04sUUFBUSxFQUFFLEtBQUs7U0FDZixDQUFDO0lBQ0gsQ0FBQztDQUNEO0FBbllELHdEQW1ZQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENoYXRPcGVuQUkgfSBmcm9tICdAbGFuZ2NoYWluL29wZW5haSc7XG5pbXBvcnQgdHlwZSB7IENsaWVudE9wdGlvbnMgfSBmcm9tICdAbGFuZ2NoYWluL29wZW5haSc7XG5pbXBvcnQgdHlwZSB7XG5cdElOb2RlVHlwZSxcblx0SU5vZGVUeXBlRGVzY3JpcHRpb24sXG5cdElTdXBwbHlEYXRhRnVuY3Rpb25zLFxuXHRTdXBwbHlEYXRhLFxuXHRJRGF0YU9iamVjdCxcbn0gZnJvbSAnbjhuLXdvcmtmbG93JztcbmltcG9ydCB7XG5cdE5vZGVDb25uZWN0aW9uVHlwZXMsXG5cdE5vZGVPcGVyYXRpb25FcnJvcixcbn0gZnJvbSAnbjhuLXdvcmtmbG93JztcblxuaW50ZXJmYWNlIE9wZW5Sb3V0ZXJDcmVkZW50aWFsIHtcblx0YXBpS2V5OiBzdHJpbmc7XG5cdHVybDogc3RyaW5nO1xuXHR1c2FnZVdlYmhvb2tVcmw/OiBzdHJpbmc7XG59XG5cbmludGVyZmFjZSBVc2FnZURhdGEge1xuXHR0aW1lc3RhbXA6IHN0cmluZztcblx0cnVuX2lkOiBzdHJpbmc7XG5cdHBhcmVudF9ydW5faWQ/OiBzdHJpbmc7XG5cdG1vZGVsOiBzdHJpbmc7XG5cdHVzZXI/OiBzdHJpbmc7XG5cdHNlc3Npb25fa2V5Pzogc3RyaW5nO1xuXHRpbnB1dF90b2tlbnM/OiBudW1iZXI7XG5cdG91dHB1dF90b2tlbnM/OiBudW1iZXI7XG5cdHRvdGFsX3Rva2Vucz86IG51bWJlcjtcblx0Y29zdD86IG51bWJlcjtcblx0Y2FjaGVkX3Rva2Vucz86IG51bWJlcjtcblx0cmVhc29uaW5nX3Rva2Vucz86IG51bWJlcjtcblx0ZmluaXNoX3JlYXNvbj86IHN0cmluZztcbn1cblxuZXhwb3J0IGNsYXNzIExtQ2hhdE9wZW5Sb3V0ZXJDdXN0b20gaW1wbGVtZW50cyBJTm9kZVR5cGUge1xuXHRkZXNjcmlwdGlvbjogSU5vZGVUeXBlRGVzY3JpcHRpb24gPSB7XG5cdFx0ZGlzcGxheU5hbWU6ICdPcGVuUm91dGVyIENoYXQgTW9kZWwgKEN1c3RvbSknLFxuXHRcdG5hbWU6ICdsbUNoYXRPcGVuUm91dGVyQ3VzdG9tJyxcblx0XHRpY29uOiAnZmlsZTpvcGVucm91dGVyLnN2ZycsXG5cdFx0Z3JvdXA6IFsndHJhbnNmb3JtJ10sXG5cdFx0dmVyc2lvbjogWzFdLFxuXHRcdGRlc2NyaXB0aW9uOiAnQ3VzdG9tIE9wZW5Sb3V0ZXIgQ2hhdCBNb2RlbCB3aXRoIHVzYWdlIHRyYWNraW5nIGFuZCBleHRlbmRlZCBvcHRpb25zJyxcblx0XHRkZWZhdWx0czoge1xuXHRcdFx0bmFtZTogJ09wZW5Sb3V0ZXIgQ3VzdG9tJyxcblx0XHR9LFxuXHRcdGNvZGV4OiB7XG5cdFx0XHRjYXRlZ29yaWVzOiBbJ0FJJ10sXG5cdFx0XHRzdWJjYXRlZ29yaWVzOiB7XG5cdFx0XHRcdEFJOiBbJ0xhbmd1YWdlIE1vZGVscycsICdSb290IE5vZGVzJ10sXG5cdFx0XHRcdCdMYW5ndWFnZSBNb2RlbHMnOiBbJ0NoYXQgTW9kZWxzIChSZWNvbW1lbmRlZCknXSxcblx0XHRcdH0sXG5cdFx0XHRyZXNvdXJjZXM6IHtcblx0XHRcdFx0cHJpbWFyeURvY3VtZW50YXRpb246IFtcblx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHR1cmw6ICdodHRwczovL29wZW5yb3V0ZXIuYWkvZG9jcycsXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XSxcblx0XHRcdH0sXG5cdFx0fSxcblxuXHRcdGlucHV0czogW10sXG5cdFx0b3V0cHV0czogW05vZGVDb25uZWN0aW9uVHlwZXMuQWlMYW5ndWFnZU1vZGVsXSxcblx0XHRvdXRwdXROYW1lczogWydNb2RlbCddLFxuXHRcdGNyZWRlbnRpYWxzOiBbXG5cdFx0XHR7XG5cdFx0XHRcdG5hbWU6ICdvcGVuUm91dGVyQ3VzdG9tQXBpJyxcblx0XHRcdFx0cmVxdWlyZWQ6IHRydWUsXG5cdFx0XHR9LFxuXHRcdF0sXG5cdFx0cmVxdWVzdERlZmF1bHRzOiB7XG5cdFx0XHRpZ25vcmVIdHRwU3RhdHVzRXJyb3JzOiB0cnVlLFxuXHRcdFx0YmFzZVVSTDogJz17eyAkY3JlZGVudGlhbHM/LnVybCB9fScsXG5cdFx0fSxcblx0XHRwcm9wZXJ0aWVzOiBbXG5cdFx0XHR7XG5cdFx0XHRcdGRpc3BsYXlOYW1lOiAnTW9kZWwnLFxuXHRcdFx0XHRuYW1lOiAnbW9kZWwnLFxuXHRcdFx0XHR0eXBlOiAnb3B0aW9ucycsXG5cdFx0XHRcdGRlc2NyaXB0aW9uOlxuXHRcdFx0XHRcdCdUaGUgbW9kZWwgd2hpY2ggd2lsbCBnZW5lcmF0ZSB0aGUgY29tcGxldGlvbi4gPGEgaHJlZj1cImh0dHBzOi8vb3BlbnJvdXRlci5haS9kb2NzL21vZGVsc1wiPkxlYXJuIG1vcmU8L2E+LicsXG5cdFx0XHRcdHR5cGVPcHRpb25zOiB7XG5cdFx0XHRcdFx0bG9hZE9wdGlvbnM6IHtcblx0XHRcdFx0XHRcdHJvdXRpbmc6IHtcblx0XHRcdFx0XHRcdFx0cmVxdWVzdDoge1xuXHRcdFx0XHRcdFx0XHRcdG1ldGhvZDogJ0dFVCcsXG5cdFx0XHRcdFx0XHRcdFx0dXJsOiAnL21vZGVscycsXG5cdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdG91dHB1dDoge1xuXHRcdFx0XHRcdFx0XHRcdHBvc3RSZWNlaXZlOiBbXG5cdFx0XHRcdFx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHRcdFx0XHRcdHR5cGU6ICdyb290UHJvcGVydHknLFxuXHRcdFx0XHRcdFx0XHRcdFx0XHRwcm9wZXJ0aWVzOiB7XG5cdFx0XHRcdFx0XHRcdFx0XHRcdFx0cHJvcGVydHk6ICdkYXRhJyxcblx0XHRcdFx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHRcdFx0XHRcdHR5cGU6ICdzZXRLZXlWYWx1ZScsXG5cdFx0XHRcdFx0XHRcdFx0XHRcdHByb3BlcnRpZXM6IHtcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHRuYW1lOiAnPXt7JHJlc3BvbnNlSXRlbS5pZH19Jyxcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHR2YWx1ZTogJz17eyRyZXNwb25zZUl0ZW0uaWR9fScsXG5cdFx0XHRcdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0XHRcdFx0XHR0eXBlOiAnc29ydCcsXG5cdFx0XHRcdFx0XHRcdFx0XHRcdHByb3BlcnRpZXM6IHtcblx0XHRcdFx0XHRcdFx0XHRcdFx0XHRrZXk6ICduYW1lJyxcblx0XHRcdFx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdFx0XSxcblx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0fSxcblx0XHRcdFx0cm91dGluZzoge1xuXHRcdFx0XHRcdHNlbmQ6IHtcblx0XHRcdFx0XHRcdHR5cGU6ICdib2R5Jyxcblx0XHRcdFx0XHRcdHByb3BlcnR5OiAnbW9kZWwnLFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdH0sXG5cdFx0XHRcdGRlZmF1bHQ6ICdvcGVuYWkvZ3B0LTQuMS1taW5pJyxcblx0XHRcdH0sXG5cdFx0XHR7XG5cdFx0XHRcdGRpc3BsYXlOYW1lOiAnT3B0aW9ucycsXG5cdFx0XHRcdG5hbWU6ICdvcHRpb25zJyxcblx0XHRcdFx0cGxhY2Vob2xkZXI6ICdBZGQgT3B0aW9uJyxcblx0XHRcdFx0ZGVzY3JpcHRpb246ICdBZGRpdGlvbmFsIG9wdGlvbnMgdG8gYWRkJyxcblx0XHRcdFx0dHlwZTogJ2NvbGxlY3Rpb24nLFxuXHRcdFx0XHRkZWZhdWx0OiB7fSxcblx0XHRcdFx0b3B0aW9uczogW1xuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnRnJlcXVlbmN5IFBlbmFsdHknLFxuXHRcdFx0XHRcdFx0bmFtZTogJ2ZyZXF1ZW5jeVBlbmFsdHknLFxuXHRcdFx0XHRcdFx0ZGVmYXVsdDogMCxcblx0XHRcdFx0XHRcdHR5cGVPcHRpb25zOiB7IG1heFZhbHVlOiAyLCBtaW5WYWx1ZTogLTIsIG51bWJlclByZWNpc2lvbjogMSB9LFxuXHRcdFx0XHRcdFx0ZGVzY3JpcHRpb246XG5cdFx0XHRcdFx0XHRcdFwiUG9zaXRpdmUgdmFsdWVzIHBlbmFsaXplIG5ldyB0b2tlbnMgYmFzZWQgb24gdGhlaXIgZXhpc3RpbmcgZnJlcXVlbmN5IGluIHRoZSB0ZXh0IHNvIGZhciwgZGVjcmVhc2luZyB0aGUgbW9kZWwncyBsaWtlbGlob29kIHRvIHJlcGVhdCB0aGUgc2FtZSBsaW5lIHZlcmJhdGltXCIsXG5cdFx0XHRcdFx0XHR0eXBlOiAnbnVtYmVyJyxcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnTWF4aW11bSBOdW1iZXIgb2YgVG9rZW5zJyxcblx0XHRcdFx0XHRcdG5hbWU6ICdtYXhUb2tlbnMnLFxuXHRcdFx0XHRcdFx0ZGVmYXVsdDogLTEsXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjpcblx0XHRcdFx0XHRcdFx0J1RoZSBtYXhpbXVtIG51bWJlciBvZiB0b2tlbnMgdG8gZ2VuZXJhdGUgaW4gdGhlIGNvbXBsZXRpb24uIE1vc3QgbW9kZWxzIGhhdmUgYSBjb250ZXh0IGxlbmd0aCBvZiAyMDQ4IHRva2VucyAoZXhjZXB0IGZvciB0aGUgbmV3ZXN0IG1vZGVscywgd2hpY2ggc3VwcG9ydCAzMiw3NjgpLicsXG5cdFx0XHRcdFx0XHR0eXBlOiAnbnVtYmVyJyxcblx0XHRcdFx0XHRcdHR5cGVPcHRpb25zOiB7XG5cdFx0XHRcdFx0XHRcdG1heFZhbHVlOiAxMjgwMDAsXG5cdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0ZGlzcGxheU5hbWU6ICdSZXNwb25zZSBGb3JtYXQnLFxuXHRcdFx0XHRcdFx0bmFtZTogJ3Jlc3BvbnNlRm9ybWF0Jyxcblx0XHRcdFx0XHRcdGRlZmF1bHQ6ICd0ZXh0Jyxcblx0XHRcdFx0XHRcdHR5cGU6ICdvcHRpb25zJyxcblx0XHRcdFx0XHRcdG9wdGlvbnM6IFtcblx0XHRcdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0XHRcdG5hbWU6ICdUZXh0Jyxcblx0XHRcdFx0XHRcdFx0XHR2YWx1ZTogJ3RleHQnLFxuXHRcdFx0XHRcdFx0XHRcdGRlc2NyaXB0aW9uOiAnUmVndWxhciB0ZXh0IHJlc3BvbnNlJyxcblx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0XHRcdG5hbWU6ICdKU09OJyxcblx0XHRcdFx0XHRcdFx0XHR2YWx1ZTogJ2pzb25fb2JqZWN0Jyxcblx0XHRcdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjpcblx0XHRcdFx0XHRcdFx0XHRcdCdFbmFibGVzIEpTT04gbW9kZSwgd2hpY2ggc2hvdWxkIGd1YXJhbnRlZSB0aGUgbWVzc2FnZSB0aGUgbW9kZWwgZ2VuZXJhdGVzIGlzIHZhbGlkIEpTT04nLFxuXHRcdFx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdFx0XSxcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnUHJlc2VuY2UgUGVuYWx0eScsXG5cdFx0XHRcdFx0XHRuYW1lOiAncHJlc2VuY2VQZW5hbHR5Jyxcblx0XHRcdFx0XHRcdGRlZmF1bHQ6IDAsXG5cdFx0XHRcdFx0XHR0eXBlT3B0aW9uczogeyBtYXhWYWx1ZTogMiwgbWluVmFsdWU6IC0yLCBudW1iZXJQcmVjaXNpb246IDEgfSxcblx0XHRcdFx0XHRcdGRlc2NyaXB0aW9uOlxuXHRcdFx0XHRcdFx0XHRcIlBvc2l0aXZlIHZhbHVlcyBwZW5hbGl6ZSBuZXcgdG9rZW5zIGJhc2VkIG9uIHdoZXRoZXIgdGhleSBhcHBlYXIgaW4gdGhlIHRleHQgc28gZmFyLCBpbmNyZWFzaW5nIHRoZSBtb2RlbCdzIGxpa2VsaWhvb2QgdG8gdGFsayBhYm91dCBuZXcgdG9waWNzXCIsXG5cdFx0XHRcdFx0XHR0eXBlOiAnbnVtYmVyJyxcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnU2FtcGxpbmcgVGVtcGVyYXR1cmUnLFxuXHRcdFx0XHRcdFx0bmFtZTogJ3RlbXBlcmF0dXJlJyxcblx0XHRcdFx0XHRcdGRlZmF1bHQ6IDAuNyxcblx0XHRcdFx0XHRcdHR5cGVPcHRpb25zOiB7IG1heFZhbHVlOiAyLCBtaW5WYWx1ZTogMCwgbnVtYmVyUHJlY2lzaW9uOiAxIH0sXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjpcblx0XHRcdFx0XHRcdFx0J0NvbnRyb2xzIHJhbmRvbW5lc3M6IExvd2VyaW5nIHJlc3VsdHMgaW4gbGVzcyByYW5kb20gY29tcGxldGlvbnMuIEFzIHRoZSB0ZW1wZXJhdHVyZSBhcHByb2FjaGVzIHplcm8sIHRoZSBtb2RlbCB3aWxsIGJlY29tZSBkZXRlcm1pbmlzdGljIGFuZCByZXBldGl0aXZlLicsXG5cdFx0XHRcdFx0XHR0eXBlOiAnbnVtYmVyJyxcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnVGltZW91dCcsXG5cdFx0XHRcdFx0XHRuYW1lOiAndGltZW91dCcsXG5cdFx0XHRcdFx0XHRkZWZhdWx0OiAzNjAwMDAsXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjogJ01heGltdW0gYW1vdW50IG9mIHRpbWUgYSByZXF1ZXN0IGlzIGFsbG93ZWQgdG8gdGFrZSBpbiBtaWxsaXNlY29uZHMnLFxuXHRcdFx0XHRcdFx0dHlwZTogJ251bWJlcicsXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHRkaXNwbGF5TmFtZTogJ01heCBSZXRyaWVzJyxcblx0XHRcdFx0XHRcdG5hbWU6ICdtYXhSZXRyaWVzJyxcblx0XHRcdFx0XHRcdGRlZmF1bHQ6IDIsXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjogJ01heGltdW0gbnVtYmVyIG9mIHJldHJpZXMgdG8gYXR0ZW1wdCcsXG5cdFx0XHRcdFx0XHR0eXBlOiAnbnVtYmVyJyxcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnVG9wIFAnLFxuXHRcdFx0XHRcdFx0bmFtZTogJ3RvcFAnLFxuXHRcdFx0XHRcdFx0ZGVmYXVsdDogMSxcblx0XHRcdFx0XHRcdHR5cGVPcHRpb25zOiB7IG1heFZhbHVlOiAxLCBtaW5WYWx1ZTogMCwgbnVtYmVyUHJlY2lzaW9uOiAxIH0sXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjpcblx0XHRcdFx0XHRcdFx0J0NvbnRyb2xzIGRpdmVyc2l0eSB2aWEgbnVjbGV1cyBzYW1wbGluZzogMC41IG1lYW5zIGhhbGYgb2YgYWxsIGxpa2VsaWhvb2Qtd2VpZ2h0ZWQgb3B0aW9ucyBhcmUgY29uc2lkZXJlZC4gV2UgZ2VuZXJhbGx5IHJlY29tbWVuZCBhbHRlcmluZyB0aGlzIG9yIHRlbXBlcmF0dXJlIGJ1dCBub3QgYm90aC4nLFxuXHRcdFx0XHRcdFx0dHlwZTogJ251bWJlcicsXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XSxcblx0XHRcdH0sXG5cdFx0XHR7XG5cdFx0XHRcdGRpc3BsYXlOYW1lOiAnVXNhZ2UgVHJhY2tpbmcnLFxuXHRcdFx0XHRuYW1lOiAndXNhZ2VUcmFja2luZycsXG5cdFx0XHRcdHBsYWNlaG9sZGVyOiAnQWRkIFVzYWdlIFRyYWNraW5nIE9wdGlvbnMnLFxuXHRcdFx0XHRkZXNjcmlwdGlvbjogJ09wdGlvbnMgZm9yIHRyYWNraW5nIHRva2VuIHVzYWdlIGFuZCBjb3N0cycsXG5cdFx0XHRcdHR5cGU6ICdjb2xsZWN0aW9uJyxcblx0XHRcdFx0ZGVmYXVsdDoge30sXG5cdFx0XHRcdG9wdGlvbnM6IFtcblx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHRkaXNwbGF5TmFtZTogJ0VuYWJsZSBVc2FnZSBUcmFja2luZycsXG5cdFx0XHRcdFx0XHRuYW1lOiAnZW5hYmxlVHJhY2tpbmcnLFxuXHRcdFx0XHRcdFx0dHlwZTogJ2Jvb2xlYW4nLFxuXHRcdFx0XHRcdFx0ZGVmYXVsdDogdHJ1ZSxcblx0XHRcdFx0XHRcdGRlc2NyaXB0aW9uOiAnV2hldGhlciB0byB0cmFjayBhbmQgbG9nIHVzYWdlIGRhdGEnLFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0ZGlzcGxheU5hbWU6ICdVc2VyIElEJyxcblx0XHRcdFx0XHRcdG5hbWU6ICd1c2VySWQnLFxuXHRcdFx0XHRcdFx0dHlwZTogJ3N0cmluZycsXG5cdFx0XHRcdFx0XHRkZWZhdWx0OiAnJyxcblx0XHRcdFx0XHRcdGRlc2NyaXB0aW9uOiAnVXNlciBpZGVudGlmaWVyIGZvciB1c2FnZSB0cmFja2luZyAoc2VudCB0byBPcGVuUm91dGVyIGFuZCBsb2dnZWQpJyxcblx0XHRcdFx0XHR9LFxuXHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdGRpc3BsYXlOYW1lOiAnU2Vzc2lvbiBLZXknLFxuXHRcdFx0XHRcdFx0bmFtZTogJ3Nlc3Npb25LZXknLFxuXHRcdFx0XHRcdFx0dHlwZTogJ3N0cmluZycsXG5cdFx0XHRcdFx0XHRkZWZhdWx0OiAnJyxcblx0XHRcdFx0XHRcdGRlc2NyaXB0aW9uOiAnQ3VzdG9tIHNlc3Npb24ga2V5IGZvciBncm91cGluZyB1c2FnZSAoZS5nLiwgYXJ0aWNsZV8xMjMsIGJhdGNoX3h5eiknLFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0ZGlzcGxheU5hbWU6ICdXZWJob29rIFVSTCBPdmVycmlkZScsXG5cdFx0XHRcdFx0XHRuYW1lOiAnd2ViaG9va1VybE92ZXJyaWRlJyxcblx0XHRcdFx0XHRcdHR5cGU6ICdzdHJpbmcnLFxuXHRcdFx0XHRcdFx0ZGVmYXVsdDogJycsXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjogJ092ZXJyaWRlIHRoZSB3ZWJob29rIFVSTCBmcm9tIGNyZWRlbnRpYWxzIGZvciB0aGlzIHNwZWNpZmljIG5vZGUnLFxuXHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0ZGlzcGxheU5hbWU6ICdJbmNsdWRlIENvc3QnLFxuXHRcdFx0XHRcdFx0bmFtZTogJ2luY2x1ZGVDb3N0Jyxcblx0XHRcdFx0XHRcdHR5cGU6ICdib29sZWFuJyxcblx0XHRcdFx0XHRcdGRlZmF1bHQ6IHRydWUsXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjogJ1doZXRoZXIgdG8gaW5jbHVkZSBjb3N0IGRhdGEgaW4gdXNhZ2UgdHJhY2tpbmcgKHJlcXVpcmVzIE9wZW5Sb3V0ZXIgdG8gcmV0dXJuIGl0KScsXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XSxcblx0XHRcdH0sXG5cdFx0XHR7XG5cdFx0XHRcdGRpc3BsYXlOYW1lOiAnQWR2YW5jZWQgT3B0aW9ucycsXG5cdFx0XHRcdG5hbWU6ICdhZHZhbmNlZE9wdGlvbnMnLFxuXHRcdFx0XHRwbGFjZWhvbGRlcjogJ0FkZCBBZHZhbmNlZCBPcHRpb24nLFxuXHRcdFx0XHRkZXNjcmlwdGlvbjogJ0FkdmFuY2VkIG1vZGVsIGNvbmZpZ3VyYXRpb24gb3B0aW9ucycsXG5cdFx0XHRcdHR5cGU6ICdjb2xsZWN0aW9uJyxcblx0XHRcdFx0ZGVmYXVsdDoge30sXG5cdFx0XHRcdG9wdGlvbnM6IFtcblx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHRkaXNwbGF5TmFtZTogJ01vZGVsIEt3YXJncyAoSlNPTiknLFxuXHRcdFx0XHRcdFx0bmFtZTogJ21vZGVsS3dhcmdzJyxcblx0XHRcdFx0XHRcdHR5cGU6ICdqc29uJyxcblx0XHRcdFx0XHRcdGRlZmF1bHQ6ICd7fScsXG5cdFx0XHRcdFx0XHRkZXNjcmlwdGlvbjogJ0FkZGl0aW9uYWwgbW9kZWwga3dhcmdzIHRvIHBhc3MgdG8gT3BlblJvdXRlciBBUEkgYXMgSlNPTicsXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XHR7XG5cdFx0XHRcdFx0XHRkaXNwbGF5TmFtZTogJ1JlYXNvbmluZyBFZmZvcnQnLFxuXHRcdFx0XHRcdFx0bmFtZTogJ3JlYXNvbmluZ0VmZm9ydCcsXG5cdFx0XHRcdFx0XHR0eXBlOiAnb3B0aW9ucycsXG5cdFx0XHRcdFx0XHRkZWZhdWx0OiAnJyxcblx0XHRcdFx0XHRcdGRlc2NyaXB0aW9uOiAnRm9yIHJlYXNvbmluZyBtb2RlbHMgKG8xLCBldGMuKSwgc2V0IHRoZSByZWFzb25pbmcgZWZmb3J0IGxldmVsJyxcblx0XHRcdFx0XHRcdG9wdGlvbnM6IFtcblx0XHRcdFx0XHRcdFx0e1xuXHRcdFx0XHRcdFx0XHRcdG5hbWU6ICdOb25lJyxcblx0XHRcdFx0XHRcdFx0XHR2YWx1ZTogJycsXG5cdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdFx0XHRuYW1lOiAnTG93Jyxcblx0XHRcdFx0XHRcdFx0XHR2YWx1ZTogJ2xvdycsXG5cdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdFx0XHRuYW1lOiAnTWVkaXVtJyxcblx0XHRcdFx0XHRcdFx0XHR2YWx1ZTogJ21lZGl1bScsXG5cdFx0XHRcdFx0XHRcdH0sXG5cdFx0XHRcdFx0XHRcdHtcblx0XHRcdFx0XHRcdFx0XHRuYW1lOiAnSGlnaCcsXG5cdFx0XHRcdFx0XHRcdFx0dmFsdWU6ICdoaWdoJyxcblx0XHRcdFx0XHRcdFx0fSxcblx0XHRcdFx0XHRcdF0sXG5cdFx0XHRcdFx0fSxcblx0XHRcdFx0XSxcblx0XHRcdH0sXG5cdFx0XSxcblx0fTtcblxuXHRhc3luYyBzdXBwbHlEYXRhKHRoaXM6IElTdXBwbHlEYXRhRnVuY3Rpb25zLCBpdGVtSW5kZXg6IG51bWJlcik6IFByb21pc2U8U3VwcGx5RGF0YT4ge1xuXHRcdGNvbnN0IGNyZWRlbnRpYWxzID0gYXdhaXQgdGhpcy5nZXRDcmVkZW50aWFsczxPcGVuUm91dGVyQ3JlZGVudGlhbD4oJ29wZW5Sb3V0ZXJDdXN0b21BcGknKTtcblxuXHRcdGNvbnN0IG1vZGVsTmFtZSA9IHRoaXMuZ2V0Tm9kZVBhcmFtZXRlcignbW9kZWwnLCBpdGVtSW5kZXgpIGFzIHN0cmluZztcblx0XHRjb25zdCBvcHRpb25zID0gdGhpcy5nZXROb2RlUGFyYW1ldGVyKCdvcHRpb25zJywgaXRlbUluZGV4LCB7fSkgYXMgSURhdGFPYmplY3Q7XG5cdFx0Y29uc3QgdXNhZ2VUcmFja2luZyA9IHRoaXMuZ2V0Tm9kZVBhcmFtZXRlcigndXNhZ2VUcmFja2luZycsIGl0ZW1JbmRleCwge30pIGFzIElEYXRhT2JqZWN0O1xuXHRcdGNvbnN0IGFkdmFuY2VkT3B0aW9ucyA9IHRoaXMuZ2V0Tm9kZVBhcmFtZXRlcignYWR2YW5jZWRPcHRpb25zJywgaXRlbUluZGV4LCB7fSkgYXMgSURhdGFPYmplY3Q7XG5cblx0XHQvLyBCdWlsZCBjb25maWd1cmF0aW9uXG5cdFx0Y29uc3QgY29uZmlndXJhdGlvbjogQ2xpZW50T3B0aW9ucyA9IHtcblx0XHRcdGJhc2VVUkw6IGNyZWRlbnRpYWxzLnVybCxcblx0XHR9O1xuXG5cdFx0Ly8gQnVpbGQgbW9kZWwga3dhcmdzXG5cdFx0bGV0IG1vZGVsS3dhcmdzOiBJRGF0YU9iamVjdCA9IHt9O1xuXG5cdFx0Ly8gQWRkIHJlc3BvbnNlIGZvcm1hdCBpZiBzcGVjaWZpZWRcblx0XHRpZiAob3B0aW9ucy5yZXNwb25zZUZvcm1hdCAmJiBvcHRpb25zLnJlc3BvbnNlRm9ybWF0ICE9PSAndGV4dCcpIHtcblx0XHRcdG1vZGVsS3dhcmdzLnJlc3BvbnNlX2Zvcm1hdCA9IHsgdHlwZTogb3B0aW9ucy5yZXNwb25zZUZvcm1hdCB9O1xuXHRcdH1cblxuXHRcdC8vIEFkZCB1c2VyIElEIGlmIHNwZWNpZmllZFxuXHRcdGlmICh1c2FnZVRyYWNraW5nLnVzZXJJZCkge1xuXHRcdFx0bW9kZWxLd2FyZ3MudXNlciA9IHVzYWdlVHJhY2tpbmcudXNlcklkO1xuXHRcdH1cblxuXHRcdC8vIEFsd2F5cyByZXF1ZXN0IHVzYWdlIGRhdGEgZnJvbSBPcGVuUm91dGVyXG5cdFx0bW9kZWxLd2FyZ3MudXNhZ2UgPSB7IGluY2x1ZGU6IHRydWUgfTtcblxuXHRcdC8vIEFkZCByZWFzb25pbmcgZWZmb3J0IGlmIHNwZWNpZmllZFxuXHRcdGlmIChhZHZhbmNlZE9wdGlvbnMucmVhc29uaW5nRWZmb3J0KSB7XG5cdFx0XHRtb2RlbEt3YXJncy5yZWFzb25pbmdfZWZmb3J0ID0gYWR2YW5jZWRPcHRpb25zLnJlYXNvbmluZ0VmZm9ydDtcblx0XHR9XG5cblx0XHQvLyBNZXJnZSBjdXN0b20gbW9kZWwga3dhcmdzXG5cdFx0aWYgKGFkdmFuY2VkT3B0aW9ucy5tb2RlbEt3YXJncykge1xuXHRcdFx0dHJ5IHtcblx0XHRcdFx0Y29uc3QgY3VzdG9tS3dhcmdzID0gdHlwZW9mIGFkdmFuY2VkT3B0aW9ucy5tb2RlbEt3YXJncyA9PT0gJ3N0cmluZydcblx0XHRcdFx0XHQ/IEpTT04ucGFyc2UoYWR2YW5jZWRPcHRpb25zLm1vZGVsS3dhcmdzKVxuXHRcdFx0XHRcdDogYWR2YW5jZWRPcHRpb25zLm1vZGVsS3dhcmdzO1xuXHRcdFx0XHRtb2RlbEt3YXJncyA9IHsgLi4ubW9kZWxLd2FyZ3MsIC4uLmN1c3RvbUt3YXJncyB9O1xuXHRcdFx0fSBjYXRjaCAoZXJyb3IpIHtcblx0XHRcdFx0dGhyb3cgbmV3IE5vZGVPcGVyYXRpb25FcnJvcihcblx0XHRcdFx0XHR0aGlzLmdldE5vZGUoKSxcblx0XHRcdFx0XHQnSW52YWxpZCBKU09OIGluIE1vZGVsIEt3YXJncycsXG5cdFx0XHRcdFx0eyBkZXNjcmlwdGlvbjogJ1BsZWFzZSBwcm92aWRlIHZhbGlkIEpTT04gZm9yIE1vZGVsIEt3YXJncycgfVxuXHRcdFx0XHQpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdC8vIERldGVybWluZSB3ZWJob29rIFVSTFxuXHRcdGNvbnN0IHdlYmhvb2tVcmwgPSAodXNhZ2VUcmFja2luZy53ZWJob29rVXJsT3ZlcnJpZGUgYXMgc3RyaW5nKSB8fCBjcmVkZW50aWFscy51c2FnZVdlYmhvb2tVcmwgfHwgJyc7XG5cdFx0Y29uc3QgZW5hYmxlVHJhY2tpbmcgPSB1c2FnZVRyYWNraW5nLmVuYWJsZVRyYWNraW5nICE9PSBmYWxzZTtcblx0XHRjb25zdCBzZXNzaW9uS2V5ID0gdXNhZ2VUcmFja2luZy5zZXNzaW9uS2V5IGFzIHN0cmluZyB8fCAnJztcblx0XHRjb25zdCB1c2VySWQgPSB1c2FnZVRyYWNraW5nLnVzZXJJZCBhcyBzdHJpbmcgfHwgJyc7XG5cblx0XHQvLyBCdWlsZCBjYWxsYmFja3Ncblx0XHRjb25zdCBjYWxsYmFja3M6IGFueVtdID0gW107XG5cblx0XHRpZiAoZW5hYmxlVHJhY2tpbmcgJiYgd2ViaG9va1VybCkge1xuXHRcdFx0Y2FsbGJhY2tzLnB1c2goe1xuXHRcdFx0XHRoYW5kbGVMTE1FbmQ6IGFzeW5jIChvdXRwdXQ6IGFueSwgcnVuSWQ6IHN0cmluZywgcGFyZW50UnVuSWQ/OiBzdHJpbmcpID0+IHtcblx0XHRcdFx0XHR0cnkge1xuXHRcdFx0XHRcdFx0Y29uc3QgZ2VuZXJhdGlvbiA9IG91dHB1dC5nZW5lcmF0aW9ucz8uWzBdPy5bMF07XG5cdFx0XHRcdFx0XHRpZiAoIWdlbmVyYXRpb24pIHJldHVybjtcblxuXHRcdFx0XHRcdFx0Y29uc3QgbWVzc2FnZSA9IGdlbmVyYXRpb24ubWVzc2FnZTtcblx0XHRcdFx0XHRcdGNvbnN0IHVzYWdlTWV0YWRhdGEgPSBtZXNzYWdlPy51c2FnZV9tZXRhZGF0YTtcblx0XHRcdFx0XHRcdGNvbnN0IHJlc3BvbnNlTWV0YWRhdGEgPSBtZXNzYWdlPy5yZXNwb25zZV9tZXRhZGF0YTtcblxuXHRcdFx0XHRcdFx0Y29uc3QgdXNhZ2VEYXRhOiBVc2FnZURhdGEgPSB7XG5cdFx0XHRcdFx0XHRcdHRpbWVzdGFtcDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuXHRcdFx0XHRcdFx0XHRydW5faWQ6IHJ1bklkLFxuXHRcdFx0XHRcdFx0XHRwYXJlbnRfcnVuX2lkOiBwYXJlbnRSdW5JZCxcblx0XHRcdFx0XHRcdFx0bW9kZWw6IG1vZGVsTmFtZSxcblx0XHRcdFx0XHRcdFx0dXNlcjogdXNlcklkIHx8IHVuZGVmaW5lZCxcblx0XHRcdFx0XHRcdFx0c2Vzc2lvbl9rZXk6IHNlc3Npb25LZXkgfHwgdW5kZWZpbmVkLFxuXHRcdFx0XHRcdFx0XHRpbnB1dF90b2tlbnM6IHVzYWdlTWV0YWRhdGE/LmlucHV0X3Rva2Vucyxcblx0XHRcdFx0XHRcdFx0b3V0cHV0X3Rva2VuczogdXNhZ2VNZXRhZGF0YT8ub3V0cHV0X3Rva2Vucyxcblx0XHRcdFx0XHRcdFx0dG90YWxfdG9rZW5zOiB1c2FnZU1ldGFkYXRhPy50b3RhbF90b2tlbnMsXG5cdFx0XHRcdFx0XHRcdGNvc3Q6IHJlc3BvbnNlTWV0YWRhdGE/LnVzYWdlPy5jb3N0LFxuXHRcdFx0XHRcdFx0XHRjYWNoZWRfdG9rZW5zOiByZXNwb25zZU1ldGFkYXRhPy51c2FnZT8ucHJvbXB0X3Rva2Vuc19kZXRhaWxzPy5jYWNoZWRfdG9rZW5zLFxuXHRcdFx0XHRcdFx0XHRyZWFzb25pbmdfdG9rZW5zOiByZXNwb25zZU1ldGFkYXRhPy51c2FnZT8uY29tcGxldGlvbl90b2tlbnNfZGV0YWlscz8ucmVhc29uaW5nX3Rva2Vucyxcblx0XHRcdFx0XHRcdFx0ZmluaXNoX3JlYXNvbjogcmVzcG9uc2VNZXRhZGF0YT8uZmluaXNoX3JlYXNvbixcblx0XHRcdFx0XHRcdH07XG5cblx0XHRcdFx0XHRcdC8vIFNlbmQgdG8gd2ViaG9va1xuXHRcdFx0XHRcdFx0YXdhaXQgZmV0Y2god2ViaG9va1VybCwge1xuXHRcdFx0XHRcdFx0XHRtZXRob2Q6ICdQT1NUJyxcblx0XHRcdFx0XHRcdFx0aGVhZGVyczogeyAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nIH0sXG5cdFx0XHRcdFx0XHRcdGJvZHk6IEpTT04uc3RyaW5naWZ5KHVzYWdlRGF0YSksXG5cdFx0XHRcdFx0XHR9KTtcblx0XHRcdFx0XHR9IGNhdGNoIChlcnIpIHtcblx0XHRcdFx0XHRcdC8vIExvZyBlcnJvciBidXQgZG9uJ3QgZmFpbCB0aGUgbWFpbiByZXF1ZXN0XG5cdFx0XHRcdFx0XHRjb25zb2xlLmVycm9yKCdVc2FnZSB0cmFja2luZyB3ZWJob29rIGZhaWxlZDonLCBlcnIpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0fSxcblx0XHRcdH0pO1xuXHRcdH1cblxuXHRcdC8vIENyZWF0ZSB0aGUgbW9kZWxcblx0XHRjb25zdCBtb2RlbCA9IG5ldyBDaGF0T3BlbkFJKHtcblx0XHRcdGFwaUtleTogY3JlZGVudGlhbHMuYXBpS2V5LFxuXHRcdFx0bW9kZWw6IG1vZGVsTmFtZSxcblx0XHRcdGNvbmZpZ3VyYXRpb24sXG5cdFx0XHR0aW1lb3V0OiAob3B0aW9ucy50aW1lb3V0IGFzIG51bWJlcikgPz8gMzYwMDAwLFxuXHRcdFx0bWF4UmV0cmllczogKG9wdGlvbnMubWF4UmV0cmllcyBhcyBudW1iZXIpID8/IDIsXG5cdFx0XHR0ZW1wZXJhdHVyZTogb3B0aW9ucy50ZW1wZXJhdHVyZSBhcyBudW1iZXIsXG5cdFx0XHRtYXhUb2tlbnM6IChvcHRpb25zLm1heFRva2VucyBhcyBudW1iZXIpID4gMCA/IChvcHRpb25zLm1heFRva2VucyBhcyBudW1iZXIpIDogdW5kZWZpbmVkLFxuXHRcdFx0dG9wUDogb3B0aW9ucy50b3BQIGFzIG51bWJlcixcblx0XHRcdGZyZXF1ZW5jeVBlbmFsdHk6IG9wdGlvbnMuZnJlcXVlbmN5UGVuYWx0eSBhcyBudW1iZXIsXG5cdFx0XHRwcmVzZW5jZVBlbmFsdHk6IG9wdGlvbnMucHJlc2VuY2VQZW5hbHR5IGFzIG51bWJlcixcblx0XHRcdG1vZGVsS3dhcmdzLFxuXHRcdFx0Y2FsbGJhY2tzLFxuXHRcdH0pO1xuXG5cdFx0cmV0dXJuIHtcblx0XHRcdHJlc3BvbnNlOiBtb2RlbCxcblx0XHR9O1xuXHR9XG59XG4iXX0=
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="white" fill-rule="evenodd" width="40" height="40" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="#94A3B8" fill-rule="evenodd" width="40" height="40" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>OpenRouter</title><path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path></svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-openrouter-custom",
|
|
3
|
+
"author": "My Author",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "Custom OpenRouter Chat Model node with usage tracking and extended options",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"n8n": {
|
|
10
|
+
"n8nNodesApiVersion": 1,
|
|
11
|
+
"nodes": [
|
|
12
|
+
"dist/nodes/LmChatOpenRouterCustom/LmChatOpenRouterCustom.node.js"
|
|
13
|
+
],
|
|
14
|
+
"credentials": [
|
|
15
|
+
"dist/credentials/OpenRouterCustomApi.credentials.js"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc && npm run copy-icons",
|
|
20
|
+
"copy-icons": "cp src/nodes/LmChatOpenRouterCustom/*.svg dist/nodes/LmChatOpenRouterCustom/ 2>/dev/null || true",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"lint": "eslint . --ext .ts",
|
|
24
|
+
"clean": "rm -rf dist"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@langchain/openai": "^0.3.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"n8n-workflow": "^1.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "*",
|
|
37
|
+
"typescript": "*",
|
|
38
|
+
"n8n-workflow": "^1.0.0"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"n8n",
|
|
42
|
+
"n8n-community-node-package",
|
|
43
|
+
"openrouter",
|
|
44
|
+
"langchain",
|
|
45
|
+
"llm",
|
|
46
|
+
"ai"
|
|
47
|
+
],
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/yourusername/n8n-nodes-openrouter-custom"
|
|
51
|
+
}
|
|
52
|
+
}
|