opencode-codex-memory 0.6.5 → 0.7.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 (41) hide show
  1. package/README.md +26 -28
  2. package/dist/opencode.json +1 -1
  3. package/dist/src/citation.d.ts +9 -0
  4. package/dist/src/citation.js +68 -11
  5. package/dist/src/db.js +10 -0
  6. package/dist/src/host-client.d.ts +1 -0
  7. package/dist/src/host-client.js +1 -0
  8. package/dist/src/index.d.ts +12 -2
  9. package/dist/src/index.js +32 -5
  10. package/dist/src/llm.d.ts +6 -0
  11. package/dist/src/llm.js +21 -10
  12. package/dist/src/phase2.d.ts +2 -0
  13. package/dist/src/phase2.js +1 -1
  14. package/dist/src/rollout-input.d.ts +6 -0
  15. package/dist/src/rollout-input.js +111 -0
  16. package/dist/src/store.d.ts +17 -1
  17. package/dist/src/store.js +90 -4
  18. package/dist/src/v2/agents.d.ts +53 -0
  19. package/dist/src/v2/agents.js +204 -0
  20. package/dist/src/v2/citation-overlay.d.ts +7 -0
  21. package/dist/src/v2/citation-overlay.js +52 -0
  22. package/dist/src/v2/index.d.ts +7 -0
  23. package/dist/src/v2/index.js +10 -0
  24. package/dist/src/v2/injection.d.ts +14 -0
  25. package/dist/src/v2/injection.js +19 -0
  26. package/dist/src/v2/plugin.d.ts +7 -0
  27. package/dist/src/v2/plugin.js +482 -0
  28. package/dist/src/v2/service.d.ts +74 -0
  29. package/dist/src/v2/service.js +173 -0
  30. package/dist/src/v2/shim.d.ts +47 -0
  31. package/dist/src/v2/shim.js +581 -0
  32. package/dist/src/v2/status-rpc.d.ts +197 -0
  33. package/dist/src/v2/status-rpc.js +159 -0
  34. package/dist/src/v2/status.d.ts +3 -0
  35. package/dist/src/v2/status.js +83 -0
  36. package/dist/src/v2/tools.d.ts +33 -0
  37. package/dist/src/v2/tools.js +57 -0
  38. package/dist/src/v2/tui.d.ts +3 -0
  39. package/dist/src/v2/tui.js +750 -0
  40. package/opencode.json +1 -1
  41. package/package.json +38 -2
