@vymalo/opencode-lightbridge 0.17.0 → 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
@@ -31,30 +31,36 @@ npm install @vymalo/opencode-lightbridge
31
31
  ```jsonc
32
32
  // opencode.json
33
33
  {
34
- "plugin": [
35
- ["@vymalo/opencode-lightbridge", {
36
- "auth": {
37
- "id": "lightbridge",
38
- "issuer": "https://authz.example.com/realms/lightbridge",
39
- "clientId": "opencode-cli",
40
- "scopes": ["openid", "offline_access"],
41
- "authFlow": "device_code"
42
- },
43
- "gateway": {
44
- "providers": ["gateway"],
45
- "exchange": false
46
- },
47
- "otel": {
48
- "endpoint": "http://localhost:4318"
49
- },
50
- "register": {
51
- "baseURL": "https://gateway.example.com/v1"
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
+ }
52
55
  }
53
- }]
56
+ }
54
57
  ]
55
58
  }
56
59
  ```
57
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
+
58
64
  - **`auth`** (required) — the one IdP login, an `AuthServerConfigInput` (same shape as
59
65
  `opencode-oauth2`/`opencode-repo-auth`'s per-provider auth block). `auth.id` also doubles as the
60
66
  OpenCode provider id `register` uses and the shared cache identity (see below).
@@ -106,3 +112,37 @@ for the full reasoning.
106
112
  ## License
107
113
 
108
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
@@ -47,7 +47,7 @@ export interface LightbridgeRegisterOptions {
47
47
  syncIntervalMinutes?: number;
48
48
  /**
49
49
  * Route inference through the OpenAI Responses API instead of Chat
50
- * Completions — see `@vymalo/opencode-provider-sync`'s `resolveProviderNpm`.
50
+ * Completions — see `@vymalo/opencode-provider-sync`'s `resolveProviderPackage`.
51
51
  */
52
52
  responseApi?: boolean;
53
53
  }
@@ -99,20 +99,23 @@ export declare function parseLightbridgeOptions(raw: unknown): ParsedLightbridge
99
99
  * `ProviderModelSyncEngine`, not `LightbridgeRuntime`.
100
100
  */
101
101
  export declare function needsProjectToken(options: ParsedLightbridgeOptions): boolean;
102
- export interface OpenCodeConfigLike {
103
- provider?: unknown;
104
- pluginConfig?: unknown;
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;
105
109
  }
106
110
  /**
107
- * Provider ids managed by `@vymalo/opencode-oauth2` via its
108
- * `pluginConfig.oauth2ModelSync.servers` channel.
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.
109
120
  */
110
- export declare function oauth2ManagedProviderIds(config: OpenCodeConfigLike): Set<string>;
111
- /**
112
- * Whether `providerId` is (also) managed by `@vymalo/opencode-oauth2` —
113
- * either via that provider's own `options.oauth2` / `options.oauth2ModelSync`
114
- * block, or via the `pluginConfig.oauth2ModelSync.servers` channel. Used by
115
- * lightbridge's `register` module to skip registering/owning a provider
116
- * oauth2 already claims (logged at `debug` — see `opencode.ts`).
117
- */
118
- export declare function hasOAuth2Conflict(config: OpenCodeConfigLike, providerId: string): boolean;
121
+ export declare function hasOAuth2Conflict(editor: ProviderLookup, providerId: string): boolean;
package/dist/config.js CHANGED
@@ -124,47 +124,28 @@ export function needsProjectToken(options) {
124
124
  //
125
125
  // If a developer configures BOTH `@vymalo/opencode-oauth2` and lightbridge's
126
126
  // `register` block against the SAME OpenCode provider id, they must not both
127
- // register/own it — mirrors `@vymalo/opencode-repo-auth`'s
128
- // `hasOAuth2Conflict`/`oauth2ManagedProviderIds` (which defers to oauth2 the
129
- // same way). Kept as a lightbridge-local copy rather than a shared import:
130
- // each consumer's own config-key literals are intentionally NOT centralized
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
131
133
  // (see `@vymalo/opencode-provider-sync`'s `opencode-helpers.ts` module doc).
132
- const OAUTH2_OPTION_KEYS = ["oauth2", "oauth2ModelSync"];
134
+ const OAUTH2_SETTINGS_KEYS = ["oauth2", "oauth2ModelSync"];
133
135
  /**
134
- * Provider ids managed by `@vymalo/opencode-oauth2` via its
135
- * `pluginConfig.oauth2ModelSync.servers` channel.
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.
136
145
  */
137
- export function oauth2ManagedProviderIds(config) {
138
- const ids = new Set();
139
- const pluginConfig = isRecord(config.pluginConfig) ? config.pluginConfig : undefined;
140
- const oauth2ModelSync = isRecord(pluginConfig?.oauth2ModelSync) ? pluginConfig?.oauth2ModelSync : undefined;
141
- const servers = oauth2ModelSync?.servers;
142
- if (Array.isArray(servers)) {
143
- for (const raw of servers) {
144
- const entry = isRecord(raw) ? raw : undefined;
145
- const id = typeof entry?.id === "string" ? entry.id : undefined;
146
- if (id) {
147
- ids.add(id);
148
- }
149
- }
150
- }
151
- return ids;
152
- }
153
- /**
154
- * Whether `providerId` is (also) managed by `@vymalo/opencode-oauth2` —
155
- * either via that provider's own `options.oauth2` / `options.oauth2ModelSync`
156
- * block, or via the `pluginConfig.oauth2ModelSync.servers` channel. Used by
157
- * lightbridge's `register` module to skip registering/owning a provider
158
- * oauth2 already claims (logged at `debug` — see `opencode.ts`).
159
- */
160
- export function hasOAuth2Conflict(config, providerId) {
161
- const providers = isRecord(config.provider) ? config.provider : undefined;
162
- const providerEntry = providers ? providers[providerId] : undefined;
163
- const providerOptions = isRecord(providerEntry) && isRecord(providerEntry.options) ? providerEntry.options : undefined;
164
- if (providerOptions && OAUTH2_OPTION_KEYS.some((key) => isRecord(providerOptions[key]))) {
165
- return true;
166
- }
167
- return oauth2ManagedProviderIds(config).has(providerId);
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]));
168
149
  }
