@rynfar/meridian 1.68.0 → 1.70.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.
Files changed (49) hide show
  1. package/README.md +1 -0
  2. package/dist/{cli-0zhb8ss4.js → cli-198xnjcn.js} +1033 -525
  3. package/dist/{cli-sh8bzdwe.js → cli-zdbv40d5.js} +26 -7
  4. package/dist/cli.js +4 -4
  5. package/dist/meridian/index.js +252 -0
  6. package/dist/meridian/package.json +6 -0
  7. package/dist/meridian-v2/index.js +20384 -41
  8. package/dist/meridian-v2.js +20382 -45
  9. package/dist/proxy/adapter.d.ts +16 -0
  10. package/dist/proxy/adapter.d.ts.map +1 -1
  11. package/dist/proxy/adapters/claudecode.d.ts +23 -0
  12. package/dist/proxy/adapters/claudecode.d.ts.map +1 -1
  13. package/dist/proxy/adapters/detect.d.ts.map +1 -1
  14. package/dist/proxy/adapters/passthrough.d.ts.map +1 -1
  15. package/dist/proxy/adapters/polytoken.d.ts +36 -0
  16. package/dist/proxy/adapters/polytoken.d.ts.map +1 -0
  17. package/dist/proxy/errors.d.ts +2 -0
  18. package/dist/proxy/errors.d.ts.map +1 -1
  19. package/dist/proxy/openaiResponses.d.ts +106 -3
  20. package/dist/proxy/openaiResponses.d.ts.map +1 -1
  21. package/dist/proxy/passthroughEarlyStop.d.ts +3 -3
  22. package/dist/proxy/passthroughEarlyStop.d.ts.map +1 -1
  23. package/dist/proxy/passthroughTools.d.ts +63 -2
  24. package/dist/proxy/passthroughTools.d.ts.map +1 -1
  25. package/dist/proxy/query.d.ts +3 -0
  26. package/dist/proxy/query.d.ts.map +1 -1
  27. package/dist/proxy/sdkFeatures.d.ts.map +1 -1
  28. package/dist/proxy/server.d.ts.map +1 -1
  29. package/dist/proxy/session/cache.d.ts +20 -3
  30. package/dist/proxy/session/cache.d.ts.map +1 -1
  31. package/dist/proxy/session/lineage.d.ts +56 -0
  32. package/dist/proxy/session/lineage.d.ts.map +1 -1
  33. package/dist/proxy/session/processIncarnation.d.ts +55 -0
  34. package/dist/proxy/session/processIncarnation.d.ts.map +1 -1
  35. package/dist/proxy/setup.d.ts +12 -2
  36. package/dist/proxy/setup.d.ts.map +1 -1
  37. package/dist/proxy/transforms/codex.d.ts +12 -0
  38. package/dist/proxy/transforms/codex.d.ts.map +1 -1
  39. package/dist/proxy/transforms/polytoken.d.ts +18 -0
  40. package/dist/proxy/transforms/polytoken.d.ts.map +1 -0
  41. package/dist/proxy/transforms/registry.d.ts.map +1 -1
  42. package/dist/server.js +2 -2
  43. package/dist/{setup-knvctar4.js → setup-b3ymd9z8.js} +3 -1
  44. package/dist/telemetry/index.d.ts.map +1 -1
  45. package/package.json +4 -4
  46. package/plugin/meridian/index.js +1 -0
  47. package/plugin/meridian/package.json +6 -0
  48. package/plugin/meridian-v2.ts +223 -0
  49. package/plugin/meridian.ts +1 -1
@@ -157,6 +157,17 @@ export interface LineageMismatch {
157
157
  previousDigest?: string;
158
158
  storedCount: number;
159
159
  incomingCount: number;
160
+ /** How many blocks the STORED message had at this index. Undefined for a
161
+ * session cached before block hashes were recorded (pre-1.61.0). */
162
+ storedBlockCount?: number;
163
+ /** Blocks the incoming message has at this index, counted over the same
164
+ * hashable domain as the stored side so the two are comparable. */
165
+ incomingBlockCount?: number;
166
+ /** What the client did to this message, when the stored block hashes make it
167
+ * decidable. Appended/dropped/rewritten are three different client bugs and
168
+ * the log could not tell them apart (#886) — which is precisely the question
169
+ * #767 turns on. */
170
+ blockChange?: "appended" | "dropped" | "rewritten" | "reordered" | "unknown";
160
171
  }
161
172
  /**
162
173
  * Locate the first message whose hash differs between a stored session and an
@@ -186,6 +197,51 @@ precomputedIncomingHashes?: string[]): LineageMismatch;
186
197
  * into a public issue.
187
198
  */
188
199
  export declare function formatLineageMismatch(mismatch: LineageMismatch): string | undefined;
