@vtstech/pi-openrouter-sync 1.0.8

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 ADDED
@@ -0,0 +1,27 @@
1
+ # @vtstech/pi-openrouter-sync
2
+
3
+ OpenRouter model sync extension for Pi Coding Agent.
4
+
5
+ Add models from OpenRouter URLs or bare model IDs directly into Pi's `models.json` configuration.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pi install @vtstech/pi-openrouter-sync
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ - `/openrouter-sync <url-or-id> [url-or-id ...]` — Add OpenRouter models by URL or ID
16
+ - Alias: `/or-sync`
17
+ - Accepts full URLs: `https://openrouter.ai/model/name:free`
18
+ - Accepts bare IDs: `model/name:free`
19
+ - Multiple models can be added in one command
20
+
21
+ ## Tools
22
+
23
+ - `openrouter_sync` — LLM-callable tool for adding OpenRouter models
24
+
25
+ ## Author
26
+
27
+ VTSTech — https://www.vts-tech.org
@@ -0,0 +1,180 @@
1
+ // .build-npm/openrouter-sync/openrouter-sync.temp.ts
2
+ import {
3
+ MODELS_JSON_PATH as MODELS_FILE,
4
+ readModelsJson,
5
+ writeModelsJson,
6
+ BUILTIN_PROVIDERS
7
+ } from "@vtstech/pi-shared/ollama";
8
+ import { section, ok, warn } from "@vtstech/pi-shared/format";
9
+ var BRANDING = [
10
+ ` \u26A1 Pi OpenRouter Sync v1.0.8`,
11
+ ` Written by VTSTech`,
12
+ ` GitHub: https://github.com/VTSTech`,
13
+ ` Website: www.vts-tech.org`
14
+ ].join("\n");
15
+ var OR_CONFIG = BUILTIN_PROVIDERS.openrouter;
16
+ function parseModelIds(args) {
17
+ return args.trim().split(/[\s,]+/).filter(Boolean).map((arg) => {
18
+ const match = arg.match(/openrouter\.ai\/(.+)/);
19
+ return match ? match[1] : arg;
20
+ });
21
+ }
22
+ function ensureProviderOrder(providers) {
23
+ const ordered = {};
24
+ const keys = Object.keys(providers);
25
+ const orIdx = keys.indexOf("openrouter");
26
+ const olIdx = keys.indexOf("ollama");
27
+ if (orIdx !== -1) {
28
+ ordered["openrouter"] = providers["openrouter"];
29
+ }
30
+ if (olIdx !== -1 && olIdx < orIdx) {
31
+ ordered["ollama"] = providers["ollama"];
32
+ }
33
+ for (const key of keys) {
34
+ if (key in ordered) continue;
35
+ ordered[key] = providers[key];
36
+ }
37
+ return ordered;
38
+ }
39
+ function openrouter_sync_temp_default(pi) {
40
+ pi.registerCommand("openrouter-sync", {
41
+ description: "Add OpenRouter model(s) to models.json. Use: /or-sync <url-or-id> [url-or-id ...]",
42
+ async handler(args, ctx) {
43
+ const modelIds = parseModelIds(args);
44
+ if (modelIds.length === 0) {
45
+ ctx.ui.notify(
46
+ "Usage: /or-sync <model-url-or-id> \u2014 e.g. /or-sync https://openrouter.ai/liquid/lfm-2.5-1.2b-thinking:free",
47
+ "info"
48
+ );
49
+ return;
50
+ }
51
+ ctx.ui.setStatus("openrouter-sync", `Adding ${modelIds.length} model(s)...`);
52
+ try {
53
+ const existing = readModelsJson();
54
+ if (!existing.providers["openrouter"]) {
55
+ existing.providers["openrouter"] = {
56
+ baseUrl: OR_CONFIG.baseUrl,
57
+ api: OR_CONFIG.api,
58
+ models: []
59
+ };
60
+ }
61
+ const orProvider = existing.providers["openrouter"];
62
+ if (!orProvider.models) orProvider.models = [];
63
+ const existingIds = new Set(orProvider.models.map((m) => m.id));
64
+ const added = [];
65
+ const skipped = [];
66
+ for (const modelId of modelIds) {
67
+ if (existingIds.has(modelId)) {
68
+ skipped.push(modelId);
69
+ continue;
70
+ }
71
+ orProvider.models.push({ id: modelId });
72
+ added.push(modelId);
73
+ }
74
+ existing.providers = ensureProviderOrder(existing.providers);
75
+ writeModelsJson(existing);
76
+ const lines = [""];
77
+ lines.push(` Provider: openrouter (built-in)`);
78
+ lines.push(` Base URL: ${OR_CONFIG.baseUrl}`);
79
+ lines.push(` Total models: ${orProvider.models.length}`);
80
+ if (added.length > 0) {
81
+ lines.push(section("Added"));
82
+ for (const id of added) lines.push(ok(id));
83
+ }
84
+ if (skipped.length > 0) {
85
+ lines.push(section("Already Present"));
86
+ for (const id of skipped) lines.push(warn(id));
87
+ }
88
+ lines.push("");
89
+ lines.push(` Written to ${MODELS_FILE}`);
90
+ lines.push(` Run /reload to pick up changes`);
91
+ lines.push(BRANDING);
92
+ const report = lines.join("\n");
93
+ const summary = [];
94
+ if (added.length > 0) summary.push(`+${added.join(", ")}`);
95
+ if (skipped.length > 0) summary.push(`skipped: ${skipped.join(", ")}`);
96
+ ctx.ui.notify(summary.join(" \xB7 ") || "No changes", added.length > 0 ? "success" : "info");
97
+ pi.sendMessage({
98
+ customType: "openrouter-sync-report",
99
+ content: report,
100
+ display: { type: "content", content: report },
101
+ details: { timestamp: (/* @__PURE__ */ new Date()).toISOString(), added: added.length, skipped: skipped.length }
102
+ });
103
+ } catch (err) {
104
+ ctx.ui.notify(`Failed: ${err.message}`, "error");
105
+ }
106
+ ctx.ui.setStatus("openrouter-sync", void 0);
107
+ }
108
+ });
109
+ pi.registerTool({
110
+ name: "openrouter_sync",
111
+ label: "OpenRouter Sync",
112
+ description: "Add one or more OpenRouter model IDs to Pi's models.json under the openrouter provider.\nAccepts full URLs (https://openrouter.ai/...) or bare model IDs (owner/model:tag).\nThe openrouter provider is created if it doesn't exist, and positioned above ollama.\nExisting models are never removed.\n\n" + BRANDING,
113
+ parameters: {
114
+ type: "object",
115
+ properties: {
116
+ models: {
117
+ type: "array",
118
+ items: { type: "string" },
119
+ description: 'Array of model identifiers \u2014 full OpenRouter URLs or bare IDs. e.g. ["https://openrouter.ai/liquid/lfm-2.5-1.2b-thinking:free", "nvidia/nemotron-3-nano-30b-a3b:free"]'
120
+ }
121
+ },
122
+ required: ["models"]
123
+ },
124
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
125
+ const rawModels = params?.models;
126
+ if (!Array.isArray(rawModels) || rawModels.length === 0) {
127
+ return {
128
+ content: [{ type: "text", text: "Error: 'models' must be a non-empty array of model IDs or URLs." }],
129
+ details: {}
130
+ };
131
+ }
132
+ const modelIds = parseModelIds(rawModels.join(" "));
133
+ const existing = readModelsJson();
134
+ if (!existing.providers["openrouter"]) {
135
+ existing.providers["openrouter"] = {
136
+ baseUrl: OR_CONFIG.baseUrl,
137
+ api: OR_CONFIG.api,
138
+ models: []
139
+ };
140
+ }
141
+ const orProvider = existing.providers["openrouter"];
142
+ if (!orProvider.models) orProvider.models = [];
143
+ const existingIds = new Set(orProvider.models.map((m) => m.id));
144
+ const added = [];
145
+ const skipped = [];
146
+ for (const modelId of modelIds) {
147
+ if (existingIds.has(modelId)) {
148
+ skipped.push(modelId);
149
+ continue;
150
+ }
151
+ orProvider.models.push({ id: modelId });
152
+ added.push(modelId);
153
+ }
154
+ existing.providers = ensureProviderOrder(existing.providers);
155
+ writeModelsJson(existing);
156
+ const modelList = orProvider.models.map((m) => ` - ${m.id}`).join("\n");
157
+ const report = [
158
+ BRANDING,
159
+ "",
160
+ `Added ${added.length} model(s) to openrouter provider (${orProvider.models.length} total).`,
161
+ ...added.length > 0 ? ["\nAdded:"] : [],
162
+ ...added.map((id) => ` + ${id}`),
163
+ ...skipped.length > 0 ? ["\nSkipped (already present):"] : [],
164
+ ...skipped.map((id) => ` = ${id}`),
165
+ "",
166
+ `Written to ${MODELS_FILE}. Run /reload to pick up changes.`,
167
+ "",
168
+ "Current openrouter models:",
169
+ modelList
170
+ ].join("\n");
171
+ return {
172
+ content: [{ type: "text", text: report }],
173
+ details: { added: added.length, skipped: skipped.length, total: orProvider.models.length }
174
+ };
175
+ }
176
+ });
177
+ }
178
+ export {
179
+ openrouter_sync_temp_default as default
180
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@vtstech/pi-openrouter-sync",
3
+ "version": "1.0.8",
4
+ "description": "OpenRouter model sync extension for Pi Coding Agent — add models from OpenRouter URLs or IDs",
5
+ "main": "openrouter-sync.js",
6
+ "keywords": ["pi-extensions"],
7
+ "license": "MIT",
8
+ "access": "public",
9
+ "type": "module",
10
+ "author": "VTSTech",
11
+ "homepage": "https://www.vts-tech.org",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/VTSTech/pi-coding-agent"
15
+ },
16
+ "dependencies": {
17
+ "@vtstech/pi-shared": "1.0.8"
18
+ },
19
+ "peerDependencies": {
20
+ "@mariozechner/pi-coding-agent": ">=0.66"
21
+ },
22
+ "pi": {
23
+ "extensions": ["./openrouter-sync.js"]
24
+ }
25
+ }