@@ -0,0 +1,173 @@
1
+ /**
2
+ * The supported OpenCode 2 connection boundary.
3
+ *
4
+ * Server plugins do not receive the complete public client. The registered
5
+ * local service does: the XDG `service.json` file is the discovery contract
6
+ * (read-only — never Service.ensure()). Auth headers are preserved, and
7
+ * GET /api/status pid must match this process. 2.0.5 dropped JSON
8
+ * /api/health (404 HTML/empty); 2.0.3 Service.discover() still probes that
9
+ * path and throws on a non-object body, so this module never calls it.
10
+ */
11
+ import { readFile } from "node:fs/promises";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
14
+ let testDependencies = null;
15
+ let clientPromise = null;
16
+ const SERVICE_REQUEST_TIMEOUT_MS = 1_000;
17
+ /** Test seam: replace discovery without changing the production connection path. */
18
+ export function setV2ServiceDependenciesForTest(dependencies) {
19
+ testDependencies = dependencies;
20
+ clientPromise = null;
21
+ }
22
+ /** Forget a cached endpoint after a service restart or failed request. */
23
+ export function invalidateOwnService() {
24
+ clientPromise = null;
25
+ }
26
+ export function parseReadyStatus(body) {
27
+ const record = unwrapStatusRecord(body);
28
+ if (!record)
29
+ return null;
30
+ const pid = record.pid;
31
+ const version = record.version;
32
+ if (typeof pid !== "number" || !Number.isFinite(pid) || typeof version !== "string" || version.length === 0) {
33
+ return null;
34
+ }
35
+ if ("healthy" in record && record.healthy !== true)
36
+ return null;
37
+ return { pid, version };
38
+ }
39
+ function unwrapStatusRecord(body) {
40
+ if (!body || typeof body !== "object")
41
+ return null;
42
+ const record = body;
43
+ if (record.data && typeof record.data === "object" && !Array.isArray(record.data)) {
44
+ return record.data;
45
+ }
46
+ return record;
47
+ }
48
+ async function withServiceTimeout(request, timeoutMs, controller) {
49
+ request.catch(() => { });
50
+ let timer;
51
+ try {
52
+ return await Promise.race([
53
+ request,
54
+ new Promise((_, reject) => {
55
+ timer = setTimeout(() => {
56
+ controller?.abort();
57
+ reject(new Error(`OpenCode service request timed out after ${timeoutMs}ms`));
58
+ }, timeoutMs);
59
+ timer.unref?.();
60
+ }),
61
+ ]);
62
+ }
63
+ finally {
64
+ clearTimeout(timer);
65
+ }
66
+ }
67
+ function registrationPath() {
68
+ return join(process.env["XDG_STATE_HOME"] ?? join(homedir(), ".local", "state"), "opencode", "service.json");
69
+ }
70
+ export async function readRegisteredEndpoint(file = registrationPath()) {
71
+ const text = await readFile(file, "utf8").catch(() => undefined);
72
+ if (text === undefined)
73
+ return undefined;
74
+ let info;
75
+ try {
76
+ info = JSON.parse(text);
77
+ }
78
+ catch {
79
+ return undefined;
80
+ }
81
+ if (!info || typeof info !== "object")
82
+ return undefined;
83
+ const record = info;
84
+ if (typeof record.url !== "string" || record.url.length === 0)
85
+ return undefined;
86
+ const password = record.password;
87
+ return {
88
+ url: record.url,
89
+ ...(typeof password === "string" && password.length > 0
90
+ ? { auth: { type: "basic", username: "opencode", password } }
91
+ : {}),
92
+ };
93
+ }
94
+ async function fetchJson(url, headers, signal) {
95
+ const response = await fetch(url, { headers, signal });
96
+ const text = await response.text();
97
+ if (!text)
98
+ return undefined;
99
+ try {
100
+ return JSON.parse(text);
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
106
+ export async function fetchServiceStatus(endpoint, headers, signal) {
107
+ const statusBody = await fetchJson(new URL("/api/status", endpoint.url), headers, signal);
108
+ const fromStatus = parseReadyStatus(statusBody);
109
+ if (fromStatus)
110
+ return fromStatus;
111
+ const healthBody = await fetchJson(new URL("/api/health", endpoint.url), headers, signal);
112
+ const fromHealth = parseReadyStatus(healthBody);
113
+ if (fromHealth)
114
+ return fromHealth;
115
+ throw new Error("registered OpenCode service is not healthy");
116
+ }
117
+ async function productionDependencies() {
118
+ const { Service } = await import("@opencode/client/service");
119
+ const { OpenCode } = await import("@opencode/client");
120
+ return {
121
+ service: {
122
+ discover: () => readRegisteredEndpoint(),
123
+ headers: (endpoint) => Service.headers(endpoint),
124
+ },
125
+ make: (options) => OpenCode.make(options),
126
+ probe: (endpoint, signal) => fetchServiceStatus(endpoint, Service.headers(endpoint), signal),
127
+ };
128
+ }
129
+ async function probeEndpoint(deps, endpoint, client, signal) {
130
+ if (deps.probe)
131
+ return deps.probe(endpoint, signal);
132
+ if (client.health?.get) {
133
+ const raw = await client.health.get({ signal });
134
+ const parsed = parseReadyStatus(raw);
135
+ if (parsed)
136
+ return parsed;
137
+ throw new Error("registered OpenCode service is not healthy");
138
+ }
139
+ return fetchServiceStatus(endpoint, deps.service.headers(endpoint), signal);
140
+ }
141
+ /**
142
+ * Find a ready, registered OpenCode service without starting or replacing one.
143
+ * A missing service is a normal unavailable result; a PID mismatch is a
144
+ * safety failure because it would make global memory operate on another host.
145
+ */
146
+ export async function discoverOwnService(dependencies, timeoutMs = SERVICE_REQUEST_TIMEOUT_MS) {
147
+ const deps = dependencies ?? testDependencies ?? (await productionDependencies());
148
+ const endpoint = await withServiceTimeout(deps.service.discover(), timeoutMs);
149
+ if (!endpoint)
150
+ return null;
151
+ const client = deps.make({ baseUrl: endpoint.url, headers: deps.service.headers(endpoint) });
152
+ const controller = new AbortController();
153
+ const health = await withServiceTimeout(probeEndpoint(deps, endpoint, client, controller.signal), timeoutMs, controller);
154
+ if (health.pid !== process.pid) {
155
+ throw new Error(`registered OpenCode service PID ${String(health.pid)} does not match plugin host PID ${process.pid}`);
156
+ }
157
+ return { endpoint, client, health };
158
+ }
159
+ /** Resolve the registered client once per live service; never start a service. */
160
+ export async function ownServiceClient() {
161
+ if (!clientPromise) {
162
+ const request = discoverOwnService().then((found) => found?.client ?? null).catch((err) => {
163
+ console.warn("[opencode-codex-memory] registered OpenCode service unavailable:", err);
164
+ return null;
165
+ });
166
+ clientPromise = request;
167
+ const result = await request;
168
+ if (!result)
169
+ clientPromise = null;
170
+ return result;
171
+ }
172
+ return clientPromise;
173
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * opencode2 host-compatibility shim.
3
+ *
4
+ * V2 intentionally reuses the V1 pipeline (phase1/phase2/capture/llm/store/…
5
+ * run byte-identical) by presenting a V1-shaped client façade backed by the
6
+ * V2 plugin context. Only genuinely missing V2 surfaces are adapted:
7
+ *
8
+ * - session list/discovery → the authenticated public service client
9
+ * discovered through the registered local service.
10
+ * - session.prompt agent/system/model/format/variant → V2 create-time
11
+ * agent/model (via switchAgent/switchModel) + generate.text for the
12
+ * json_schema extraction path (V2 prompts carry text only).
13
+ * - session.messages → public message.list with V1-shaped row adaptation.
14
+ * - session.delete → public session.remove, with a released-set fallback so
15
+ * the V1 shutdown/liveness logic keeps working across hosts.
16
+ * - config.get → public service config documents adapted for the V1 resolver;
17
+ * callers fall back to session defaults if the service is unavailable.
18
+ * - provider.list → catalog.model.list adapted to the V1 catalog shape for
19
+ * reasoning-variant mapping.
20
+ * - mcp.status → mcp.list adapted to the V1 status map.
21
+ *
22
+ * Nothing in this file changes V1 behavior: V1 hosts never load it.
23
+ */
24
+ import type { Plugin } from "@opencode/plugin";
25
+ export type V2Context = Plugin.Context;
26
+ export declare function setV2Context(ctx: V2Context | null): void;
27
+ export declare function isReleasedSubSession(id: string): boolean;
28
+ /** Stable synthetic id for extraction helpers (see create below). */
29
+ export declare const EXTRACT_STUB_SESSION_ID = "codex-memory-extract-stub";
30
+ /** Test seam. */
31
+ export declare function resetV2ShimStateForTest(): void;
32
+ /**
33
+ * V2 public transcript messages → V1 session.messages rows
34
+ * ({info:{role}, parts:[...]}) consumed by capture.ts extractText.
35
+ */
36
+ export declare function adaptV2Messages(msgs: unknown): Array<{
37
+ info?: {
38
+ role?: string;
39
+ };
40
+ parts?: unknown[];
41
+ }>;
42
+ /** V2 catalog.model.list → V1 provider-list shape for catalogVariantKeys. */
43
+ export declare function adaptProviderCatalog(v2: unknown): unknown;
44
+ /** V2 mcp.list → V1 mcp.status map shape. */
45
+ export declare function adaptMcpStatus(v2: unknown): unknown;
46
+ /** Build the V1-shaped client. Passed to setPluginInput() by V2 setup(). */
47
+ export declare function buildV1ClientShim(): unknown;