@cortexkit/opencode-magic-context 0.29.0 → 0.29.1
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/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts +0 -1
- package/dist/features/magic-context/dreamer/retrospective-raw-provider.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts +10 -0
- package/dist/features/magic-context/dreamer/storage-task-schedule.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-executor.d.ts +0 -3
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +0 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts +0 -3
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +2 -21
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-schema-helpers.d.ts +30 -0
- package/dist/features/magic-context/storage-schema-helpers.d.ts.map +1 -0
- package/dist/hooks/magic-context/channel2-delivery.d.ts +9 -5
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts +1 -4
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +0 -2
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2970 -4960
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/shared/exit-abort-registry.d.ts +25 -0
- package/dist/shared/exit-abort-registry.d.ts.map +1 -0
- package/dist/shared/harness-provider-map.d.ts +30 -0
- package/dist/shared/harness-provider-map.d.ts.map +1 -0
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/exit-abort-registry.test.ts +50 -0
- package/src/shared/exit-abort-registry.ts +46 -0
- package/src/shared/harness-provider-map.test.ts +63 -0
- package/src/shared/harness-provider-map.ts +56 -0
- package/dist/hooks/is-anthropic-provider.d.ts +0 -2
- package/dist/hooks/is-anthropic-provider.d.ts.map +0 -1
- package/dist/shared/live-server-client.d.ts +0 -50
- package/dist/shared/live-server-client.d.ts.map +0 -1
- package/src/shared/live-server-client.ts +0 -152
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAO7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;
|
|
1
|
+
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAO7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BjF;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;qBA2B28K,CAAC;;;;;;;;;;;;qBAAjhE,CAAC;mBAAyB,CAAC;iBAAuB,CAAC;iBAAuB,CAAC;0BAAc,CAAC;uBAAiB,CAAC;;;;;;0BAA20mC,CAAC;;;;;;EADn3tC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAIzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAc3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAqBlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAe,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0FtF,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,EAK9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,eAAe,CAuVjB;AAED,wBAAgB,iBAAiB,CAC7B,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,GAC/B,YAAY,CAyKd;AA4BD;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,GACF,IAAI,CAyPN"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-global registry of AbortControllers to abort on process exit, backed
|
|
3
|
+
* by a SINGLE `process.once("exit")` listener no matter how many controllers
|
|
4
|
+
* register.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the plugin factory runs once per plugin instance, and
|
|
7
|
+
* OpenCode Desktop loads many instances in one Node process (one per open
|
|
8
|
+
* project). Registering a `process.once("exit")` per instance added one listener
|
|
9
|
+
* each, so past Node's default 10-listener cap it logged a
|
|
10
|
+
* `MaxListenersExceededWarning` ("11 exit listeners added to [process]"). One
|
|
11
|
+
* module-global listener that fans out to every registered controller keeps the
|
|
12
|
+
* count at one.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Abort `controller` when the process exits. The underlying `process.once("exit")`
|
|
16
|
+
* listener is installed on the first call only; subsequent calls just add to the
|
|
17
|
+
* fan-out set.
|
|
18
|
+
*/
|
|
19
|
+
export declare function registerExitAbort(controller: AbortController): void;
|
|
20
|
+
/**
|
|
21
|
+
* Stop tracking `controller` (e.g. when its plugin instance is disposed) so the
|
|
22
|
+
* set doesn't grow without bound as Desktop opens and closes projects.
|
|
23
|
+
*/
|
|
24
|
+
export declare function unregisterExitAbort(controller: AbortController): void;
|
|
25
|
+
//# sourceMappingURL=exit-abort-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-abort-registry.d.ts","sourceRoot":"","sources":["../../src/shared/exit-abort-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAeH;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAKnE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAErE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-id translation between the canonical (OpenCode) form stored in the
|
|
3
|
+
* shared magic-context config and Pi's harness-native provider ids.
|
|
4
|
+
*
|
|
5
|
+
* OpenCode and Pi now share ONE config, but a few auth-plugin providers were
|
|
6
|
+
* named differently on each side. The model id AFTER the slash is identical;
|
|
7
|
+
* only the provider prefix differs:
|
|
8
|
+
*
|
|
9
|
+
* canonical (OpenCode) Pi
|
|
10
|
+
* -------------------- -------------------
|
|
11
|
+
* openai/<model> openai-codex/<model>
|
|
12
|
+
* google/<model> google-antigravity/<model>
|
|
13
|
+
* anthropic/<model> anthropic/<model> (same; every other provider too)
|
|
14
|
+
*
|
|
15
|
+
* Canonical = OpenCode: the config always stores the OpenCode form. Pi
|
|
16
|
+
* translates at its edges:
|
|
17
|
+
* - read: canonical -> Pi when spawning a configured model (subagent-runner).
|
|
18
|
+
* - write: Pi -> canonical in the Pi setup wizard, so a config written from
|
|
19
|
+
* the Pi side stays readable by OpenCode.
|
|
20
|
+
*
|
|
21
|
+
* OpenCode needs no translation (canonical IS the OpenCode form).
|
|
22
|
+
*/
|
|
23
|
+
/** Pi-native `provider/model` -> canonical (OpenCode). Identity when unmapped.
|
|
24
|
+
* Used by the Pi setup wizard so configs it writes stay OpenCode-readable. */
|
|
25
|
+
export declare function piModelRefToCanonical(ref: string): string;
|
|
26
|
+
/** Canonical (OpenCode) `provider/model` -> Pi-native, for spawning a model on
|
|
27
|
+
* Pi. Idempotent: normalizes any Pi-form prefix back to canonical first, so it
|
|
28
|
+
* is safe on a config that already holds Pi-form ids (hand-edited or pre-fix). */
|
|
29
|
+
export declare function resolveModelRefForPi(ref: string): string;
|
|
30
|
+
//# sourceMappingURL=harness-provider-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-provider-map.d.ts","sourceRoot":"","sources":["../../src/shared/harness-provider-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAuBH;+EAC+E;AAC/E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;mFAEmF;AACnF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAYhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAYhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD;AAuTD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { registerExitAbort, unregisterExitAbort } from "./exit-abort-registry";
|
|
3
|
+
|
|
4
|
+
// Captured before any registration so we can isolate the ONE listener the
|
|
5
|
+
// registry installs process-wide (it intentionally never removes it, mirroring
|
|
6
|
+
// production, so the suite must not strip it either).
|
|
7
|
+
const baseline = process.listenerCount("exit");
|
|
8
|
+
|
|
9
|
+
/** The registry's single 'exit' listener (the first one added past baseline). */
|
|
10
|
+
function registryListener(): () => void {
|
|
11
|
+
return process.listeners("exit").slice(baseline)[0] as () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("exit-abort-registry", () => {
|
|
15
|
+
it("adds exactly ONE process exit listener no matter how many controllers register", () => {
|
|
16
|
+
registerExitAbort(new AbortController());
|
|
17
|
+
registerExitAbort(new AbortController());
|
|
18
|
+
registerExitAbort(new AbortController());
|
|
19
|
+
expect(process.listenerCount("exit") - baseline).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("aborts every registered controller when the exit listener fires", () => {
|
|
23
|
+
const a = new AbortController();
|
|
24
|
+
const b = new AbortController();
|
|
25
|
+
registerExitAbort(a);
|
|
26
|
+
registerExitAbort(b);
|
|
27
|
+
|
|
28
|
+
// Invoke the registry's listener directly (emitting 'exit' would end the
|
|
29
|
+
// test process).
|
|
30
|
+
registryListener()();
|
|
31
|
+
|
|
32
|
+
expect(a.signal.aborted).toBe(true);
|
|
33
|
+
expect(b.signal.aborted).toBe(true);
|
|
34
|
+
// Still exactly one listener after firing.
|
|
35
|
+
expect(process.listenerCount("exit") - baseline).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does not abort a controller that was unregistered before exit", () => {
|
|
39
|
+
const keep = new AbortController();
|
|
40
|
+
const drop = new AbortController();
|
|
41
|
+
registerExitAbort(keep);
|
|
42
|
+
registerExitAbort(drop);
|
|
43
|
+
unregisterExitAbort(drop);
|
|
44
|
+
|
|
45
|
+
registryListener()();
|
|
46
|
+
|
|
47
|
+
expect(keep.signal.aborted).toBe(true);
|
|
48
|
+
expect(drop.signal.aborted).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-global registry of AbortControllers to abort on process exit, backed
|
|
3
|
+
* by a SINGLE `process.once("exit")` listener no matter how many controllers
|
|
4
|
+
* register.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: the plugin factory runs once per plugin instance, and
|
|
7
|
+
* OpenCode Desktop loads many instances in one Node process (one per open
|
|
8
|
+
* project). Registering a `process.once("exit")` per instance added one listener
|
|
9
|
+
* each, so past Node's default 10-listener cap it logged a
|
|
10
|
+
* `MaxListenersExceededWarning` ("11 exit listeners added to [process]"). One
|
|
11
|
+
* module-global listener that fans out to every registered controller keeps the
|
|
12
|
+
* count at one.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const controllers = new Set<AbortController>();
|
|
16
|
+
let listenerRegistered = false;
|
|
17
|
+
|
|
18
|
+
function abortAll(): void {
|
|
19
|
+
for (const controller of controllers) {
|
|
20
|
+
try {
|
|
21
|
+
controller.abort();
|
|
22
|
+
} catch {
|
|
23
|
+
// best-effort: the process is exiting anyway
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Abort `controller` when the process exits. The underlying `process.once("exit")`
|
|
30
|
+
* listener is installed on the first call only; subsequent calls just add to the
|
|
31
|
+
* fan-out set.
|
|
32
|
+
*/
|
|
33
|
+
export function registerExitAbort(controller: AbortController): void {
|
|
34
|
+
controllers.add(controller);
|
|
35
|
+
if (listenerRegistered) return;
|
|
36
|
+
listenerRegistered = true;
|
|
37
|
+
process.once("exit", abortAll);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Stop tracking `controller` (e.g. when its plugin instance is disposed) so the
|
|
42
|
+
* set doesn't grow without bound as Desktop opens and closes projects.
|
|
43
|
+
*/
|
|
44
|
+
export function unregisterExitAbort(controller: AbortController): void {
|
|
45
|
+
controllers.delete(controller);
|
|
46
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { piModelRefToCanonical, resolveModelRefForPi } from "./harness-provider-map";
|
|
3
|
+
|
|
4
|
+
describe("harness-provider-map", () => {
|
|
5
|
+
describe("resolveModelRefForPi (canonical -> Pi, used when spawning)", () => {
|
|
6
|
+
it("maps the diverging auth-plugin providers, preserving the model id", () => {
|
|
7
|
+
expect(resolveModelRefForPi("openai/gpt-5.5")).toBe("openai-codex/gpt-5.5");
|
|
8
|
+
expect(resolveModelRefForPi("google/antigravity-gemini-3.5-flash")).toBe(
|
|
9
|
+
"google-antigravity/antigravity-gemini-3.5-flash",
|
|
10
|
+
);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("leaves anthropic and every other provider unchanged", () => {
|
|
14
|
+
expect(resolveModelRefForPi("anthropic/claude-opus-4-8")).toBe(
|
|
15
|
+
"anthropic/claude-opus-4-8",
|
|
16
|
+
);
|
|
17
|
+
expect(resolveModelRefForPi("cerebras/gpt-oss-120b")).toBe("cerebras/gpt-oss-120b");
|
|
18
|
+
expect(resolveModelRefForPi("openrouter/openai/gpt-5.5")).toBe(
|
|
19
|
+
"openrouter/openai/gpt-5.5",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("is idempotent: a config already in Pi form still resolves to Pi form", () => {
|
|
24
|
+
expect(resolveModelRefForPi("openai-codex/gpt-5.5")).toBe("openai-codex/gpt-5.5");
|
|
25
|
+
expect(resolveModelRefForPi("google-antigravity/antigravity-gemini-3.1-pro")).toBe(
|
|
26
|
+
"google-antigravity/antigravity-gemini-3.1-pro",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("preserves model ids that themselves contain slashes", () => {
|
|
31
|
+
expect(resolveModelRefForPi("openai/some/nested/id")).toBe(
|
|
32
|
+
"openai-codex/some/nested/id",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("passes through malformed refs (no slash, empty provider) unchanged", () => {
|
|
37
|
+
expect(resolveModelRefForPi("gpt-5.5")).toBe("gpt-5.5");
|
|
38
|
+
expect(resolveModelRefForPi("/gpt-5.5")).toBe("/gpt-5.5");
|
|
39
|
+
expect(resolveModelRefForPi("")).toBe("");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("piModelRefToCanonical (Pi -> canonical, used by Pi setup write)", () => {
|
|
44
|
+
it("normalizes Pi-native provider ids to the OpenCode form", () => {
|
|
45
|
+
expect(piModelRefToCanonical("openai-codex/gpt-5.5")).toBe("openai/gpt-5.5");
|
|
46
|
+
expect(piModelRefToCanonical("google-antigravity/antigravity-gemini-3.5-flash")).toBe(
|
|
47
|
+
"google/antigravity-gemini-3.5-flash",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("leaves already-canonical and unmapped providers unchanged", () => {
|
|
52
|
+
expect(piModelRefToCanonical("anthropic/claude-opus-4-8")).toBe(
|
|
53
|
+
"anthropic/claude-opus-4-8",
|
|
54
|
+
);
|
|
55
|
+
expect(piModelRefToCanonical("openai/gpt-5.5")).toBe("openai/gpt-5.5");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("round-trips with resolveModelRefForPi", () => {
|
|
59
|
+
const piForm = "openai-codex/gpt-5.5";
|
|
60
|
+
expect(resolveModelRefForPi(piModelRefToCanonical(piForm))).toBe(piForm);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-id translation between the canonical (OpenCode) form stored in the
|
|
3
|
+
* shared magic-context config and Pi's harness-native provider ids.
|
|
4
|
+
*
|
|
5
|
+
* OpenCode and Pi now share ONE config, but a few auth-plugin providers were
|
|
6
|
+
* named differently on each side. The model id AFTER the slash is identical;
|
|
7
|
+
* only the provider prefix differs:
|
|
8
|
+
*
|
|
9
|
+
* canonical (OpenCode) Pi
|
|
10
|
+
* -------------------- -------------------
|
|
11
|
+
* openai/<model> openai-codex/<model>
|
|
12
|
+
* google/<model> google-antigravity/<model>
|
|
13
|
+
* anthropic/<model> anthropic/<model> (same; every other provider too)
|
|
14
|
+
*
|
|
15
|
+
* Canonical = OpenCode: the config always stores the OpenCode form. Pi
|
|
16
|
+
* translates at its edges:
|
|
17
|
+
* - read: canonical -> Pi when spawning a configured model (subagent-runner).
|
|
18
|
+
* - write: Pi -> canonical in the Pi setup wizard, so a config written from
|
|
19
|
+
* the Pi side stays readable by OpenCode.
|
|
20
|
+
*
|
|
21
|
+
* OpenCode needs no translation (canonical IS the OpenCode form).
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const CANONICAL_TO_PI_PROVIDER: Record<string, string> = {
|
|
25
|
+
openai: "openai-codex",
|
|
26
|
+
google: "google-antigravity",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const PI_TO_CANONICAL_PROVIDER: Record<string, string> = {
|
|
30
|
+
"openai-codex": "openai",
|
|
31
|
+
"google-antigravity": "google",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Remap only the provider prefix (text before the first "/"), preserving the
|
|
35
|
+
* model id verbatim. No "/", empty provider, or unmapped provider -> unchanged. */
|
|
36
|
+
function remapProviderPrefix(ref: string, map: Record<string, string>): string {
|
|
37
|
+
if (typeof ref !== "string") return ref;
|
|
38
|
+
const slash = ref.indexOf("/");
|
|
39
|
+
if (slash <= 0) return ref;
|
|
40
|
+
const provider = ref.slice(0, slash);
|
|
41
|
+
const mapped = map[provider];
|
|
42
|
+
return mapped ? `${mapped}${ref.slice(slash)}` : ref;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Pi-native `provider/model` -> canonical (OpenCode). Identity when unmapped.
|
|
46
|
+
* Used by the Pi setup wizard so configs it writes stay OpenCode-readable. */
|
|
47
|
+
export function piModelRefToCanonical(ref: string): string {
|
|
48
|
+
return remapProviderPrefix(ref, PI_TO_CANONICAL_PROVIDER);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Canonical (OpenCode) `provider/model` -> Pi-native, for spawning a model on
|
|
52
|
+
* Pi. Idempotent: normalizes any Pi-form prefix back to canonical first, so it
|
|
53
|
+
* is safe on a config that already holds Pi-form ids (hand-edited or pre-fix). */
|
|
54
|
+
export function resolveModelRefForPi(ref: string): string {
|
|
55
|
+
return remapProviderPrefix(piModelRefToCanonical(ref), CANONICAL_TO_PI_PROVIDER);
|
|
56
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"is-anthropic-provider.d.ts","sourceRoot":"","sources":["../../src/hooks/is-anthropic-provider.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAE/D"}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live-server client for the Channel 2 ctx_reduce ceiling nudge (a synthetic
|
|
3
|
-
* user `<system-reminder>` delivered via `promptAsync`).
|
|
4
|
-
*
|
|
5
|
-
* WHY a separate client instead of the plugin-provided `input.client`:
|
|
6
|
-
* OpenCode's plugin `input.client` routes through `Server.Default().app.fetch`,
|
|
7
|
-
* which uses a SEPARATE Effect `memoMap` from the live HTTP listener the UI
|
|
8
|
-
* uses. `SessionRunState` lives per-memoMap, so a plugin-origin `promptAsync`
|
|
9
|
-
* observes an "idle" runner while the live turn is still running, `ensureRunning`
|
|
10
|
-
* fails to coalesce, and OpenCode persists duplicate assistant children
|
|
11
|
-
* (upstream bug anomalyco/opencode#28202). Building a `createOpencodeClient`
|
|
12
|
-
* aimed at `input.serverUrl` via `globalThis.fetch` enters the SAME live
|
|
13
|
-
* listener, so `ensureRunning` sees the real run and coalesces — the synthetic
|
|
14
|
-
* message lands at the tail after the current assistant step.
|
|
15
|
-
*
|
|
16
|
-
* The live listener is only reachable on OpenCode Desktop (Electron+Node) and
|
|
17
|
-
* TUI launched with `--port 0`; plain TUI binds an internal listener that 404s
|
|
18
|
-
* `/session/*`. We probe once at init and cache per `serverUrl`. When
|
|
19
|
-
* unreachable, Channel 2 is DISABLED (Channel 1 + 85% force-materialization
|
|
20
|
-
* remain the backstop) — MC deliberately does NOT fall back to the in-process
|
|
21
|
-
* client because that would knowingly trigger #28202.
|
|
22
|
-
*/
|
|
23
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
24
|
-
export type LiveServerClient = ReturnType<typeof createOpencodeClient>;
|
|
25
|
-
/**
|
|
26
|
-
* Cached `createOpencodeClient` aimed at the live HTTP listener for the given
|
|
27
|
-
* `(serverUrl, directory)`. One client is reused across deliveries.
|
|
28
|
-
*/
|
|
29
|
-
export declare function getLiveServerClient(serverUrl: string, directory: string): LiveServerClient;
|
|
30
|
-
/**
|
|
31
|
-
* Probe whether `serverUrl` serves OpenCode's HTTP API within `timeoutMs`.
|
|
32
|
-
* `true` only when `/session` proves the API is usable: any 2xx, or 401/403
|
|
33
|
-
* (auth-protected listener still exists). `false` for 404 (plain TUI internal
|
|
34
|
-
* listener), 5xx, connection refused, DNS failure, timeout, or malformed URL.
|
|
35
|
-
* Records the result + timestamp in the per-serverUrl cache.
|
|
36
|
-
*/
|
|
37
|
-
export declare function probeServerReachable(serverUrl: string | undefined, timeoutMs?: number): Promise<boolean>;
|
|
38
|
-
/** Record a probe result directly (test helper / explicit override). */
|
|
39
|
-
export declare function setLiveServerWakeAvailable(serverUrl: string | undefined, available: boolean): void;
|
|
40
|
-
/**
|
|
41
|
-
* Should Channel 2 deliver through the live-server client for `serverUrl`?
|
|
42
|
-
* Returns false when never probed or the last probe failed. A stale decision
|
|
43
|
-
* (older than the TTL) returns false so the caller re-probes before delivering.
|
|
44
|
-
*/
|
|
45
|
-
export declare function useLiveServerWake(serverUrl?: string): boolean;
|
|
46
|
-
/** True when a usable (non-stale) probe decision exists, regardless of outcome. */
|
|
47
|
-
export declare function hasFreshProbe(serverUrl?: string): boolean;
|
|
48
|
-
/** Test helper — reset both caches between cases. */
|
|
49
|
-
export declare function __resetLiveServerClientForTests(): void;
|
|
50
|
-
//# sourceMappingURL=live-server-client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-server-client.d.ts","sourceRoot":"","sources":["../../src/shared/live-server-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA0BvE;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAY1F;AAcD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACtC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,SAAO,GACjB,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAED,wEAAwE;AACxE,wBAAgB,0BAA0B,CACtC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,OAAO,GACnB,IAAI,CAMN;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAM7D;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAKzD;AAED,qDAAqD;AACrD,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD"}
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live-server client for the Channel 2 ctx_reduce ceiling nudge (a synthetic
|
|
3
|
-
* user `<system-reminder>` delivered via `promptAsync`).
|
|
4
|
-
*
|
|
5
|
-
* WHY a separate client instead of the plugin-provided `input.client`:
|
|
6
|
-
* OpenCode's plugin `input.client` routes through `Server.Default().app.fetch`,
|
|
7
|
-
* which uses a SEPARATE Effect `memoMap` from the live HTTP listener the UI
|
|
8
|
-
* uses. `SessionRunState` lives per-memoMap, so a plugin-origin `promptAsync`
|
|
9
|
-
* observes an "idle" runner while the live turn is still running, `ensureRunning`
|
|
10
|
-
* fails to coalesce, and OpenCode persists duplicate assistant children
|
|
11
|
-
* (upstream bug anomalyco/opencode#28202). Building a `createOpencodeClient`
|
|
12
|
-
* aimed at `input.serverUrl` via `globalThis.fetch` enters the SAME live
|
|
13
|
-
* listener, so `ensureRunning` sees the real run and coalesces — the synthetic
|
|
14
|
-
* message lands at the tail after the current assistant step.
|
|
15
|
-
*
|
|
16
|
-
* The live listener is only reachable on OpenCode Desktop (Electron+Node) and
|
|
17
|
-
* TUI launched with `--port 0`; plain TUI binds an internal listener that 404s
|
|
18
|
-
* `/session/*`. We probe once at init and cache per `serverUrl`. When
|
|
19
|
-
* unreachable, Channel 2 is DISABLED (Channel 1 + 85% force-materialization
|
|
20
|
-
* remain the backstop) — MC deliberately does NOT fall back to the in-process
|
|
21
|
-
* client because that would knowingly trigger #28202.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { createOpencodeClient } from "@opencode-ai/sdk";
|
|
25
|
-
|
|
26
|
-
export type LiveServerClient = ReturnType<typeof createOpencodeClient>;
|
|
27
|
-
|
|
28
|
-
const clientCache = new Map<string, LiveServerClient>();
|
|
29
|
-
|
|
30
|
-
function cacheKey(serverUrl: string, directory: string): string {
|
|
31
|
-
return `${serverUrl}|${directory}`;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function normalizeServerUrl(serverUrl: string): string {
|
|
35
|
-
try {
|
|
36
|
-
return new URL(serverUrl).toString();
|
|
37
|
-
} catch {
|
|
38
|
-
return serverUrl;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Basic-auth header OpenCode expects when `OPENCODE_SERVER_PASSWORD` is set. */
|
|
43
|
-
function serverAuthHeaders(): Record<string, string> | undefined {
|
|
44
|
-
const password = process.env.OPENCODE_SERVER_PASSWORD;
|
|
45
|
-
if (!password) return undefined;
|
|
46
|
-
const username = process.env.OPENCODE_SERVER_USERNAME ?? "opencode";
|
|
47
|
-
return {
|
|
48
|
-
Authorization: `Basic ${Buffer.from(`${username}:${password}`).toString("base64")}`,
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Cached `createOpencodeClient` aimed at the live HTTP listener for the given
|
|
54
|
-
* `(serverUrl, directory)`. One client is reused across deliveries.
|
|
55
|
-
*/
|
|
56
|
-
export function getLiveServerClient(serverUrl: string, directory: string): LiveServerClient {
|
|
57
|
-
const key = cacheKey(serverUrl, directory);
|
|
58
|
-
const cached = clientCache.get(key);
|
|
59
|
-
if (cached) return cached;
|
|
60
|
-
const client = createOpencodeClient({
|
|
61
|
-
baseUrl: serverUrl,
|
|
62
|
-
directory,
|
|
63
|
-
headers: serverAuthHeaders(),
|
|
64
|
-
fetch: globalThis.fetch,
|
|
65
|
-
});
|
|
66
|
-
clientCache.set(key, client);
|
|
67
|
-
return client;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Per-serverUrl wake decision + probe TTL. One plugin process can host multiple
|
|
71
|
-
// OpenCode windows with different listener URLs, so the decision must be keyed.
|
|
72
|
-
interface ProbeDecision {
|
|
73
|
-
reachable: boolean;
|
|
74
|
-
probedAt: number;
|
|
75
|
-
}
|
|
76
|
-
const wakeDecisionByServerUrl = new Map<string, ProbeDecision>();
|
|
77
|
-
|
|
78
|
-
// Re-probe window: a transient 404/timeout shouldn't permanently disable
|
|
79
|
-
// Channel 2 for the whole session lifetime (per council-r3).
|
|
80
|
-
const PROBE_TTL_MS = 10 * 60_000;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Probe whether `serverUrl` serves OpenCode's HTTP API within `timeoutMs`.
|
|
84
|
-
* `true` only when `/session` proves the API is usable: any 2xx, or 401/403
|
|
85
|
-
* (auth-protected listener still exists). `false` for 404 (plain TUI internal
|
|
86
|
-
* listener), 5xx, connection refused, DNS failure, timeout, or malformed URL.
|
|
87
|
-
* Records the result + timestamp in the per-serverUrl cache.
|
|
88
|
-
*/
|
|
89
|
-
export async function probeServerReachable(
|
|
90
|
-
serverUrl: string | undefined,
|
|
91
|
-
timeoutMs = 1500,
|
|
92
|
-
): Promise<boolean> {
|
|
93
|
-
if (!serverUrl) return false;
|
|
94
|
-
const normalized = normalizeServerUrl(serverUrl);
|
|
95
|
-
const controller = new AbortController();
|
|
96
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
97
|
-
let reachable = false;
|
|
98
|
-
try {
|
|
99
|
-
const probeUrl = new URL("/session", serverUrl).toString();
|
|
100
|
-
const res = await globalThis.fetch(probeUrl, {
|
|
101
|
-
method: "GET",
|
|
102
|
-
headers: serverAuthHeaders(),
|
|
103
|
-
signal: controller.signal,
|
|
104
|
-
});
|
|
105
|
-
reachable = res.ok || res.status === 401 || res.status === 403;
|
|
106
|
-
} catch {
|
|
107
|
-
reachable = false;
|
|
108
|
-
} finally {
|
|
109
|
-
clearTimeout(timer);
|
|
110
|
-
wakeDecisionByServerUrl.set(normalized, { reachable, probedAt: Date.now() });
|
|
111
|
-
}
|
|
112
|
-
return reachable;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/** Record a probe result directly (test helper / explicit override). */
|
|
116
|
-
export function setLiveServerWakeAvailable(
|
|
117
|
-
serverUrl: string | undefined,
|
|
118
|
-
available: boolean,
|
|
119
|
-
): void {
|
|
120
|
-
if (!serverUrl) return;
|
|
121
|
-
wakeDecisionByServerUrl.set(normalizeServerUrl(serverUrl), {
|
|
122
|
-
reachable: available,
|
|
123
|
-
probedAt: Date.now(),
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Should Channel 2 deliver through the live-server client for `serverUrl`?
|
|
129
|
-
* Returns false when never probed or the last probe failed. A stale decision
|
|
130
|
-
* (older than the TTL) returns false so the caller re-probes before delivering.
|
|
131
|
-
*/
|
|
132
|
-
export function useLiveServerWake(serverUrl?: string): boolean {
|
|
133
|
-
if (!serverUrl) return false;
|
|
134
|
-
const decision = wakeDecisionByServerUrl.get(normalizeServerUrl(serverUrl));
|
|
135
|
-
if (!decision) return false;
|
|
136
|
-
if (Date.now() - decision.probedAt > PROBE_TTL_MS) return false;
|
|
137
|
-
return decision.reachable;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/** True when a usable (non-stale) probe decision exists, regardless of outcome. */
|
|
141
|
-
export function hasFreshProbe(serverUrl?: string): boolean {
|
|
142
|
-
if (!serverUrl) return false;
|
|
143
|
-
const decision = wakeDecisionByServerUrl.get(normalizeServerUrl(serverUrl));
|
|
144
|
-
if (!decision) return false;
|
|
145
|
-
return Date.now() - decision.probedAt <= PROBE_TTL_MS;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/** Test helper — reset both caches between cases. */
|
|
149
|
-
export function __resetLiveServerClientForTests(): void {
|
|
150
|
-
clientCache.clear();
|
|
151
|
-
wakeDecisionByServerUrl.clear();
|
|
152
|
-
}
|