om-memory-system 5.11.0-next.234 → 6.1.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.
Files changed (105) hide show
  1. package/README.md +1 -1
  2. package/dist/adapters/opencode/auto-capture-host.d.ts +2 -2
  3. package/dist/adapters/opencode/auto-capture-host.js +5 -15
  4. package/dist/adapters/opencode/auto-capture-summary.js +9 -6
  5. package/dist/adapters/opencode/backfill-models.js +1 -1
  6. package/dist/adapters/opencode/backfill-startup.js +5 -4
  7. package/dist/adapters/opencode/conversation.d.ts +5 -1
  8. package/dist/adapters/opencode/conversation.js +23 -23
  9. package/dist/adapters/opencode/import-command.d.ts +0 -12
  10. package/dist/adapters/opencode/import-command.js +1 -17
  11. package/dist/adapters/opencode/internal-capture-sessions.d.ts +0 -3
  12. package/dist/adapters/opencode/internal-capture-sessions.js +0 -26
  13. package/dist/adapters/opencode/memory-tool.d.ts +3 -4
  14. package/dist/adapters/opencode/memory-tool.js +8 -50
  15. package/dist/adapters/opencode/opencode-host.d.ts +27 -0
  16. package/dist/adapters/opencode/opencode-host.js +30 -0
  17. package/dist/adapters/opencode/opencode-import-models.d.ts +2 -2
  18. package/dist/adapters/opencode/opencode-import-models.js +8 -10
  19. package/dist/adapters/opencode/opencode-provider.d.ts +23 -62
  20. package/dist/adapters/opencode/opencode-provider.js +75 -378
  21. package/dist/adapters/opencode/package-skills.d.ts +1 -6
  22. package/dist/adapters/opencode/package-skills.js +2 -17
  23. package/dist/adapters/opencode/profile-learning.d.ts +1 -2
  24. package/dist/adapters/opencode/profile-learning.js +15 -19
  25. package/dist/adapters/opencode/profile-llm-client.d.ts +3 -2
  26. package/dist/adapters/opencode/profile-llm-client.js +8 -17
  27. package/dist/adapters/opencode/profile-model.d.ts +2 -18
  28. package/dist/adapters/opencode/profile-model.js +18 -69
  29. package/dist/adapters/opencode/prompt-model.d.ts +9 -0
  30. package/dist/adapters/opencode/prompt-model.js +21 -0
  31. package/dist/adapters/opencode/skills-tool.d.ts +3 -3
  32. package/dist/adapters/opencode/skills-tool.js +4 -30
  33. package/dist/adapters/opencode/toast.d.ts +11 -0
  34. package/dist/adapters/opencode/toast.js +11 -0
  35. package/dist/adapters/opencode/user-prompt.d.ts +2 -2
  36. package/dist/adapters/opencode/user-prompt.js +4 -5
  37. package/dist/config-template.d.ts +1 -1
  38. package/dist/config-template.js +5 -6
  39. package/dist/config.d.ts +4 -9
  40. package/dist/config.js +1 -9
  41. package/dist/core/memorisation-operations.d.ts +5 -4
  42. package/dist/core/memorisation-operations.js +22 -12
  43. package/dist/core/memorisation-settings.d.ts +2 -2
  44. package/dist/core/memorisation-settings.js +11 -2
  45. package/dist/core/memorisation.d.ts +6 -0
  46. package/dist/core/memorisation.js +10 -0
  47. package/dist/core/memory-operations.js +1 -1
  48. package/dist/core/retrieval.d.ts +0 -21
  49. package/dist/core/retrieval.js +0 -29
  50. package/dist/importer/handled-lookup.d.ts +17 -0
  51. package/dist/importer/handled-lookup.js +13 -0
  52. package/dist/importer/importer.js +3 -3
  53. package/dist/importer/ledger.d.ts +9 -0
  54. package/dist/importer/ledger.js +21 -0
  55. package/dist/importer/opencode-reader.d.ts +1 -1
  56. package/dist/importer/opencode-reader.js +88 -34
  57. package/dist/importer/opencode-v2-units.d.ts +16 -0
  58. package/dist/importer/opencode-v2-units.js +93 -0
  59. package/dist/importer/settings-models.d.ts +11 -6
  60. package/dist/importer/settings-models.js +15 -11
  61. package/dist/plugin.d.ts +0 -3
  62. package/dist/plugin.js +1 -7
  63. package/dist/services/client.js +8 -5
  64. package/dist/services/global-config-writer.js +5 -3
  65. package/dist/services/linked-search.d.ts +4 -0
  66. package/dist/services/memorisation-routes.d.ts +7 -6
  67. package/dist/services/memorisation-routes.js +18 -27
  68. package/dist/services/memorisation-store.d.ts +9 -7
  69. package/dist/services/memorisation-store.js +11 -7
  70. package/dist/services/reranker.d.ts +6 -0
  71. package/dist/services/reranker.js +12 -0
  72. package/dist/services/retrieval-rerank.d.ts +2 -0
  73. package/dist/services/retrieval-rerank.js +4 -1
  74. package/dist/services/settings-snapshot.js +8 -6
  75. package/dist/services/turso/vector-search.js +4 -2
  76. package/dist/services/web-route-modules.js +1 -1
  77. package/dist/shared/api/schemas.d.ts +1 -1
  78. package/dist/utils/memory-limits.d.ts +0 -1
  79. package/dist/utils/memory-limits.js +0 -1
  80. package/dist/v2/adapter.d.ts +16 -84
  81. package/dist/v2/adapter.js +17 -88
  82. package/dist/v2/memory-bridge.js +1 -1
  83. package/dist/v2/plugin.js +27 -16
  84. package/dist/v2/session-events.d.ts +30 -0
  85. package/dist/v2/session-events.js +130 -0
  86. package/dist/v2/startup.d.ts +14 -0
  87. package/dist/v2/startup.js +210 -0
  88. package/dist/v2/tools.d.ts +106 -0
  89. package/dist/v2/tools.js +89 -0
  90. package/dist/web/assets/index-jbZuE9HV.js +171 -0
  91. package/dist/web/index.html +1 -1
  92. package/package.json +2 -14
  93. package/dist/adapters/opencode/in-process-client.d.ts +0 -2
  94. package/dist/adapters/opencode/in-process-client.js +0 -16
  95. package/dist/adapters/opencode/opencode-diagnostics.d.ts +0 -7
  96. package/dist/adapters/opencode/opencode-diagnostics.js +0 -37
  97. package/dist/adapters/opencode/opencode-host-config.d.ts +0 -13
  98. package/dist/adapters/opencode/opencode-host-config.js +0 -53
  99. package/dist/adapters/opencode/opencode-sdk-client.d.ts +0 -6
  100. package/dist/adapters/opencode/opencode-sdk-client.js +0 -38
  101. package/dist/index.d.ts +0 -28
  102. package/dist/index.js +0 -620
  103. package/dist/v2/legacy-client.d.ts +0 -15
  104. package/dist/v2/legacy-client.js +0 -275
  105. package/dist/web/assets/index-DQgI670D.js +0 -171