169
150
 
170
151
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"mappings":"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","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,4 +1,4 @@
1
- export { createLightbridgePlugin, LightbridgePlugin, type LightbridgePluginFactoryOptions } from "./opencode.js";
1
+ export { createLightbridgePlugin, LightbridgePlugin, PLUGIN_ID, type LightbridgePluginFactoryOptions, type LightbridgeRegisterHandle } from "./opencode.js";
2
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, oauth2ManagedProviderIds, parseLightbridgeOptions, type LightbridgeGatewayOptions, type LightbridgeOptions, type LightbridgeRegisterOptions, type OpenCodeConfigLike, type ParsedLightbridgeOptions } from "./config.js";
3
+ export { hasOAuth2Conflict, needsProjectToken, parseLightbridgeOptions, type LightbridgeGatewayOptions, type LightbridgeOptions, type LightbridgeRegisterOptions, type ParsedLightbridgeOptions, type ProviderLookup } from "./config.js";
4
4
  export { migrateRootTokenIfNeeded } from "./migration.js";
package/dist/lib.js CHANGED
@@ -1,6 +1,6 @@
1
- export { createLightbridgePlugin, LightbridgePlugin } from "./opencode.js";
1
+ export { createLightbridgePlugin, LightbridgePlugin, PLUGIN_ID } from "./opencode.js";
2
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, oauth2ManagedProviderIds, parseLightbridgeOptions } from "./config.js";
3
+ export { hasOAuth2Conflict, needsProjectToken, parseLightbridgeOptions } from "./config.js";
4
4
  export { migrateRootTokenIfNeeded } from "./migration.js";
5
5
 
6
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,sBACA,oBACA,qBACA,oBAIK;AAEP,SACE,mBACA,mBACA,0BACA,+BAMK;AAEP,SAAS,gCAAgC","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":""}
@@ -1,9 +1,19 @@
1
- import type { Hooks, Plugin } from "@opencode-ai/plugin";
2
- import { type Logger } from "@vymalo/opencode-auth-core/lib";
1
+ import { Plugin } from "@opencode/plugin/effect";
3
2
  import { type EnvSource, type ExporterFactories } from "@vymalo/opencode-core-otel";
3
+ import { ProviderModelSyncEngine } from "@vymalo/opencode-provider-sync/lib";
4
+ import { type Logger } from "./logging.js";
4
5
  import { type LightbridgeRuntimeFactory } from "./plugin.js";
