@vymalo/opencode-oauth2 0.2.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +213 -0
  3. package/dist/cache.d.ts +9 -0
  4. package/dist/cache.js +91 -0
  5. package/dist/cache.js.map +1 -0
  6. package/dist/config.d.ts +97 -0
  7. package/dist/config.js +179 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/index.d.ts +9 -0
  10. package/dist/index.js +8 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/logging.d.ts +11 -0
  13. package/dist/logging.js +59 -0
  14. package/dist/logging.js.map +1 -0
  15. package/dist/model-discovery.d.ts +9 -0
  16. package/dist/model-discovery.js +74 -0
  17. package/dist/model-discovery.js.map +1 -0
  18. package/dist/model-normalization.d.ts +4 -0
  19. package/dist/model-normalization.js +86 -0
  20. package/dist/model-normalization.js.map +1 -0
  21. package/dist/oauth/browser.d.ts +1 -0
  22. package/dist/oauth/browser.js +29 -0
  23. package/dist/oauth/browser.js.map +1 -0
  24. package/dist/oauth/client.d.ts +56 -0
  25. package/dist/oauth/client.js +436 -0
  26. package/dist/oauth/client.js.map +1 -0
  27. package/dist/oauth/device-code.d.ts +22 -0
  28. package/dist/oauth/device-code.js +240 -0
  29. package/dist/oauth/device-code.js.map +1 -0
  30. package/dist/oauth/discovery.d.ts +8 -0
  31. package/dist/oauth/discovery.js +38 -0
  32. package/dist/oauth/discovery.js.map +1 -0
  33. package/dist/oauth/http-utils.d.ts +33 -0
  34. package/dist/oauth/http-utils.js +129 -0
  35. package/dist/oauth/http-utils.js.map +1 -0
  36. package/dist/oauth/local-callback.d.ts +10 -0
  37. package/dist/oauth/local-callback.js +82 -0
  38. package/dist/oauth/local-callback.js.map +1 -0
  39. package/dist/oauth/pkce.d.ts +5 -0
  40. package/dist/oauth/pkce.js +13 -0
  41. package/dist/oauth/pkce.js.map +1 -0
  42. package/dist/oauth/subject-token.d.ts +20 -0
  43. package/dist/oauth/subject-token.js +86 -0
  44. package/dist/oauth/subject-token.js.map +1 -0
  45. package/dist/opencode.d.ts +11 -0
  46. package/dist/opencode.js +347 -0
  47. package/dist/opencode.js.map +1 -0
  48. package/dist/plugin.d.ts +47 -0
  49. package/dist/plugin.js +220 -0
  50. package/dist/plugin.js.map +1 -0
  51. package/dist/scheduler.d.ts +11 -0
  52. package/dist/scheduler.js +44 -0
  53. package/dist/scheduler.js.map +1 -0
  54. package/dist/types.d.ts +37 -0
  55. package/dist/types.js +2 -0
  56. package/dist/types.js.map +1 -0
  57. package/package.json +53 -0
