@vymalo/opencode-lightbridge 0.16.1 → 0.18.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 CHANGED
@@ -2,21 +2,25 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@vymalo/opencode-lightbridge?label=npm&color=CB3837&logo=npm)](https://www.npmjs.com/package/@vymalo/opencode-lightbridge)
4
4
 
5
- **One login, every egress.** The umbrella OpenCode plugin for [ADR-0012](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0012-single-auth-across-gateway-and-otel.md):
6
- a single shared `TokenRuntime` drives **both** the LLM gateway bearer and the OTEL export
7
- credential, because both validate the same `lightbridge-authz` issuer + `aud=lightbridge-api-key`.
8
- Log in once as yourself; every egress that needs a project-scoped credential rides the same token.
5
+ **The all-in-one plugin.** One credential drives everything: provider registration + model
6
+ discovery ([`register`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0017-lightbridge-all-in-one.md) —
7
+ "everything `@vymalo/opencode-oauth2` does"), the LLM gateway bearer (`gateway`), and the OTEL
8
+ export credential (`otel`) — see [ADR-0012](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0012-single-auth-across-gateway-and-otel.md)
9
+ and [ADR-0017](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0017-lightbridge-all-in-one.md).
10
+ Log in once as yourself; every module that needs a credential rides the same login — and since
11
+ ADR-0017, that login is shared with `@vymalo/opencode-oauth2` too, when both target the same IdP.
9
12
 
10
13
  Part of the [OpenCode Toolbelt](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit).
11
14
 
12
- ## Why this instead of `opencode-repo-auth` + `opencode-otel`
15
+ ## Why this instead of `opencode-oauth2` + `opencode-repo-auth` + `opencode-otel`
13
16
 
14
- Running both standalone plugins works, but each holds its **own** credential in its **own** cache
15
- namespace — two logins, two token stores, no relationship between them. `@vymalo/opencode-lightbridge`
16
- composes `@vymalo/opencode-auth-core` (the OAuth/token-exchange primitive) and
17
- `@vymalo/opencode-core-otel` (the OTel engine) over **one** `TokenRuntime`, so the project-scoped
18
- token minted for the gateway is the exact same token OTEL presents to the collector. No forked
19
- engine logic — this package is a thin composition layer over its two sibling libraries.
17
+ Running the standalone plugins works, but each holds its **own** credential in its **own** cache
18
+ namespace — up to three logins, three token stores, no relationship between them.
19
+ `@vymalo/opencode-lightbridge` composes `@vymalo/opencode-provider-sync` (the model-sync engine,
20
+ for `register`), `@vymalo/opencode-auth-core` (the OAuth/token-exchange primitive), and
21
+ `@vymalo/opencode-core-otel` (the OTel engine) over **one shared credential**, so the same token
22
+ that registers your provider is the one the gateway injects and OTEL presents to the collector. No
23
+ forked engine logic — this package composes its sibling libraries.
20
24
 
21
25
  ## Install
22
26
 
@@ -27,39 +31,61 @@ npm install @vymalo/opencode-lightbridge
27
31
  ```jsonc
28
32
  // opencode.json
29
33
  {
30
- "plugin": [
31
- ["@vymalo/opencode-lightbridge", {
32
- "auth": {
33
- "id": "lightbridge",
34
- "issuer": "https://authz.example.com/realms/lightbridge",
35
- "clientId": "opencode-cli",
36
- "scopes": ["openid", "offline_access"],
37
- "authFlow": "device_code"
38
- },
39
- "gateway": {
40
- "projectId": "proj-123",
41
- "providers": ["gateway"]
42
- },
43
- "otel": {
44
- "endpoint": "http://localhost:4318"
34
+ "plugins": [
35
+ {
36
+ "package": "@vymalo/opencode-lightbridge",
37
+ "options": {
38
+ "auth": {
39
+ "id": "lightbridge",
40
+ "issuer": "https://authz.example.com/realms/lightbridge",
41
+ "clientId": "opencode-cli",
42
+ "scopes": ["openid", "offline_access"],
43
+ "authFlow": "device_code"
44
+ },
45
+ "gateway": {
46
+ "providers": ["gateway"],
47
+ "exchange": false
48
+ },
49
+ "otel": {
50
+ "endpoint": "http://localhost:4318"
51
+ },
52
+ "register": {
53
+ "baseURL": "https://gateway.example.com/v1"
54
+ }
45
55
  }
46
- }]
56
+ }
47
57
  ]
48
58
  }
49
59
  ```
50
60
 
61
+ If you also run `@vymalo/opencode-oauth2`, list it **before** lightbridge: lightbridge detects an
62
+ oauth2-managed provider id from the `settings.oauth2` marker oauth2 stamps on the provider record.
63
+
51
64
  - **`auth`** (required) — the one IdP login, an `AuthServerConfigInput` (same shape as
52
- `opencode-oauth2`/`opencode-repo-auth`'s per-provider auth block).
53
- - **`gateway`** (optional) — the OpenCode provider ids to inject `Authorization: Bearer
54
- <project-token>` on, per request (plus an optional `projectId`).
65
+ `opencode-oauth2`/`opencode-repo-auth`'s per-provider auth block). `auth.id` also doubles as the
66
+ OpenCode provider id `register` uses and the shared cache identity (see below).
67
+ - **`register`** (optional, [ADR-0017](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0017-lightbridge-all-in-one.md)) —
68
+ register `auth`'s IdP as an OpenCode provider and keep its models in sync, via the same
69
+ `@vymalo/opencode-provider-sync` engine `opencode-oauth2` uses. Requires `register.baseURL`.
70
+ - **`gateway`** (optional) — the OpenCode provider ids to inject a bearer on, per request (plus an
71
+ optional `gateway.exchange`/`projectId`).
72
+ - **`gateway.exchange`** (optional, default `false`) — `false`: use the IdP access token directly.
73
+ `true`: RFC 8693-exchange it for a project-scoped token first (ADR-0012's original behaviour).
55
74
  - **`otel`** (optional) — the same `OtelPluginOptions` shape as `@vymalo/opencode-otel`
56
75
  (`endpoint`, `exporters`, `serviceName`, …), minus `tokenCommand`/`tokenHeader`/`tokenPrefix`: the
57
76
  shared runtime supersedes that seam entirely.
58
- - **`projectId`** (optional, top-level or under `gateway`) — **fully optional**: omit it and the
59
- exchange sends no `project_id`, so the backend mints a token for your **default project**. An
60
- explicit top-level `projectId` wins over `gateway.projectId`.
77
+ - **`projectId`** (optional, top-level or under `gateway`) — only meaningful when
78
+ `gateway.exchange: true`. **Fully optional**: omit it and the exchange sends no `project_id`, so
79
+ the backend mints a token for your **default project**. An explicit top-level `projectId` wins
80
+ over `gateway.projectId`.
81
+
82
+ Omitting `gateway`, `otel` and `register` is a valid, inert config — the plugin logs and no-ops.
61
83
 
62
- Omitting both `gateway` and `otel` is a valid, inert config — the plugin logs and no-ops.
84
+ **One login, shared with oauth2.** Configure the same `id`/`issuer`/`clientId` as an
85
+ `@vymalo/opencode-oauth2` server entry and logging in through either plugin makes the token
86
+ available to both — no second device-code/browser flow. An existing pre-ADR-0017 install's cached
87
+ login is migrated automatically on first use; no re-login is forced. See
88
+ [`docs/lightbridge.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/lightbridge.md#one-login-shared-cache-with-oauth2-adr-0017).
63
89
 
64
90
  ## Scope: gateway + OTEL, not MCP
65
91
 
@@ -73,13 +99,50 @@ for the full reasoning.
73
99
  ## Full reference
74
100
 
75
101
  - [`docs/lightbridge.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/lightbridge.md)
76
- — the one-credential design, full config reference, the three egresses.
102
+ — the all-in-one design, full config reference, migration notes.
77
103
  - [ADR-0012](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0012-single-auth-across-gateway-and-otel.md)
78
104
  — why one runtime, why MCP is excluded, the alternatives considered.
79
- - [`docs/repo-auth.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/repo-auth.md)
80
- and [`docs/otel.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/otel.md)
81
- — the two standalone plugins this package composes; useful for the config shapes it reuses.
105
+ - [ADR-0017](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0017-lightbridge-all-in-one.md)
106
+ — `register`, the shared cache with oauth2, and the exchange becoming opt-in.
107
+ - [`docs/repo-auth.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/repo-auth.md),
108
+ [`docs/otel.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/otel.md) and
109
+ [`docs/architecture.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/architecture.md)
110
+ (oauth2) — the standalone plugins this package composes; useful for the config shapes it reuses.
82
111
 
83
112
  ## License
84
113
 
85
114
  MIT
115
+
116
+ ## Migrating from v1
117
+
118
+ `0.18.0` targets the OpenCode **v2** plugin API; `0.17.x` was the last v1 release. See
119
+ [ADR-0019](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0019-opencode-v2-cutover.md)
120
+ for why there is no dual-support build.
121
+
122
+ | v1 | v2 |
123
+ | --- | --- |
124
+ | `"plugin": [["@vymalo/opencode-lightbridge", { ... }]]` | `"plugins": [{ "package": "@vymalo/opencode-lightbridge", "options": { ... } }]` |
125
+ | `config.provider[<auth.id>]` registered by `register` | provider editor: `package: "aisdk:@ai-sdk/openai-compatible"`, `settings.baseURL` |
126
+ | `chat.headers` (gateway bearer) | `session.hook("model.request")` |
127
+ | OTEL observers (`event`, `chat.message`, `tool.execute.*`, …) | `event.subscribe` + `prompt` / `context` / `tool` / `permission` hooks |
128
+ | trace propagation via `options.fetch` | `session.hook("http.request")` |
129
+ | host `config.logLevel` | `options.logLevel`, else `OPENCODE_LOG_LEVEL` |
130
+ | oauth2 collision via `pluginConfig` / `options.oauth2` | `settings.oauth2` marker on the provider record |
131
+
132
+ The `auth`, `gateway`, `otel` and `register` option blocks are unchanged, as is every event name
133
+ except where listed below.
134
+
135
+ ### Behaviour changes in v2
136
+
137
+ - **Gateway failures now refuse the request.** A failed token resolution used to leave the request
138
+ to go out bearer-less (the gateway 401'd); it now dies the hook so a stale stamped `Authorization`
139
+ cannot ride along (`lightbridge_gateway_no_bearer`, now `warn`).
140
+ - **List oauth2 before lightbridge** so the oauth2 collision marker is visible on the first pass.
141
+ - **`session.diff`, `todo.updated` and `command.executed` are no longer observed** (no v2 mapping):
142
+ the lines-of-code metric and `opencode.file_edited` event stop being produced.
143
+ - `experimental.compaction.autocontinue` is gone with no v2 equivalent.
144
+ - `./lib` drops `oauth2ManagedProviderIds` and `OpenCodeConfigLike`; `hasOAuth2Conflict` now takes
145
+ a provider editor.
146
+
147
+ The full list is in
148
+ [`docs/lightbridge.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/lightbridge.md#behaviour-changes-in-v2).
package/dist/config.d.ts CHANGED
@@ -7,9 +7,49 @@ export interface LightbridgeGatewayOptions {
7
7
  /**
8
8
  * Optional project id for the shared RFC 8693 exchange (ADR-0012). When
9
9
  * omitted, the exchange sends no `project_id` and the backend mints a token
10
- * for the caller's **default project**.
10
+ * for the caller's **default project**. Only meaningful when `exchange` is
11
+ * `true` — ignored otherwise (there is no exchange call to attach it to).
11
12
  */
12
13
  projectId?: string;
14
+ /**
15
+ * Whether to perform the RFC 8693 token exchange (ADR-0012) before using
16
+ * the result as the gateway bearer. Defaults to `false` (ADR-0017 amends
17
+ * ADR-0012): most IdP setups hand out an access token that is already
18
+ * scoped for the gateway, and an unconditional exchange fails outright for
19
+ * a client that has no `token-exchange` grant registered (`unauthorized_client`).
20
+ *
21
+ * - `false` (default): the IdP access token from `auth` is used directly
22
+ * as `Authorization: Bearer <token>` — no second network call, no extra
23
+ * grant required.
24
+ * - `true`: today's ADR-0012 behaviour, unchanged — the human token is
25
+ * exchanged for a project-scoped token (optionally carrying `project_id`)
26
+ * and THAT is used as the bearer.
27
+ */
28
+ exchange?: boolean;
29
+ }
30
+ /**
31
+ * The `register` opt-in (ADR-0017): register `auth`'s IdP as an OpenCode
32
+ * provider and keep its model list in sync via the SAME
33
+ * `@vymalo/opencode-provider-sync` engine `@vymalo/opencode-oauth2` uses —
34
+ * "everything oauth2 does", now available from lightbridge too. Independent
35
+ * of `gateway` (header injection) and `otel`: a config can register a
36
+ * provider without ever using it as the gateway bearer target, though the
37
+ * common case sets `gateway.providers` to the same id.
38
+ */
39
+ export interface LightbridgeRegisterOptions {
40
+ /** Base URL of the OpenAI-compatible (or Responses) inference endpoint. */
41
+ baseURL: string;
42
+ /** Display name for the registered OpenCode provider. Defaults to `auth.id`. */
43
+ name?: string;
44
+ /** Raw model id -> display name overrides, applied at discovery time. */
45
+ nameOverrides?: Record<string, string>;
46
+ /** Minutes between model-discovery syncs. Defaults to 60 (matches oauth2). */
47
+ syncIntervalMinutes?: number;
48
+ /**
49
+ * Route inference through the OpenAI Responses API instead of Chat
50
+ * Completions — see `@vymalo/opencode-provider-sync`'s `resolveProviderPackage`.
51
+ */
52
+ responseApi?: boolean;
13
53
  }
14
54
  /**
15
55
  * Plugin options as written under the `opencode-lightbridge` entry of
@@ -26,9 +66,12 @@ export interface LightbridgeOptions {
26
66
  * Optional project id for the shared project-scoped token exchange. Fully
27
67
  * optional: when omitted, the exchange sends no `project_id` and the backend
28
68
  * mints a token for the caller's **default project**. An explicit `projectId`
29
- * here wins over `gateway.projectId` when both are set.
69
+ * here wins over `gateway.projectId` when both are set. Only meaningful
70
+ * when `gateway.exchange` is `true`.
30
71
  */
31
72
  projectId?: string;
73
+ /** NEW (ADR-0017): register `auth`'s IdP as an OpenCode provider + sync its models. */
74
+ register?: LightbridgeRegisterOptions;
32
75
  }
33
76
  /** `LightbridgeOptions` after parsing: `auth` fully validated + defaulted. */
34
77
  export interface ParsedLightbridgeOptions {
@@ -37,6 +80,7 @@ export interface ParsedLightbridgeOptions {
37
80
  otel?: OtelPluginOptions;
38
81
  /** Resolved project id (`projectId` ?? `gateway.projectId`), if either was set. */
39
82
  projectId?: string;
83
+ register?: LightbridgeRegisterOptions;
40
84
  }
41
85
  /**
42
86
  * Parse + validate the plugin's `pluginOptions` (2nd factory arg). `auth` is
@@ -49,7 +93,29 @@ export interface ParsedLightbridgeOptions {
49
93
  export declare function parseLightbridgeOptions(raw: unknown): ParsedLightbridgeOptions;
50
94
  /**
51
95
  * Whether the parsed config has a module (gateway or otel) that needs the
52
- * shared token — project-scoped when `projectId` is set, else a default-project
53
- * token. Determines whether the one shared runtime is built.
96
+ * shared token — project-scoped when `projectId` is set (and `gateway.exchange`
97
+ * is `true`), else the raw IdP token. Determines whether the one shared
98
+ * runtime is built. `register` doesn't gate this: it drives a separate
99
+ * `ProviderModelSyncEngine`, not `LightbridgeRuntime`.
54
100
  */
55
101
  export declare function needsProjectToken(options: ParsedLightbridgeOptions): boolean;
102
+ /** The slice of a `ProviderEditor` the collision guard reads. */
103
+ export interface ProviderLookup {
104
+ get(id: string): {
105
+ readonly provider: {
106
+ readonly settings?: unknown;
107
+ };
108
+ } | undefined;
109
+ }
110
+ /**
111
+ * Whether `providerId` is (also) managed by `@vymalo/opencode-oauth2`, judged
112
+ * from the provider record's `settings.oauth2` / `settings.oauth2ModelSync`
113
+ * block. Used by lightbridge's `register` module to skip registering or owning
114
+ * a provider oauth2 already claims (logged at `debug`).
115
+ *
116
+ * Order-sensitive: oauth2 stamps its marker inside its own provider transform,
117
+ * so oauth2 must be listed BEFORE lightbridge in `plugin` for lightbridge to
118
+ * see it on the first pass. A later pass (oauth2's own `provider.reload()`)
119
+ * still detects it and lightbridge then stands its engine down.
120
+ */
121
+ export declare function hasOAuth2Conflict(editor: ProviderLookup, providerId: string): boolean;
package/dist/config.js CHANGED
@@ -14,6 +14,15 @@ function asProviderList(value, path) {
14
14
  }
15
15
  return value.map((entry, index) => asNonEmptyString(entry, `${path}[${index}]`));
16
16
  }
17
+ function asBoolean(value, path) {
18
+ if (value === undefined || value === null) {
19
+ return undefined;
20
+ }
21
+ if (typeof value !== "boolean") {
22
+ throw new Error(`${path} must be a boolean (received ${JSON.stringify(value)})`);
23
+ }
24
+ return value;
25
+ }
17
26
  function parseGateway(raw, path) {
18
27
  if (raw === undefined || raw === null) {
19
28
  return undefined;
@@ -22,11 +31,44 @@ function parseGateway(raw, path) {
22
31
  throw new Error(`${path} must be an object with \`providers\` (and an optional \`projectId\`)`);
23
32
  }
24
33
  const projectId = raw.projectId === undefined || raw.projectId === null ? undefined : asNonEmptyString(raw.projectId, `${path}.projectId`);
34
+ // Opt-in (ADR-0017, amends ADR-0012): defaults to `false` — see
35
+ // `LightbridgeGatewayOptions.exchange`'s doc comment for the rationale.
36
+ const exchange = asBoolean(raw.exchange, `${path}.exchange`) ?? false;
25
37
  return {
26
38
  providers: asProviderList(raw.providers, `${path}.providers`),
39
+ exchange,
27
40
  ...projectId ? { projectId } : {}
28
41
  };
29
42
  }
43
+ function asStringMap(value, path) {
44
+ if (value === undefined || value === null) {
45
+ return undefined;
46
+ }
47
+ if (!isRecord(value)) {
48
+ throw new Error(`${path} must be an object mapping model id -> display name`);
49
+ }
50
+ const normalized = {};
51
+ for (const [key, entry] of Object.entries(value)) {
52
+ normalized[key] = asNonEmptyString(entry, `${path}.${key}`);
53
+ }
54
+ return normalized;
55
+ }
56
+ function parseRegister(raw, path) {
57
+ if (raw === undefined || raw === null) {
58
+ return undefined;
59
+ }
60
+ if (!isRecord(raw)) {
61
+ throw new Error(`${path} must be an object with at least \`baseURL\``);
62
+ }
63
+ const syncIntervalMinutes = typeof raw.syncIntervalMinutes === "number" && Number.isFinite(raw.syncIntervalMinutes) && raw.syncIntervalMinutes > 0 ? raw.syncIntervalMinutes : undefined;
64
+ return {
65
+ baseURL: asNonEmptyString(raw.baseURL, `${path}.baseURL`),
66
+ name: raw.name === undefined || raw.name === null ? undefined : asNonEmptyString(raw.name, `${path}.name`),
67
+ nameOverrides: asStringMap(raw.nameOverrides, `${path}.nameOverrides`),
68
+ syncIntervalMinutes,
69
+ responseApi: asBoolean(raw.responseApi, `${path}.responseApi`)
70
+ };
71
+ }
30
72
  function parseOtel(raw, path) {
31
73
  if (raw === undefined || raw === null) {
32
74
  return undefined;
@@ -57,22 +99,53 @@ export function parseLightbridgeOptions(raw) {
57
99
  const auth = validateAuthConfig(raw.auth);
58
100
  const gateway = parseGateway(raw.gateway, "lightbridge.gateway");
59
101
  const otel = parseOtel(raw.otel, "lightbridge.otel");
102
+ const register = parseRegister(raw.register, "lightbridge.register");
60
103
  const explicitProjectId = typeof raw.projectId === "string" && raw.projectId.trim().length > 0 ? raw.projectId.trim() : undefined;
61
104
  const projectId = explicitProjectId ?? gateway?.projectId;
62
105
  return {
63
106
  auth,
64
107
  gateway,
65
108
  otel,
66
- projectId
109
+ projectId,
110
+ register
67
111
  };
68
112
  }
69
113
  /**
70
114
  * Whether the parsed config has a module (gateway or otel) that needs the
71
- * shared token — project-scoped when `projectId` is set, else a default-project
72
- * token. Determines whether the one shared runtime is built.
115
+ * shared token — project-scoped when `projectId` is set (and `gateway.exchange`
116
+ * is `true`), else the raw IdP token. Determines whether the one shared
117
+ * runtime is built. `register` doesn't gate this: it drives a separate
118
+ * `ProviderModelSyncEngine`, not `LightbridgeRuntime`.
73
119
  */
74
120
  export function needsProjectToken(options) {
75
121
  return Boolean(options.gateway) || Boolean(options.otel);
76
122
  }
123
+ // ---- oauth2 provider-id collision guard (ADR-0017 requirement 4) ----------
124
+ //
125
+ // If a developer configures BOTH `@vymalo/opencode-oauth2` and lightbridge's
126
+ // `register` block against the SAME OpenCode provider id, they must not both
127
+ // register/own it. v1 read oauth2's private `pluginConfig` off the shared
128
+ // config object; v2 plugins cannot see each other's options, so the signal is
129
+ // the provider RECORD: oauth2 stamps `settings.oauth2` (a user block, or the
130
+ // marker `{ managed: true, source: "options.servers" }`) on every provider it
131
+ // manages. Kept as a lightbridge-local literal rather than a shared import:
132
+ // each consumer's own settings-key literals are intentionally NOT centralized
133
+ // (see `@vymalo/opencode-provider-sync`'s `opencode-helpers.ts` module doc).
134
+ const OAUTH2_SETTINGS_KEYS = ["oauth2", "oauth2ModelSync"];
135
+ /**
136
+ * Whether `providerId` is (also) managed by `@vymalo/opencode-oauth2`, judged
137
+ * from the provider record's `settings.oauth2` / `settings.oauth2ModelSync`
138
+ * block. Used by lightbridge's `register` module to skip registering or owning
139
+ * a provider oauth2 already claims (logged at `debug`).
140
+ *
141
+ * Order-sensitive: oauth2 stamps its marker inside its own provider transform,
142
+ * so oauth2 must be listed BEFORE lightbridge in `plugin` for lightbridge to
143
+ * see it on the first pass. A later pass (oauth2's own `provider.reload()`)
144
+ * still detects it and lightbridge then stands its engine down.
145
+ */
146
+ export function hasOAuth2Conflict(editor, providerId) {
147
+ const settings = editor.get(providerId)?.provider.settings;
148
+ return isRecord(settings) && OAUTH2_SETTINGS_KEYS.some((key) => isRecord(settings[key]));
149
+ }
77
150
 
78
151
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"mappings":"AAAA,SACE,0BAGK;AA4CP,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAiB,OAAgB,MAAsB;CAC9D,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,CAAC,CAAC,WAAW,GAAG;EAC1D,MAAM,IAAI,MAAM,GAAG,KAAK,4BAA4B;CACtD;CACA,OAAO,MAAM,KAAK;AACpB;AAEA,SAAS,eAAe,OAAgB,MAAwB;CAC9D,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG;EAC/C,MAAM,IAAI,MAAM,GAAG,KAAK,2CAA2C;CACrE;CACA,OAAO,MAAM,KAAK,OAAO,UAAU,iBAAiB,OAAO,GAAG,KAAK,GAAG,MAAM,EAAE,CAAC;AACjF;AAEA,SAAS,aAAa,KAAc,MAAqD;CACvF,IAAI,QAAQ,aAAa,QAAQ,MAAM;EACrC,OAAO;CACT;CACA,IAAI,CAAC,SAAS,GAAG,GAAG;EAClB,MAAM,IAAI,MAAM,GAAG,KAAK,sEAAsE;CAChG;CACA,MAAM,YACJ,IAAI,cAAc,aAAa,IAAI,cAAc,OAC7C,YACA,iBAAiB,IAAI,WAAW,GAAG,KAAK,WAAW;CACzD,OAAO;EACL,WAAW,eAAe,IAAI,WAAW,GAAG,KAAK,WAAW;EAC5D,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;CACnC;AACF;AAEA,SAAS,UAAU,KAAc,MAA6C;CAC5E,IAAI,QAAQ,aAAa,QAAQ,MAAM;EACrC,OAAO;CACT;CACA,IAAI,CAAC,SAAS,GAAG,GAAG;EAClB,MAAM,IAAI,MAAM,GAAG,KAAK,uCAAuC;CACjE;;;;CAIA,OAAO;AACT;;;;;;;;;AAUA,OAAO,SAAS,wBAAwB,KAAwC;CAC9E,IAAI,CAAC,SAAS,GAAG,GAAG;EAClB,MAAM,IAAI,MACR,sFACF;CACF;CACA,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG;EACvB,MAAM,IAAI,MAAM,4EAA4E;CAC9F;CAEA,MAAM,OAAO,mBAAmB,IAAI,IAAwC;CAC5E,MAAM,UAAU,aAAa,IAAI,SAAS,qBAAqB;CAC/D,MAAM,OAAO,UAAU,IAAI,MAAM,kBAAkB;CAEnD,MAAM,oBACJ,OAAO,IAAI,cAAc,YAAY,IAAI,UAAU,KAAK,CAAC,CAAC,SAAS,IAC/D,IAAI,UAAU,KAAK,IACnB;CACN,MAAM,YAAY,qBAAqB,SAAS;CAEhD,OAAO;EAAE;EAAM;EAAS;EAAM;CAAU;AAC1C;;;;;;AAOA,OAAO,SAAS,kBAAkB,SAA4C;CAC5E,OAAO,QAAQ,QAAQ,OAAO,KAAK,QAAQ,QAAQ,IAAI;AACzD","names":[],"sources":["../src/config.ts"],"version":3,"file":"config.js","sourceRoot":""}
1
+ {"mappings":"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","names":[],"sources":["../src/config.ts"],"version":3,"file":"config.js","sourceRoot":""}
package/dist/lib.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { createLightbridgePlugin, LightbridgePlugin, type LightbridgePluginFactoryOptions } from "./opencode.js";
2
- export { LightbridgeRuntime, LIGHTBRIDGE_IDENTITY, DEFAULT_CACHE_NAMESPACE, DEFAULT_PROJECT_KEY, lightbridgeCacheDir, type LightbridgeRuntimeFactory, type LightbridgeRuntimeLike, type LightbridgeRuntimeOptions } from "./plugin.js";
3
- export { parseLightbridgeOptions, needsProjectToken, type LightbridgeGatewayOptions, type LightbridgeOptions, type ParsedLightbridgeOptions } from "./config.js";
1
+ export { createLightbridgePlugin, LightbridgePlugin, PLUGIN_ID, type LightbridgePluginFactoryOptions, type LightbridgeRegisterHandle } from "./opencode.js";
2
+ export { LightbridgeRuntime, LIGHTBRIDGE_IDENTITY, DEFAULT_CACHE_NAMESPACE, DEFAULT_PROJECT_KEY, ROOT_CACHE_NAMESPACE, ROOT_CACHE_SEGMENT, lightbridgeCacheDir, rootCacheDir, type LightbridgeRuntimeFactory, type LightbridgeRuntimeLike, type LightbridgeRuntimeOptions } from "./plugin.js";
3
+ export { hasOAuth2Conflict, needsProjectToken, parseLightbridgeOptions, type LightbridgeGatewayOptions, type LightbridgeOptions, type LightbridgeRegisterOptions, type ParsedLightbridgeOptions, type ProviderLookup } from "./config.js";
4
+ export { migrateRootTokenIfNeeded } from "./migration.js";
package/dist/lib.js CHANGED
@@ -1,5 +1,6 @@
1
- export { createLightbridgePlugin, LightbridgePlugin } from "./opencode.js";
2
- export { LightbridgeRuntime, LIGHTBRIDGE_IDENTITY, DEFAULT_CACHE_NAMESPACE, DEFAULT_PROJECT_KEY, lightbridgeCacheDir } from "./plugin.js";
3
- export { parseLightbridgeOptions, needsProjectToken } from "./config.js";
1
+ export { createLightbridgePlugin, LightbridgePlugin, PLUGIN_ID } from "./opencode.js";
2
+ export { LightbridgeRuntime, LIGHTBRIDGE_IDENTITY, DEFAULT_CACHE_NAMESPACE, DEFAULT_PROJECT_KEY, ROOT_CACHE_NAMESPACE, ROOT_CACHE_SEGMENT, lightbridgeCacheDir, rootCacheDir } from "./plugin.js";
3
+ export { hasOAuth2Conflict, needsProjectToken, parseLightbridgeOptions } from "./config.js";
4
+ export { migrateRootTokenIfNeeded } from "./migration.js";
4
5
 
5
6
  //# sourceMappingURL=lib.js.map
package/dist/lib.js.map CHANGED
@@ -1 +1 @@
1
- {"mappings":"AAAA,SACE,yBACA,yBAEK;AAEP,SACE,oBACA,sBACA,yBACA,qBACA,2BAIK;AAEP,SACE,yBACA,yBAIK","names":[],"sources":["../src/lib.ts"],"version":3,"file":"lib.js","sourceRoot":""}
1
+ {"mappings":"AAAA,SACE,yBACA,mBACA,iBAGK;AAEP,SACE,oBACA,sBACA,yBACA,qBACA,sBACA,oBACA,qBACA,oBAIK;AAEP,SACE,mBACA,mBACA,+BAMK;AAEP,SAAS,gCAAgC","names":[],"sources":["../src/lib.ts"],"version":3,"file":"lib.js","sourceRoot":""}
@@ -0,0 +1,44 @@
1
+ import { createJsonConsoleLogger, LOG_LEVEL_PRIORITY, type LogFields, type Logger, type LogLevel } from "@vymalo/opencode-auth-core/lib";
2
+ import { type Context as EffectContext, Effect } from "effect";
3
+ import { DEFAULT_LOG_LEVEL } from "@vymalo/opencode-auth-core/lib";
4
+ export { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, LOG_LEVEL_PRIORITY };
5
+ export type { LogFields, Logger, LogLevel };
6
+ /**
7
+ * Capture the host's Effect context from inside the plugin's own fiber. The
8
+ * returned value is what {@link createEffectLogger} (and {@link runInHost})
9
+ * needs to put a record on the host's logger rather than on a default one.
10
+ *
11
+ * OpenCode installs its file logger as a `Logger.layer(...)` on the runtime
12
+ * that runs plugin setup, and Effect carries loggers as fiber-level references
13
+ * — so the context captured here is the only route to that sink. A bare
14
+ * `Effect.runFork` would log to Effect's default logger (the terminal) instead,
15
+ * which is exactly what ADR-0014 forbids.
16
+ */
17
+ export declare const captureHostContext: Effect.Effect<EffectContext.Context<never>>;
18
+ /**
19
+ * Structured logger that writes through the host's Effect logger — the v2
20
+ * replacement for v1's `client.app.log`.
21
+ *
22
+ * ADR-0014 (no plugin writes to the user's TUI) is preserved: OpenCode's own
23
+ * logger is a `Logger.toFile` sink over the shared `opencode-<channel>.log`,
24
+ * so records land in the host log stream and never on the terminal. The event
25
+ * name becomes the log message and `fields` become log annotations.
26
+ *
27
+ * Host-side severity filtering is Effect's job (`OPENCODE_LOG_LEVEL`); the
28
+ * `getMinLevel` gate here is only this plugin's own `logLevel` option, applied
29
+ * before the record is built so a filtered-out call costs nothing.
30
+ */
31
+ export declare function createEffectLogger(context: EffectContext.Context<never>, getMinLevel: () => LogLevel): Logger;
32
+ /**
33
+ * Run an Effect from a detached callback (a timer, a promise continuation)
34
+ * inside the captured host context, so anything it logs reaches the host
35
+ * logger instead of Effect's default one (ADR-0014).
36
+ */
37
+ export declare function runInHost(context: EffectContext.Context<never>, effect: Effect.Effect<void>): Promise<void>;
38
+ /**
39
+ * Resolve this plugin's log level from its own `logLevel` option, falling back
40
+ * to the host's `OPENCODE_LOG_LEVEL` env var. v2 gives plugins no `config`
41
+ * object, so the v1 `config.logLevel` channel is gone. Replaces v1's
42
+ * `fromOpenCodeLogLevel`.
43
+ */
44
+ export declare function resolveLogLevel(value: unknown): LogLevel | undefined;
@@ -0,0 +1,100 @@
1
+ import { createJsonConsoleLogger, LOG_LEVEL_PRIORITY } from "@vymalo/opencode-auth-core/lib";
2
+ import { Effect } from "effect";
3
+ import { DEFAULT_LOG_LEVEL } from "@vymalo/opencode-auth-core/lib";
4
+ export { createJsonConsoleLogger, DEFAULT_LOG_LEVEL, LOG_LEVEL_PRIORITY };
5
+ function redactFields(fields) {
6
+ if (!fields) {
7
+ return undefined;
8
+ }
9
+ const redacted = {};
10
+ for (const [key, value] of Object.entries(fields)) {
11
+ if (/token|secret|password|authorization/i.test(key)) {
12
+ redacted[key] = "[redacted]";
13
+ continue;
14
+ }
15
+ redacted[key] = value;
16
+ }
17
+ return redacted;
18
+ }
19
+ /**
20
+ * Capture the host's Effect context from inside the plugin's own fiber. The
21
+ * returned value is what {@link createEffectLogger} (and {@link runInHost})
22
+ * needs to put a record on the host's logger rather than on a default one.
23
+ *
24
+ * OpenCode installs its file logger as a `Logger.layer(...)` on the runtime
25
+ * that runs plugin setup, and Effect carries loggers as fiber-level references
26
+ * — so the context captured here is the only route to that sink. A bare
27
+ * `Effect.runFork` would log to Effect's default logger (the terminal) instead,
28
+ * which is exactly what ADR-0014 forbids.
29
+ */
30
+ export const captureHostContext = Effect.withFiber((fiber) => Effect.succeed(fiber.context));
31
+ /**
32
+ * Structured logger that writes through the host's Effect logger — the v2
33
+ * replacement for v1's `client.app.log`.
34
+ *
35
+ * ADR-0014 (no plugin writes to the user's TUI) is preserved: OpenCode's own
36
+ * logger is a `Logger.toFile` sink over the shared `opencode-<channel>.log`,
37
+ * so records land in the host log stream and never on the terminal. The event
38
+ * name becomes the log message and `fields` become log annotations.
39
+ *
40
+ * Host-side severity filtering is Effect's job (`OPENCODE_LOG_LEVEL`); the
41
+ * `getMinLevel` gate here is only this plugin's own `logLevel` option, applied
42
+ * before the record is built so a filtered-out call costs nothing.
43
+ */
44
+ export function createEffectLogger(context, getMinLevel) {
45
+ // `VYMALO_PLUGIN_CONSOLE_LOG=1` mirrors to stdout for local debugging. Off by
46
+ // default precisely because stdout is the user's terminal (ADR-0014).
47
+ const consoleAll = /^(1|true|yes|on)$/i.test(process.env.VYMALO_PLUGIN_CONSOLE_LOG ?? "");
48
+ const fallback = createJsonConsoleLogger("trace");
49
+ const write = (level, event, fields) => {
50
+ if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[getMinLevel()]) {
51
+ return;
52
+ }
53
+ if (consoleAll) {
54
+ fallback[level](event, fields);
55
+ }
56
+ const redacted = redactFields(fields);
57
+ // Effect has no `trace` severity below `debug`; fold the most-verbose tier
58
+ // onto debug, as the v1 adapter did for the host log API (ADR-0008).
59
+ const record = level === "trace" || level === "debug" ? Effect.logDebug(event) : level === "info" ? Effect.logInfo(event) : level === "warn" ? Effect.logWarning(event) : Effect.logError(event);
60
+ const annotated = redacted ? Effect.annotateLogs(record, redacted) : record;
61
+ Effect.runFork(Effect.provideContext(annotated, context));
62
+ };
63
+ return {
64
+ trace: (event, fields) => write("trace", event, fields),
65
+ debug: (event, fields) => write("debug", event, fields),
66
+ info: (event, fields) => write("info", event, fields),
67
+ warn: (event, fields) => write("warn", event, fields),
68
+ error: (event, fields) => write("error", event, fields)
69
+ };
70
+ }
71
+ /**
72
+ * Run an Effect from a detached callback (a timer, a promise continuation)
73
+ * inside the captured host context, so anything it logs reaches the host
74
+ * logger instead of Effect's default one (ADR-0014).
75
+ */
76
+ export function runInHost(context, effect) {
77
+ return Effect.runPromise(Effect.provideContext(effect, context));
78
+ }
79
+ /**
80
+ * Resolve this plugin's log level from its own `logLevel` option, falling back
81
+ * to the host's `OPENCODE_LOG_LEVEL` env var. v2 gives plugins no `config`
82
+ * object, so the v1 `config.logLevel` channel is gone. Replaces v1's
83
+ * `fromOpenCodeLogLevel`.
84
+ */
85
+ export function resolveLogLevel(value) {
86
+ if (typeof value !== "string") {
87
+ return undefined;
88
+ }
89
+ switch (value.toUpperCase()) {
90
+ // DEBUG unlocks the most-verbose `trace` tier (host has no finer level).
91
+ case "DEBUG":
92
+ case "TRACE": return "trace";
93
+ case "INFO": return "info";
94
+ case "WARN": return "warn";
95
+ case "ERROR": return "error";
96
+ default: return undefined;
97
+ }
98
+ }
99
+
100
+ //# sourceMappingURL=logging.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"AAAA,SACE,yBACA,0BAIK;AACP,SAAwC,cAAc;AAEtD,SAAS,yBAAyB;AAElC,SAAS,yBAAyB,mBAAmB;AAGrD,SAAS,aAAa,QAA2C;CAC/D,IAAI,CAAC,QAAQ;EACX,OAAO;CACT;CACA,MAAM,WAAsB,CAAC;CAC7B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EACjD,IAAI,uCAAuC,KAAK,GAAG,GAAG;GACpD,SAAS,OAAO;GAChB;EACF;EACA,SAAS,OAAO;CAClB;CACA,OAAO;AACT;;;;;;;;;;;;AAaA,OAAO,MAAM,qBAAkE,OAAO,WACnF,UAAU,OAAO,QAAQ,MAAM,OAAO,CACzC;;;;;;;;;;;;;;AAeA,OAAO,SAAS,mBACd,SACA,aACQ;;;CAGR,MAAM,aAAa,qBAAqB,KAAK,QAAQ,IAAI,6BAA6B,EAAE;CACxF,MAAM,WAAW,wBAAwB,OAAO;CAEhD,MAAM,SAAS,OAAiB,OAAe,WAA6B;EAC1E,IAAI,mBAAmB,SAAS,mBAAmB,YAAY,IAAI;GACjE;EACF;EACA,IAAI,YAAY;GACd,SAAS,MAAM,CAAC,OAAO,MAAM;EAC/B;EAEA,MAAM,WAAW,aAAa,MAAM;;;EAGpC,MAAM,SACJ,UAAU,WAAW,UAAU,UAC3B,OAAO,SAAS,KAAK,IACrB,UAAU,SACR,OAAO,QAAQ,KAAK,IACpB,UAAU,SACR,OAAO,WAAW,KAAK,IACvB,OAAO,SAAS,KAAK;EAE/B,MAAM,YAAY,WAAW,OAAO,aAAa,QAAQ,QAAQ,IAAI;EACrE,OAAO,QAAQ,OAAO,eAAe,WAAW,OAAO,CAAC;CAC1D;CAEA,OAAO;EACL,QAAQ,OAAO,WAAW,MAAM,SAAS,OAAO,MAAM;EACtD,QAAQ,OAAO,WAAW,MAAM,SAAS,OAAO,MAAM;EACtD,OAAO,OAAO,WAAW,MAAM,QAAQ,OAAO,MAAM;EACpD,OAAO,OAAO,WAAW,MAAM,QAAQ,OAAO,MAAM;EACpD,QAAQ,OAAO,WAAW,MAAM,SAAS,OAAO,MAAM;CACxD;AACF;;;;;;AAOA,OAAO,SAAS,UACd,SACA,QACe;CACf,OAAO,OAAO,WAAW,OAAO,eAAe,QAAQ,OAAO,CAAC;AACjE;;;;;;;AAQA,OAAO,SAAS,gBAAgB,OAAsC;CACpE,IAAI,OAAO,UAAU,UAAU;EAC7B,OAAO;CACT;CACA,QAAQ,MAAM,YAAY,GAA1B;;EAEE,KAAK;EACL,KAAK,SACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,SACE,OAAO;CACX;AACF","names":[],"sources":["../src/logging.ts"],"version":3,"file":"logging.js","sourceRoot":""}
@@ -0,0 +1,31 @@
1
+ import { type Logger } from "@vymalo/opencode-auth-core/lib";
2
+ /**
3
+ * One-time migration read-path (ADR-0017, mandatory — not optional polish).
4
+ *
5
+ * v0.17 moves lightbridge's human root token from its own bespoke file
6
+ * (`<cacheRoot>/opencode-lightbridge/lightbridge.json`, a bare `TokenSet`) to
7
+ * the SAME fused `CachedServerState` file `@vymalo/opencode-oauth2` uses
8
+ * (`<cacheRoot>/<sharedSegment>/<sharedNamespace>/<serverId>.json`) so the two
9
+ * plugins can share one login (ADR-0017 §"one login, shared cache"). An
10
+ * install that already has a valid old-format token must NOT be forced
11
+ * through a fresh device-code/browser login just because the file moved.
12
+ *
13
+ * Behaviour:
14
+ * - If the NEW location already has a persisted state (with or without a
15
+ * token), this is a no-op — never clobbers a fresher state (e.g. one
16
+ * oauth2 has already been writing to, with real discovered models).
17
+ * - Else, read the OLD location. If it holds a structurally usable token
18
+ * (`accessToken` + `tokenType` present — possibly expired; an expired
19
+ * token with a refresh token is still worth adopting, since `TokenRuntime`
20
+ * will refresh it transparently), write it into the NEW location as a
21
+ * fresh `CachedServerState` (empty `models`/`rawModels` — nothing to carry
22
+ * over, since the old shape never tracked models).
23
+ * - Else (no old file, or it's empty/malformed) this is a no-op; the caller
24
+ * proceeds to a normal login.
25
+ *
26
+ * The old file is left in place (non-destructive) — this function only ever
27
+ * ADDS the new file, never deletes/mutates the old one. Safe to call more
28
+ * than once: the "new location already has a state" check makes every call
29
+ * after the first a cheap no-op.
30
+ */
31
+ export declare function migrateRootTokenIfNeeded(cacheRoot: string, sharedSegment: string, sharedNamespace: string, serverId: string, logger: Logger): Promise<void>;