@vymalo/opencode-lightbridge 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -38
- package/dist/config.d.ts +70 -4
- package/dist/config.js +76 -3
- package/dist/config.js.map +1 -1
- package/dist/lib.d.ts +4 -3
- package/dist/lib.js +4 -3
- package/dist/lib.js.map +1 -1
- package/dist/logging.d.ts +44 -0
- package/dist/logging.js +100 -0
- package/dist/logging.js.map +1 -0
- package/dist/migration.d.ts +31 -0
- package/dist/migration.js +91 -0
- package/dist/migration.js.map +1 -0
- package/dist/opencode.d.ts +33 -15
- package/dist/opencode.js +390 -194
- package/dist/opencode.js.map +1 -1
- package/dist/otel-adapter.d.ts +68 -0
- package/dist/otel-adapter.js +315 -0
- package/dist/otel-adapter.js.map +1 -0
- package/dist/plugin.d.ts +77 -23
- package/dist/plugin.js +157 -33
- package/dist/plugin.js.map +1 -1
- package/package.json +15 -9
|
@@ -0,0 +1,91 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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":""}
|
package/dist/opencode.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
-
import
|
|
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
|
-
|
|
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`
|
|
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
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* `
|
|
44
|
-
*
|
|
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 };
|