200
+ /**
201
+ * Why a request skipped session lookup entirely.
202
+ *
203
+ * `independent-request` is assigned in server.ts before `classifyLineage` runs,
204
+ * so it is the one divergence that emits no diagnostic at all — and four
205
+ * unrelated causes collapse into that single silent outcome. #820 was a log
206
+ * full of `lineage=new` with zero explanatory lines; the reporter could only
207
+ * identify the bypass by reading server.ts.
208
+ *
209
+ * Naming the cause is log-only. The `LineageDivergenceReason` handed to the
210
+ * `onSession` transform hook is unchanged, so plugins switching on it are
211
+ * unaffected.
212
+ */
213
+ export type IndependentRequestCause = "fork-source" | "subagent" | "headerless-tool-result" | "no-cache-identity";
214
+ /**
215
+ * Decide whether a request bypasses session lookup, and say which rule did it.
216
+ *
217
+ * The caller derives `isIndependentSession` from this result rather than
218
+ * computing it separately, so the reported cause cannot drift away from the
219
+ * decision it explains. Evaluation order mirrors the guards' own precedence.
220
+ */
221
+ export declare function independentRequestCause(input: {
222
+ /** An explicit session key. Distinct flows carry distinct keys, so a keyed
223
+ * request cannot collide and never needs the independence guard. */
224
+ hasSessionKey: boolean;
225
+ /** `x-meridian-source: fork-*` — a declared independent sub-request flow. */
226
+ forkSource: boolean;
227
+ isSubagent: boolean;
228
+ /** The last message carries a tool_result and the request has no session
229
+ * key, so it is a self-contained round of the client's own tool loop. */
230
+ clientDrivenLoop: boolean;
231
+ /** Whether a session key or a conversation fingerprint could be derived.
232
+ * Image-only and otherwise text-free headerless requests have neither. */
233
+ hasDurableKey: boolean;
234
+ }): IndependentRequestCause | undefined;
235
+ /**
236
+ * The `diverged=` field for the request log line.
237
+ *
238
+ * The line renders `lineage=new` for every divergence without a cached
239
+ * session, so a key that never resolved, a history that did not match and a
240
+ * request that never looked are indistinguishable (#820). The reason is
241
+ * already in memory on every request; it was only reachable by writing a
242
+ * plugin. Reasons are fixed identifiers, never message content.
243
+ */
244
+ export declare function formatDivergence(result: LineageResult, cause?: IndependentRequestCause): string | undefined;
189
245
  /**
190
246
  * Compute per-message hashes for an entire message array.
191
247
  */
