@rynfar/meridian 1.69.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.
- package/README.md +1 -0
- package/dist/{cli-x1sv3bqf.js → cli-198xnjcn.js} +136 -21
- package/dist/cli.js +1 -1
- package/dist/meridian-v2/index.js +20384 -41
- package/dist/meridian-v2.js +20382 -45
- package/dist/proxy/adapters/detect.d.ts.map +1 -1
- package/dist/proxy/adapters/polytoken.d.ts +36 -0
- package/dist/proxy/adapters/polytoken.d.ts.map +1 -0
- package/dist/proxy/errors.d.ts.map +1 -1
- package/dist/proxy/sdkFeatures.d.ts.map +1 -1
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/transforms/polytoken.d.ts +18 -0
- package/dist/proxy/transforms/polytoken.d.ts.map +1 -0
- package/dist/proxy/transforms/registry.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/plugin/meridian-v2.ts +223 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/detect.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAkD9C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C;AAqED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,YAAY,CAkHtD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polytoken native adapter.
|
|
3
|
+
*
|
|
4
|
+
* Polytoken sends Anthropic Messages requests with a stable
|
|
5
|
+
* `X-Polytoken-Session` header and executes its own tools client-side — the
|
|
6
|
+
* client owns the tool loop, so the proxy must ALWAYS run in passthrough mode
|
|
7
|
+
* and never activate OpenCode-style agent behavior (subagent parsing, fuzzy
|
|
8
|
+
* aliasing, Task-tool routing, file-change tracking).
|
|
9
|
+
*
|
|
10
|
+
* Identity contract:
|
|
11
|
+
* - Session identity comes ONLY from X-Polytoken-Session, trimmed. No
|
|
12
|
+
* parent identity, no agent-mode scoping, no attestation, no OpenCode
|
|
13
|
+
* lineage normalization. A missing/blank header means "no identity": the
|
|
14
|
+
* request runs fingerprint-less and independent (never resumed) rather
|
|
15
|
+
* than being given an invented key.
|
|
16
|
+
* - The header is a session key, not an authentication credential — same
|
|
17
|
+
* trust model as every other adapter's headers.
|
|
18
|
+
*
|
|
19
|
+
* CWD contract: a network-hosted Polytoken may run its tools and environment
|
|
20
|
+
* far from the proxy, and the client does not advertise a machine-readable
|
|
21
|
+
* working directory, so extraction returns undefined (SDK cwd falls back to
|
|
22
|
+
* MERIDIAN_WORKDIR/process.cwd()) and `clientEnvironmentMayDifferFromProxy`
|
|
23
|
+
* keeps the environment distinction explicit.
|
|
24
|
+
*/
|
|
25
|
+
import type { AgentAdapter } from "../adapter";
|
|
26
|
+
/**
|
|
27
|
+
* Normalize the native session id: trim surrounding whitespace; a nonempty
|
|
28
|
+
* trimmed string is returned unchanged. Missing/empty/whitespace-only →
|
|
29
|
+
* undefined. No charset restriction, fixed fallback, profile prefix, or
|
|
30
|
+
* affinity-header fallback — the client's value IS the identity.
|
|
31
|
+
*/
|
|
32
|
+
export declare function normalizePolytokenSessionId(value: string | undefined): string | undefined;
|
|
33
|
+
export declare const polytokenAdapter: AgentAdapter;
|
|
34
|
+
import { polytokenTransforms } from "../transforms/polytoken";
|
|
35
|
+
export { polytokenTransforms };
|
|
36
|
+
//# sourceMappingURL=polytoken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polytoken.d.ts","sourceRoot":"","sources":["../../../src/proxy/adapters/polytoken.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAK9C;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAED,eAAO,MAAM,gBAAgB,EAAE,YA8F9B,CAAA;AAED,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAc1D;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/proxy/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAc1D;AAoKD,2EAA2E;AAC3E,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,OAAO,CAEpF;AAqFD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,eAAe,CA0L7E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,aAAa,GAAG,iBAAiB,CAAA;AAEtE;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAahG;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAI3E;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGxD;AAuBD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,IAAI,MAAM,CAEhG;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,GAAG,eAAe,GAAG,kBAAkB,GAAG,SAAS,CAAA;IACnG,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;wCAEoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAuBD;;;;;;GAMG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;IAChC,WAAW,EAAE,OAAO,CAAA;IACpB,gBAAgB,EAAE,MAAM,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;CACrB,GAAG,OAAO,CAYV;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAiEpE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,cAAc,EACjB,GAAG,EAAE;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GACA,MAAM,CAYR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdkFeatures.d.ts","sourceRoot":"","sources":["../../src/proxy/sdkFeatures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,gBAAgB,EAAE,OAAO,CAAA;IACzB,kFAAkF;IAClF,kBAAkB,EAAE,OAAO,CAAA;IAC3B,4DAA4D;IAC5D,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAA;IACf,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;IAC7C,4CAA4C;IAC5C,mBAAmB,EAAE,OAAO,CAAA;IAC5B,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAA;IAC1B;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,CAAA;IAC3B,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"sdkFeatures.d.ts","sourceRoot":"","sources":["../../src/proxy/sdkFeatures.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,gBAAgB,EAAE,OAAO,CAAA;IACzB,kFAAkF;IAClF,kBAAkB,EAAE,OAAO,CAAA;IAC3B,4DAA4D;IAC5D,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAA;IACf,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAA;IACjB,0DAA0D;IAC1D,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;IAC7C,4CAA4C;IAC5C,mBAAmB,EAAE,OAAO,CAAA;IAC5B,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAA;IAC1B;;;;;;OAMG;IACH,kBAAkB,EAAE,OAAO,CAAA;IAC3B,iDAAiD;IACjD,YAAY,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAA;IACrB,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,uEAAuE;IACvE,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;AA8IpE;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAU1E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAAC,UAAU,CAAC,GAAG,SAAS,CAKhG;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAOtE;AAKD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAgC5E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAInG;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAI9D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AA4EnG,OAAO,EACL,kBAAkB,EAElB,WAAW,EACX,oBAAoB,EAMpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAI1B,OAAO,EAKL,iBAAiB,EACjB,mBAAmB,EAKpB,MAAM,iBAAiB,CAAA;AA0CxB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAsY7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/proxy/server.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACtE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,CAAA;AAGvD,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,WAAW,GACZ,MAAM,aAAa,CAAA;AAKpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AA4EnG,OAAO,EACL,kBAAkB,EAElB,WAAW,EACX,oBAAoB,EAMpB,KAAK,aAAa,EAGnB,MAAM,mBAAmB,CAAA;AAI1B,OAAO,EAKL,iBAAiB,EACjB,mBAAmB,EAKpB,MAAM,iBAAiB,CAAA;AA0CxB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,oBAAoB,EAAE,CAAA;AAChE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAA;AACjD,YAAY,EAAE,aAAa,EAAE,CAAA;AAsY7B,wBAAgB,iBAAiB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAs8OhF;AAWD,wBAAgB,gCAAgC,IAAI,IAAI,CAavD;AAED,wBAAsB,gBAAgB,CAAC,MAAM,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA8NhG"}
|
|
@@ -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;
|
|
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
package/package.json
CHANGED
package/plugin/meridian-v2.ts
CHANGED
|
@@ -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")
|