package/dist/plugin.js ADDED
@@ -0,0 +1,220 @@
1
+ import { FileCacheStore, resolveCacheDir } from "./cache.js";
2
+ import { validateConfig } from "./config.js";
3
+ import { createJsonConsoleLogger } from "./logging.js";
4
+ import { fetchModels } from "./model-discovery.js";
5
+ import { diffModels, normalizeModelList } from "./model-normalization.js";
6
+ import { OAuthClient } from "./oauth/client.js";
7
+ import { startScheduler } from "./scheduler.js";
8
+ export class OAuth2ModelSyncPlugin {
9
+ configInput;
10
+ options;
11
+ logger;
12
+ config;
13
+ cacheStore;
14
+ runtimeByServer = new Map();
15
+ initialized = false;
16
+ started = false;
17
+ constructor(configInput, options = {}) {
18
+ this.configInput = configInput;
19
+ this.options = options;
20
+ this.config = validateConfig(this.configInput);
21
+ this.logger = options.logger ?? createJsonConsoleLogger("info");
22
+ this.cacheStore = new FileCacheStore(options.cacheDir ?? resolveCacheDir(this.config.cacheNamespace));
23
+ }
24
+ async initialize() {
25
+ if (this.initialized) {
26
+ return;
27
+ }
28
+ await this.cacheStore.ensureReady();
29
+ for (const server of this.config.servers) {
30
+ const cached = await this.cacheStore.loadServerState(server.id);
31
+ const initialState = cached ?? {
32
+ serverId: server.id,
33
+ updatedAt: Date.now(),
34
+ models: [],
35
+ rawModels: []
36
+ };
37
+ this.runtimeByServer.set(server.id, { state: initialState });
38
+ }
39
+ this.logger.info("plugin_initialized", {
40
+ serverCount: this.config.servers.length
41
+ });
42
+ this.initialized = true;
43
+ }
44
+ async start(options = {}) {
45
+ if (this.started) {
46
+ return;
47
+ }
48
+ await this.initialize();
49
+ const warmup = options.warmup ?? true;
50
+ // Warmup interactivity tracks the host process: an attached TTY (a real
51
+ // terminal) means a user can complete an interactive flow, so warmup
52
+ // allows browser/device-code paths if needed for first-time provider
53
+ // setup. Without a TTY (CI, daemonized run, piped stdin) we stay
54
+ // non-interactive so startup never hangs on a callback that will never
55
+ // arrive. Callers can override explicitly via `interactive`.
56
+ const interactiveWarmup = options.interactive ?? Boolean(process.stdin?.isTTY && process.stdout?.isTTY);
57
+ for (const server of this.config.servers) {
58
+ if (warmup) {
59
+ try {
60
+ await this.syncServer(server.id, { interactive: interactiveWarmup });
61
+ }
62
+ catch (error) {
63
+ this.logger.warn("sync_startup_failed", {
64
+ serverId: server.id,
65
+ error: error instanceof Error ? error.message : String(error)
66
+ });
67
+ }
68
+ }
69
+ const handle = startScheduler({
70
+ intervalMs: Math.round(server.syncIntervalMinutes * 60_000),
71
+ logger: this.logger,
72
+ taskName: `sync:${server.id}`,
73
+ run: async () => {
74
+ await this.syncServer(server.id);
75
+ }
76
+ });
77
+ const runtime = this.runtimeByServer.get(server.id);
78
+ if (runtime) {
79
+ runtime.scheduler = handle;
80
+ }
81
+ }
82
+ this.started = true;
83
+ }
84
+ stop() {
85
+ for (const runtime of this.runtimeByServer.values()) {
86
+ runtime.scheduler?.stop();
87
+ }
88
+ this.started = false;
89
+ }
90
+ async syncAll() {
91
+ const snapshots = [];
92
+ for (const server of this.config.servers) {
93
+ snapshots.push(await this.syncServer(server.id));
94
+ }
95
+ return snapshots;
96
+ }
97
+ async syncServer(serverId, options = {}) {
98
+ const server = this.requireServerConfig(serverId);
99
+ const runtime = this.runtimeByServer.get(serverId);
100
+ if (!runtime) {
101
+ throw new Error(`runtime not initialized for server: ${serverId}`);
102
+ }
103
+ this.logger.info("sync_start", { serverId, interactive: options.interactive !== false });
104
+ const oauth = new OAuthClient(server, {
105
+ fetchImpl: this.options.fetchImpl,
106
+ logger: this.logger,
107
+ timeoutMs: this.config.httpTimeoutMs,
108
+ onAuthorizationUrl: this.options.onAuthorizationUrl,
109
+ tokenExpirySkewMs: this.config.tokenExpirySkewMs
110
+ });
111
+ const previousState = runtime.state;
112
+ try {
113
+ const token = await oauth.ensureToken(previousState.token, {
114
+ interactive: options.interactive
115
+ });
116
+ const rawModels = await fetchModels(server.baseURL, token, {
117
+ fetchImpl: this.options.fetchImpl,
118
+ timeoutMs: this.config.httpTimeoutMs,
119
+ logger: this.logger
120
+ });
121
+ const normalizedModels = normalizeModelList(rawModels, server.nameOverrides);
122
+ const diff = diffModels(previousState.models, normalizedModels);
123
+ const nextState = {
124
+ ...previousState,
125
+ updatedAt: Date.now(),
126
+ lastSyncAt: Date.now(),
127
+ token,
128
+ rawModels,
129
+ models: normalizedModels
130
+ };
131
+ await this.cacheStore.saveServerState(nextState);
132
+ runtime.state = nextState;
133
+ this.logger.info("sync_success", {
134
+ serverId,
135
+ modelCount: normalizedModels.length,
136
+ added: diff.added.length,
137
+ removed: diff.removed.length,
138
+ renamed: diff.renamed.length
139
+ });
140
+ return {
141
+ serverId,
142
+ models: normalizedModels,
143
+ lastSyncAt: nextState.lastSyncAt
144
+ };
145
+ }
146
+ catch (error) {
147
+ this.logger.error("sync_failed", {
148
+ serverId,
149
+ error: error instanceof Error ? error.message : String(error)
150
+ });
151
+ // Keep last-known-good state by not mutating runtime state.
152
+ return {
153
+ serverId,
154
+ models: previousState.models,
155
+ lastSyncAt: previousState.lastSyncAt
156
+ };
157
+ }
158
+ }
159
+ async ensureAccessToken(serverId) {
160
+ const server = this.requireServerConfig(serverId);
161
+ const runtime = this.runtimeByServer.get(serverId);
162
+ if (!runtime) {
163
+ throw new Error(`runtime not initialized for server: ${serverId}`);
164
+ }
165
+ const oauth = new OAuthClient(server, {
166
+ fetchImpl: this.options.fetchImpl,
167
+ logger: this.logger,
168
+ timeoutMs: this.config.httpTimeoutMs,
169
+ onAuthorizationUrl: this.options.onAuthorizationUrl,
170
+ tokenExpirySkewMs: this.config.tokenExpirySkewMs
171
+ });
172
+ const token = await oauth.ensureToken(runtime.state.token);
173
+ if (token.accessToken !== runtime.state.token?.accessToken) {
174
+ const nextState = {
175
+ ...runtime.state,
176
+ updatedAt: Date.now(),
177
+ token
178
+ };
179
+ await this.cacheStore.saveServerState(nextState);
180
+ runtime.state = nextState;
181
+ }
182
+ return token;
183
+ }
184
+ async ensureServerReady(serverId) {
185
+ const runtime = this.runtimeByServer.get(serverId);
186
+ if (!runtime) {
187
+ throw new Error(`runtime not initialized for server: ${serverId}`);
188
+ }
189
+ if (!runtime.state.lastSyncAt || runtime.state.models.length === 0) {
190
+ return this.syncServer(serverId);
191
+ }
192
+ return {
193
+ serverId,
194
+ models: runtime.state.models,
195
+ lastSyncAt: runtime.state.lastSyncAt
196
+ };
197
+ }
198
+ getServerModels(serverId) {
199
+ const runtime = this.runtimeByServer.get(serverId);
200
+ if (!runtime) {
201
+ throw new Error(`runtime not initialized for server: ${serverId}`);
202
+ }
203
+ return [...runtime.state.models];
204
+ }
205
+ getProviderModelMap(serverId) {
206
+ const models = this.getServerModels(serverId);
207
+ return Object.fromEntries(models.map((model) => [model.id, { name: model.displayName }]));
208
+ }
209
+ getCachedToken(serverId) {
210
+ return this.runtimeByServer.get(serverId)?.state.token;
211
+ }
212
+ requireServerConfig(serverId) {
213
+ const server = this.config.servers.find((item) => item.id === serverId);
214
+ if (!server) {
215
+ throw new Error(`unknown server id: ${serverId}`);
216
+ }
217
+ return server;
218
+ }
219
+ }
220
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { Logger } from "./logging.js";
2
+ export interface SchedulerHandle {
3
+ stop(): void;
4
+ }
5
+ export interface SchedulerOptions {
6
+ intervalMs: number;
7
+ logger: Logger;
8
+ taskName: string;
9
+ run: () => Promise<void>;
10
+ }
11
+ export declare function startScheduler(options: SchedulerOptions): SchedulerHandle;
@@ -0,0 +1,44 @@
1
+ export function startScheduler(options) {
2
+ let failures = 0;
3
+ let stopped = false;
4
+ let timeout;
5
+ const schedule = (delayMs) => {
6
+ timeout = setTimeout(() => {
7
+ void tick();
8
+ }, delayMs);
9
+ };
10
+ const backoffDelay = () => {
11
+ const retryWindow = 15_000 * 2 ** Math.max(0, failures - 1);
12
+ return Math.min(options.intervalMs, retryWindow);
13
+ };
14
+ const tick = async () => {
15
+ if (stopped) {
16
+ return;
17
+ }
18
+ try {
19
+ await options.run();
20
+ failures = 0;
21
+ schedule(options.intervalMs);
22
+ }
23
+ catch (error) {
24
+ failures += 1;
25
+ options.logger.warn("sync_schedule_retry", {
26
+ taskName: options.taskName,
27
+ failures,
28
+ delayMs: backoffDelay(),
29
+ error: error instanceof Error ? error.message : String(error)
30
+ });
31
+ schedule(backoffDelay());
32
+ }
33
+ };
34
+ schedule(options.intervalMs);
35
+ return {
36
+ stop() {
37
+ stopped = true;
38
+ if (timeout) {
39
+ clearTimeout(timeout);
40
+ }
41
+ }
42
+ };
43
+ }
44
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,cAAc,CAAC,OAAyB;IACtD,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,OAAmC,CAAC;IAExC,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAQ,EAAE;QACzC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE;YACxB,KAAK,IAAI,EAAE,CAAC;QACd,CAAC,EAAE,OAAO,CAAC,CAAC;IACd,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,GAAW,EAAE;QAChC,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACnD,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAK,IAAmB,EAAE;QACrC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC;YACpB,QAAQ,GAAG,CAAC,CAAC;YACb,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,IAAI,CAAC,CAAC;YACd,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE;gBACzC,QAAQ,EAAE,OAAO,CAAC,QAAQ;gBAC1B,QAAQ;gBACR,OAAO,EAAE,YAAY,EAAE;gBACvB,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC9D,CAAC,CAAC;YACH,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC,CAAC;IAEF,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAE7B,OAAO;QACL,IAAI;YACF,OAAO,GAAG,IAAI,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,YAAY,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ export interface RawModel {
2
+ id: string;
3
+ [key: string]: unknown;
4
+ }
5
+ export interface NormalizedModel {
6
+ id: string;
7
+ displayName: string;
8
+ }
9
+ export interface ModelDiff {
10
+ added: string[];
11
+ removed: string[];
12
+ renamed: Array<{
13
+ id: string;
14
+ before: string;
15
+ after: string;
16
+ }>;
17
+ }
18
+ export interface TokenSet {
19
+ accessToken: string;
20
+ tokenType: string;
21
+ refreshToken?: string;
22
+ scope?: string;
23
+ expiresAt?: number;
24
+ }
25
+ export interface CachedServerState {
26
+ serverId: string;
27
+ updatedAt: number;
28
+ lastSyncAt?: number;
29
+ models: NormalizedModel[];
30
+ rawModels: RawModel[];
31
+ token?: TokenSet;
32
+ }
33
+ export interface ServerSnapshot {
34
+ serverId: string;
35
+ models: NormalizedModel[];
36
+ lastSyncAt?: number;
37
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@vymalo/opencode-oauth2",
3
+ "version": "0.2.0",
4
+ "description": "OpenCode plugin for OAuth2/OIDC-secured, OpenAI-compatible model providers with periodic model sync.",
5
+ "license": "MIT",
6
+ "author": "vymalo contributors",
7
+ "homepage": "https://github.com/vymalo/opencode-oauth2#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vymalo/opencode-oauth2.git",
11
+ "directory": "packages/opencode-oauth2"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/vymalo/opencode-oauth2/issues"
15
+ },
16
+ "keywords": [
17
+ "opencode",
18
+ "opencode-plugin",
19
+ "oauth2",
20
+ "oidc",
21
+ "openai-compatible",
22
+ "ai-sdk",
23
+ "model-sync"
24
+ ],
25
+ "type": "module",
26
+ "main": "dist/index.js",
27
+ "types": "dist/index.d.ts",
28
+ "sideEffects": false,
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "engines": {
33
+ "node": ">=22"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "dependencies": {
39
+ "@opencode-ai/plugin": "1.15.10"
40
+ },
41
+ "devDependencies": {
42
+ "vite": "^8.0.14",
43
+ "vitest": "^4.1.7"
44
+ },
45
+ "scripts": {
46
+ "build": "tsc -p tsconfig.json",
47
+ "lint": "biome lint .",
48
+ "typecheck": "tsc -p tsconfig.json --noEmit",
49
+ "test": "vitest run",
50
+ "format": "biome format --write .",
51
+ "format:check": "biome format ."
52
+ }
53
+ }