@@ -1 +1 @@
1
- {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lineage.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/lineage.ts"],"names":[],"mappings":"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"}
@@ -36,6 +36,61 @@ export declare function parseProcessIncarnationJson(raw: string): ProcessIncarna
36
36
  * Unknown hosts and every unavailable or malformed observation fail closed.
37
37
  */
38
38
  export declare function evaluateProcessIncarnation(owner: ProcessIncarnation, localBoot: LocalBootIdentity | undefined, observedStart: ProcessStartObservation | undefined): ProcessIncarnationProbe;
39
+ /**
40
+ * Operator-pinned host identity (#905).
41
+ *
42
+ * The derived `hostId` is wrong in both directions inside a container:
43
+ *
44
+ * - NOT STABLE. It mixes in the pid-namespace inode, which changes on every
45
+ * `docker restart`. The session store lives in the writable layer and
46
+ * survives that restart, so a proxy SIGKILLed while holding a store lock
47
+ * comes back with a different `hostId`; `probeProcessIncarnation` then
48
+ * returns `indeterminate` rather than `dead`, `retireStaleLock` refuses to
49
+ * retire it, and every request fails with `timed out waiting for lock`
50
+ * until the container is recreated. It fails toward a permanent hang.
51
+ * - NOT UNIQUE. Every container from a given image tag has a byte-identical
52
+ * `/etc/machine-id`, so `hostId` reduces to that pid-namespace inode, which
53
+ * is allocated from a fixed base at boot. Two freshly-booted hosts sharing
54
+ * a session directory can each read the other's LIVE lock as `dead` and
55
+ * retire it, deleting session resources under a running owner.
56
+ *
57
+ * Pinning it — the container ID works — fixes both. Unset outside containers,
58
+ * where the derived value is genuinely stable and unique, so nothing changes.
59
+ *
60
+ * Hashed like the derived value rather than used raw: `hostId` is compared for
61
+ * equality only, and hashing keeps a pinned value from leaking an operator's
62
+ * hostname into a lock file that is read cross-host.
63
+ */
64
+ export declare function pinnedHostIdFor(pin: string | undefined): string | undefined;
65
+ /**
66
+ * Whether this host can produce a boot identity, and what to do if it cannot.
67
+ *
68
+ * Every session-store write takes a lock stamped with a process incarnation,
69
+ * and `captureProcessIncarnation` returns undefined without a boot identity —
70
+ * so `acquireLock` throws and EVERY request that touches a session fails with a
71
+ * 500. `/health` never probed this, so a container missing `/etc/machine-id`
72
+ * reported healthy to Docker and to any orchestrator while serving nothing,
73
+ * which is why the original occurrence took three days to find (#906, split
74
+ * from #903).
75
+ *
76
+ * Exported so both the startup check and `/health` read the same source rather
77
+ * than each inferring it.
78
+ */
79
+ export interface BootIdentityStatus {
80
+ available: boolean;
81
+ platform: string;
82
+ /** Actionable next step, present only when unavailable. */
83
+ hint?: string;
84
+ }
85
+ /**
86
+ * What to tell an operator when this platform produced no boot identity.
87
+ *
88
+ * Pure and exported separately so every branch is testable without breaking the
89
+ * host it runs on. "cannot capture lock owner process incarnation" is what the
90
+ * original failure said, and it names nothing an operator can act on.
91
+ */
92
+ export declare function bootIdentityHint(platform: string): string;
93
+ export declare function describeLocalBootIdentity(): BootIdentityStatus;
39
94
  /** Parse field 22 (`starttime`) without assuming that comm contains no `)`. */
40
95
  export declare function parseLinuxProcStatStartId(raw: string, expectedPid: number): string | undefined;
41
96
  /** Capture metadata for a lock created by `pid` (normally the current process). */
@@ -1 +1 @@
1
- {"version":3,"file":"processIncarnation.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/processIncarnation.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,MAAM,MAAM,kBAAkB,GAC1B,wBAAwB,GACxB,kBAAkB,GAClB,qBAAqB,CAAA;AAEzB;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,2BAA2B,CAAA;IAC3C,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,kBAAkB,CAAA;CAChC;AAED,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,MAAM,GAAG,eAAe,CAAA;AAExE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,kBAAkB,CAAA;CAAE,GACrE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA;AAkC/B,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAqBtF;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAMvF;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,iBAAiB,GAAG,SAAS,EACxC,aAAa,EAAE,uBAAuB,GAAG,SAAS,GACjD,uBAAuB,CAsBzB;AA6HD,+EAA+E;AAC/E,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ9F;AAsDD,mFAAmF;AACnF,wBAAgB,yBAAyB,CAAC,GAAG,SAAc,GAAG,kBAAkB,GAAG,SAAS,CAkB3F;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,uBAAuB,CAS1F;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAE3E"}
1
+ {"version":3,"file":"processIncarnation.d.ts","sourceRoot":"","sources":["../../../src/proxy/session/processIncarnation.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAE5C,MAAM,MAAM,kBAAkB,GAC1B,wBAAwB,GACxB,kBAAkB,GAClB,qBAAqB,CAAA;AAEzB;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,2BAA2B,CAAA;IAC3C,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,kBAAkB,CAAA;CAChC;AAED,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,MAAM,GAAG,eAAe,CAAA;AAExE,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,MAAM,uBAAuB,GAC/B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,kBAAkB,CAAA;CAAE,GACrE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA;AAkC/B,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAqBtF;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAMvF;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,kBAAkB,EACzB,SAAS,EAAE,iBAAiB,GAAG,SAAS,EACxC,aAAa,EAAE,uBAAuB,GAAG,SAAS,GACjD,uBAAuB,CAsBzB;AAkGD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAG3E;AA2BD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,wBAAgB,yBAAyB,IAAI,kBAAkB,CAI9D;AAYD,+EAA+E;AAC/E,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQ9F;AAsDD,mFAAmF;AACnF,wBAAgB,yBAAyB,CAAC,GAAG,SAAc,GAAG,kBAAkB,GAAG,SAAS,CAkB3F;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,uBAAuB,CAS1F;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAE3E"}
@@ -18,6 +18,10 @@ export declare class UnparseableConfigError extends Error {
18
18
  readonly configPath: string;
19
19
  constructor(configPath: string);
20
20
  }
21
+ export declare class MissingV1PluginError extends Error {
22
+ readonly expectedPath: string;
23
+ constructor(expectedPath: string);
24
+ }
21
25
  export declare class MissingV2PluginError extends Error {
22
26
  readonly expectedPath: string;
23
27
  constructor(expectedPath: string);
@@ -29,8 +33,14 @@ export declare class DuplicateMeridianConfigError extends Error {
29
33
  }
30
34
  export declare function findOpencodeConfigPath(): string;
31
35
  /**
32
- * Resolve the absolute path to plugin/meridian.ts from any entry point.
33
- * Works whether called from bin/cli.ts (dev) or dist/cli.js (installed).
36
+ * Resolve the V1 plugin package from any entry point. Works whether called from
37
+ * bin/cli.ts (dev) or dist/cli.js (installed).
38
+ *
39
+ * NOTE: an installed CLI must select the compiled package, not TypeScript.
40
+ * OpenCode Desktop runs its server inside Electron's Node rather than Bun, and
41
+ * Node refuses to strip types for files under node_modules
42
+ * (ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so a published install that
43
+ * points at plugin/meridian.ts cannot load the plugin at all.
34
44
  */
35
45
  export declare function findPluginPath(fromUrl: string): string;
36
46
  export type OpenCodeGeneration = "v1" | "v2";
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/proxy/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aACnB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,YAAY,EAAE,MAAM;gBAApB,YAAY,EAAE,MAAM;CAIjD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;aAEnC,UAAU,EAAE,MAAM;aAClB,WAAW,EAAE,MAAM;gBADnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM;CAKtC;AA+BD,wBAAgB,sBAAsB,IAAI,MAAM,CAK/C;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE5C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,kBAAkB,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,8BAA8B,aAGzC,CAAA;AAeF,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAYrF;AAED,KAAK,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE3D;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,GAAE,SAAS,MAAM,EAEI,EAC7B,KAAK,GAAE,YASN,GACA,iBAAiB,CAQnB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAE/F;AA6BD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAgB/F;AAaD,sDAAsD;AACtD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,mEAAmE;IACnE,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,mEAAmE;IACnE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,GAAG,MAAM,GAAG,SAAS,CAkBrB;AAMD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,GAAE,kBAAyB,GACpC,WAAW,CAiFb"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/proxy/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAWH;;;;GAIG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;aACnB,UAAU,EAAE,MAAM;gBAAlB,UAAU,EAAE,MAAM;CAI/C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,YAAY,EAAE,MAAM;gBAApB,YAAY,EAAE,MAAM;CAIjD;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,YAAY,EAAE,MAAM;gBAApB,YAAY,EAAE,MAAM;CAIjD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;aAEnC,UAAU,EAAE,MAAM;aAClB,WAAW,EAAE,MAAM;gBADnB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM;CAKtC;AA+BD,wBAAgB,sBAAsB,IAAI,MAAM,CAK/C;AASD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBtD;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,IAAI,CAAA;AAE5C,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,kBAAkB,CAAA;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,eAAO,MAAM,8BAA8B,aAGzC,CAAA;AAeF,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBxD;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAYrF;AAED,KAAK,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAA;AAE3D;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,GAAE,SAAS,MAAM,EAEI,EAC7B,KAAK,GAAE,YASN,GACA,iBAAiB,CAQnB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAE/F;AAgCD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAgB/F;AAaD,sDAAsD;AACtD,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,mEAAmE;IACnE,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,mEAAmE;IACnE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B,GAAG,MAAM,GAAG,SAAS,CAkBrB;AAMD,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CACtB,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,MAAM,EACnB,UAAU,GAAE,kBAAyB,GACpC,WAAW,CAiFb"}
@@ -8,5 +8,17 @@ import type { Transform } from "../transform";
8
8
  * global MERIDIAN_PASSTHROUGH setting. Internal mode (SDK executes tools) would
9
9
  * leave Codex waiting for tool calls that never come back.
10
10
  */
11
+ /**
12
+ * Codex sends every MCP server as a `namespace` entry with the tool
13
+ * definitions inline, so a desktop session easily carries 250+ tools and
14
+ * crosses the auto-defer threshold. Deferring them is a net loss here: the
15
+ * deferred budget lifts the single-turn cap (see computePassthroughMaxTurns),
16
+ * so every tool-calling turn also generates the SDK's discarded digest turn —
17
+ * one or two extra reads of the full context, measured 2-3x cache reads per
18
+ * turn on a 680k-token session — while the alternative, loading the schemas,
19
+ * costs ~125k cached tokens once. Codex never sets `defer_loading` itself, so
20
+ * with no core set auto-defer stays off, the cap stays at 1, and a tool turn
21
+ * is one model call again.
22
+ */
11
23
  export declare const codexTransforms: Transform[];
12
24
  //# sourceMappingURL=codex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/codex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AAE7D;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,EAQtC,CAAA"}
1
+ {"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/codex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AAE7D;;;;;;;;GAQG;AACH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,EAAE,SAAS,EAQtC,CAAA"}
@@ -0,0 +1,18 @@
1
+ import type { Transform } from "../transform";
2
+ /**
3
+ * Polytoken native transforms.
4
+ *
5
+ * Scoped to adapter base `polytoken` ONLY — deliberately not spread onto
6
+ * openai/jcode/codex, whose tool configs differ. The client owns the tool
7
+ * loop, so this is pure passthrough plumbing:
8
+ * - passthrough forced true (mandatory client-owned tool execution);
9
+ * - signed/redacted thinking preserved (supportsThinking true);
10
+ * - body/system/tools and the client's stream preference preserved;
11
+ * - empty SDK agents/hooks — no Task/task parsing, no fuzzy aliases;
12
+ * - no file-change tracking or core-tool rewriting;
13
+ * - empty blocked/incompatible/allowed MCP lists and coreToolNames: the
14
+ * generated client-passthrough MCP names are the only allowed tools, and
15
+ * an OpenCode-style allowlist would collide with client tool names.
16
+ */
17
+ export declare const polytokenTransforms: Transform[];
18
+ //# sourceMappingURL=polytoken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polytoken.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/polytoken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAkB,MAAM,cAAc,CAAA;AAE7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,EAAE,SAAS,EAqB1C,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAyC7C,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAE9E"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/proxy/transforms/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AA8C7C,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAE9E"}
package/dist/server.js CHANGED
@@ -11,14 +11,14 @@ import {
11
11
  runObserveHook,
12
12
  runTransformHook,
13
13
  startProxyServer
14
- } from "./cli-0zhb8ss4.js";
14
+ } from "./cli-198xnjcn.js";
15
15
  import"./cli-5jxyma6z.js";
16
16
  import"./cli-sry5aqdj.js";
17
17
  import"./cli-8yp89fan.js";
18
18
  import"./cli-9e5cxp89.js";
19
19
  import"./cli-khhjyk04.js";
20
20
  import"./cli-vj9cv18n.js";
21
- import"./cli-sh8bzdwe.js";
21
+ import"./cli-zdbv40d5.js";
22
22
  import"./cli-p9swy5t3.js";
23
23
  export {
24
24
  startProxyServer,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  DuplicateMeridianConfigError,
3
+ MissingV1PluginError,
3
4
  MissingV2PluginError,
4
5
  SUPPORTED_OPENCODE_V2_VERSIONS,
5
6
  UnparseableConfigError,
@@ -13,7 +14,7 @@ import {
13
14
  notePluginlessOpenCodeRequest,
14
15
  pluginPathForGeneration,
15
16
  runSetup
16
- } from "./cli-sh8bzdwe.js";
17
+ } from "./cli-zdbv40d5.js";
17
18
  import"./cli-p9swy5t3.js";
18
19
  export {
19
20
  runSetup,
@@ -29,5 +30,6 @@ export {
29
30
  UnparseableConfigError,
30
31
  SUPPORTED_OPENCODE_V2_VERSIONS,
31
32
  MissingV2PluginError,
33
+ MissingV1PluginError,
32
34
  DuplicateMeridianConfigError
33
35
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AAgCnE,eAAO,MAAM,cAAc,EAAE,eAAkC,CAAA;AAC/D,eAAO,MAAM,aAAa,EAAE,mBAAwC,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC7C,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA;AA2CnE,eAAO,MAAM,cAAc,EAAE,eAAkC,CAAA;AAC/D,eAAO,MAAM,aAAa,EAAE,mBAAwC,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAA;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAC7C,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,aAAa,GACd,MAAM,SAAS,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rynfar/meridian",
3
- "version": "1.68.0",
3
+ "version": "1.70.0",
4
4
  "description": "Local Anthropic API powered by your Claude Max subscription. One subscription, every agent.",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",
@@ -21,11 +21,11 @@
21
21
  "scripts": {
22
22
  "start": "./bin/claude-proxy-supervisor.sh",
23
23
  "proxy": "./bin/claude-proxy-supervisor.sh",
24
- "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun build bin/cli.ts src/proxy/server.ts plugin/meridian-v2.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && bun build plugin/meridian-v2/index.js --outdir dist/meridian-v2 --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && node scripts/package-meridian-v2-plugin.mjs && tsc -p tsconfig.build.json",
25
- "postbuild": "node scripts/fix-bun-exports.mjs && node --check dist/cli.js && node --check dist/server.js && node --check dist/meridian-v2.js && node --check dist/meridian-v2/index.js && node -e \"if(!require('fs').existsSync('dist/proxy/server.d.ts'))process.exit(1)\"",
24
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && bun build bin/cli.ts src/proxy/server.ts plugin/meridian-v2.ts --outdir dist --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && bun build plugin/meridian/index.js --outdir dist/meridian --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && bun build plugin/meridian-v2/index.js --outdir dist/meridian-v2 --target node --splitting --external @anthropic-ai/claude-agent-sdk --external jsonc-parser --entry-naming \"[name].js\" && node scripts/package-opencode-plugins.mjs && tsc -p tsconfig.build.json",
25
+ "postbuild": "node scripts/fix-bun-exports.mjs && node --check dist/cli.js && node --check dist/server.js && node --check dist/meridian-v2.js && node --check dist/meridian/index.js && node --check dist/meridian-v2/index.js && node -e \"if(!require('fs').existsSync('dist/proxy/server.d.ts'))process.exit(1)\"",
26
26
  "postinstall": "node -e \"try{require('child_process').spawnSync(process.execPath,[require.resolve('@anthropic-ai/claude-code/install.cjs')],{stdio:'inherit'})}catch(e){console.error('[meridian] claude-code postinstall skipped:',e.message)}\"",
27
27
  "prepublishOnly": "bun run build",
28
- "test": "bun test --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*session-fingerprint-context*' --path-ignore-patterns '**/*priority-routing-integration*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' --path-ignore-patterns '**/*proxy-thinking-setting*' --path-ignore-patterns '**/*session-recovery*' --path-ignore-patterns '**/*models.test*' --path-ignore-patterns '**/*proxy-health-build*' && bun test src/__tests__/proxy-async-ops.test.ts && bun test src/__tests__/proxy-session-store.test.ts && bun test src/__tests__/session-store-pruning.test.ts && bun test src/__tests__/proxy-session-store-locking.test.ts && bun test src/__tests__/session-fingerprint-context.test.ts && bun test src/__tests__/proxy-context-usage-store.test.ts && bun test src/__tests__/models-auth-status.test.ts && bun test src/__tests__/proxy-passthrough-thinking.test.ts && bun test src/__tests__/proxy-session-recovery.test.ts && bun test src/__tests__/proxy-thinking-setting.test.ts && bun test src/__tests__/models.test.ts && bun test src/__tests__/proxy-health-build.test.ts && bun test src/__tests__/priority-routing-integration.test.ts",
28
+ "test": "bun test --timeout 30000 --path-ignore-patterns '**/*session-store*' --path-ignore-patterns '**/*session-fingerprint-context*' --path-ignore-patterns '**/*priority-routing-integration*' --path-ignore-patterns '**/*proxy-async-ops*' --path-ignore-patterns '**/*models-auth-status*' --path-ignore-patterns '**/*proxy-context-usage-store*' --path-ignore-patterns '**/*proxy-passthrough-thinking*' --path-ignore-patterns '**/*proxy-thinking-setting*' --path-ignore-patterns '**/*session-recovery*' --path-ignore-patterns '**/*models.test*' --path-ignore-patterns '**/*proxy-health-build*' && bun test --timeout 30000 src/__tests__/proxy-async-ops.test.ts && bun test --timeout 30000 src/__tests__/proxy-session-store.test.ts && bun test --timeout 30000 src/__tests__/session-store-pruning.test.ts && bun test --timeout 30000 src/__tests__/proxy-session-store-locking.test.ts && bun test --timeout 30000 src/__tests__/session-fingerprint-context.test.ts && bun test --timeout 30000 src/__tests__/proxy-context-usage-store.test.ts && bun test --timeout 30000 src/__tests__/models-auth-status.test.ts && bun test --timeout 30000 src/__tests__/proxy-passthrough-thinking.test.ts && bun test --timeout 30000 src/__tests__/proxy-session-recovery.test.ts && bun test --timeout 30000 src/__tests__/proxy-thinking-setting.test.ts && bun test --timeout 30000 src/__tests__/models.test.ts && bun test --timeout 30000 src/__tests__/proxy-health-build.test.ts && bun test --timeout 30000 src/__tests__/priority-routing-integration.test.ts",
29
29
  "nix:lock": "bun2nix -o bun.nix",
30
30
  "typecheck": "tsc --noEmit",
31
31
  "proxy:direct": "bun run ./bin/cli.ts"
@@ -0,0 +1 @@
1
+ export { default } from "../meridian.ts"
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@rynfar/meridian-opencode-v1-plugin",
3
+ "private": true,
4
+ "type": "module",
5
+ "main": "./index.js"
6
+ }
@@ -16,6 +16,8 @@
16
16
  */
17
17
 
18
18
  import * as Plugin from "@opencode-ai/plugin/promise/plugin"
19
+ import { Model } from "@opencode-ai/schema/model"
20
+ import type { CatalogDraft } from "@opencode-ai/plugin/promise/catalog"
19
21
  import {
20
22
  PRIORITY_ATTESTATION_HEADER,
21
23
  createPriorityAttestation,
@@ -29,8 +31,18 @@ import {
29
31
  export const SUPPORTED_OPENCODE_V2_VERSION = "0.0.0-beta-18314"
30
32
 
31
33
  const MERIDIAN_PROVIDERS = new Set(["anthropic", "meridian"])
34
+ const MERIDIAN_CATALOG_PROVIDER = "meridian"
32
35
  const PARENT_SESSION_ONE_SHOTS = new Set(["title", "summary"])
33
36
  const ATTACHED_COMPACTION_AGENT = "compaction"
37
+ const MODEL_DISCOVERY_TIMEOUT_MS = 3_000
38
+ const PROVIDER_READY_POLL_MS = 25
39
+ // The plugin tree does not import from src/, so this mirrors VALID_EFFORTS in
40
+ // src/proxy/effort.ts. A test asserts the two stay identical. Filtering in this
41
+ // order also gives every model its variants low -> max regardless of the order
42
+ // the proxy happens to serialise its capability object in.
43
+ const MERIDIAN_EFFORTS = ["low", "medium", "high", "xhigh", "max"] as const
44
+
45
+ export const MERIDIAN_V2_EFFORTS: readonly string[] = MERIDIAN_EFFORTS
34
46
 
35
47
  const SESSION_AFFINITY_HEADERS = [
36
48
  "x-opencode-session",
@@ -131,6 +143,186 @@ function isRecord(value: unknown): value is Record<string, unknown> {
131
143
  return typeof value === "object" && value !== null && !Array.isArray(value)
132
144
  }
133
145
 
146
+ export interface MeridianModel {
147
+ id: string
148
+ name: string
149
+ contextWindow: number
150
+ efforts: string[]
151
+ }
152
+
153
+ export interface MeridianProviderModels {
154
+ providerID: string
155
+ models: MeridianModel[]
156
+ }
157
+
158
+ type MeridianCatalogClient = {
159
+ provider: {
160
+ get(input: { providerID: string }): Promise<unknown>
161
+ }
162
+ reload(): Promise<void>
163
+ }
164
+
165
+ type ModelFetcher = (input: string | URL | Request, init?: RequestInit) => Promise<Response>
166
+
167
+ export function meridianModelsURL(baseURL: unknown): string | undefined {
168
+ if (typeof baseURL !== "string" || baseURL.length === 0) return undefined
169
+ try {
170
+ const url = new URL(baseURL)
171
+ if (url.protocol !== "http:" && url.protocol !== "https:") return undefined
172
+ url.search = ""
173
+ url.hash = ""
174
+ if (!url.pathname.endsWith("/")) url.pathname += "/"
175
+ // OpenCode's Anthropic provider carries the API version in the base URL
176
+ // (`http://127.0.0.1:3456/v1`), which is also the shape the V2 package gate
177
+ // configures. Appending `v1/models` there requests `/v1/v1/models`, a 404
178
+ // that silently disables discovery, so resolve `models` against an existing
179
+ // version segment instead.
180
+ const endpoint = /(?:^|\/)v1\/$/.test(url.pathname) ? "models" : "v1/models"
181
+ return new URL(endpoint, url).toString()
182
+ } catch {
183
+ return undefined
184
+ }
185
+ }
186
+
187
+ export function parseMeridianModels(value: unknown): MeridianModel[] | undefined {
188
+ if (!isRecord(value) || !Array.isArray(value.data)) return undefined
189
+
190
+ const models: MeridianModel[] = []
191
+ const ids = new Set<string>()
192
+ for (const item of value.data) {
193
+ if (!isRecord(item)) return undefined
194
+ const id = item.id
195
+ const name = item.display_name
196
+ const contextWindow = item.context_window
197
+ if (
198
+ typeof id !== "string"
199
+ || id.length === 0
200
+ || id.length > 256
201
+ || /[^\x21-\x7E]/.test(id)
202
+ || ids.has(id)
203
+ || typeof name !== "string"
204
+ || name.trim().length === 0
205
+ || name.length > 256
206
+ || typeof contextWindow !== "number"
207
+ || !Number.isSafeInteger(contextWindow)
208
+ || contextWindow <= 0
209
+ ) {
210
+ return undefined
211
+ }
212
+ const capabilities = isRecord(item.capabilities) ? item.capabilities : undefined
213
+ const effort = capabilities && isRecord(capabilities.effort) ? capabilities.effort : undefined
214
+ const efforts = MERIDIAN_EFFORTS.filter((level) => isRecord(effort?.[level]) && effort[level].supported === true)
215
+ ids.add(id)
216
+ models.push({ id, name, contextWindow, efforts })
217
+ }
218
+ return models
219
+ }
220
+
221
+ export async function fetchMeridianModels(
222
+ baseURL: unknown,
223
+ signal: AbortSignal,
224
+ fetcher: ModelFetcher = globalThis.fetch,
225
+ ): Promise<MeridianModel[] | undefined> {
226
+ const url = meridianModelsURL(baseURL)
227
+ if (!url || signal.aborted) return undefined
228
+
229
+ const controller = new AbortController()
230
+ const abort = () => controller.abort()
231
+ const timeout = setTimeout(abort, MODEL_DISCOVERY_TIMEOUT_MS)
232
+ timeout.unref?.()
233
+ signal.addEventListener("abort", abort, { once: true })
234
+ try {
235
+ const response = await fetcher(url, { signal: controller.signal })
236
+ if (!response.ok) return undefined
237
+ return parseMeridianModels(await response.json())
238
+ } catch {
239
+ return undefined
240
+ } finally {
241
+ clearTimeout(timeout)
242
+ signal.removeEventListener("abort", abort)
243
+ }
244
+ }
245
+
246
+ function meridianProviderBaseURL(value: unknown): unknown {
247
+ if (!isRecord(value)) return undefined
248
+ const provider = isRecord(value.data) ? value.data : value
249
+ if (!isRecord(provider.settings)) return undefined
250
+ return provider.settings.baseURL
251
+ }
252
+
253
+ function isLegacyMeridianBaseURL(baseURL: unknown): boolean {
254
+ if (typeof baseURL !== "string") return false
255
+ try {
256
+ const { hostname } = new URL(baseURL)
257
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]"
258
+ } catch {
259
+ return false
260
+ }
261
+ }
262
+
263
+ export async function loadMeridianModels(
264
+ catalog: MeridianCatalogClient,
265
+ signal: AbortSignal,
266
+ fetcher: ModelFetcher = globalThis.fetch,
267
+ ): Promise<MeridianProviderModels[]> {
268
+ const deadline = Date.now() + MODEL_DISCOVERY_TIMEOUT_MS
269
+ while (!signal.aborted) {
270
+ const providers = await Promise.all([...MERIDIAN_PROVIDERS].map(async (providerID) => {
271
+ try {
272
+ const baseURL = meridianProviderBaseURL(await catalog.provider.get({ providerID }))
273
+ if (providerID === MERIDIAN_CATALOG_PROVIDER && meridianModelsURL(baseURL) === undefined) return undefined
274
+ if (providerID !== MERIDIAN_CATALOG_PROVIDER && !isLegacyMeridianBaseURL(baseURL)) return undefined
275
+ return { providerID, baseURL }
276
+ } catch {
277
+ return undefined
278
+ }
279
+ }))
280
+ const configured = providers.flatMap(provider => provider ? [provider] : [])
281
+ if (configured.length > 0) {
282
+ const discovered = await Promise.all(configured.map(async ({ providerID, baseURL }) => {
283
+ const models = await fetchMeridianModels(baseURL, signal, fetcher)
284
+ return models ? { providerID, models } : undefined
285
+ }))
286
+ return discovered.flatMap(result => result ? [result] : [])
287
+ }
288
+ if (Date.now() >= deadline) return []
289
+ await new Promise<void>((resolve) => setTimeout(resolve, PROVIDER_READY_POLL_MS))
290
+ }
291
+ return []
292
+ }
293
+
294
+ /**
295
+ * Write Meridian's advertised models over the assembled V2 catalog.
296
+ *
297
+ * Meridian is authoritative for what it will actually serve: the context window
298
+ * follows the signed-in subscription (Sonnet stays 200k so a 1M turn is not
299
+ * billed as Extra Usage), and the effort levels are the ones the proxy accepts.
300
+ * OpenCode's built-in models.dev entries disagree — beta-18866 advertises a 1M
301
+ * Sonnet — so an entry that already exists must be corrected, not skipped.
302
+ *
303
+ * This does not overwrite user configuration. V2 layers `providers.<id>.models`
304
+ * on top of plugin transforms, verified live on beta-18866: a configured
305
+ * `claude-opus-5` override survived a transform that wrote a different name and
306
+ * context to the same model.
307
+ */
308
+ export function applyMeridianModels(
309
+ catalog: CatalogDraft,
310
+ providerID: string,
311
+ models: readonly MeridianModel[],
312
+ ): void {
313
+ for (const model of models) {
314
+ catalog.model.update(providerID, model.id, (entry) => {
315
+ entry.name = model.name
316
+ entry.limit.context = model.contextWindow
317
+ entry.variants = model.efforts.map((effort) => ({
318
+ id: Model.VariantID.make(effort),
319
+ headers: {},
320
+ body: { effort },
321
+ }))
322
+ })
323
+ }
324
+ }
325
+
134
326
  async function withTimeout<T>(pending: Promise<T>, timeoutMs: number): Promise<T | undefined> {
135
327
  let timer: ReturnType<typeof setTimeout> | undefined
136
328
  const timedOut = new Promise<undefined>((resolve) => {
@@ -196,6 +388,33 @@ const MeridianV2Plugin = Plugin.define({
196
388
  pending: Promise<ResolvedAgentMetadata>
197
389
  }>()
198
390
  const registered: Array<{ dispose: () => Promise<void> }> = []
391
+ const modelDiscoveryController = new AbortController()
392
+ let discoveredModels: MeridianProviderModels[] = []
393
+ let discoveryStarted = false
394
+
395
+ registered.push(await context.catalog.transform((catalog) => {
396
+ for (const discovered of discoveredModels) {
397
+ applyMeridianModels(catalog, discovered.providerID, discovered.models)
398
+ }
399
+ }))
400
+
401
+ const discoverModels = async () => {
402
+ if (discoveryStarted || modelDiscoveryController.signal.aborted) return false
403
+ const models = await loadMeridianModels(context.catalog, modelDiscoveryController.signal).catch(() => [])
404
+ if (models.length === 0 || modelDiscoveryController.signal.aborted) return false
405
+ discoveryStarted = true
406
+ discoveredModels = models
407
+ await context.catalog.reload()
408
+ return true
409
+ }
410
+
411
+ const catalogEvents = context.event.subscribe({ signal: modelDiscoveryController.signal })
412
+ void (async () => {
413
+ for await (const event of catalogEvents) {
414
+ if (event.type !== "catalog.updated") continue
415
+ if (await discoverModels()) return
416
+ }
417
+ })().catch(() => {})
199
418
 
200
419
  const resolveAgentTraits = (agent: string, refresh = false): Promise<ResolvedAgentMetadata> => {
201
420
  const cached = traitsByAgent.get(agent)
@@ -320,11 +539,15 @@ const MeridianV2Plugin = Plugin.define({
320
539
  }, { providerID }))
321
540
  }
322
541
  } catch (error) {
542
+ // Setup never returns its cleanup on this path, so the discovery
543
+ // subscription and its poll loop would otherwise outlive the failure.
544
+ modelDiscoveryController.abort()
323
545
  await Promise.allSettled(registered.map(({ dispose }) => dispose()))
324
546
  throw error
325
547
  }
326
548
 
327
549
  return async () => {
550
+ modelDiscoveryController.abort()
328
551
  const results = await Promise.allSettled(registered.map(({ dispose }) => dispose()))
329
552
  const failures = results.flatMap(result => result.status === "rejected" ? [result.reason] : [])
330
553
  if (failures.length > 0) throw new AggregateError(failures, "Failed to dispose Meridian V2 hooks")
@@ -11,7 +11,7 @@
11
11
  * meridian setup
12
12
  *
13
13
  * Or manually add to ~/.config/opencode/opencode.json:
14
- * { "plugin": ["/absolute/path/to/plugin/meridian.ts"] }
14
+ * { "plugin": ["/absolute/path/to/dist/meridian"] }
15
15
  */
16
16
 
17
17
  import {