@vymalo/opencode-lightbridge 0.18.1 → 0.20.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
@@ -83,8 +83,9 @@ Omitting `gateway`, `otel` and `register` is a valid, inert config — the plugi
83
83
 
84
84
  **One login, shared with oauth2.** Configure the same `id`/`issuer`/`clientId` as an
85
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
86
+ available to both — no second device-code/browser flow. The token lives in the durable OS **state**
87
+ directory, not the cache directory ([ADR-0022](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/adr/0022-credentials-in-state-dir.md)).
88
+ **Upgrading to that release needs one re-login** (no migration, no fallback read). See
88
89
  [`docs/lightbridge.md`](https://github.com/ADORSYS-GIS/lightbridge-opencode-toolbeit/blob/main/docs/lightbridge.md#one-login-shared-cache-with-oauth2-adr-0017).
89
90
 
90
91
  ## Scope: gateway + OTEL, not MCP
package/dist/lib.d.ts CHANGED
@@ -1,4 +1,3 @@
1
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";
2
+ export { LightbridgeRuntime, LIGHTBRIDGE_IDENTITY, EXCHANGE_STATE_NAMESPACE, DEFAULT_PROJECT_KEY, ROOT_CACHE_NAMESPACE, ROOT_CACHE_SEGMENT, lightbridgeStateDir, rootCacheDir, rootStateDir, type LightbridgeRuntimeFactory, type LightbridgeRuntimeLike, type LightbridgeRuntimeOptions } from "./plugin.js";
3
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,6 +1,5 @@
1
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";
2
+ export { LightbridgeRuntime, LIGHTBRIDGE_IDENTITY, EXCHANGE_STATE_NAMESPACE, DEFAULT_PROJECT_KEY, ROOT_CACHE_NAMESPACE, ROOT_CACHE_SEGMENT, lightbridgeStateDir, rootCacheDir, rootStateDir } from "./plugin.js";
3
3
  export { hasOAuth2Conflict, needsProjectToken, parseLightbridgeOptions } from "./config.js";
4
- export { migrateRootTokenIfNeeded } from "./migration.js";
5
4
 
6
5
  //# sourceMappingURL=lib.js.map
package/dist/lib.js.map CHANGED
@@ -1 +1 @@
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":""}
1
+ {"mappings":"AAAA,SACE,yBACA,mBACA,iBAGK;AAEP,SACE,oBACA,sBACA,0BACA,qBACA,sBACA,oBACA,qBACA,cACA,oBAIK;AAEP,SACE,mBACA,mBACA,+BAMK","names":[],"sources":["../src/lib.ts"],"version":3,"file":"lib.js","sourceRoot":""}
@@ -18,8 +18,10 @@ export interface LightbridgePluginFactoryOptions {
18
18
  /** Shared-runtime HTTP client (auth-core `TokenRuntime`). Tests only. */
19
19
  fetchImpl?: typeof fetch;
20
20
  onAuthorizationUrl?: (url: string) => Promise<void> | void;
21
- /** Override the shared-runtime cache root (defaults to the OS cache dir). */
21
+ /** Override the CACHE root for the register module's disposable model list (defaults to the OS cache dir). */
22
22
  cacheDir?: string;
23
+ /** Override the STATE root for credentials — tokens + refresh locks (defaults to the OS state dir, ADR-0022). */
24
+ stateDir?: string;
23
25
  tokenExpirySkewMs?: number;
24
26
  /**
25
27
  * Build the shared `LightbridgeRuntimeLike` for `(auth, projectId)`.
package/dist/opencode.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { hostname } from "node:os";
2
- import { join } from "node:path";
3
2
  import { Plugin } from "@opencode/plugin/effect";
4
3
  import { defaultTextMapSetter } from "@opentelemetry/api";
5
4
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
5
+ import { isInteractiveAuthRequiredError } from "@vymalo/opencode-auth-core/lib";
6
6
  import { buildResource, createProviders, deferredAttribute, describeError, readVcsInfo, registerExitHandlers, resolveOtelConfig, TelemetryRecorder } from "@vymalo/opencode-core-otel";
7
7
  import { applyResponsesApiSettings, mergeDiscoveredModels, propagateCachedBearer, ProviderModelSyncEngine, resolveCachedBearer, resolveProviderPackage, upsertManagedProvider } from "@vymalo/opencode-provider-sync/lib";
8
8
  import { Effect, Stream } from "effect";
9
9
  import { hasOAuth2Conflict, needsProjectToken, parseLightbridgeOptions } from "./config.js";
10
10
  import { captureHostContext, createEffectLogger, DEFAULT_LOG_LEVEL, resolveLogLevel, runInHost } from "./logging.js";
11
11
  import { adaptContext, adaptPermission, adaptPrompt, adaptToolAfter, adaptToolBefore, OtelEventAdapter } from "./otel-adapter.js";
12
- import { LightbridgeRuntime, ROOT_CACHE_NAMESPACE, ROOT_CACHE_SEGMENT } from "./plugin.js";
12
+ import { LightbridgeRuntime, ROOT_CACHE_NAMESPACE, ROOT_CACHE_SEGMENT, rootCacheDir, rootStateDir } from "./plugin.js";
13
+ import { createSignInRegistration, createSignInRequiredNotifier, signInPath } from "./sign-in.js";
13
14
  /** Stable plugin id. v2 requires one and keys storage/registrations by it. */
14
15
  export const PLUGIN_ID = "vymalo.lightbridge";
15
16
  function errorMessage(error) {
@@ -37,13 +38,16 @@ function safeHostname() {
37
38
  * every export attempt, a later successful login resumes exporting on the
38
39
  * next flush with nothing to restart.
39
40
  */
40
- function createRuntimeTokenSource(runtime) {
41
+ function createRuntimeTokenSource(runtime, onSignInRequired) {
41
42
  return {
42
43
  headers: async () => {
43
44
  try {
44
- const token = await runtime.getProjectToken({ interactive: false });
45
+ const token = await runtime.getProjectToken();
45
46
  return { Authorization: `${token.tokenType || "Bearer"} ${token.accessToken}` };
46
- } catch {
47
+ } catch (error) {
48
+ if (isInteractiveAuthRequiredError(error)) {
49
+ onSignInRequired("otel");
50
+ }
47
51
  return undefined;
48
52
  }
49
53
  },
@@ -129,7 +133,7 @@ function warnIfTokenCommandConfigIgnored(otel, env, logger) {
129
133
  * handlers. Mirrors `@vymalo/opencode-otel`'s orchestration verbatim, minus
130
134
  * the standalone `tokenCommand` seam.
131
135
  */
132
- async function buildOtelModule(location, otelConfig, logger, factoryOptions, runtime) {
136
+ async function buildOtelModule(location, otelConfig, logger, factoryOptions, runtime, onSignInRequired) {
133
137
  const version = deferredAttribute(factoryOptions.deferredTimeoutMs);
134
138
  const branch = deferredAttribute(factoryOptions.deferredTimeoutMs);
135
139
  if (factoryOptions.hostInfo?.version) {
@@ -148,7 +152,7 @@ async function buildOtelModule(location, otelConfig, logger, factoryOptions, run
148
152
  branch: branch.value,
149
153
  vcs
150
154
  });
151
- const tokenSource = runtime ? createRuntimeTokenSource(runtime) : undefined;
155
+ const tokenSource = runtime ? createRuntimeTokenSource(runtime, onSignInRequired) : undefined;
152
156
  const providers = createProviders(otelConfig, resource, logger, factoryOptions.exporters, tokenSource);
153
157
  const recorder = new TelemetryRecorder({
154
158
  providers,
@@ -249,15 +253,28 @@ export function createLightbridgePlugin(factoryOptions = {}) {
249
253
  // and the OTEL token source below. `undefined` only for a register/auth-only
250
254
  // config.
251
255
  const buildRuntime = factoryOptions.runtimeFactory ?? ((auth, pid, options) => new LightbridgeRuntime(auth, pid, options));
252
- const sharedRuntime = wantsProjectToken ? buildRuntime(parsed.auth, parsed.projectId, {
256
+ // Built whenever a module needs a token OR the user must be able to sign in
257
+ // (device_code, ADR-0023): the sign-in method drives this same runtime.
258
+ const signInEnabled = parsed.auth.authFlow === "device_code";
259
+ const sharedRuntime = wantsProjectToken || signInEnabled ? buildRuntime(parsed.auth, parsed.projectId, {
253
260
  logger,
254
261
  fetchImpl: factoryOptions.fetchImpl,
255
262
  onAuthorizationUrl: factoryOptions.onAuthorizationUrl,
256
- cacheDir: factoryOptions.cacheDir,
263
+ stateDir: factoryOptions.stateDir,
257
264
  tokenExpirySkewMs: factoryOptions.tokenExpirySkewMs,
258
265
  exchange: parsed.gateway?.exchange ?? false
259
266
  }) : undefined;
260
267
  const reloadProviders = () => runInHost(hostContext, ctx.provider.reload());
268
+ const signInName = parsed.register?.name ?? parsed.auth.id;
269
+ const signInNotice = createSignInRequiredNotifier(logger, signInName);
270
+ // Only meaningful when the Connect dialog can actually be used to fix it.
271
+ const notifySignInRequired = (source) => {
272
+ if (signInEnabled) {
273
+ signInNotice.notify(source);
274
+ }
275
+ };
276
+ // Set below when sign-in is registered; the gateway hook calls it (cached).
277
+ let observeConnectionCached = async () => {};
261
278
  // ---- register module (ADR-0017 requirement 1) ----
262
279
  let engine;
263
280
  let registerChain = Promise.resolve();
@@ -282,10 +299,14 @@ export function createLightbridgePlugin(factoryOptions = {}) {
282
299
  servers: [buildRegisterServerConfig(parsed.auth, register)],
283
300
  cacheNamespace: ROOT_CACHE_NAMESPACE
284
301
  }, {
302
+ // Sign-in happens only in the host's Connect dialog (ADR-0023): the
303
+ // engine's warmup, scheduler and token reads are refresh-only.
304
+ neverInteractive: true,
285
305
  logger,
286
306
  fetchImpl: factoryOptions.fetchImpl,
287
307
  onAuthorizationUrl: factoryOptions.onAuthorizationUrl,
288
- cacheDir: factoryOptions.cacheDir ? join(factoryOptions.cacheDir, ROOT_CACHE_SEGMENT, ROOT_CACHE_NAMESPACE) : undefined,
308
+ cacheDir: factoryOptions.cacheDir ? rootCacheDir(factoryOptions.cacheDir) : undefined,
309
+ stateDir: factoryOptions.stateDir ? rootStateDir(factoryOptions.stateDir) : undefined,
289
310
  cacheNamespaceSegment: ROOT_CACHE_SEGMENT,
290
311
  serviceLabel: "opencode-lightbridge"
291
312
  });
@@ -301,6 +322,17 @@ export function createLightbridgePlugin(factoryOptions = {}) {
301
322
  }
302
323
  engine = next;
303
324
  registerBearer = await resolveCachedBearer(parsed.auth.id, next, logger);
325
+ if (registerBearer === undefined && sharedRuntime) {
326
+ // resolveCachedBearer swallows why; ask the runtime so only a genuine
327
+ // "needs sign-in" (not a network/IdP outage) raises the notice.
328
+ try {
329
+ await sharedRuntime.ensureRootToken();
330
+ } catch (error) {
331
+ if (isInteractiveAuthRequiredError(error)) {
332
+ notifySignInRequired("register");
333
+ }
334
+ }
335
+ }
304
336
  // Models and bearer are now known: ask the host to re-run transforms.
305
337
  await reloadProviders();
306
338
  };
@@ -355,6 +387,116 @@ export function createLightbridgePlugin(factoryOptions = {}) {
355
387
  }
356
388
  });
357
389
  }
390
+ // ---- sign-in (ADR-0023): the ONLY place an interactive login can start ----
391
+ if (signInEnabled && sharedRuntime) {
392
+ const runtime = sharedRuntime;
393
+ const afterSignIn = async () => {
394
+ try {
395
+ // Pick up the new credential in the register module's engine and
396
+ // refresh the stamped bearer, then ask the host to re-run transforms.
397
+ if (engine) {
398
+ await engine.syncServer(parsed.auth.id);
399
+ registerBearer = await resolveCachedBearer(parsed.auth.id, engine, logger);
400
+ }
401
+ } catch (error) {
402
+ logger.warn("lightbridge_sign_in_sync_failed", { error: errorMessage(error) });
403
+ }
404
+ try {
405
+ await reloadProviders();
406
+ } catch (error) {
407
+ logger.warn("lightbridge_sign_in_reload_failed", { error: errorMessage(error) });
408
+ }
409
+ };
410
+ const registration = createSignInRegistration({
411
+ integrationID: parsed.auth.id,
412
+ runtime,
413
+ logger,
414
+ afterSignIn,
415
+ onSignedIn: () => signInNotice.reset(),
416
+ onSignInRequired: notifySignInRequired
417
+ });
418
+ // The integration id is `auth.id`, never the well-known URL's: the host
419
+ // loads the well-known manifest with `credentials.at(-1)` of ITS
420
+ // integration, and an oauth credential there would freeze it (ADR-0023).
421
+ yield* ctx.integration.transform((editor) => {
422
+ editor.update(parsed.auth.id, (integration) => {
423
+ integration.name = signInName;
424
+ });
425
+ editor.method.update(registration);
426
+ });
427
+ // ---- host disconnect => sign out (ADR-0023) ----
428
+ // TRANSITION-based: only a connection we saw present in THIS process and
429
+ // that is then gone signs us out. A host with no credential at startup is
430
+ // normal (users who signed in before sign-in moved into the dialog have a
431
+ // token and no host credential) and must keep working.
432
+ let connectionSeen = false;
433
+ let signingOut = false;
434
+ let lastObservedAt = 0;
435
+ const OBSERVE_CACHE_MS = 5e3;
436
+ const signOut = async () => {
437
+ logger.info("lightbridge_signed_out_by_host", { integrationID: parsed.auth.id });
438
+ try {
439
+ await runtime.reset();
440
+ } catch (error) {
441
+ logger.warn("lightbridge_sign_out_reset_failed", { error: errorMessage(error) });
442
+ }
443
+ // The engine keeps its token in memory even when the file is gone:
444
+ // drop it, and let the next transform build a fresh (signed-out) one.
445
+ stopEngine();
446
+ registerQueued = false;
447
+ signInNotice.reset();
448
+ notifySignInRequired("disconnect");
449
+ try {
450
+ await reloadProviders();
451
+ } catch (error) {
452
+ logger.warn("lightbridge_sign_out_reload_failed", { error: errorMessage(error) });
453
+ }
454
+ };
455
+ const onConnection = async (present) => {
456
+ if (present) {
457
+ connectionSeen = true;
458
+ return;
459
+ }
460
+ if (!connectionSeen || signingOut) {
461
+ return;
462
+ }
463
+ connectionSeen = false;
464
+ signingOut = true;
465
+ try {
466
+ await signOut();
467
+ } finally {
468
+ signingOut = false;
469
+ }
470
+ };
471
+ const observeConnection = (force) => {
472
+ if (!force && Date.now() - lastObservedAt < OBSERVE_CACHE_MS) {
473
+ return Promise.resolve();
474
+ }
475
+ lastObservedAt = Date.now();
476
+ return runInHost(hostContext, ctx.integration.connection.active(parsed.auth.id).pipe(Effect.flatMap((info) => Effect.promise(() => onConnection(info !== undefined))), Effect.ignore));
477
+ };
478
+ observeConnectionCached = () => observeConnection(false);
479
+ yield* Effect.forkScoped(Stream.runForEach(ctx.event.subscribe({ types: [
480
+ "credential.updated",
481
+ "credential.switched",
482
+ "integration.updated"
483
+ ] }), () => Effect.promise(() => observeConnection(true))).pipe(Effect.tapError((error) => Effect.sync(() => logger.warn("lightbridge_connection_watch_failed", { error: errorMessage(error) }))), Effect.ignore));
484
+ void observeConnection(true);
485
+ // Report a missing sign-in once, up front, without any network on the
486
+ // signed-out path (a stored token may refresh, which is fine).
487
+ void runtime.ensureRootToken().catch((error) => {
488
+ if (isInteractiveAuthRequiredError(error)) {
489
+ notifySignInRequired("startup");
490
+ } else {
491
+ logger.debug("lightbridge_startup_token_check_failed", { error: errorMessage(error) });
492
+ }
493
+ });
494
+ } else if (parsed.auth.authFlow === "authorization_code") {
495
+ logger.warn("lightbridge_sign_in_unsupported_flow", {
496
+ authFlow: parsed.auth.authFlow,
497
+ message: `lightbridge signs in through the host's Connect dialog (${signInPath(signInName)}), which needs authFlow "device_code"`
498
+ });
499
+ }
358
500
  // ---- gateway module ----
359
501
  // `sharedRuntime` is always defined here: it is built whenever `gateway`
360
502
  // or `otel` is configured (`wantsProjectToken`).
@@ -376,7 +518,8 @@ export function createLightbridgePlugin(factoryOptions = {}) {
376
518
  }
377
519
  return Effect.tryPromise({
378
520
  try: async () => {
379
- const token = await runtime.getProjectToken({ interactive: true });
521
+ await observeConnectionCached();
522
+ const token = await runtime.getProjectToken();
380
523
  // Header names are case-insensitive: drop any variant already set
381
524
  // (a stamped record header, the placeholder key's, a lowercase copy).
382
525
  for (const key of Object.keys(event.headers)) {
@@ -388,10 +531,18 @@ export function createLightbridgePlugin(factoryOptions = {}) {
388
531
  logger.trace("lightbridge_gateway_bearer_injected", { providerId });
389
532
  },
390
533
  catch: (error) => error
391
- }).pipe(Effect.tapError((error) => Effect.sync(() => logger.warn("lightbridge_gateway_no_bearer", {
392
- providerId,
393
- error: errorMessage(error)
394
- }))), Effect.orDie);
534
+ }).pipe(Effect.tapError((error) => Effect.sync(() => {
535
+ const needsSignIn = isInteractiveAuthRequiredError(error);
536
+ // While signed out every request repeats this; the one-time
537
+ // lightbridge_sign_in_required warning already says what to do.
538
+ logger[needsSignIn && signInEnabled ? "debug" : "warn"]("lightbridge_gateway_no_bearer", {
539
+ providerId,
540
+ error: errorMessage(error)
541
+ });
542
+ if (needsSignIn) {
543
+ notifySignInRequired("gateway");
544
+ }
545
+ })), Effect.orDie);
395
546
  });
396
547
  }
397
548
  // ---- otel module ----
@@ -404,7 +555,7 @@ export function createLightbridgePlugin(factoryOptions = {}) {
404
555
  const built = yield* Effect.promise(() => buildOtelModule({
405
556
  directory: ctx.location.directory,
406
557
  projectId: ctx.location.project?.id
407
- }, otelConfig, logger, factoryOptions, sharedRuntime));
558
+ }, otelConfig, logger, factoryOptions, sharedRuntime, notifySignInRequired));
408
559
  otel = built;
409
560
  const { recorder } = built;
410
561
  const adapter = new OtelEventAdapter(recorder, factoryOptions.now);
@@ -1 +1 @@
1
- {"mappings":"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","names":[],"sources":["../src/opencode.ts"],"version":3,"file":"opencode.js","sourceRoot":""}
1
+ {"mappings":"AAAA,SAAS,gBAAgB;AAEzB,SAAS,cAAc;AACvB,SAAS,4BAA4B;AACrC,SAAS,iCAAiC;AAC1C,SACE,sCAEK;AACP,SACE,eACA,iBACA,mBACA,eACA,aACA,sBACA,mBACA,yBAOK;AACP,SACE,2BACA,uBACA,uBACA,yBACA,qBACA,wBACA,6BAGK;AACP,SAAS,QAAQ,cAAc;AAE/B,SACE,mBACA,mBACA,+BAGK;AACP,SACE,oBACA,oBACA,mBAGA,iBACA,iBACK;AACP,SACE,cACA,iBACA,aACA,gBACA,iBAEA,wBACK;AACP,SACE,oBACA,sBACA,oBACA,cACA,oBAIK;AACP,SAAS,0BAA0B,8BAA8B,kBAAkB;;AAGnF,OAAO,MAAM,YAAY;AA6CzB,SAAS,aAAa,OAAwB;CAC5C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,SAAS,eAAmC;CAC1C,IAAI;EACF,OAAO,SAAS,KAAK;CACvB,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;AAiBA,SAAS,yBACP,SACA,kBACa;CACb,OAAO;EACL,SAAS,YAAyD;GAChE,IAAI;IACF,MAAM,QAAQ,MAAM,QAAQ,gBAAgB;IAC5C,OAAO,EAAE,eAAe,GAAG,MAAM,aAAa,SAAS,GAAG,MAAM,cAAc;GAChF,SAAS,OAAO;IACd,IAAI,+BAA+B,KAAK,GAAG;KACzC,iBAAiB,MAAM;IACzB;IACA,OAAO;GACT;EACF;;;;;EAKA,kBAAkB,CAAC;CACrB;AACF;;;;;;;;AASA,SAAS,0BACP,MACA,UACsB;CACtB,OAAO;EACL,IAAI,KAAK;EACT,MAAM,SAAS,QAAQ,KAAK;EAC5B,QAAQ,KAAK;EACb,SAAS,SAAS;EAClB,UAAU,KAAK;EACf,cAAc,KAAK;EACnB,QAAQ,KAAK;EACb,qBAAqB,SAAS,uBAAuB;EACrD,eAAe,SAAS,iBAAiB,CAAC;EAC1C,uBAAuB,KAAK;EAC5B,eAAe,KAAK;EACpB,6BAA6B,KAAK;EAClC,SAAS,KAAK;EACd,cAAc,KAAK;EACnB,UAAU,KAAK;EACf,MAAM,KAAK;EACX,oBAAoB,KAAK;EACzB,uBAAuB,KAAK;CAC9B;AACF;AAEA,SAAS,WAAW,OAAyC;CAC3D,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK,IACrE,QACD,CAAC;AACP;;;;;;AAOA,SAAS,yBACP,QACA,YACA,UACA,QACM;CACN,MAAM,WAAW,OAAO,IAAI,UAAU,CAAC,EAAE;;;CAGzC,MAAM,eAAe,YAAY,SAAS,SAAS,aAAa,SAAS,OAAO;CAChF,sBAAsB,QAAQ,YAAY;EACxC,MAAM,gBAAgB,SAAS,QAAQ;EACvC,SAAS,uBAAuB,SAAS,WAAW;EACpD,UAAU,0BACR;GAAE,GAAG,WAAW,UAAU,QAAQ;GAAG,SAAS,SAAS;EAAQ,GAC/D,SAAS,aACT,YACA,MACF;CACF,CAAC;AACH;;;;;;;;;;;;;AAmBA,SAAS,gCACP,MACA,KACA,QACM;CACN,MAAM,WACJ,KAAK,iBAAiB,aACtB,KAAK,gBAAgB,aACrB,KAAK,gBAAgB,aACrB,QAAQ,IAAI,2BAA2B,KACvC,QAAQ,IAAI,0BAA0B,KACtC,QAAQ,IAAI,0BAA0B;CACxC,IAAI,UAAU;EACZ,OAAO,MAAM,0CAA0C,EACrD,QACE,mHACJ,CAAC;CACH;AACF;;;;;;;AAaA,eAAe,gBACb,UACA,YACA,QACA,gBACA,SACA,kBACyE;CACzE,MAAM,UAAU,kBAAkB,eAAe,iBAAiB;CAClE,MAAM,SAAS,kBAAkB,eAAe,iBAAiB;CACjE,IAAI,eAAe,UAAU,SAAS;EACpC,QAAQ,OAAO,eAAe,SAAS,OAAO;CAChD;CAEA,MAAM,MAAe,WAAW,aAC5B,MAAM,YAAY,SAAS,SAAS,CAAC,CAAC,aAAa,CAAC,EAAE,IACtD,CAAC;CACL,IAAI,IAAI,KAAK;EACX,OAAO,OAAO,IAAI,GAAG;CACvB;CAEA,MAAM,WAAW,cAAc,YAAY;EACzC,SAAS,QAAQ;EACjB,UAAU,eAAe,UAAU,YAAY,aAAa;EAC5D,aAAa,SAAS;EACtB,WAAW,SAAS;EACpB,UAAU,SAAS;EACnB,QAAQ,OAAO;EACf;CACF,CAAC;CAED,MAAM,cAAc,UAAU,yBAAyB,SAAS,gBAAgB,IAAI;CACpF,MAAM,YAAY,gBAChB,YACA,UACA,QACA,eAAe,WACf,WACF;CACA,MAAM,WAAW,IAAI,kBAAkB;EACrC;EACA,QAAQ;EACR;EACA,KAAK,eAAe;EACpB,eAAe;GACb,UAAU,UAAU,QAAQ,OAAO,KAAK;GACxC,SAAS,UAAU,OAAO,OAAO,KAAK;EACxC;CACF,CAAC;CAED,IAAI,eAAe,4BAA4B,OAAO;;EAEpD,qBAAqB,WAAW,QAAQ,CAAC,SAAS,MAAM,GAAG,EACzD,aAAa,mBACf,CAAC;CACH;CAEA,OAAO,KAAK,4BAA4B;EACtC,aAAa,WAAW;EACxB,WAAW,WAAW;EACtB,UAAU,WAAW;EACrB,0BAA0B,QAAQ,WAAW;CAC/C,CAAC;CAED,OAAO;EAAE,QAAQ;EAAY;EAAU;CAAQ;AACjD;AAEA,MAAM,aAAa,IAAI,0BAA0B;;;;;AAMjD,MAAM,mBAAmB;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;;;;;;;;;;;;AAmBA,OAAO,SAAS,wBACd,iBAAkD,CAAC,GACpC;CACf,OAAO,OAAO,OAAO;EACnB,IAAI;EACJ,QAAQ,OAAO,GAAG,WAAW,KAAK;;;GAGhC,MAAM,WACJ,gBAAgB,IAAI,QAAQ,QAAQ,KACpC,gBAAgB,QAAQ,IAAI,kBAAkB,KAC9C;;;GAIF,MAAM,cAAc,OAAO;GAC3B,MAAM,SACJ,eAAe,UAAU,mBAAmB,mBAAmB,QAAQ;GAEzE,IAAI;GACJ,IAAI;IACF,SAAS,wBAAwB,IAAI,OAAO;GAC9C,SAAS,OAAO;;;IAGd,OAAO,MAAM,8BAA8B,EAAE,OAAO,cAAc,KAAK,EAAE,CAAC;IAC1E;GACF;GAEA,MAAM,oBAAoB,kBAAkB,MAAM;GAClD,IAAI,qBAAqB,CAAC,OAAO,WAAW;;;IAG1C,OAAO,KAAK,+BAA+B;KACzC,SAAS,QAAQ,OAAO,OAAO;KAC/B,MAAM,QAAQ,OAAO,IAAI;IAC3B,CAAC;GACH;;;;;GAMA,MAAM,eACJ,eAAe,oBACb,MAAwB,KAAyB,YACjD,IAAI,mBAAmB,MAAM,KAAK,OAAO;;;GAG7C,MAAM,gBAAgB,OAAO,KAAK,aAAa;GAC/C,MAAM,gBACJ,qBAAqB,gBACjB,aAAa,OAAO,MAAM,OAAO,WAAW;IAC1C;IACA,WAAW,eAAe;IAC1B,oBAAoB,eAAe;IACnC,UAAU,eAAe;IACzB,mBAAmB,eAAe;IAClC,UAAU,OAAO,SAAS,YAAY;GACxC,CAAC,IACD;GAEN,MAAM,wBAAuC,UAAU,aAAa,IAAI,SAAS,OAAO,CAAC;GAEzF,MAAM,aAAa,OAAO,UAAU,QAAQ,OAAO,KAAK;GACxD,MAAM,eAAe,6BAA6B,QAAQ,UAAU;;GAEpE,MAAM,wBAAwB,WAAyB;IACrD,IAAI,eAAe;KACjB,aAAa,OAAO,MAAM;IAC5B;GACF;;GAEA,IAAI,0BAA+C,YAAY,CAAC;;GAGhE,IAAI;GACJ,IAAI,gBAA+B,QAAQ,QAAQ;;GAEnD,IAAI,iBAAiB;;GAErB,IAAI,qBAAqB;GACzB,IAAI,aAAa;GACjB,IAAI;GACJ,MAAM,UAAU,IAAI,IAAoB;GAExC,MAAM,mBAAyB;IAC7B,QAAQ,KAAK;IACb,SAAS;IACT,iBAAiB;GACnB;GAEA,MAAM,sBAAsB,OAAO,aAAwD;;IAEzF,IAAI,oBAAoB;KACtB;IACF;IACA,MAAM,OAAO,IAAI,wBACf;KACE,SAAS,CAAC,0BAA0B,OAAO,MAAM,QAAQ,CAAC;KAC1D,gBAAgB;IAClB,GACA;;;KAGE,kBAAkB;KAClB;KACA,WAAW,eAAe;KAC1B,oBAAoB,eAAe;KACnC,UAAU,eAAe,WAAW,aAAa,eAAe,QAAQ,IAAI;KAC5E,UAAU,eAAe,WAAW,aAAa,eAAe,QAAQ,IAAI;KAC5E,uBAAuB;KACvB,cAAc;IAChB,CACF;IACA,MAAM,KAAK,WAAW;IACtB,IAAI,oBAAoB;KACtB;IACF;IACA,MAAM,KAAK,MAAM,EAAE,QAAQ,KAAK,CAAC;IACjC,IAAI,oBAAoB;;KAEtB,KAAK,KAAK;KACV;IACF;IACA,SAAS;IACT,iBAAiB,MAAM,oBAAoB,OAAO,KAAK,IAAI,MAAM,MAAM;IACvE,IAAI,mBAAmB,aAAa,eAAe;;;KAGjD,IAAI;MACF,MAAM,cAAc,gBAAgB;KACtC,SAAS,OAAO;MACd,IAAI,+BAA+B,KAAK,GAAG;OACzC,qBAAqB,UAAU;MACjC;KACF;IACF;;IAEA,MAAM,gBAAgB;GACxB;GAEA,IAAI,OAAO,UAAU;IACnB,MAAM,WAAW,OAAO;IACxB,MAAM,aAAa,OAAO,KAAK;IAE/B,eAAe,aAAa;KAC1B,MAAM,UAAU;MACd,IAAI;MACJ,GAAG;OACD,UAAU;OACV,MAAM;MACR,SAAS,YAAY;KACvB;KACA,cAAc;IAChB,CAAC;IAED,OAAO,IAAI,SAAS,WAAW,WAAW;KACxC,IAAI,kBAAkB,QAAQ,UAAU,GAAG;MACzC,qBAAqB;MACrB,iBAAiB;MACjB,WAAW;MACX,IAAI,CAAC,YAAY;OACf,aAAa;OACb,OAAO,MAAM,gDAAgD,EAAE,WAAW,CAAC;MAC7E;MACA;KACF;KACA,qBAAqB;KAErB,yBAAyB,QAAQ,YAAY,UAAU,MAAM;KAC7D,IAAI,aAAa;KACjB,IAAI,QAAQ;MACV,IAAI;OACF,MAAM,SAAS,OAAO,gBAAgB,UAAU;OAChD,aAAa,OAAO;OACpB,IAAI,OAAO,SAAS,GAAG;QACrB,sBAAsB,QAAQ,YAAY,MAAM;OAClD;MACF,QAAQ,CAER;KACF;KACA,sBAAsB,QAAQ,YAAY,gBAAgB,QAAQ,OAAO;KACzE,OAAO,MAAM,uCAAuC;MAAE;MAAY;KAAW,CAAC;KAE9E,IAAI,CAAC,gBAAgB;MACnB,iBAAiB;MACjB,gBAAgB,cACb,WAAW,oBAAoB,QAAQ,CAAC,CAAC,CACzC,OAAO,UAAU;;OAEhB,iBAAiB;OACjB,OAAO,MAAM,qCAAqC,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC;MAClF,CAAC;KACL;IACF,CAAC;GACH;;GAGA,IAAI,iBAAiB,eAAe;IAClC,MAAM,UAAU;IAChB,MAAM,cAAc,YAA2B;KAC7C,IAAI;;;MAGF,IAAI,QAAQ;OACV,MAAM,OAAO,WAAW,OAAO,KAAK,EAAE;OACtC,iBAAiB,MAAM,oBAAoB,OAAO,KAAK,IAAI,QAAQ,MAAM;MAC3E;KACF,SAAS,OAAO;MACd,OAAO,KAAK,mCAAmC,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC;KAC/E;KACA,IAAI;MACF,MAAM,gBAAgB;KACxB,SAAS,OAAO;MACd,OAAO,KAAK,qCAAqC,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC;KACjF;IACF;IACA,MAAM,eAAe,yBAAyB;KAC5C,eAAe,OAAO,KAAK;KAC3B;KACA;KACA;KACA,kBAAkB,aAAa,MAAM;KACrC,kBAAkB;IACpB,CAAC;;;;IAID,OAAO,IAAI,YAAY,WAAW,WAAW;KAC3C,OAAO,OAAO,OAAO,KAAK,KAAK,gBAAgB;MAC7C,YAAY,OAAO;KACrB,CAAC;KACD,OAAO,OAAO,OAAO,YAAY;IACnC,CAAC;;;;;;IAOD,IAAI,iBAAiB;IACrB,IAAI,aAAa;IACjB,IAAI,iBAAiB;IACrB,MAAM,mBAAmB;IACzB,MAAM,UAAU,YAA2B;KACzC,OAAO,KAAK,kCAAkC,EAAE,eAAe,OAAO,KAAK,GAAG,CAAC;KAC/E,IAAI;MACF,MAAM,QAAQ,MAAM;KACtB,SAAS,OAAO;MACd,OAAO,KAAK,qCAAqC,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC;KACjF;;;KAGA,WAAW;KACX,iBAAiB;KACjB,aAAa,MAAM;KACnB,qBAAqB,YAAY;KACjC,IAAI;MACF,MAAM,gBAAgB;KACxB,SAAS,OAAO;MACd,OAAO,KAAK,sCAAsC,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC;KAClF;IACF;IACA,MAAM,eAAe,OAAO,YAAoC;KAC9D,IAAI,SAAS;MACX,iBAAiB;MACjB;KACF;KACA,IAAI,CAAC,kBAAkB,YAAY;MACjC;KACF;KACA,iBAAiB;KACjB,aAAa;KACb,IAAI;MACF,MAAM,QAAQ;KAChB,UAAU;MACR,aAAa;KACf;IACF;IACA,MAAM,qBAAqB,UAAkC;KAC3D,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,iBAAiB,kBAAkB;MAC5D,OAAO,QAAQ,QAAQ;KACzB;KACA,iBAAiB,KAAK,IAAI;KAC1B,OAAO,UACL,aACA,IAAI,YAAY,WAAW,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,KAChD,OAAO,SAAS,SAAS,OAAO,cAAc,aAAa,SAAS,SAAS,CAAC,CAAC,GAC/E,OAAO,MACT,CACF;IACF;IACA,gCAAgC,kBAAkB,KAAK;IACvD,OAAO,OAAO,WACZ,OAAO,WACL,IAAI,MAAM,UAAU,EAClB,OAAO;KAAC;KAAsB;KAAuB;IAAqB,EAC5E,CAAC,SACK,OAAO,cAAc,kBAAkB,IAAI,CAAC,CACpD,CAAC,CAAC,KACA,OAAO,UAAU,UACf,OAAO,WACL,OAAO,KAAK,uCAAuC,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC,CACnF,CACF,GACA,OAAO,MACT,CACF;IACA,KAAK,kBAAkB,IAAI;;;IAI3B,KAAK,QAAQ,gBAAgB,CAAC,CAAC,OAAO,UAAmB;KACvD,IAAI,+BAA+B,KAAK,GAAG;MACzC,qBAAqB,SAAS;KAChC,OAAO;MACL,OAAO,MAAM,0CAA0C,EAAE,OAAO,aAAa,KAAK,EAAE,CAAC;KACvF;IACF,CAAC;GACH,OAAO,IAAI,OAAO,KAAK,aAAa,sBAAsB;IACxD,OAAO,KAAK,wCAAwC;KAClD,UAAU,OAAO,KAAK;KACtB,SAAS,2DAA2D,WAAW,UAAU,EAAE;IAC7F,CAAC;GACH;;;;GAKA,IAAI,OAAO,WAAW,eAAe;IACnC,MAAM,YAAY,IAAI,IAAI,OAAO,QAAQ,SAAS;IAClD,MAAM,UAAU;;;;;;;;IAQhB,OAAO,IAAI,QAAQ,KAAK,kBAAkB,UAAU;KAClD,MAAM,aAAa,MAAM,MAAM;KAC/B,IAAI,CAAC,UAAU,IAAI,UAAU,GAAG;MAC9B,OAAO,MAAM,6CAA6C,EAAE,WAAW,CAAC;MACxE,OAAO,OAAO;KAChB;KACA,OAAO,OAAO,WAAW;MACvB,KAAK,YAAY;OACf,MAAM,wBAAwB;OAC9B,MAAM,QAAQ,MAAM,QAAQ,gBAAgB;;;OAG5C,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,OAAO,GAAG;QAC5C,IAAI,IAAI,YAAY,MAAM,iBAAiB;SACzC,OAAO,MAAM,QAAQ;QACvB;OACF;OACA,MAAM,QAAQ,gBAAgB,GAAG,MAAM,aAAa,SAAS,GAAG,MAAM;OACtE,OAAO,MAAM,uCAAuC,EAAE,WAAW,CAAC;MACpE;MACA,QAAQ,UAAU;KACpB,CAAC,CAAC,CAAC,KACD,OAAO,UAAU,UACf,OAAO,WAAW;MAChB,MAAM,cAAc,+BAA+B,KAAK;;;MAGxD,OAAO,eAAe,gBAAgB,UAAU,OAAO,CACrD,iCACA;OAAE;OAAY,OAAO,aAAa,KAAK;MAAE,CAC3C;MACA,IAAI,aAAa;OACf,qBAAqB,SAAS;MAChC;KACF,CAAC,CACH,GACA,OAAO,KACT;IACF,CAAC;GACH;;GAGA,IAAI;GACJ,IAAI,OAAO,MAAM;IACf,MAAM,MAAM,eAAe,OAAO,QAAQ;IAC1C,MAAM,aAAa,kBAAkB,OAAO,MAAM,GAAG;IACrD,IAAI,WAAW,QAAQ;KACrB,gCAAgC,OAAO,MAAM,KAAK,MAAM;KACxD,MAAM,QAAQ,OAAO,OAAO,cAC1B,gBACE;MAAE,WAAW,IAAI,SAAS;MAAW,WAAW,IAAI,SAAS,SAAS;KAAG,GACzE,YACA,QACA,gBACA,eACA,oBACF,CACF;KACA,OAAO;KACP,MAAM,EAAE,aAAa;KACrB,MAAM,UAAU,IAAI,iBAAiB,UAAU,eAAe,GAAG;KAEjE,OAAO,OAAO,WACZ,OAAO,WAAW,IAAI,MAAM,UAAU,EAAE,OAAO,iBAAiB,CAAC,IAAI,UACnE,OAAO,WAAW,QAAQ,OAAO,KAA6B,CAAC,CACjE,CAAC,CAAC,KACA,OAAO,UAAU,UACf,OAAO,WACL,OAAO,KAAK,wCAAwC,EAClD,OAAO,aAAa,KAAK,EAC3B,CAAC,CACH,CACF,GACA,OAAO,MACT,CACF;KAEA,OAAO,IAAI,QAAQ,KAAK,WAAW,UACjC,OAAO,WAAW;MAChB,MAAM,CAAC,OAAO,UAAU,YAAY,KAAK;MACzC,SAAS,cAAc,OAAO,MAAM;KACtC,CAAC,CACH;KACA,OAAO,IAAI,QAAQ,KAAK,YAAY,UAClC,OAAO,WAAW;MAChB,MAAM,CAAC,OAAO,UAAU,aAAa,KAAK;MAC1C,SAAS,aAAa,OAAO,MAAM;KACrC,CAAC,CACH;KACA,OAAO,IAAI,KAAK,KAAK,mBAAmB,UACtC,OAAO,WAAW,SAAS,aAAa,gBAAgB,KAAK,CAAC,CAAC,CACjE;KACA,OAAO,IAAI,KAAK,KAAK,kBAAkB,UACrC,OAAO,WAAW;MAChB,MAAM,CAAC,OAAO,UAAU,eAAe;OACrC,MAAM,MAAM;OACZ,WAAW,MAAM;OACjB,IAAI,MAAM;OACV,QAAQ,MAAM;OACd,QAAQ,MAAM,WAAW,cAAc,MAAM,SAAS;MACxD,CAAC;MACD,SAAS,YAAY,OAAO,MAAM;KACpC,CAAC,CACH;KACA,OAAO,IAAI,WAAW,KAAK,aAAa,UACtC,OAAO,WAAW;MAChB,MAAM,CAAC,OAAO,UAAU,gBAAgB,KAAK;MAC7C,SAAS,gBAAgB,OAAO,MAAM;KACxC,CAAC,CACH;KAEA,IAAI,WAAW,uBAAuB;;;MAGpC,OAAO,IAAI,QAAQ,KAAK,iBAAiB,UACvC,OAAO,WAAW;OAChB,MAAM,UAAU,SAAS,mBAAmB;OAC5C,IAAI,CAAC,SAAS;QACZ;OACF;OACA,MAAM,UAAkC,CAAC;OACzC,WAAW,OAAO,SAAS,SAAS,oBAAoB;OACxD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;;QAElD,IAAI,CAAC,MAAM,QAAQ,QAAQ,IAAI,GAAG,GAAG;SACnC,MAAM,QAAQ,QAAQ,IAAI,KAAK,KAAK;QACtC;OACF;MACF,CAAC,CACH;MACA,OAAO,MAAM,4CAA4C,CAAC,CAAC;KAC7D;IACF,OAAO;KACL,OAAO,KAAK,6BAA6B;MACvC,SAAS,WAAW;MACpB,QAAQ,WAAW,UAAU,2BAA2B;KAC1D,CAAC;IACH;GACF;GAEA,OAAO,OAAO,mBACZ,OAAO,WAAW;IAChB,WAAW;;IAEX,MAAM,SAAS,QAAQ,EAAE,MAAM,2BAA2B,CAAC;GAC7D,CAAC,CACH;GAEA,OAAO,KAAK,4BAA4B;IACtC,SAAS,QAAQ,OAAO,WAAW,aAAa;IAChD,UAAU,OAAO,SAAS,YAAY;IACtC,MAAM,QAAQ,IAAI;IAClB,UAAU,QAAQ,OAAO,QAAQ;IACjC,WAAW,OAAO,cAAc,oBAAoB,cAAc;GACpE,CAAC;EACH,CAAC;CACH,CAAC;AACH;AAEA,OAAO,MAAM,oBAAmC,wBAAwB;AAExE,eAAe","names":[],"sources":["../src/opencode.ts"],"version":3,"file":"opencode.js","sourceRoot":""}
package/dist/plugin.d.ts CHANGED
@@ -1,24 +1,28 @@
1
- import { type AuthServerConfig, type Logger, type TokenSet } from "@vymalo/opencode-auth-core/lib";
1
+ import { type AuthServerConfig, type DeviceSignIn, type Logger, type TokenSet } from "@vymalo/opencode-auth-core/lib";
2
2
  /**
3
- * Legacy identity for the exchanged project-token store (UNCHANGED by
4
- * ADR-0017 — see `migration.ts`'s module doc for why only the ROOT token
5
- * relocated). Kept as a constant, not `auth.id`, because the exchanged-token
6
- * cache key has always been derived from this literal, independent of the
7
- * configured IdP's `id`.
3
+ * Identity for the exchanged project-token store. Kept as a constant, not
4
+ * `auth.id`, because the exchanged-token key has always been derived from this
5
+ * literal, independent of the configured IdP's `id`.
8
6
  */
9
7
  export declare const LIGHTBRIDGE_IDENTITY = "lightbridge";
10
- /** Own cache namespace for the exchanged-token store — separate from oauth2/repo-auth/otel's. */
11
- export declare const DEFAULT_CACHE_NAMESPACE = "opencode-lightbridge";
12
8
  /**
13
- * ADR-0017: the human ROOT token now lives in the SAME cache file
9
+ * Own namespace for the exchanged-token store — separate from oauth2/repo-auth/
10
+ * otel's. It sits under the durable STATE root (ADR-0022): a project token is a
11
+ * credential, and is re-exchangeable only while the human root token survives.
12
+ */
13
+ export declare const EXCHANGE_STATE_NAMESPACE = "opencode-lightbridge";
14
+ /**
15
+ * ADR-0017: the human ROOT token lives in the SAME credential file
14
16
  * `@vymalo/opencode-oauth2` uses for a server of this id —
15
- * `<cacheRoot>/opencode-oauth2/opencode-oauth2-model-sync/<id>.json`, a fused
16
- * `CachedServerState` (token + discovered models). This is what lets a
17
- * developer who configures the same IdP (`auth.id` == an oauth2 `servers[].id`,
18
- * same issuer/clientId) log in ONCE and have both plugins read/write the same
19
- * on-disk state. The literals are pinned here (not imported from
20
- * `@vymalo/opencode-oauth2`, which lightbridge does not depend on) — see
21
- * `docs/adr/0017-lightbridge-all-in-one.md` for the shared-contract rationale.
17
+ * `<stateRoot>/opencode-oauth2/opencode-oauth2-model-sync/<id>.json`
18
+ * (ADR-0022 moved the root from the cache dir to the state dir; the relative
19
+ * path is unchanged). This is what lets a developer who configures the same
20
+ * IdP (`auth.id` == an oauth2 `servers[].id`, same issuer/clientId) log in ONCE
21
+ * and have both plugins read/write the same on-disk credential. The literals
22
+ * are pinned here (not imported from `@vymalo/opencode-oauth2`, which
23
+ * lightbridge does not depend on) — see `docs/adr/0017-lightbridge-all-in-one.md`
24
+ * for the shared-contract rationale. The discovered-model list for that server
25
+ * is a separate record in the CACHE root, owned by whichever engine syncs it.
22
26
  */
23
27
  export declare const ROOT_CACHE_SEGMENT = "opencode-oauth2";
24
28
  export declare const ROOT_CACHE_NAMESPACE = "opencode-oauth2-model-sync";
@@ -33,10 +37,10 @@ export interface LightbridgeRuntimeOptions {
33
37
  logger?: Logger;
34
38
  fetchImpl?: typeof fetch;
35
39
  onAuthorizationUrl?: (url: string) => Promise<void> | void;
36
- /** Override the cache ROOT (defaults to the OS cache dir convention) — both
37
- * the shared root-token store and the exchange-only store are joined from
38
- * this. Test seam; production callers omit it. */
39
- cacheDir?: string;
40
+ /** Override the STATE root (defaults to the OS state dir convention, ADR-0022) —
41
+ * both the shared root-token store and the exchange-only store are joined
42
+ * from this. Test seam; production callers omit it. */
43
+ stateDir?: string;
40
44
  tokenExpirySkewMs?: number;
41
45
  /**
42
46
  * Whether to perform the RFC 8693 exchange (ADR-0012) rather than use the
@@ -50,10 +54,28 @@ export interface LightbridgeRuntimeOptions {
50
54
  * `TokenRuntime` (no network, no disk).
51
55
  */
52
56
  export interface LightbridgeRuntimeLike {
53
- getProjectToken(options?: {
54
- interactive?: boolean;
57
+ /**
58
+ * A usable gateway/OTEL bearer. NEVER interactive (ADR-0023): when no usable
59
+ * token exists it rejects with `InteractiveAuthRequiredError` and the caller
60
+ * fails closed. The only way a human signs in is `beginDeviceSignIn`, driven
61
+ * from the host's Connect dialog.
62
+ */
63
+ getProjectToken(): Promise<TokenSet>;
64
+ /**
65
+ * The shared human/IdP root token, refresh-only (never interactive).
66
+ * `minValidityMs` forces a real refresh when the cached token has less than
67
+ * that left (the host's 5-minute refresh window is wider than our skew).
68
+ */
69
+ ensureRootToken(options?: {
70
+ minValidityMs?: number;
55
71
  }): Promise<TokenSet>;
56
- reset?(): Promise<void>;
72
+ /**
73
+ * Request a device code for the Connect dialog; `complete` polls and stores.
74
+ * `signal` cancels the device-authorization request.
75
+ */
76
+ beginDeviceSignIn(signal?: AbortSignal): Promise<DeviceSignIn>;
77
+ /** Drop every stored token for this identity (the host disconnected it). */
78
+ reset(): Promise<void>;
57
79
  }
58
80
  /**
59
81
  * Builds a `LightbridgeRuntimeLike` for a given `(auth, projectId)` pair.
@@ -68,12 +90,11 @@ export type LightbridgeRuntimeFactory = (auth: AuthServerConfig, projectId: stri
68
90
  * BOTH the gateway (`chat.headers`) and OTEL (`TokenSource.headers()`).
69
91
  *
70
92
  * The human root token itself is no longer lightbridge-exclusive storage: it
71
- * lives in the SAME cache file `@vymalo/opencode-oauth2` writes for a server
72
- * of this id (`ROOT_CACHE_SEGMENT`/`ROOT_CACHE_NAMESPACE`), so configuring
73
- * both plugins against the same IdP (`auth.id` matching an oauth2
74
- * `servers[].id`, same issuer/clientId) means logging in once. An upgrading
75
- * install's pre-existing root token is migrated in automatically — see
76
- * `migration.ts`.
93
+ * lives in the SAME credential file `@vymalo/opencode-oauth2` writes for a
94
+ * server of this id (`ROOT_CACHE_SEGMENT`/`ROOT_CACHE_NAMESPACE`, under the
95
+ * durable state root — ADR-0022), so configuring both plugins against the same
96
+ * IdP (`auth.id` matching an oauth2 `servers[].id`, same issuer/clientId) means
97
+ * logging in once.
77
98
  */
78
99
  export declare class LightbridgeRuntime implements LightbridgeRuntimeLike {
79
100
  private readonly projectId;
@@ -83,20 +104,12 @@ export declare class LightbridgeRuntime implements LightbridgeRuntimeLike {
83
104
  private readonly logger;
84
105
  private readonly tokenExpirySkewMs;
85
106
  private readonly exchange;
86
- private readonly cacheRoot;
107
+ private readonly stateRoot;
87
108
  private readonly serverId;
88
109
  /** Token-cache key: the project id, or `DEFAULT_PROJECT_KEY` when unset. */
89
110
  private readonly projectKey;
90
111
  private inFlightExchange?;
91
- private migration?;
92
112
  constructor(auth: AuthServerConfig, projectId: string | undefined, options?: LightbridgeRuntimeOptions);
93
- /**
94
- * One-time (per instance) migration of a pre-ADR-0017 root token into the
95
- * shared cache file — see `migration.ts`. Idempotent and cheap on every
96
- * call after the first (the underlying function also short-circuits once
97
- * the new location has ANY state).
98
- */
99
- private ensureMigrated;
100
113
  /** Non-network read of the cached project token, if any. */
101
114
  getCachedProjectToken(): Promise<TokenSet | undefined>;
102
115
  /**
@@ -122,20 +135,29 @@ export declare class LightbridgeRuntime implements LightbridgeRuntimeLike {
122
135
  * invents a token: a failure propagates to the caller, which is expected to
123
136
  * fail closed (no header / empty `headers()`).
124
137
  */
125
- getProjectToken(options?: {
126
- interactive?: boolean;
127
- }): Promise<TokenSet>;
138
+ getProjectToken(): Promise<TokenSet>;
128
139
  private performExchange;
140
+ /** See {@link LightbridgeRuntimeLike.ensureRootToken}. */
141
+ ensureRootToken(options?: {
142
+ minValidityMs?: number;
143
+ }): Promise<TokenSet>;
144
+ /**
145
+ * The ONLY interactive entry point (ADR-0023): the root runtime requests a
146
+ * device code and, on `complete`, stores the result through the shared
147
+ * credential file's single writer (`setCached` under the refresh lock).
148
+ */
149
+ beginDeviceSignIn(signal?: AbortSignal): Promise<DeviceSignIn>;
129
150
  /**
130
151
  * Drop the cached tokens for this identity: the exchanged project token
131
- * entirely (its own exclusive store), and the shared root token's `token`
132
- * field only — NOT the whole shared cache file, which may also hold
133
- * `@vymalo/opencode-oauth2`'s discovered models for this server id and must
134
- * survive a lightbridge-side logout.
152
+ * entirely (its own exclusive store), and the shared root credential. The
153
+ * discovered-model record is a separate file in the cache root and is not
154
+ * touched, so oauth2's models survive a lightbridge-side logout.
135
155
  */
136
156
  reset(): Promise<void>;
137
157
  }
138
- /** Absolute path to the lightbridge-exclusive (exchanged-token) cache directory. */
139
- export declare function lightbridgeCacheDir(cacheRoot: string): string;
140
- /** Absolute path to the SHARED root-token cache directory (ADR-0017). */
158
+ /** Absolute path to the lightbridge-exclusive (exchanged-token) STATE directory. */
159
+ export declare function lightbridgeStateDir(stateRoot: string): string;
160
+ /** Absolute path to the SHARED root-credential STATE directory (ADR-0017, ADR-0022). */
161
+ export declare function rootStateDir(stateRoot: string): string;
162
+ /** Absolute path to the SHARED discovered-model CACHE directory (disposable; ADR-0022). */
141
163
  export declare function rootCacheDir(cacheRoot: string): string;
package/dist/plugin.js CHANGED
@@ -1,27 +1,30 @@
1
1
  import { join } from "node:path";
2
- import { createJsonConsoleLogger, DEFAULT_TOKEN_EXPIRY_SKEW_MS, resolveCacheRoot, TokenRuntime } from "@vymalo/opencode-auth-core/lib";
3
- import { FileCacheStore as SharedFileCacheStore } from "@vymalo/opencode-provider-sync/lib";
4
- import { migrateRootTokenIfNeeded } from "./migration.js";
2
+ import { createJsonConsoleLogger, DEFAULT_TOKEN_EXPIRY_SKEW_MS, resolveStateRoot, TokenRuntime } from "@vymalo/opencode-auth-core/lib";
3
+ import { ServerCredentialStore } from "@vymalo/opencode-provider-sync/lib";
5
4
  /**
6
- * Legacy identity for the exchanged project-token store (UNCHANGED by
7
- * ADR-0017 — see `migration.ts`'s module doc for why only the ROOT token
8
- * relocated). Kept as a constant, not `auth.id`, because the exchanged-token
9
- * cache key has always been derived from this literal, independent of the
10
- * configured IdP's `id`.
5
+ * Identity for the exchanged project-token store. Kept as a constant, not
6
+ * `auth.id`, because the exchanged-token key has always been derived from this
7
+ * literal, independent of the configured IdP's `id`.
11
8
  */
12
9
  export const LIGHTBRIDGE_IDENTITY = "lightbridge";
13
- /** Own cache namespace for the exchanged-token store — separate from oauth2/repo-auth/otel's. */
14
- export const DEFAULT_CACHE_NAMESPACE = "opencode-lightbridge";
15
10
  /**
16
- * ADR-0017: the human ROOT token now lives in the SAME cache file
11
+ * Own namespace for the exchanged-token store — separate from oauth2/repo-auth/
12
+ * otel's. It sits under the durable STATE root (ADR-0022): a project token is a
13
+ * credential, and is re-exchangeable only while the human root token survives.
14
+ */
15
+ export const EXCHANGE_STATE_NAMESPACE = "opencode-lightbridge";
16
+ /**
17
+ * ADR-0017: the human ROOT token lives in the SAME credential file
17
18
  * `@vymalo/opencode-oauth2` uses for a server of this id —
18
- * `<cacheRoot>/opencode-oauth2/opencode-oauth2-model-sync/<id>.json`, a fused
19
- * `CachedServerState` (token + discovered models). This is what lets a
20
- * developer who configures the same IdP (`auth.id` == an oauth2 `servers[].id`,
21
- * same issuer/clientId) log in ONCE and have both plugins read/write the same
22
- * on-disk state. The literals are pinned here (not imported from
23
- * `@vymalo/opencode-oauth2`, which lightbridge does not depend on) — see
24
- * `docs/adr/0017-lightbridge-all-in-one.md` for the shared-contract rationale.
19
+ * `<stateRoot>/opencode-oauth2/opencode-oauth2-model-sync/<id>.json`
20
+ * (ADR-0022 moved the root from the cache dir to the state dir; the relative
21
+ * path is unchanged). This is what lets a developer who configures the same
22
+ * IdP (`auth.id` == an oauth2 `servers[].id`, same issuer/clientId) log in ONCE
23
+ * and have both plugins read/write the same on-disk credential. The literals
24
+ * are pinned here (not imported from `@vymalo/opencode-oauth2`, which
25
+ * lightbridge does not depend on) — see `docs/adr/0017-lightbridge-all-in-one.md`
26
+ * for the shared-contract rationale. The discovered-model list for that server
27
+ * is a separate record in the CACHE root, owned by whichever engine syncs it.
25
28
  */
26
29
  export const ROOT_CACHE_SEGMENT = "opencode-oauth2";
27
30
  export const ROOT_CACHE_NAMESPACE = "opencode-oauth2-model-sync";
@@ -48,14 +51,19 @@ function isProjectTokenUsable(token, skewMs) {
48
51
  }
49
52
  /**
50
53
  * Build the ONE shared root `TokenRuntime` for `serverId`, persisting through
51
- * the SAME fused `CachedServerState` shape+location `ProviderModelSyncEngine`
52
- * uses (see `ROOT_CACHE_SEGMENT`/`ROOT_CACHE_NAMESPACE` above) — mirroring the
53
- * override pattern in `@vymalo/opencode-provider-sync`'s
54
- * `ProviderModelSyncEngine.buildTokenRuntime` (extract `.token`, preserve
55
- * `.models`/`.rawModels` on write) but WITHOUT running that engine's
56
- * scheduler/model-discovery — this is a read/refresh-only participant in the
57
- * shared cache file, not a second poller (see ADR-0017's double-scheduling
58
- * guard, enforced inside the engine itself for the `register` path).
54
+ * the SAME credential record `ProviderModelSyncEngine` uses (see
55
+ * `ROOT_CACHE_SEGMENT`/`ROOT_CACHE_NAMESPACE` above) — mirroring the override
56
+ * pattern in `@vymalo/opencode-provider-sync`'s
57
+ * `ProviderModelSyncEngine.buildTokenRuntime`, but WITHOUT running that
58
+ * engine's scheduler/model-discovery — this is a read/refresh-only participant
59
+ * in the shared credential file, not a second poller (see ADR-0017's
60
+ * double-scheduling guard, enforced inside the engine itself for the `register`
61
+ * path). It never touches the model-cache record, so a lightbridge-side write
62
+ * can no longer clobber oauth2's discovered models.
63
+ *
64
+ * `stateDir` is the credential store's directory, so the cross-process refresh
65
+ * lock (`<stateDir>/locks/<id>.lock`) sits beside the token it guards and is
66
+ * the very lock oauth2's engine takes for the same id.
59
67
  *
60
68
  * Unlike `ProviderModelSyncEngine`, this performs no cross-process
61
69
  * "adopt-if-newer" reconciliation — every `getCached()` call here reads disk
@@ -65,28 +73,22 @@ function isProjectTokenUsable(token, skewMs) {
65
73
  * fallback), and refreshes are still coordinated via `TokenRuntime`'s own
66
74
  * cross-process file lock.
67
75
  */
68
- function buildSharedRootTokenRuntime(serverId, auth, sharedStore, options) {
76
+ function buildSharedRootTokenRuntime(serverId, auth, credentials, options) {
69
77
  return new TokenRuntime(serverId, auth, {
70
78
  logger: options.logger,
71
79
  fetchImpl: options.fetchImpl,
72
80
  onAuthorizationUrl: options.onAuthorizationUrl,
73
81
  tokenExpirySkewMs: options.tokenExpirySkewMs,
74
82
  serviceLabel: "opencode-lightbridge",
75
- getCached: async () => {
76
- const state = await sharedStore.loadServerState(serverId);
77
- return state?.token;
78
- },
83
+ stateDir: credentials.directory,
84
+ getCached: async () => (await credentials.load(serverId))?.token,
79
85
  setCached: async (token) => {
80
- const existing = await sharedStore.loadServerState(serverId);
81
- const next = {
82
- serverId,
83
- updatedAt: Date.now(),
84
- lastSyncAt: existing?.lastSyncAt,
85
- models: existing?.models ?? [],
86
- rawModels: existing?.rawModels ?? [],
87
- token
88
- };
89
- await sharedStore.saveServerState(next);
86
+ await credentials.save(serverId, token, Date.now());
87
+ },
88
+ // Compare-and-clear of a refresh token the IdP rejected is done by
89
+ // TokenRuntime under the lock; this only performs the removal.
90
+ clearCached: async () => {
91
+ await credentials.remove(serverId);
90
92
  }
91
93
  });
92
94
  }
@@ -98,12 +100,11 @@ function buildSharedRootTokenRuntime(serverId, auth, sharedStore, options) {
98
100
  * BOTH the gateway (`chat.headers`) and OTEL (`TokenSource.headers()`).
99
101
  *
100
102
  * The human root token itself is no longer lightbridge-exclusive storage: it
101
- * lives in the SAME cache file `@vymalo/opencode-oauth2` writes for a server
102
- * of this id (`ROOT_CACHE_SEGMENT`/`ROOT_CACHE_NAMESPACE`), so configuring
103
- * both plugins against the same IdP (`auth.id` matching an oauth2
104
- * `servers[].id`, same issuer/clientId) means logging in once. An upgrading
105
- * install's pre-existing root token is migrated in automatically — see
106
- * `migration.ts`.
103
+ * lives in the SAME credential file `@vymalo/opencode-oauth2` writes for a
104
+ * server of this id (`ROOT_CACHE_SEGMENT`/`ROOT_CACHE_NAMESPACE`, under the
105
+ * durable state root — ADR-0022), so configuring both plugins against the same
106
+ * IdP (`auth.id` matching an oauth2 `servers[].id`, same issuer/clientId) means
107
+ * logging in once.
107
108
  */
108
109
  export class LightbridgeRuntime {
109
110
  projectId;
@@ -113,21 +114,20 @@ export class LightbridgeRuntime {
113
114
  logger;
114
115
  tokenExpirySkewMs;
115
116
  exchange;
116
- cacheRoot;
117
+ stateRoot;
117
118
  serverId;
118
119
  /** Token-cache key: the project id, or `DEFAULT_PROJECT_KEY` when unset. */
119
120
  projectKey;
120
121
  inFlightExchange;
121
- migration;
122
122
  constructor(auth, projectId, options = {}) {
123
123
  this.projectId = projectId;
124
124
  this.serverId = auth.id;
125
125
  this.projectKey = projectId ?? DEFAULT_PROJECT_KEY;
126
126
  this.logger = options.logger ?? createJsonConsoleLogger("info");
127
127
  this.exchange = options.exchange ?? false;
128
- this.cacheRoot = options.cacheDir ?? resolveCacheRoot();
128
+ this.stateRoot = options.stateDir ?? resolveStateRoot();
129
129
  this.tokenExpirySkewMs = typeof options.tokenExpirySkewMs === "number" && Number.isFinite(options.tokenExpirySkewMs) && options.tokenExpirySkewMs > 0 ? options.tokenExpirySkewMs : DEFAULT_TOKEN_EXPIRY_SKEW_MS;
130
- this.sharedStore = new SharedFileCacheStore(join(this.cacheRoot, ROOT_CACHE_SEGMENT, ROOT_CACHE_NAMESPACE), this.logger);
130
+ this.sharedStore = new ServerCredentialStore(rootStateDir(this.stateRoot), this.logger);
131
131
  this.rootRuntime = buildSharedRootTokenRuntime(this.serverId, auth, this.sharedStore, options);
132
132
  // The exchange-only runtime keeps its OWN dedicated, lightbridge-exclusive
133
133
  // store (unchanged location/shape) — there is no oauth2 equivalent for a
@@ -136,27 +136,11 @@ export class LightbridgeRuntime {
136
136
  logger: this.logger,
137
137
  fetchImpl: options.fetchImpl,
138
138
  onAuthorizationUrl: options.onAuthorizationUrl,
139
- cacheDir: join(this.cacheRoot, DEFAULT_CACHE_NAMESPACE),
139
+ stateDir: lightbridgeStateDir(this.stateRoot),
140
140
  tokenExpirySkewMs: this.tokenExpirySkewMs,
141
141
  serviceLabel: "opencode-lightbridge"
142
142
  });
143
143
  }
144
- /**
145
- * One-time (per instance) migration of a pre-ADR-0017 root token into the
146
- * shared cache file — see `migration.ts`. Idempotent and cheap on every
147
- * call after the first (the underlying function also short-circuits once
148
- * the new location has ANY state).
149
- */
150
- ensureMigrated() {
151
- if (!this.migration) {
152
- this.migration = migrateRootTokenIfNeeded(this.cacheRoot, ROOT_CACHE_SEGMENT, ROOT_CACHE_NAMESPACE, this.serverId, this.logger).catch((error) => {
153
- // Never fatal: worst case is a fresh login, same as a first-ever
154
- // install. Swallow so a migration hiccup can't break normal auth.
155
- this.logger.debug("lightbridge_root_migration_error", { error: error instanceof Error ? error.message : String(error) });
156
- });
157
- }
158
- return this.migration;
159
- }
160
144
  /** Non-network read of the cached project token, if any. */
161
145
  async getCachedProjectToken() {
162
146
  return this.exchangeRuntime.getExchangedByKey(this.projectKey);
@@ -184,10 +168,9 @@ export class LightbridgeRuntime {
184
168
  * invents a token: a failure propagates to the caller, which is expected to
185
169
  * fail closed (no header / empty `headers()`).
186
170
  */
187
- async getProjectToken(options = {}) {
188
- await this.ensureMigrated();
171
+ async getProjectToken() {
189
172
  if (!this.exchange) {
190
- return this.rootRuntime.ensure({ interactive: options.interactive });
173
+ return this.rootRuntime.ensure({ interactive: false });
191
174
  }
192
175
  const cached = await this.getCachedProjectToken();
193
176
  if (isProjectTokenUsable(cached, this.tokenExpirySkewMs)) {
@@ -198,7 +181,7 @@ export class LightbridgeRuntime {
198
181
  if (this.inFlightExchange) {
199
182
  return this.inFlightExchange;
200
183
  }
201
- const exchange = this.performExchange(options);
184
+ const exchange = this.performExchange();
202
185
  this.inFlightExchange = exchange;
203
186
  try {
204
187
  return await exchange;
@@ -208,8 +191,8 @@ export class LightbridgeRuntime {
208
191
  }
209
192
  }
210
193
  }
211
- async performExchange(options) {
212
- const human = await this.rootRuntime.ensure({ interactive: options.interactive });
194
+ async performExchange() {
195
+ const human = await this.rootRuntime.ensure({ interactive: false });
213
196
  this.logger.info("lightbridge_exchange_started", { projectId: this.projectId ?? "(default)" });
214
197
  try {
215
198
  // No `projectId` → send no `project_id` param; the backend mints a token
@@ -225,30 +208,41 @@ export class LightbridgeRuntime {
225
208
  throw error;
226
209
  }
227
210
  }
211
+ /** See {@link LightbridgeRuntimeLike.ensureRootToken}. */
212
+ async ensureRootToken(options = {}) {
213
+ return this.rootRuntime.ensure({
214
+ interactive: false,
215
+ minValidityMs: options.minValidityMs
216
+ });
217
+ }
218
+ /**
219
+ * The ONLY interactive entry point (ADR-0023): the root runtime requests a
220
+ * device code and, on `complete`, stores the result through the shared
221
+ * credential file's single writer (`setCached` under the refresh lock).
222
+ */
223
+ async beginDeviceSignIn(signal) {
224
+ return this.rootRuntime.beginDeviceSignIn(signal);
225
+ }
228
226
  /**
229
227
  * Drop the cached tokens for this identity: the exchanged project token
230
- * entirely (its own exclusive store), and the shared root token's `token`
231
- * field only — NOT the whole shared cache file, which may also hold
232
- * `@vymalo/opencode-oauth2`'s discovered models for this server id and must
233
- * survive a lightbridge-side logout.
228
+ * entirely (its own exclusive store), and the shared root credential. The
229
+ * discovered-model record is a separate file in the cache root and is not
230
+ * touched, so oauth2's models survive a lightbridge-side logout.
234
231
  */
235
232
  async reset() {
236
233
  await this.exchangeRuntime.reset();
237
- const existing = await this.sharedStore.loadServerState(this.serverId);
238
- if (existing?.token) {
239
- await this.sharedStore.saveServerState({
240
- ...existing,
241
- updatedAt: Date.now(),
242
- token: undefined
243
- });
244
- }
234
+ await this.sharedStore.remove(this.serverId);
245
235
  }
246
236
  }
247
- /** Absolute path to the lightbridge-exclusive (exchanged-token) cache directory. */
248
- export function lightbridgeCacheDir(cacheRoot) {
249
- return join(cacheRoot, DEFAULT_CACHE_NAMESPACE);
237
+ /** Absolute path to the lightbridge-exclusive (exchanged-token) STATE directory. */
238
+ export function lightbridgeStateDir(stateRoot) {
239
+ return join(stateRoot, EXCHANGE_STATE_NAMESPACE);
240
+ }
241
+ /** Absolute path to the SHARED root-credential STATE directory (ADR-0017, ADR-0022). */
242
+ export function rootStateDir(stateRoot) {
243
+ return join(stateRoot, ROOT_CACHE_SEGMENT, ROOT_CACHE_NAMESPACE);
250
244
  }
251
- /** Absolute path to the SHARED root-token cache directory (ADR-0017). */
245
+ /** Absolute path to the SHARED discovered-model CACHE directory (disposable; ADR-0022). */
252
246
  export function rootCacheDir(cacheRoot) {
253
247
  return join(cacheRoot, ROOT_CACHE_SEGMENT, ROOT_CACHE_NAMESPACE);
254
248
  }
@@ -1 +1 @@
1
- {"mappings":"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","names":[],"sources":["../src/plugin.ts"],"version":3,"file":"plugin.js","sourceRoot":""}
1
+ {"mappings":"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","names":[],"sources":["../src/plugin.ts"],"version":3,"file":"plugin.js","sourceRoot":""}
@@ -0,0 +1,70 @@
1
+ import type { Credential, Plugin } from "@opencode/plugin/effect";
2
+ import { type TokenSet } from "@vymalo/opencode-auth-core/lib";
3
+ import type { Logger } from "./logging.js";
4
+ import type { LightbridgeRuntimeLike } from "./plugin.js";
5
+ type IntegrationEditor = Parameters<Parameters<Plugin.Context["integration"]["transform"]>[0]>[0];
6
+ /** The host's oauth-method registration shape (the package does not export it by name). */
7
+ export type IntegrationOAuthMethodRegistration = Extract<Parameters<IntegrationEditor["method"]["update"]>[0], {
8
+ method: {
9
+ type: "oauth";
10
+ };
11
+ }>;
12
+ /** Id of the one sign-in method lightbridge registers on its integration (ADR-0023). */
13
+ export declare const SIGN_IN_METHOD_ID = "device";
14
+ /** Label of the button the host renders for that method. */
15
+ export declare const SIGN_IN_METHOD_LABEL = "Sign in";
16
+ /** The host calls a method's `refresh` once the credential is within this of `expires`. */
17
+ export declare const HOST_REFRESH_WINDOW_MS: number;
18
+ /** The exact path a user follows to sign in, for the `lightbridge_sign_in_required` hint. */
19
+ export declare function signInPath(name: string): string;
20
+ /**
21
+ * Display-only account label (email / username) read from the access token's
22
+ * claims. Deliberately UNVERIFIED: nothing security-relevant ever reads it, it
23
+ * only decorates the host's connection row. Opaque (non-JWT) tokens and any
24
+ * decoding problem yield `undefined`.
25
+ */
26
+ export declare function accountLabel(accessToken: string): string | undefined;
27
+ /**
28
+ * The host-side projection of our token: a "connected" marker plus the current
29
+ * access token. CUSTODY RULE (ADR-0023): `refresh` is ALWAYS `""` — the refresh
30
+ * token never leaves lightbridge's own state file, so a host credential-store
31
+ * leak or a host-driven refresh cannot replay (and thereby rotate away) it.
32
+ */
33
+ export declare function projectCredential(token: TokenSet, now?: number): Credential.OAuth;
34
+ export interface SignInDependencies {
35
+ integrationID: string;
36
+ runtime: LightbridgeRuntimeLike;
37
+ logger: Logger;
38
+ /**
39
+ * Runs after the projection was handed back (kick the model sync, reload the
40
+ * host's providers). Detached: the dialog closes without waiting for it, and
41
+ * a failure or a late interrupt cannot undo the sign-in.
42
+ */
43
+ afterSignIn: () => Promise<void>;
44
+ /** Called synchronously on a successful sign-in (re-arms the sign-in notice). */
45
+ onSignedIn?: () => void;
46
+ /** Called when a sign-in-related call finds no usable token (refresh-only failure). */
47
+ onSignInRequired?: (source: string) => void;
48
+ }
49
+ /**
50
+ * The oauth method registration behind the host's "Connect" dialog (ADR-0023).
51
+ * `authorize` requests a device code and hands the host the URL + code to show;
52
+ * `callback` awaits the user's approval (interruptible: the host closing the
53
+ * dialog interrupts it, which aborts the polling) and returns the projection.
54
+ */
55
+ export declare function createSignInRegistration(deps: SignInDependencies): IntegrationOAuthMethodRegistration;
56
+ export interface SignInNotice {
57
+ /** Warn (once until re-armed) that sign-in is needed; always records the source at debug. */
58
+ notify(source: string): void;
59
+ /** Re-arm: the next `notify` warns again (after a sign-in, or a host disconnect). */
60
+ reset(): void;
61
+ }
62
+ /**
63
+ * Emit `lightbridge_sign_in_required` at most once per arming, naming the exact
64
+ * path to sign in. Every non-interactive caller that finds no usable token
65
+ * (startup check, gateway header, OTEL token source, the register sync) reports
66
+ * through this one latch; each call is also recorded at `debug` as
67
+ * `lightbridge_sign_in_required_seen` so the sources stay observable.
68
+ */
69
+ export declare function createSignInRequiredNotifier(logger: Logger, name: string): SignInNotice;
70
+ export {};
@@ -0,0 +1,152 @@
1
+ import { isInteractiveAuthRequiredError } from "@vymalo/opencode-auth-core/lib";
2
+ import { Effect } from "effect";
3
+ /** Id of the one sign-in method lightbridge registers on its integration (ADR-0023). */
4
+ export const SIGN_IN_METHOD_ID = "device";
5
+ /** Label of the button the host renders for that method. */
6
+ export const SIGN_IN_METHOD_LABEL = "Sign in";
7
+ /** Fallback lifetime for a token the IdP gave no `expires_in` for. */
8
+ const FALLBACK_LIFETIME_MS = 60 * 60 * 1e3;
9
+ const MAX_LABEL_CHARS = 80;
10
+ /** The host calls a method's `refresh` once the credential is within this of `expires`. */
11
+ export const HOST_REFRESH_WINDOW_MS = 5 * 60 * 1e3;
12
+ /** The exact path a user follows to sign in, for the `lightbridge_sign_in_required` hint. */
13
+ export function signInPath(name) {
14
+ return `Settings → Providers → Show more providers → (Integrations dialog) search "${name}" → Sign in`;
15
+ }
16
+ /**
17
+ * Display-only account label (email / username) read from the access token's
18
+ * claims. Deliberately UNVERIFIED: nothing security-relevant ever reads it, it
19
+ * only decorates the host's connection row. Opaque (non-JWT) tokens and any
20
+ * decoding problem yield `undefined`.
21
+ */
22
+ export function accountLabel(accessToken) {
23
+ const parts = accessToken.split(".");
24
+ if (parts.length !== 3 || !parts[1]) {
25
+ return undefined;
26
+ }
27
+ try {
28
+ const claims = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
29
+ for (const key of ["email", "preferred_username"]) {
30
+ const value = claims[key];
31
+ if (typeof value === "string" && value.trim().length > 0) {
32
+ return value.trim().slice(0, MAX_LABEL_CHARS);
33
+ }
34
+ }
35
+ } catch {}
36
+ return undefined;
37
+ }
38
+ /**
39
+ * The host-side projection of our token: a "connected" marker plus the current
40
+ * access token. CUSTODY RULE (ADR-0023): `refresh` is ALWAYS `""` — the refresh
41
+ * token never leaves lightbridge's own state file, so a host credential-store
42
+ * leak or a host-driven refresh cannot replay (and thereby rotate away) it.
43
+ */
44
+ export function projectCredential(token, now = Date.now()) {
45
+ const label = accountLabel(token.accessToken);
46
+ return {
47
+ type: "oauth",
48
+ methodID: SIGN_IN_METHOD_ID,
49
+ access: token.accessToken,
50
+ refresh: "",
51
+ expires: Math.floor(token.expiresAt ?? now + FALLBACK_LIFETIME_MS),
52
+ ...label ? { metadata: { label } } : {}
53
+ };
54
+ }
55
+ /**
56
+ * The oauth method registration behind the host's "Connect" dialog (ADR-0023).
57
+ * `authorize` requests a device code and hands the host the URL + code to show;
58
+ * `callback` awaits the user's approval (interruptible: the host closing the
59
+ * dialog interrupts it, which aborts the polling) and returns the projection.
60
+ */
61
+ export function createSignInRegistration(deps) {
62
+ const { integrationID, runtime, logger } = deps;
63
+ return {
64
+ integrationID,
65
+ method: {
66
+ id: SIGN_IN_METHOD_ID,
67
+ type: "oauth",
68
+ label: SIGN_IN_METHOD_LABEL
69
+ },
70
+ authorize: () => Effect.tryPromise({
71
+ // The effect's signal aborts when the host abandons the attempt, which
72
+ // also cancels the device-authorization request.
73
+ try: (signal) => runtime.beginDeviceSignIn(signal),
74
+ catch: (error) => error
75
+ }).pipe(Effect.tapError((error) => Effect.sync(() => logger.error("lightbridge_sign_in_begin_failed", errorFields(error)))), Effect.map((session) => ({
76
+ url: session.verificationUriComplete ?? session.verificationUri,
77
+ instructions: `Confirm the code ${session.userCode} on the page that opens, then approve the request.`,
78
+ expiresAt: session.expiresAt,
79
+ mode: "auto",
80
+ callback: Effect.tryPromise({
81
+ try: (signal) => session.complete(signal),
82
+ catch: (error) => error
83
+ }).pipe(Effect.tapError((error) => Effect.sync(() => logger.warn("lightbridge_sign_in_failed", errorFields(error)))), Effect.onInterrupt(() => Effect.sync(() => logger.info("lightbridge_sign_in_cancelled", { integrationID }))), Effect.map((token) => {
84
+ logger.info("lightbridge_sign_in_completed", { integrationID });
85
+ deps.onSignedIn?.();
86
+ // Detached on purpose (see `afterSignIn`).
87
+ void (async () => {
88
+ try {
89
+ await deps.afterSignIn();
90
+ } catch (error) {
91
+ logger.warn("lightbridge_sign_in_followup_failed", errorFields(error));
92
+ }
93
+ })();
94
+ return projectCredential(token);
95
+ }))
96
+ }))),
97
+ // The host calls this within HOST_REFRESH_WINDOW_MS of `expires`. Whatever it
98
+ // holds is ignored (it has no refresh token): the truth is our own runtime,
99
+ // forced to a real, non-interactive refresh through the single writer. A
100
+ // revoked/cleared root fails here - it never starts a flow.
101
+ refresh: () => Effect.tryPromise({
102
+ try: () => runtime.ensureRootToken({ minValidityMs: HOST_REFRESH_WINDOW_MS }),
103
+ catch: (error) => error
104
+ }).pipe(Effect.tapError((error) => Effect.sync(() => {
105
+ logger.warn("lightbridge_sign_in_refresh_failed", errorFields(error));
106
+ if (isInteractiveAuthRequiredError(error)) {
107
+ deps.onSignInRequired?.("refresh");
108
+ }
109
+ })), Effect.tap((token) => Effect.sync(() => {
110
+ // A refresh that "succeeds" with a token still inside the window means
111
+ // the refresh itself failed and we served what was left.
112
+ if (token.expiresAt !== undefined && token.expiresAt - Date.now() <= HOST_REFRESH_WINDOW_MS) {
113
+ logger.warn("lightbridge_sign_in_refresh_failed", { error: "refresh returned a token still inside the host refresh window" });
114
+ }
115
+ })), Effect.map((token) => projectCredential(token))),
116
+ label: (credential) => {
117
+ const label = credential.metadata?.label;
118
+ return typeof label === "string" ? label : undefined;
119
+ }
120
+ };
121
+ }
122
+ function errorFields(error) {
123
+ return { error: error instanceof Error ? error.message : String(error) };
124
+ }
125
+ /**
126
+ * Emit `lightbridge_sign_in_required` at most once per arming, naming the exact
127
+ * path to sign in. Every non-interactive caller that finds no usable token
128
+ * (startup check, gateway header, OTEL token source, the register sync) reports
129
+ * through this one latch; each call is also recorded at `debug` as
130
+ * `lightbridge_sign_in_required_seen` so the sources stay observable.
131
+ */
132
+ export function createSignInRequiredNotifier(logger, name) {
133
+ let notified = false;
134
+ return {
135
+ notify(source) {
136
+ logger.debug("lightbridge_sign_in_required_seen", { source });
137
+ if (notified) {
138
+ return;
139
+ }
140
+ notified = true;
141
+ logger.warn("lightbridge_sign_in_required", {
142
+ source,
143
+ message: `lightbridge is not signed in: open ${signInPath(name)}`
144
+ });
145
+ },
146
+ reset() {
147
+ notified = false;
148
+ }
149
+ };
150
+ }
151
+
152
+ //# sourceMappingURL=sign-in.js.map
@@ -0,0 +1 @@
1
+ {"mappings":"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","names":[],"sources":["../src/sign-in.ts"],"version":3,"file":"sign-in.js","sourceRoot":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vymalo/opencode-lightbridge",
3
- "version": "0.18.1",
3
+ "version": "0.20.0",
4
4
  "description": "Umbrella OpenCode plugin for ADR-0012: one shared credential (TokenRuntime) drives both the LLM gateway bearer and OTEL export, so a developer authenticates once and every egress rides the same project-scoped token.",
5
5
  "license": "MIT",
6
6
  "author": "vymalo contributors",
@@ -54,9 +54,9 @@
54
54
  "@opentelemetry/api": "^1.9.1",
55
55
  "@opentelemetry/api-logs": "^0.222.0",
56
56
  "@opentelemetry/core": "^2.11.0",
57
- "@vymalo/opencode-auth-core": "0.18.1",
58
- "@vymalo/opencode-provider-sync": "0.18.1",
59
- "@vymalo/opencode-core-otel": "0.18.1"
57
+ "@vymalo/opencode-auth-core": "0.20.0",
58
+ "@vymalo/opencode-provider-sync": "0.20.0",
59
+ "@vymalo/opencode-core-otel": "0.20.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@opentelemetry/sdk-logs": "^0.222.0",
@@ -1,31 +0,0 @@
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>;
package/dist/migration.js DELETED
@@ -1,91 +0,0 @@
1
- import { join } from "node:path";
2
- import { FileCacheStore as LegacyFileCacheStore } from "@vymalo/opencode-auth-core/lib";
3
- import { FileCacheStore as SharedFileCacheStore } from "@vymalo/opencode-provider-sync/lib";
4
- /**
5
- * The pre-ADR-0017 on-disk location of lightbridge's human root token:
6
- * `<cacheRoot>/opencode-lightbridge/lightbridge.json`, a bare `TokenSet`
7
- * (see the OLD `LIGHTBRIDGE_IDENTITY`/`DEFAULT_CACHE_NAMESPACE` in `plugin.ts`,
8
- * which are still used verbatim for the exchanged project-token store — only
9
- * the ROOT token relocated).
10
- */
11
- const OLD_ROOT_CACHE_NAMESPACE = "opencode-lightbridge";
12
- const OLD_ROOT_KEY = "lightbridge";
13
- function hasUsableTokenShape(value) {
14
- if (!value || typeof value !== "object" || Array.isArray(value)) {
15
- return false;
16
- }
17
- const candidate = value;
18
- return typeof candidate.accessToken === "string" && candidate.accessToken.length > 0 && typeof candidate.tokenType === "string" && candidate.tokenType.length > 0;
19
- }
20
- /**
21
- * One-time migration read-path (ADR-0017, mandatory — not optional polish).
22
- *
23
- * v0.17 moves lightbridge's human root token from its own bespoke file
24
- * (`<cacheRoot>/opencode-lightbridge/lightbridge.json`, a bare `TokenSet`) to
25
- * the SAME fused `CachedServerState` file `@vymalo/opencode-oauth2` uses
26
- * (`<cacheRoot>/<sharedSegment>/<sharedNamespace>/<serverId>.json`) so the two
27
- * plugins can share one login (ADR-0017 §"one login, shared cache"). An
28
- * install that already has a valid old-format token must NOT be forced
29
- * through a fresh device-code/browser login just because the file moved.
30
- *
31
- * Behaviour:
32
- * - If the NEW location already has a persisted state (with or without a
33
- * token), this is a no-op — never clobbers a fresher state (e.g. one
34
- * oauth2 has already been writing to, with real discovered models).
35
- * - Else, read the OLD location. If it holds a structurally usable token
36
- * (`accessToken` + `tokenType` present — possibly expired; an expired
37
- * token with a refresh token is still worth adopting, since `TokenRuntime`
38
- * will refresh it transparently), write it into the NEW location as a
39
- * fresh `CachedServerState` (empty `models`/`rawModels` — nothing to carry
40
- * over, since the old shape never tracked models).
41
- * - Else (no old file, or it's empty/malformed) this is a no-op; the caller
42
- * proceeds to a normal login.
43
- *
44
- * The old file is left in place (non-destructive) — this function only ever
45
- * ADDS the new file, never deletes/mutates the old one. Safe to call more
46
- * than once: the "new location already has a state" check makes every call
47
- * after the first a cheap no-op.
48
- */
49
- export async function migrateRootTokenIfNeeded(cacheRoot, sharedSegment, sharedNamespace, serverId, logger) {
50
- const sharedStore = new SharedFileCacheStore(join(cacheRoot, sharedSegment, sharedNamespace), logger);
51
- let alreadyMigrated;
52
- try {
53
- alreadyMigrated = await sharedStore.loadServerState(serverId);
54
- } catch (error) {
55
- logger.debug("lightbridge_root_migration_check_failed", { error: error instanceof Error ? error.message : String(error) });
56
- return;
57
- }
58
- if (alreadyMigrated) {
59
- // New location already has SOME state (with or without a token) — either
60
- // a previous migration ran, or oauth2/register already populated it.
61
- // Never overwrite it from the old file.
62
- return;
63
- }
64
- const legacyStore = new LegacyFileCacheStore(join(cacheRoot, OLD_ROOT_CACHE_NAMESPACE), logger);
65
- let legacyToken;
66
- try {
67
- legacyToken = await legacyStore.load(OLD_ROOT_KEY);
68
- } catch (error) {
69
- logger.debug("lightbridge_root_migration_legacy_read_failed", { error: error instanceof Error ? error.message : String(error) });
70
- return;
71
- }
72
- if (!hasUsableTokenShape(legacyToken)) {
73
- logger.trace("lightbridge_root_migration_nothing_to_adopt", { serverId });
74
- return;
75
- }
76
- const migrated = {
77
- serverId,
78
- updatedAt: Date.now(),
79
- models: [],
80
- rawModels: [],
81
- token: legacyToken
82
- };
83
- await sharedStore.ensureReady();
84
- await sharedStore.saveServerState(migrated);
85
- logger.info("lightbridge_root_migration_adopted", {
86
- serverId,
87
- hadRefreshToken: Boolean(legacyToken.refreshToken)
88
- });
89
- }
90
-
91
- //# sourceMappingURL=migration.js.map
@@ -1 +0,0 @@
1
- {"mappings":"AAAA,SAAS,YAAY;AAErB,SACE,kBAAkB,4BAGb;AACP,SACE,kBAAkB,4BAEb;;;;;;;;AASP,MAAM,2BAA2B;AACjC,MAAM,eAAe;AAErB,SAAS,oBAAoB,OAAmC;CAC9D,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;EAC/D,OAAO;CACT;CACA,MAAM,YAAY;CAClB,OACE,OAAO,UAAU,gBAAgB,YACjC,UAAU,YAAY,SAAS,KAC/B,OAAO,UAAU,cAAc,YAC/B,UAAU,UAAU,SAAS;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,OAAO,eAAe,yBACpB,WACA,eACA,iBACA,UACA,QACe;CACf,MAAM,cAAc,IAAI,qBACtB,KAAK,WAAW,eAAe,eAAe,GAC9C,MACF;CAEA,IAAI;CACJ,IAAI;EACF,kBAAkB,MAAM,YAAY,gBAAgB,QAAQ;CAC9D,SAAS,OAAO;EACd,OAAO,MAAM,2CAA2C,EACtD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;EACD;CACF;CACA,IAAI,iBAAiB;;;;EAInB;CACF;CAEA,MAAM,cAAc,IAAI,qBAAqB,KAAK,WAAW,wBAAwB,GAAG,MAAM;CAC9F,IAAI;CACJ,IAAI;EACF,cAAc,MAAM,YAAY,KAAe,YAAY;CAC7D,SAAS,OAAO;EACd,OAAO,MAAM,iDAAiD,EAC5D,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;EACD;CACF;CAEA,IAAI,CAAC,oBAAoB,WAAW,GAAG;EACrC,OAAO,MAAM,+CAA+C,EAAE,SAAS,CAAC;EACxE;CACF;CAEA,MAAM,WAA8B;EAClC;EACA,WAAW,KAAK,IAAI;EACpB,QAAQ,CAAC;EACT,WAAW,CAAC;EACZ,OAAO;CACT;CAEA,MAAM,YAAY,YAAY;CAC9B,MAAM,YAAY,gBAAgB,QAAQ;CAC1C,OAAO,KAAK,sCAAsC;EAChD;EACA,iBAAiB,QAAQ,YAAY,YAAY;CACnD,CAAC;AACH","names":[],"sources":["../src/migration.ts"],"version":3,"file":"migration.js","sourceRoot":""}