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