Skip to content

Commit 0b63439

Browse files
committed
fix: support default opencode session client
1 parent 87ed289 commit 0b63439

5 files changed

Lines changed: 175 additions & 82 deletions

File tree

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { tool } from "@opencode-ai/plugin"
33
import { parse, resolve } from "path"
44
import { buildMemorySystemPrompt } from "./prompt.js"
55
import { formatRecalledMemories, recallSelectedMemories, type RecalledMemory } from "./recall.js"
6-
import { assertSupportedRecallSelectorClient, selectRelevantMemoryFilenames, type SessionClient } from "./recallSelector.js"
6+
import { isSupportedRecallSelectorClient, selectRelevantMemoryFilenames, type SessionClient } from "./recallSelector.js"
77
import { scanMemoryFiles, type MemoryHeader } from "./memoryScan.js"
88
import {
99
saveMemory,
@@ -197,7 +197,7 @@ function startRecallPrefetch(input: {
197197
}): RecallPrefetch | undefined {
198198
if (!input.client || !isUsefulRecallQuery(input.query)) return undefined
199199

200-
assertSupportedRecallSelectorClient(input.client)
200+
if (!isSupportedRecallSelectorClient(input.client)) return undefined
201201

202202
const memoryDir = getMemoryDir(input.worktree)
203203
const headers = scanMemoryFiles(memoryDir).filter((header) => !input.alreadySurfaced.has(alreadySurfacedKey(header)))

src/recallSelector.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const SELECT_MEMORIES_FORMAT = {
2121
} as const
2222

2323
export const UNSUPPORTED_RECALL_SELECTOR_CLIENT_MESSAGE =
24-
"opencode-claude-memory LLM recall requires an OpenCode SDK with structured output session.prompt support. Please upgrade OpenCode/@opencode-ai/plugin."
24+
"opencode-claude-memory LLM recall requires an OpenCode SDK session client with create/prompt/delete support."
2525

2626
export type SessionClient = {
2727
session?: {
@@ -93,12 +93,9 @@ function extractSelectedMemories(response: unknown): string[] {
9393
export function isSupportedRecallSelectorClient(client: SessionClient | undefined): boolean {
9494
const session = client?.session
9595
return Boolean(
96-
session?.create &&
97-
session?.prompt &&
98-
session?.delete &&
99-
session.create.length >= 2 &&
100-
session.prompt.length >= 2 &&
101-
session.delete.length >= 2,
96+
typeof session?.create === "function" &&
97+
typeof session.prompt === "function" &&
98+
typeof session.delete === "function",
10299
)
103100
}
104101

@@ -116,9 +113,11 @@ async function createSelectorSession(
116113
if (!client.session?.create) return undefined
117114

118115
const response = await client.session.create({
119-
directory,
120-
parentID: parentSessionID,
121-
title: "opencode-memory recall selector",
116+
body: {
117+
parentID: parentSessionID,
118+
title: "opencode-memory recall selector",
119+
},
120+
query: { directory },
122121
})
123122

124123
return extractSessionID(response)
@@ -143,7 +142,11 @@ async function promptSelectorSession(
143142
parts: [{ type: "text", text: content }],
144143
}
145144

146-
return client.session.prompt({ sessionID, directory, ...body })
145+
return client.session.prompt({
146+
path: { id: sessionID },
147+
query: { directory },
148+
body,
149+
})
147150
}
148151

149152
async function deleteSelectorSession(
@@ -154,7 +157,10 @@ async function deleteSelectorSession(
154157
if (!client.session?.delete) return
155158

156159
try {
157-
await client.session.delete({ sessionID, directory })
160+
await client.session.delete({
161+
path: { id: sessionID },
162+
query: { directory },
163+
})
158164
} catch {
159165
// Best-effort cleanup. A failed selector deletion should not affect recall.
160166
}

test/index.test.ts

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,11 @@ function makeCompletedSelectorClient(selections: string[][]) {
6767
let sessionCount = 0
6868
return {
6969
session: {
70-
async create(_parameters?: unknown, _requestOptions?: unknown) {
70+
async create(_parameters?: unknown) {
7171
sessionCount += 1
7272
return { data: { id: `selector-session-${sessionCount}` } }
7373
},
74-
async prompt(_parameters: unknown, _requestOptions?: unknown) {
74+
async prompt(_parameters: unknown) {
7575
const selected = selections[promptCount] ?? selections.at(-1) ?? []
7676
promptCount += 1
7777
return {
@@ -85,7 +85,7 @@ function makeCompletedSelectorClient(selections: string[][]) {
8585
},
8686
}
8787
},
88-
async delete(_parameters: unknown, _requestOptions?: unknown) {
88+
async delete(_parameters: unknown) {
8989
return { data: true }
9090
},
9191
},
@@ -261,16 +261,22 @@ describe("MemoryPlugin system transform", () => {
261261
})
262262

263263
describe("MemoryPlugin LLM recall prefetch", () => {
264-
test("fails fast when recall prefetch receives an unsupported session client", async () => {
264+
test("prefetches without failing user message transform when runtime client uses the default SDK shape", async () => {
265265
const repo = makeTempGitRepo()
266266
saveMemory(repo, "testing_pref_unsupported", "Testing Preference", "Database integration test guidance", "feedback", "Use real databases in integration tests.")
267+
let createCalled = false
267268
const client = {
268269
session: {
269270
async create(_parameters?: unknown) {
271+
createCalled = true
270272
return { data: { id: "selector-session" } }
271273
},
272274
async prompt(_parameters: unknown) {
273-
return { data: { parts: [] } }
275+
return {
276+
data: {
277+
parts: [{ text: JSON.stringify({ selected_memories: ["testing_pref_unsupported.md"] }) }],
278+
},
279+
}
274280
},
275281
async delete(_parameters: unknown) {
276282
return { data: true }
@@ -280,26 +286,28 @@ describe("MemoryPlugin LLM recall prefetch", () => {
280286

281287
const plugin = await MemoryPlugin({ worktree: repo, directory: repo, client } as never)
282288
const messagesTransform = plugin["experimental.chat.messages.transform"] as unknown as MessagesTransform
289+
const transform = plugin["experimental.chat.system.transform"] as unknown as SystemTransform
283290

284-
let error: unknown
285-
try {
286-
await messagesTransform(
287-
{},
288-
{
289-
messages: [
290-
{
291-
info: { role: "user", sessionID: "ses_unsupported_prefetch" },
292-
parts: [{ type: "text", text: "How should we test database changes?" }],
293-
},
294-
],
295-
},
296-
)
297-
} catch (caught) {
298-
error = caught
299-
}
291+
await messagesTransform(
292+
{},
293+
{
294+
messages: [
295+
{
296+
info: { role: "user", sessionID: "ses_unsupported_prefetch" },
297+
parts: [{ type: "text", text: "How should we test database changes?" }],
298+
},
299+
],
300+
},
301+
)
302+
await flushPromises()
303+
304+
const output = { system: [] as string[] }
305+
await transform({ model: "test-model", sessionID: "ses_unsupported_prefetch" }, output)
300306

301-
expect(error).toBeInstanceOf(Error)
302-
expect((error as Error).message).toContain("requires an OpenCode SDK with structured output session.prompt support")
307+
expect(createCalled).toBe(true)
308+
expect(output.system[0]).toContain("## MEMORY.md")
309+
expect(output.system[0]).toContain("## Recalled Memories")
310+
expect(output.system[0]).toContain("Testing Preference")
303311
})
304312

305313
test("does not wait for an unfinished selector and injects completed recall on the next loop", async () => {
@@ -309,13 +317,13 @@ describe("MemoryPlugin LLM recall prefetch", () => {
309317
const promptResult = deferred<unknown>()
310318
const client = {
311319
session: {
312-
async create(_parameters?: unknown, _requestOptions?: unknown) {
320+
async create(_parameters?: unknown) {
313321
return { data: { id: "selector-session" } }
314322
},
315-
async prompt(_parameters: unknown, _requestOptions?: unknown) {
323+
async prompt(_parameters: unknown) {
316324
return promptResult.promise
317325
},
318-
async delete(_parameters: unknown, _requestOptions?: unknown) {
326+
async delete(_parameters: unknown) {
319327
return { data: true }
320328
},
321329
},

test/memory-recall-prefetch-e2e.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async function flushPromises(): Promise<void> {
6666
}
6767

6868
function selectorPromptText(options: unknown): string {
69-
const parts = (options as { parts?: Array<{ text?: string }> }).parts
69+
const parts = (options as { body?: { parts?: Array<{ text?: string }> } }).body?.parts
7070
return parts?.[0]?.text ?? ""
7171
}
7272

@@ -82,11 +82,11 @@ function makeManifestSelectingClient() {
8282
calls,
8383
client: {
8484
session: {
85-
async create(_parameters?: unknown, _requestOptions?: unknown) {
85+
async create(_parameters?: unknown) {
8686
calls.create += 1
8787
return { data: { id: `selector-session-${calls.create}` } }
8888
},
89-
async prompt(options: unknown, _requestOptions?: unknown) {
89+
async prompt(options: unknown) {
9090
calls.prompt += 1
9191
calls.promptText = selectorPromptText(options)
9292
const selected = calls.promptText.includes("database_rules.md") ? ["database_rules.md"] : []
@@ -102,7 +102,7 @@ function makeManifestSelectingClient() {
102102
},
103103
}
104104
},
105-
async delete(_parameters: unknown, _requestOptions?: unknown) {
105+
async delete(_parameters: unknown) {
106106
calls.delete += 1
107107
return { data: true }
108108
},

0 commit comments

Comments
 (0)