@vheins/opencode-9router 0.4.5 → 0.5.1

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.
Files changed (3) hide show
  1. package/README.md +61 -53
  2. package/dist/plugin.js +29 -101
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,29 +2,27 @@
2
2
 
3
3
  OpenCode plugin provider for [9Router](https://github.com/decolua/9router) — FREE AI Router & Token Saver. 40+ providers, 100+ models.
4
4
 
5
- Mendaftarkan 9Router sebagai custom provider di OpenCode dengan auto-discovery models dan konfigurasi baseURL via `/connect`.
5
+ Mendaftarkan 9Router sebagai custom provider di OpenCode dengan auto-discovery models.
6
6
 
7
7
  ## Quick Start
8
8
 
9
9
  ```json
10
10
  {
11
11
  "$schema": "https://opencode.ai/config.json",
12
- "plugin": ["@vheins/opencode-9router@latest"]
12
+ "plugin": ["@vheins/opencode-9router@0.5.0"]
13
13
  }
14
14
  ```
15
15
 
16
16
  1. Tambahkan plugin ke `opencode.json`
17
17
  2. Restart OpenCode
18
- 3. `/connect` → pilih **Connect to 9Router**
19
- 4. Masukkan Base URL (default: `http://localhost:20128`) dan API Key
20
- 5. `/models` pilih model 9Router
18
+ 3. `/models` → pilih model 9Router
19
+
20
+ Plugin akan auto-discover models dari `http://localhost:20128` (default).
21
21
 
22
22
  ## Features
23
23
 
24
24
  - **Auto-discover models** — Models dari 9Router otomatis terdeteksi saat startup
25
- - **Configurable baseURL** — Atur Base URL via `/connect`, plugin options, atau environment variable
26
25
  - **Dynamic model list** — Semua model dari 9Router tersedia, termasuk combo kustom
27
- - **27+ fallback models** — Well-known models tersedia jika 9Router belum running
28
26
  - **OpenAI-compatible** — Menggunakan `@ai-sdk/openai-compatible`
29
27
  - **Type-safe** — Menggunakan `config` hook untuk registrasi provider yang sesuai dengan OpenCode config schema
30
28
 
@@ -35,61 +33,75 @@ Mendaftarkan 9Router sebagai custom provider di OpenCode dengan auto-discovery m
35
33
  ```json
36
34
  {
37
35
  "$schema": "https://opencode.ai/config.json",
38
- "plugin": ["@vheins/opencode-9router@latest"]
36
+ "plugin": ["@vheins/opencode-9router@0.5.0"]
39
37
  }
40
38
  ```
41
39
 
42
- Tidak perlu mendefinisikan models atau provider secara manual — plugin mendaftarkan semuanya otomatis.
43
-
44
- ### Local file
45
-
46
- ```bash
47
- cp src/plugin.ts .opencode/plugins/9router-provider.ts
48
- cp src/constants.ts .opencode/plugins/constants.ts
49
- ```
40
+ Tidak perlu mendefinisikan provider secara manual — plugin mendaftarkannya otomatis.
50
41
 
51
- ## Usage
42
+ ### Custom Base URL
52
43
 
53
- ### 1. Connect via `/connect`
44
+ Jika 9Router berjalan di host/port berbeda, tambahkan provider config:
54
45
 
55
- ```
56
- /connect
57
- → Select: Connect to 9Router
58
- → Base URL: http://localhost:20128 (customize if needed)
59
- → API Key: [paste from 9Router Dashboard → Endpoints]
46
+ ```json
47
+ {
48
+ "$schema": "https://opencode.ai/config.json",
49
+ "plugin": ["@vheins/opencode-9router@0.5.0"],
50
+ "provider": {
51
+ "9router": {
52
+ "options": {
53
+ "baseURL": "https://model.idsolutions.id/v1"
54
+ }
55
+ }
56
+ }
57
+ }
60
58
  ```
61
59
 
62
- ### 2. Select model
60
+ ### With API Key
63
61
 
62
+ ```json
63
+ {
64
+ "$schema": "https://opencode.ai/config.json",
65
+ "plugin": ["@vheins/opencode-9router@0.5.0"],
66
+ "provider": {
67
+ "9router": {
68
+ "options": {
69
+ "baseURL": "https://model.idsolutions.id/v1",
70
+ "apiKey": "your-api-key-here"
71
+ }
72
+ }
73
+ }
74
+ }
64
75
  ```
65
- /models
66
- → Find 9Router provider
67
- → Pick any model (e.g., kr/claude-sonnet-4.5, cc/claude-opus-4-7)
68
- ```
69
-
70
- ### Custom Base URL
71
-
72
- Jika 9Router berjalan di host/port berbeda, ada 3 cara konfigurasi:
73
-
74
- #### Via `/connect` (recommended)
75
76
 
76
- Masukkan URL custom saat diminta Base URL.
77
-
78
- #### Via plugin options
77
+ Atau pakai environment variable:
79
78
 
80
79
  ```json
81
80
  {
82
- "$schema": "https://opencode.ai/config.json",
83
- "plugin": [
84
- ["@vheins/opencode-9router@latest", { "baseURL": "http://192.168.1.100:20128" }]
85
- ]
81
+ "provider": {
82
+ "9router": {
83
+ "options": {
84
+ "baseURL": "https://model.idsolutions.id/v1",
85
+ "apiKey": "{env:ROUTER_API_KEY}"
86
+ }
87
+ }
88
+ }
86
89
  }
87
90
  ```
88
91
 
89
- #### Via environment variable
90
-
91
92
  ```bash
92
- export ROUTER_BASE_URL=http://192.168.1.100:20128
93
+ export ROUTER_API_KEY=your-api-key-here
94
+ opencode
95
+ ```
96
+
97
+ ## Usage
98
+
99
+ ### Select model
100
+
101
+ ```
102
+ /models
103
+ → Find 9Router provider
104
+ → Pick any model (e.g., kr/claude-sonnet-4.5, cc/claude-opus-4-7)
93
105
  ```
94
106
 
95
107
  ## Model Prefixes
@@ -113,23 +125,19 @@ export ROUTER_BASE_URL=http://192.168.1.100:20128
113
125
  ## How It Works
114
126
 
115
127
  ```
116
- opencode.json "plugin": ["@vheins/opencode-9router@latest"]
128
+ opencode.json "plugin": ["@vheins/opencode-9router@0.5.0"]
117
129
 
118
130
  Bun installs package from npm
119
131
 
120
132
  Plugin loads at startup:
121
- 1. Resolve baseURL (options > env > default)
122
- 2. Try GET /v1/models from 9Router (3s timeout)
133
+ 1. Read baseURL from provider config (or use default http://localhost:20128)
134
+ 2. Try GET /v1/models from baseURL (3s timeout)
123
135
  3. If OK → register live models
124
- 4. If fail → register 27 fallback models
136
+ 4. If fail → log warning, no models registered
125
137
 
126
- config hook injects provider "9router" into OpenCode config
138
+ config hook creates/updates provider "9router" with discovered models
127
139
 
128
140
  Provider "9router" appears in /models
129
-
130
- User runs /connect → enters baseURL + API key
131
-
132
- Auth loader overrides provider config with user's baseURL
133
141
  ```
134
142
 
135
143
  ## Development
package/dist/plugin.js CHANGED
@@ -1,7 +1,4 @@
1
1
  import { PLUGIN_NAME, PROVIDER_DISPLAY_NAME, DEFAULT_BASE_URL, DEFAULT_API_PATH, KNOWN_PROVIDER_PREFIXES, } from "./constants.js";
2
- import * as fs from "node:fs";
3
- import * as path from "node:path";
4
- import * as os from "node:os";
5
2
  function formatModelName(modelId) {
6
3
  for (const [prefix, provider] of Object.entries(KNOWN_PROVIDER_PREFIXES)) {
7
4
  if (modelId.startsWith(prefix)) {
@@ -13,39 +10,9 @@ function formatModelName(modelId) {
13
10
  function normalizeBaseURL(url) {
14
11
  return url.replace(/\/+$/, "");
15
12
  }
16
- function resolveBaseURL(options) {
17
- if (options?.baseURL && typeof options.baseURL === "string") {
18
- return { url: normalizeBaseURL(options.baseURL), isDefault: false };
19
- }
20
- const envURL = globalThis.process;
21
- if (envURL?.env?.ROUTER_BASE_URL) {
22
- return { url: normalizeBaseURL(envURL.env.ROUTER_BASE_URL), isDefault: false };
23
- }
24
- return { url: DEFAULT_BASE_URL, isDefault: true };
25
- }
26
13
  function ensureAPIPath(baseURL) {
27
14
  return baseURL.endsWith(DEFAULT_API_PATH) ? baseURL : `${baseURL}${DEFAULT_API_PATH}`;
28
15
  }
29
- async function getAuthBaseURL() {
30
- try {
31
- const authPath = path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
32
- const authContent = await fs.promises.readFile(authPath, "utf-8");
33
- const auth = JSON.parse(authContent);
34
- // Find 9Router auth entry
35
- for (const key of Object.keys(auth)) {
36
- if (key.toLowerCase().includes("9router")) {
37
- const entry = auth[key];
38
- if (entry?.baseURL && typeof entry.baseURL === "string") {
39
- return normalizeBaseURL(entry.baseURL);
40
- }
41
- }
42
- }
43
- }
44
- catch {
45
- // Auth file doesn't exist or can't be read
46
- }
47
- return null;
48
- }
49
16
  async function discoverModels(baseURL) {
50
17
  const apiURL = ensureAPIPath(baseURL);
51
18
  try {
@@ -68,84 +35,45 @@ async function discoverModels(baseURL) {
68
35
  return null;
69
36
  }
70
37
  }
71
- export const NineRouterPlugin = async ({ client }, options) => {
72
- const { url: configuredBaseURL, isDefault } = resolveBaseURL(options);
73
- // Try to get baseURL from auth file (set via opencode auth login)
74
- const authBaseURL = await getAuthBaseURL();
75
- const effectiveBaseURL = authBaseURL ?? configuredBaseURL;
76
- // Always try to discover models from effective baseURL
77
- const discovered = await discoverModels(effectiveBaseURL);
78
- if (!discovered && client?.app?.log) {
79
- const level = authBaseURL ? "error" : "info";
80
- const message = authBaseURL
81
- ? `9Router not reachable at ${effectiveBaseURL}. Please check if 9Router is running and accessible.`
82
- : `9Router baseURL not configured. Set it via plugin options, ROUTER_BASE_URL env var, or 'opencode auth login' to auto-discover models.`;
83
- await client.app.log({
84
- body: {
85
- service: "9router-provider",
86
- level,
87
- message,
88
- },
89
- });
90
- }
38
+ export const NineRouterPlugin = async ({ client }) => {
39
+ const log = async (level, message) => {
40
+ try {
41
+ await client.app.log({
42
+ body: {
43
+ service: "9router-provider",
44
+ level,
45
+ message,
46
+ },
47
+ });
48
+ }
49
+ catch {
50
+ // Logging is best-effort
51
+ }
52
+ };
91
53
  return {
92
54
  config: async (config) => {
55
+ const existingProvider = config.provider?.[PLUGIN_NAME];
56
+ const options = existingProvider?.options;
57
+ const baseURL = options?.baseURL ?? DEFAULT_BASE_URL;
58
+ const normalizedURL = normalizeBaseURL(baseURL);
59
+ const apiURL = ensureAPIPath(normalizedURL);
60
+ const discovered = await discoverModels(normalizedURL);
93
61
  config.provider ??= {};
94
62
  config.provider[PLUGIN_NAME] = {
95
63
  npm: "@ai-sdk/openai-compatible",
96
64
  name: PROVIDER_DISPLAY_NAME,
97
65
  options: {
98
- baseURL: ensureAPIPath(effectiveBaseURL),
66
+ ...options,
67
+ baseURL: apiURL,
99
68
  },
100
69
  models: discovered ?? {},
101
70
  };
102
- },
103
- auth: {
104
- provider: PLUGIN_NAME,
105
- async loader(getAuth) {
106
- const auth = await getAuth();
107
- if (auth && typeof auth === "object" && "baseURL" in auth) {
108
- const userURL = normalizeBaseURL(String(auth.baseURL));
109
- const apiURL = ensureAPIPath(userURL);
110
- // Re-discover models with user-provided baseURL
111
- const authDiscovered = await discoverModels(userURL);
112
- if (authDiscovered) {
113
- if (client?.app?.log) {
114
- await client.app.log({
115
- body: {
116
- service: "9router-provider",
117
- level: "info",
118
- message: `Discovered ${Object.keys(authDiscovered).length} models from ${apiURL}`,
119
- },
120
- });
121
- }
122
- }
123
- else if (client?.app?.log) {
124
- await client.app.log({
125
- body: {
126
- service: "9router-provider",
127
- level: "error",
128
- message: `Failed to discover models from ${apiURL}. Check if 9Router is running and accessible.`,
129
- },
130
- });
131
- }
132
- return { baseURL: apiURL };
133
- }
134
- return {};
135
- },
136
- methods: [
137
- {
138
- label: `Connect to ${PROVIDER_DISPLAY_NAME}`,
139
- type: "api",
140
- prompts: [
141
- {
142
- type: "text",
143
- message: `${PROVIDER_DISPLAY_NAME} Base URL (default: ${DEFAULT_BASE_URL})`,
144
- key: "baseURL",
145
- },
146
- ],
147
- },
148
- ],
71
+ if (discovered) {
72
+ await log("info", `Discovered ${Object.keys(discovered).length} models from ${apiURL}`);
73
+ }
74
+ else {
75
+ await log("warn", `Failed to discover models from ${apiURL}. Check if 9Router is running and accessible.`);
76
+ }
149
77
  },
150
78
  };
151
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vheins/opencode-9router",
3
- "version": "0.4.5",
3
+ "version": "0.5.1",
4
4
  "description": "OpenCode plugin provider for 9Router — FREE AI Router & Token Saver. 40+ providers, 100+ models.",
5
5
  "author": "vheins",
6
6
  "keywords": [