5
- type OpenCodeConfig = Parameters<NonNullable<Hooks["config"]>>[0];
6
+ /** Stable plugin id. v2 requires one and keys storage/registrations by it. */
7
+ export declare const PLUGIN_ID = "vymalo.lightbridge";
8
+ /** Test/embedder handle onto the `register` module's background engine. */
9
+ export interface LightbridgeRegisterHandle {
10
+ /** Resolves once every queued engine build + warmup (and its reload) has finished. */
11
+ settled(): Promise<void>;
12
+ /** The current engine, or `undefined` before it is ready / after a collision stand-down. */
13
+ engine(): ProviderModelSyncEngine | undefined;
14
+ }
6
15
  export interface LightbridgePluginFactoryOptions {
16
+ /** Override the logger (tests). Defaults to the host's Effect logger. */
7
17
  logger?: Logger;
8
18
  /** Shared-runtime HTTP client (auth-core `TokenRuntime`). Tests only. */
9
19
  fetchImpl?: typeof fetch;
@@ -17,13 +27,15 @@ export interface LightbridgePluginFactoryOptions {
17
27
  * neither module ever touches the network.
18
28
  */
19
29
  runtimeFactory?: LightbridgeRuntimeFactory;
30
+ /** Observe the `register` module's background engine (tests). */
31
+ onRegister?: (handle: LightbridgeRegisterHandle) => void;
20
32
  /** Injected environment; defaults to `process.env`. */
21
33
  env?: EnvSource;
22
34
  /** Substitute exporters (tests use in-memory ones). */
23
35
  exporters?: ExporterFactories;
24
36
  /** Injectable clock; defaults to `Date.now`. */
25
37
  now?: () => number;
26
- /** Skip `beforeExit`/`SIGINT`/`SIGTERM` registration (tests). */
38
+ /** Skip the `beforeExit`/`SIGINT`/`SIGTERM` exit handlers (tests). See ADR-0018. */
27
39
  registerProcessHandlers?: boolean;
28
40
  /** Override the resolved host metadata (hostname, version) for tests. */
29
41
  hostInfo?: {
@@ -34,16 +46,22 @@ export interface LightbridgePluginFactoryOptions {
34
46
  deferredTimeoutMs?: number;
35
47
  }
36
48
  /**
37
- * Create the `@vymalo/opencode-lightbridge` umbrella plugin (ADR-0012): ONE
38
- * shared `TokenRuntime`, built once from `auth`, drives both the gateway
39
- * bearer (`gateway`) and the OTEL export credential (`otel`) — each module
40
- * activates independently, and an `auth`-only config is a valid, inert
41
- * plugin. `config` runs both modules' config-time logic (host log level +
42
- * OTEL trace propagation); `chat.headers` is the gateway injector;
43
- * `event`/`chat.message`/`tool.execute.*`/`chat.params`/`permission.ask`/
44
- * `experimental.*` are the OTEL observers.
49
+ * Create the `@vymalo/opencode-lightbridge` umbrella plugin (ADR-0012,
50
+ * ADR-0017) for the OpenCode v2 plugin API (ADR-0019): ONE shared
51
+ * `TokenRuntime`, built once from `auth`, drives both the gateway bearer
52
+ * (`gateway`) and the OTEL export credential (`otel`) — each module activates
53
+ * independently, and an `auth`-only config is a valid, inert plugin.
54
+ *
55
+ * - `register`: `provider.transform` registers the provider and merges the
56
+ * discovered models and cached bearer; a queued background build runs the
57
+ * `ProviderModelSyncEngine` and calls `provider.reload()` (the transform is
58
+ * synchronous and cheap by contract — same shape as oauth2).
59
+ * - `gateway`: `session.hook("model.request")` injects the shared bearer and
60
+ * fails closed by dying the request when no bearer can be produced.
61
+ * - `otel`: `event.subscribe` + `prompt`/`context`/`tool`/`permission` hooks
62
+ * feed the recorder through `otel-adapter.ts`; `http.request` carries W3C
63
+ * trace context.
45
64
  */
46
- export declare function createLightbridgePlugin(factoryOptions?: LightbridgePluginFactoryOptions): Plugin;
47
- export declare const LightbridgePlugin: Plugin;
65
+ export declare function createLightbridgePlugin(factoryOptions?: LightbridgePluginFactoryOptions): Plugin.Plugin;
66
+ export declare const LightbridgePlugin: Plugin.Plugin;
48
67
  export default LightbridgePlugin;
49
- export type { OpenCodeConfig };