package/README.md CHANGED
@@ -23,7 +23,7 @@ for model choices and privacy controls.
23
23
  ## Set up
24
24
 
25
25
  Use Node.js **22.14 or later** for the terminal command and Claude Code hooks.
26
- OMMS supports OpenCode v2, Claude Code and the
26
+ OMMS supports OpenCode v2 (not older OpenCode versions), Claude Code and the
27
27
  [Pi coding agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
28
28
 
29
29
  > Automatic history import is enabled by default and makes model calls.
@@ -1,3 +1,3 @@
1
- import type { PluginInput } from "@opencode-ai/plugin";
2
1
  import type { AutoCaptureHost } from "../../core/host.js";
3
- export declare function createOpenCodeAutoCaptureHost(ctx: PluginInput): AutoCaptureHost;
2
+ import type { OpencodeHost } from "./opencode-host.js";
3
+ export declare function createOpenCodeAutoCaptureHost(host: Pick<OpencodeHost, "session">): AutoCaptureHost;
@@ -1,27 +1,17 @@
1
1
  import { CONFIG } from "../../config.js";
2
2
  import { extractOpenCodeConversation } from "./conversation.js";
3
3
  import { generateOpenCodeAutoCaptureSummary } from "./auto-capture-summary.js";
4
- export function createOpenCodeAutoCaptureHost(ctx) {
4
+ import { showOpencodeNotice } from "./toast.js";
5
+ export function createOpenCodeAutoCaptureHost(host) {
5
6
  const notify = async (notification) => {
6
- if (!ctx.client?.tui)
7
- return;
8
- await ctx.client.tui
9
- .showToast({
10
- body: notification,
11
- })
12
- .then(() => undefined)
13
- .catch(() => undefined);
7
+ showOpencodeNotice(notification);
14
8
  };
15
9
  return {
16
10
  host: "opencode",
17
11
  isCaptureReady: () => CONFIG.autoCaptureProviderStatus.ready,
18
12
  getConversation: async (sessionId, promptMessageId) => {
19
- if (!ctx.client)
20
- throw new Error("Client not available");
21
- const response = await ctx.client.session.messages({ path: { id: sessionId } });
22
- if (!response.data)
23
- return null;
24
- return extractOpenCodeConversation(response.data, promptMessageId);
13
+ const messages = await host.session.context({ sessionID: sessionId });
14
+ return extractOpenCodeConversation(messages, promptMessageId);
25
15
  },
26
16
  summarize: (request) => generateOpenCodeAutoCaptureSummary(request, notify),
27
17
  notify,
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { OPENCODE_EMPTY_REPLY, OPENCODE_NO_JSON_REPLY } from "./opencode-provider.js";
2
3
  import { CONFIG } from "../../config.js";
3
4
  import { resolveOpencodeHostModel } from "../../services/ai/live-model-choice.js";
4
5
  import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
@@ -31,8 +32,10 @@ CAPTURE if: code changed, bug fixed, feature added, decision made`;
31
32
  function hostModelFailureReason(error) {
32
33
  if (error instanceof Error && error.name === "ZodError")
33
34
  return "schema-mismatch";
34
- if (String(error).includes("returned no structured output"))
35
+ if (String(error).includes(OPENCODE_EMPTY_REPLY))
35
36
  return "empty-text";
37
+ if (String(error).includes(OPENCODE_NO_JSON_REPLY))
38
+ return "invalid-json";
36
39
  return "call-error";
37
40
  }
38
41
  export async function generateOpenCodeAutoCaptureSummary(request, notify) {
@@ -46,7 +49,7 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
46
49
  if (CONFIG.memoryModel && CONFIG.opencodeProvider) {
47
50
  log("opencodeProvider takes precedence over memoryModel for auto-capture");
48
51
  }
49
- const { isProviderConnected, getV2Client, generateStructuredOutput, resolveOpencodeModelRef, } = await loadOpencodeProvider();
52
+ const { isProviderConnected, getOpencodeHost, generateStructuredOutput, resolveOpencodeModelRef, } = await loadOpencodeProvider();
50
53
  // "inherit" (explicit, or no model configured at all) follows this prompt's session model.
51
54
  const { providerID, modelID } = resolveOpencodeModelRef({
52
55
  ...hostModel,
@@ -58,9 +61,9 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
58
61
  if (!isProviderConnected(providerID)) {
59
62
  throw new Error(`opencode provider '${providerID}' is not connected. Check your opencode provider configuration.`);
60
63
  }
61
- const v2Client = getV2Client();
62
- if (!v2Client) {
63
- throw new Error("omms: v2 client not initialized; cannot perform structured-output capture");
64
+ const host = getOpencodeHost();
65
+ if (!host) {
66
+ throw new Error("omms: the OpenCode plugin context is not ready; cannot perform structured-output capture");
64
67
  }
65
68
  const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
66
69
  const targetLang = CONFIG.autoCaptureLanguage === "auto" || !CONFIG.autoCaptureLanguage
@@ -77,7 +80,7 @@ export async function generateOpenCodeAutoCaptureSummary(request, notify) {
77
80
  diagnostics.systemPrompt = systemPrompt;
78
81
  diagnostics.userPrompt = aiPrompt;
79
82
  const result = await generateStructuredOutput({
80
- client: v2Client,
83
+ host,
81
84
  providerID,
82
85
  modelID,
83
86
  systemPrompt,
@@ -38,6 +38,6 @@ export async function resolveOpencodeBackfillModels(config = CONFIG, context) {
38
38
  if (!context.connected.includes(ref.providerID)) {
39
39
  throw new Error(`OpenCode backfill provider ${ref.providerID} is not connected`);
40
40
  }
41
- const models = await (context.makeModels ?? createOpencodeImportModels)(ref, context.directory);
41
+ const models = await (context.makeModels ?? createOpencodeImportModels)(ref);
42
42
  return { model: `${ref.providerID}/${ref.modelID}`, models };
43
43
  }
@@ -6,7 +6,8 @@ import { drainOpencodeCaptureRetries } from "../../services/auto-capture.js";
6
6
  import { registerCaptureRetryDrain } from "../../services/capture-retry-drain.js";
7
7
  import { resolveOpencodeBackfillModels } from "./backfill-models.js";
8
8
  import { createOpencodeImportModels } from "./opencode-import-models.js";
9
- import { getV2Client, isProviderConnected } from "./opencode-provider.js";
9
+ import { listOpencodeModels } from "./opencode-host.js";
10
+ import { getOpencodeHost, isProviderConnected } from "./opencode-provider.js";
10
11
  /** Start after connected providers load, without delaying plugin initialisation. */
11
12
  export function startOpencodeBackfill(input) {
12
13
  return scheduleAutoBackfill({
@@ -24,10 +25,10 @@ export function registerOpencodeBackfillModels(input) {
24
25
  export function registerOpencodeImportModels() {
25
26
  registerOpencodeHostModels({
26
27
  isProviderConnected: (providerID) => isProviderConnected(providerID),
27
- createImportModels: (ref, directory) => createOpencodeImportModels(ref, directory),
28
+ createImportModels: (ref) => createOpencodeImportModels(ref),
28
29
  async listSettingsModels() {
29
- const client = getV2Client();
30
- return client ? listOpencodeClientModels(client) : null;
30
+ const host = getOpencodeHost();
31
+ return host ? listOpencodeClientModels(() => listOpencodeModels(host)) : null;
31
32
  },
32
33
  });
33
34
  }
@@ -1,2 +1,6 @@
1
1
  import type { CaptureConversation } from "../../core/host.js";
2
- export declare function extractOpenCodeConversation(messages: any[], promptMessageId: string): CaptureConversation | null;
2
+ /**
3
+ * The reply to one prompt from OpenCode V2 session messages (`ctx.session.context`):
4
+ * the assistant messages after the prompt, up to the next user, synthetic or system message.
5
+ */
6
+ export declare function extractOpenCodeConversation(messages: readonly any[], promptMessageId: string): CaptureConversation | null;
@@ -12,36 +12,36 @@ function formatToolInput(inputValue) {
12
12
  }
13
13
  return "";
14
14
  }
15
+ /** Message types that start a new turn: the user's prompt and messages sent for the user. */
16
+ const TURN_START_TYPES = new Set(["user", "synthetic", "system"]);
17
+ /**
18
+ * The reply to one prompt from OpenCode V2 session messages (`ctx.session.context`):
19
+ * the assistant messages after the prompt, up to the next user, synthetic or system message.
20
+ */
15
21
  export function extractOpenCodeConversation(messages, promptMessageId) {
16
- const promptIndex = messages.findIndex((message) => message.info?.id === promptMessageId);
22
+ const promptIndex = messages.findIndex((message) => message?.id === promptMessageId);
17
23
  if (promptIndex === -1)
18
24
  return null;
19
- const responseMessages = [];
20
- for (const message of messages.slice(promptIndex + 1)) {
21
- if (message.info?.role === "user")
22
- break;
23
- responseMessages.push(message);
24
- }
25
25
  const textResponses = [];
26
26
  const toolCalls = [];
27
27
  const sourceEntryIds = [];
28
- for (const message of responseMessages) {
29
- if (message.info?.role !== "assistant")
30
- continue;
31
- if (typeof message.info?.id === "string")
32
- sourceEntryIds.push(message.info.id);
33
- if (!Array.isArray(message.parts))
28
+ for (const message of messages.slice(promptIndex + 1)) {
29
+ if (TURN_START_TYPES.has(message?.type))
30
+ break;
31
+ if (message?.type !== "assistant")
34
32
  continue;
35
- const textParts = message.parts.filter((part) => part.type === "text" && typeof part.text === "string" && part.text.trim());
36
- if (textParts.length > 0) {
37
- textResponses.push(textParts
38
- .map((part) => part.text)
39
- .join("\n")
40
- .trim());
41
- }
42
- for (const tool of message.parts.filter((part) => part.type === "tool")) {
43
- // V1 parts name the tool `tool`; the V2 plugin API names it `name`.
44
- const name = tool.tool || tool.name || "unknown";
33
+ if (typeof message.id === "string")
34
+ sourceEntryIds.push(message.id);
35
+ const content = Array.isArray(message.content) ? message.content : [];
36
+ const text = content
37
+ .filter((part) => part?.type === "text" && typeof part.text === "string" && part.text.trim())
38
+ .map((part) => part.text)
39
+ .join("\n")
40
+ .trim();
41
+ if (text)
42
+ textResponses.push(text);
43
+ for (const tool of content.filter((part) => part?.type === "tool")) {
44
+ const name = tool.name || "unknown";
45
45
  const input = boundToolInput(formatToolInput(tool.state?.input));
46
46
  const path = readToolPath(name, tool.state?.input);
47
47
  toolCalls.push({
@@ -24,15 +24,3 @@ export interface OpencodeImportCommandInput {
24
24
  }
25
25
  /** Parse, run, and describe one import; always resolves to text for the session. */
26
26
  export declare function runOpencodeImportCommand(input: OpencodeImportCommandInput): Promise<string>;
27
- /** The model of the session's latest user message, from the V1 message list. */
28
- export declare function latestUserMessageModel(client: {
29
- session: {
30
- messages: (input: {
31
- path: {
32
- id: string;
33
- };
34
- }) => Promise<{
35
- data?: any;
36
- }>;
37
- };
38
- }, sessionID: string): Promise<OpencodeModelRef | null>;
@@ -55,7 +55,7 @@ export async function runOpencodeImportCommand(input) {
55
55
  return `${name}: OpenCode provider "${ref.providerID}" is not connected`;
56
56
  }
57
57
  }
58
- const models = ref && !parsed.dryRun ? await createOpencodeImportModels(ref, input.directory) : {};
58
+ const models = ref && !parsed.dryRun ? await createOpencodeImportModels(ref) : {};
59
59
  if (!parsed.dryRun) {
60
60
  try {
61
61
  await memoryClient.warmup();
@@ -89,19 +89,3 @@ export async function runOpencodeImportCommand(input) {
89
89
  importCommandRunning = false;
90
90
  }
91
91
  }
92
- /** The model of the session's latest user message, from the V1 message list. */
93
- export async function latestUserMessageModel(client, sessionID) {
94
- try {
95
- const messages = (await client.session.messages({ path: { id: sessionID } })).data ?? [];
96
- for (let i = messages.length - 1; i >= 0; i--) {
97
- const model = messages[i]?.info?.role === "user" ? messages[i].info.model : undefined;
98
- if (model?.providerID && model?.modelID) {
99
- return { providerID: model.providerID, modelID: model.modelID };
100
- }
101
- }
102
- }
103
- catch (error) {
104
- log("OpenCode import: could not read the session model", { error: String(error) });
105
- }
106
- return null;
107
- }
@@ -1,4 +1 @@
1
1
  export { INTERNAL_CAPTURE_SESSION_TITLE, INTERNAL_CAPTURE_SESSION_TITLES, isInternalCaptureSessionTitle, } from "../../importer/opencode-internal-sessions.js";
2
- export declare function trackInternalCaptureSession(sessionID: string): void;
3
- export declare function untrackInternalCaptureSession(sessionID: string): void;
4
- export declare function isTrackedInternalCaptureSession(sessionID: string): boolean;
@@ -1,27 +1 @@
1
1
  export { INTERNAL_CAPTURE_SESSION_TITLE, INTERNAL_CAPTURE_SESSION_TITLES, isInternalCaptureSessionTitle, } from "../../importer/opencode-internal-sessions.js";
2
- /** Grace period so session.idle can still match after best-effort delete. */
3
- const UNTRACK_GRACE_MS = 60_000;
4
- const trackedSessionIDs = new Set();
5
- const untrackTimers = new Map();
6
- export function trackInternalCaptureSession(sessionID) {
7
- const pending = untrackTimers.get(sessionID);
8
- if (pending) {
9
- clearTimeout(pending);
10
- untrackTimers.delete(sessionID);
11
- }
12
- trackedSessionIDs.add(sessionID);
13
- }
14
- export function untrackInternalCaptureSession(sessionID) {
15
- const pending = untrackTimers.get(sessionID);
16
- if (pending) {
17
- clearTimeout(pending);
18
- }
19
- const timer = setTimeout(() => {
20
- trackedSessionIDs.delete(sessionID);
21
- untrackTimers.delete(sessionID);
22
- }, UNTRACK_GRACE_MS);
23
- untrackTimers.set(sessionID, timer);
24
- }
25
- export function isTrackedInternalCaptureSession(sessionID) {
26
- return trackedSessionIDs.has(sessionID);
27
- }
@@ -1,4 +1,3 @@
1
- import { type ToolDefinition } from "@opencode-ai/plugin";
2
- import { type MemoryOperationContext } from "../../core/memory-operations.js";
3
- /** The OpenCode V1 `memory` tool. V2 reaches it through the V1 plugin's `tool.memory`. */
4
- export declare function opencodeMemoryTool(description: string, base: Pick<MemoryOperationContext, "directory" | "tags">): ToolDefinition;
1
+ import { type MemoryOperationArgs, type MemoryOperationContext } from "../../core/memory-operations.js";
2
+ /** Run the OpenCode `memory` tool. The V2 plugin registers it in `src/v2/tools.ts`. */
3
+ export declare function runOpencodeMemoryTool(args: MemoryOperationArgs, base: Pick<MemoryOperationContext, "directory" | "tags">, sessionID: string | undefined): Promise<string>;
@@ -1,53 +1,11 @@
1
- import { tool } from "@opencode-ai/plugin";
2
1
  import { executeMemoryOperation, } from "../../core/memory-operations.js";
3
- /** The OpenCode V1 `memory` tool. V2 reaches it through the V1 plugin's `tool.memory`. */
4
- export function opencodeMemoryTool(description, base) {
5
- return tool({
6
- description,
7
- args: {
8
- mode: tool.schema
9
- .enum([
10
- "add",
11
- "memorise",
12
- "memorize",
13
- "recall",
14
- "search",
15
- "profile",
16
- "list",
17
- "forget",
18
- "help",
19
- "migrate",
20
- "list-shards",
21
- "export",
22
- "import",
23
- ])
24
- .optional(),
25
- content: tool.schema.string().optional(),
26
- query: tool.schema.string().optional(),
27
- tags: tool.schema.string().optional(),
28
- type: tool.schema.string().optional(),
29
- topic: tool.schema.string().optional(),
30
- done: tool.schema.string().optional(),
31
- doing: tool.schema.string().optional(),
32
- next: tool.schema.string().optional(),
33
- memoryId: tool.schema.string().optional(),
34
- limit: tool.schema.number().optional(),
35
- scope: tool.schema.enum(["project", "all-projects"]).optional(),
36
- fromPath: tool.schema.string().optional(),
37
- fromHash: tool.schema.string().optional(),
38
- outputPath: tool.schema.string().optional(),
39
- inputPath: tool.schema.string().optional(),
40
- dryRun: tool.schema.boolean().optional(),
41
- allowLinkedSource: tool.schema.boolean().optional(),
42
- },
43
- async execute(args, context) {
44
- // The session ID lets `memorise` replace its own snapshot.
45
- const result = await executeMemoryOperation(args, {
46
- ...base,
47
- host: "opencode",
48
- hostSessionId: context?.sessionID,
49
- });
50
- return JSON.stringify(result);
51
- },
2
+ /** Run the OpenCode `memory` tool. The V2 plugin registers it in `src/v2/tools.ts`. */
3
+ export async function runOpencodeMemoryTool(args, base, sessionID) {
4
+ // The session ID lets `memorise` replace its own snapshot.
5
+ const result = await executeMemoryOperation(args, {
6
+ ...base,
7
+ host: "opencode",
8
+ hostSessionId: sessionID,
52
9
  });
10
+ return JSON.stringify(result);
53
11
  }
@@ -0,0 +1,27 @@
1
+ import type { Context } from "@opencode/plugin/promise/plugin";
2
+ /** The parts of the OpenCode V2 plugin context that OMMS reads outside `setup`. */
3
+ export type OpencodeHost = Pick<Context, "session" | "generate" | "model">;
4
+ /** One OpenCode model, as the Settings page and backfill list it. */
5
+ export type OpencodeModelInfo = {
6
+ readonly providerID: string;
7
+ readonly id: string;
8
+ readonly name: string;
9
+ };
10
+ /** Set once in the V2 plugin's `setup`; holds no store handle, so a space switch keeps it. */
11
+ export declare function setOpencodeHost(host: OpencodeHost | undefined): void;
12
+ export declare function getOpencodeHost(): OpencodeHost | undefined;
13
+ /**
14
+ * Remember the model of the latest model request this plugin saw. `inherit` falls back to it
15
+ * when a call has no prompt model, because headless OpenCode writes no `model.json`.
16
+ * Pass `undefined` to forget it.
17
+ */
18
+ export declare function rememberOpencodeSessionModel(model: {
19
+ readonly providerID: string;
20
+ readonly id: string;
21
+ } | undefined): void;
22
+ export declare function latestOpencodeSessionModel(): {
23
+ providerID: string;
24
+ modelID: string;
25
+ } | undefined;
26
+ /** The models OpenCode lists, each with a display name. */
27
+ export declare function listOpencodeModels(host: Pick<OpencodeHost, "model">): Promise<OpencodeModelInfo[]>;
@@ -0,0 +1,30 @@
1
+ let current;
2
+ /** Set once in the V2 plugin's `setup`; holds no store handle, so a space switch keeps it. */
3
+ export function setOpencodeHost(host) {
4
+ current = host;
5
+ }
6
+ export function getOpencodeHost() {
7
+ return current;
8
+ }
9
+ let sessionModel;
10
+ /**
11
+ * Remember the model of the latest model request this plugin saw. `inherit` falls back to it
12
+ * when a call has no prompt model, because headless OpenCode writes no `model.json`.
13
+ * Pass `undefined` to forget it.
14
+ */
15
+ export function rememberOpencodeSessionModel(model) {
16
+ sessionModel =
17
+ model?.providerID && model.id ? { providerID: model.providerID, modelID: model.id } : undefined;
18
+ }
19
+ export function latestOpencodeSessionModel() {
20
+ return sessionModel;
21
+ }
22
+ /** The models OpenCode lists, each with a display name. */
23
+ export async function listOpencodeModels(host) {
24
+ const result = await host.model.list();
25
+ return result.data.map((model) => ({
26
+ providerID: model.providerID,
27
+ id: model.id,
28
+ name: model.name || model.id,
29
+ }));
30
+ }
@@ -4,8 +4,8 @@ export interface OpencodeModelRef {
4
4
  providerID: string;
5
5
  modelID: string;
6
6
  }
7
- /** Capture and profile calls routed through OpenCode's structured-output sessions. */
8
- export declare function createOpencodeImportModels(ref: OpencodeModelRef, directory: string): Promise<{
7
+ /** Capture and profile calls routed through OpenCode's sessionless generation. */
8
+ export declare function createOpencodeImportModels(ref: OpencodeModelRef): Promise<{
9
9
  capture: CaptureSummaryProvider;
10
10
  profile: ModelPort;
11
11
  }>;
@@ -1,12 +1,12 @@
1
1
  import { CONFIG } from "../../config.js";
2
2
  import { buildBoundedSummaryPrompt } from "../../core/capture-context.js";
3
3
  import { loadOpencodeProvider } from "./opencode-provider-loader.js";
4
- /** Capture and profile calls routed through OpenCode's structured-output sessions. */
5
- export async function createOpencodeImportModels(ref, directory) {
6
- const { getV2Client, generateStructuredOutput } = await loadOpencodeProvider();
7
- const client = getV2Client();
8
- if (!client)
9
- throw new Error("the OpenCode client is not ready; retry in a moment");
4
+ /** Capture and profile calls routed through OpenCode's sessionless generation. */
5
+ export async function createOpencodeImportModels(ref) {
6
+ const { getOpencodeHost, generateStructuredOutput } = await loadOpencodeProvider();
7
+ const host = getOpencodeHost();
8
+ if (!host)
9
+ throw new Error("the OpenCode plugin context is not ready; retry in a moment");
10
10
  const { z } = await import("zod");
11
11
  const { buildCaptureSystemPrompt, createUserProfileAnalysisSchema, parseCaptureSummary } = await import("../../core/extraction.js");
12
12
  const { detectLanguage, getLanguageName } = await import("../../services/language-detector.js");
@@ -22,12 +22,11 @@ export async function createOpencodeImportModels(ref, directory) {
22
22
  : detectLanguage(request.userPrompt);
23
23
  const systemPrompt = buildCaptureSystemPrompt(getLanguageName(target));
24
24
  const result = await generateStructuredOutput({
25
- client,
25
+ host,
26
26
  ...ref,
27
27
  systemPrompt,
28
28
  userPrompt: buildBoundedSummaryPrompt(request.context, systemPrompt, z.toJSONSchema(captureSchema)),
29
29
  schema: captureSchema,
30
- directory,
31
30
  });
32
31
  const parsed = parseCaptureSummary(JSON.stringify(result));
33
32
  if (!parsed)
@@ -41,12 +40,11 @@ export async function createOpencodeImportModels(ref, directory) {
41
40
  modelId: ref.modelID,
42
41
  async complete(systemPrompt, userPrompt) {
43
42
  const result = await generateStructuredOutput({
44
- client,
43
+ host,
45
44
  ...ref,
46
45
  systemPrompt,
47
46
  userPrompt,
48
47
  schema: profileSchema,
49
- directory,
50
48
  });
51
49
  return JSON.stringify(result);
52
50
  },
@@ -1,72 +1,32 @@
1
1
  /**
2
- * Structured output via the opencode HTTP server.
2
+ * Structured output through the OpenCode V2 plugin context.
3
3
  *
4
- * Replaces the older auth.json/OAuth-juggling flow. Instead of forging
5
- * requests to provider HTTP endpoints ourselves, we delegate to the
6
- * running opencode server: it already owns the user's auth (any provider,
7
- * including github-copilot personal/business), token refresh, and provider
8
- * routing.
9
- *
10
- * Per call we create a transient session, prompt it with a JSON schema,
11
- * then delete the session so it does not pollute the user's TUI session
12
- * list.
13
- *
14
- * Internal capture sessions are least-privilege (issue #189): ordinary
15
- * agent tools are denied, only StructuredOutput is allowed, a dedicated
16
- * agent caps steps, and a hard timeout fails closed.
17
- *
18
- * The primary transport is the authenticated v2 SDK client initialized from
19
- * the plugin host's client configuration. A raw fetch fallback remains for
20
- * older SDK builds that do not expose the v2 session methods.
4
+ * OpenCode owns the user's provider sign-in, token refresh and routing, so OMMS asks it
5
+ * for one sessionless text generation (`ctx.generate.text`) per call. The generation
6
+ * has no session, agent or tools, so it cannot touch the user's session list or files.
7
+ * The reply must be one JSON object; OMMS parses it and validates it with the caller's
8
+ * schema. A hard timeout fails closed.
21
9
  */
22
10
  import type { z } from "zod";
23
- import type { OpencodeClient } from "@opencode-ai/sdk/v2/client";
24
- import { type HostTransport } from "./opencode-sdk-client.js";
25
- /** Dedicated agent registered via the plugin config hook (step-capped). */
26
- export declare const STRUCTURED_OUTPUT_AGENT = "omms-structured";
27
- /** Hard ceiling for a single internal structured-output prompt. */
11
+ import { type OpencodeHost } from "./opencode-host.js";
12
+ export { getOpencodeHost } from "./opencode-host.js";
13
+ /** Error texts for a reply that holds no usable JSON; capture diagnostics classify them. */
14
+ export declare const OPENCODE_EMPTY_REPLY = "omms: opencode returned an empty reply";
15
+ export declare const OPENCODE_NO_JSON_REPLY = "omms: the model did not return a JSON object";
16
+ /** Hard ceiling for a single internal structured-output request. */
28
17
  export declare const STRUCTURED_OUTPUT_TIMEOUT_MS = 90000;
29
- /** Test helper: override the structured-output prompt timeout. Pass undefined to reset. */
18
+ /** Test helper: override the structured-output timeout. Pass undefined to reset. */
30
19
  export declare function setStructuredOutputTimeoutMsForTests(ms: number | undefined): void;
31
- export declare const STRUCTURED_OUTPUT_PERMISSIONS: ({
32
- permission: string;
33
- pattern: string;
34
- action: "deny";
35
- } | {
36
- permission: string;
37
- pattern: string;
38
- action: "allow";
39
- })[];
40
- export declare const STRUCTURED_OUTPUT_TOOLS: Record<string, boolean>;
41
- export declare const STRUCTURED_OUTPUT_METADATA: {
42
- omms: {
43
- internal: boolean;
44
- purpose: string;
45
- };
46
- };
47
- export declare function setHostFetch(customFetch: typeof fetch): void;
48
- export declare function resetHostFetch(): void;
49
- /** Test helper: forget the server URL, as in the native V2 plugin. */
50
- export declare function resetV2BaseUrlForTests(): void;
51
20
  export declare function setConnectedProviders(providers: string[]): void;
52
21
  export declare function isProviderConnected(providerName: string): boolean;
53
- export declare function setV2Client(client: OpencodeClient): void;
54
- export declare function getV2Client(): OpencodeClient | undefined;
55
- export declare function createV2Client(serverUrl: URL | string, transport?: HostTransport): OpencodeClient;
56
- /** True while an internal structured-output session is live (create → delete). */
57
- export declare function isInternalStructuredSession(sessionID: string): boolean;
58
- /** Test helper: clear tracked internal session IDs. */
59
- export declare function resetInternalStructuredSessions(): void;
60
22
  export interface StructuredOutputOptions<T> {
61
- client: OpencodeClient;
23
+ host: Pick<OpencodeHost, "generate">;
62
24
  providerID: string;
63
25
  modelID: string;
64
26
  systemPrompt: string;
65
27
  userPrompt: string;
66
28
  schema: z.ZodType<T>;
67
- directory?: string;
68
- retryCount?: number;
69
- /** Called with the raw reply before it is parsed, for capture diagnostics. */
29
+ /** Called with the parsed reply before it is validated, for capture diagnostics. */
70
30
  onReply?: (reply: StructuredOutputReply) => void;
71
31
  }
72
32
  export interface StructuredOutputReply {
@@ -77,9 +37,10 @@ export interface StructuredOutputReply {
77
37
  /**
78
38
  * Resolve `opencodeModel: "inherit"` to a concrete provider/model.
79
39
  *
80
- * Prefer an explicit prompt-recorded model (auto-capture path). Otherwise fall
81
- * back to OpenCode's recent model list so profile-learning / conflict / dedup
82
- * paths don't send the literal model id "inherit" (ProviderModelNotFoundError).
40
+ * Prefer an explicit prompt-recorded model (auto-capture path), then the latest
41
+ * model request this plugin saw, then OpenCode's recent model list, so
42
+ * profile-learning / conflict / dedup paths don't send the literal model id
43
+ * "inherit" (ProviderModelNotFoundError).
83
44
  */
84
45
  export declare function resolveOpencodeModelRef(opts: {
85
46
  providerID: string;
@@ -92,10 +53,10 @@ export declare function resolveOpencodeModelRef(opts: {
92
53
  providerID: string;
93
54
  modelID: string;
94
55
  };
56
+ /** The JSON object in a model reply: the whole reply, a fenced block, or the outer braces. */
57
+ export declare function parseJsonReply(text: string): unknown;
95
58
  /**
96
- * Generate one structured-output completion via opencode's HTTP API.
97
- * Throws on: session.create failure, prompt failure, AssistantMessage.error
98
- * (StructuredOutputError / ApiError / ...), missing `info.structured`,
99
- * timeout, or final Zod validation failure.
59
+ * Generate one structured-output reply with one sessionless `ctx.generate.text` request.
60
+ * Throws on a failed request, a reply with no JSON object, a timeout, or a schema mismatch.
100
61
  */
101
62
  export declare function generateStructuredOutput<T>(opts: StructuredOutputOptions<T>): Promise<T>;