opencode-codex-memory 0.6.3 → 0.6.5
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 +44 -92
- package/dist/src/host-client.d.ts +8 -0
- package/dist/src/host-client.js +12 -0
- package/dist/src/index.js +9 -1
- package/dist/src/llm.d.ts +2 -0
- package/dist/src/llm.js +62 -6
- package/dist/src/reasoning-variant.d.ts +5 -0
- package/dist/src/reasoning-variant.js +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,55 +48,33 @@ If you want the mental model before the details, jump to
|
|
|
48
48
|
|
|
49
49
|
## Install
|
|
50
50
|
|
|
51
|
-
1. Add the plugin to your `~/.config/opencode/opencode.json`:
|
|
51
|
+
**1. Add the plugin** to your `~/.config/opencode/opencode.json`:
|
|
52
52
|
|
|
53
53
|
```json
|
|
54
54
|
{
|
|
55
|
-
"plugin": ["opencode-codex-memory@0.6.
|
|
55
|
+
"plugin": ["opencode-codex-memory@0.6.5"]
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
instead of the package name.)
|
|
79
|
-
|
|
80
|
-
2. That's it. The memory workspace is created on first use. Installing the
|
|
81
|
-
plugin is the opt-in: background learning and summary injection are active
|
|
82
|
-
immediately (Codex ships the same system behind a default-off feature flag
|
|
83
|
-
with a consent prompt; a standalone memory plugin *is* the consent).
|
|
84
|
-
|
|
85
|
-
Requires OpenCode 1.18 or newer (official release). Git is bundled
|
|
86
|
-
(`isomorphic-git`) — no `git` binary or any other external tool needed.
|
|
87
|
-
|
|
88
|
-
The two restricted sub-agents that do the background learning (`memorize`,
|
|
89
|
-
`memorize-extract`) register themselves automatically while background learning
|
|
90
|
-
is enabled. To choose which models
|
|
91
|
-
they use, set the `extract_model` / `consolidation_model` plugin options (see
|
|
92
|
-
[Configuration](#configuration)) — don't override the agents for that. Defining
|
|
93
|
-
an agent with the same name in your own config is only for advanced tweaks
|
|
94
|
-
(e.g. permissions); your definition then replaces the shipped one. If you
|
|
95
|
-
override `memorize`, keep an `external_directory` allow for
|
|
96
|
-
`~/.local/share/opencode/memories/*` (e.g.
|
|
97
|
-
`"external_directory": { "$HOME/.local/share/opencode/memories/*": "allow" }`
|
|
98
|
-
after the wildcard deny) — the memory folder lives outside your project, and
|
|
99
|
-
without that grant OpenCode blocks the consolidator's file access.
|
|
59
|
+
Pin the version — OpenCode installs a plugin spec once and never re-resolves
|
|
60
|
+
it, so a bare `"opencode-codex-memory"` is not "always latest". Check
|
|
61
|
+
[npm](https://www.npmjs.com/package/opencode-codex-memory) for the current
|
|
62
|
+
version.
|
|
63
|
+
|
|
64
|
+
**2. That's it.** The memory workspace is created on first use; background
|
|
65
|
+
learning starts immediately.
|
|
66
|
+
|
|
67
|
+
Requires OpenCode 1.18 or newer. Models and other options: see
|
|
68
|
+
[Configuration](#configuration).
|
|
69
|
+
|
|
70
|
+
### Installation hints
|
|
71
|
+
|
|
72
|
+
To bump pins, copy
|
|
73
|
+
[`docs/commands/update-plugins.md`](./docs/commands/update-plugins.md) to
|
|
74
|
+
`~/.config/opencode/command/update-plugins.md` and run `/update-plugins`.
|
|
75
|
+
This command is not part of the plugin package — nothing about it is
|
|
76
|
+
installed with the plugin. It works for every pinned plugin in your config,
|
|
77
|
+
not just this one.
|
|
100
78
|
|
|
101
79
|
## Try it
|
|
102
80
|
|
|
@@ -123,51 +101,20 @@ echo 'I prefer TypeScript strict mode and 2-space indentation.' \
|
|
|
123
101
|
|
|
124
102
|
## How it works
|
|
125
103
|
|
|
126
|
-
You don't need to know any of this to use the plugin.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
comes back as structured data — a detailed note plus a short recap of the
|
|
141
|
-
session — and lands in a local SQLite database. One session in, one record out.
|
|
142
|
-
Sessions are independent, so this part is easy to parallelize and to retry when
|
|
143
|
-
it fails.
|
|
144
|
-
|
|
145
|
-
**Phase 2 — merge all those notes into one memory.** Every few hours at most
|
|
146
|
-
(and only one run at a time across all your OpenCode windows), a second pass
|
|
147
|
-
takes the most relevant per-session notes and rewrites the actual memory files:
|
|
148
|
-
`MEMORY.md` as the full index, `memory_summary.md` as the short version, and
|
|
149
|
-
`skills/` for procedures worth repeating. This is where the interesting work
|
|
150
|
-
happens — ten similar observations collapse into one rule, contradictions get
|
|
151
|
-
resolved, and notes nothing ever used age out. Forgetting is a feature: memory
|
|
152
|
-
that only grows is memory that stops being useful.
|
|
153
|
-
|
|
154
|
-
The split exists because the two halves have opposite needs. Phase 1 is
|
|
155
|
-
per-session and can run many at once; phase 2 touches the single shared memory,
|
|
156
|
-
so it has to be serialized. Keeping them apart means one slow or failing session
|
|
157
|
-
extraction can't corrupt or block the shared store.
|
|
158
|
-
|
|
159
|
-
**The read path — actually remembering.** Every turn, the short summary is
|
|
160
|
-
appended to the system prompt (capped at ~2500 tokens, so the cost is small and
|
|
161
|
-
predictable). That's the always-on layer. When a task looks related to past
|
|
162
|
-
work, the agent goes further and searches the full memory itself with the
|
|
163
|
-
`memory_*` tools — the equivalent of "I've seen this before, let me look it up"
|
|
164
|
-
rather than carrying everything around all the time.
|
|
165
|
-
|
|
166
|
-
**The feedback loop.** When the agent uses a memory, it cites it. The citation
|
|
167
|
-
is recorded and then stripped before it reaches your screen, and those usage
|
|
168
|
-
counts feed back into phase 2's ranking. Memories that keep proving useful get
|
|
169
|
-
kept and sharpened; memories nothing has touched in a month drop out. The system
|
|
170
|
-
finds out which of its own notes were worth writing.
|
|
104
|
+
You don't need to know any of this to use the plugin. In short: two background
|
|
105
|
+
writers and one reader. Once a session has been idle long enough (default 6 h),
|
|
106
|
+
a cheap model reviews its transcript and writes a structured note (**phase 1 —
|
|
107
|
+
extraction**). Every few hours, a stronger model merges those notes into the
|
|
108
|
+
actual memory files — the index, the short summary, reusable skills — resolving
|
|
109
|
+
duplicates and dropping what went stale (**phase 2 — consolidation**). On every
|
|
110
|
+
turn, the short summary goes into the system prompt, and the agent searches
|
|
111
|
+
deeper layers itself when a task looks familiar (**read path**). When the agent
|
|
112
|
+
uses a memory it cites it; those citations feed consolidation's ranking, so
|
|
113
|
+
useful memories survive and unused ones age out.
|
|
114
|
+
|
|
115
|
+
For the full architecture — learning, remembering, forgetting, and the
|
|
116
|
+
trade-offs behind each — see
|
|
117
|
+
[How OpenCode Codex Memory works](./docs/how-ai-memory-works.md).
|
|
171
118
|
|
|
172
119
|
## Where your data lives
|
|
173
120
|
|
|
@@ -254,7 +201,7 @@ To set options, turn the plugin entry into a `[name, options]` pair:
|
|
|
254
201
|
```json
|
|
255
202
|
{
|
|
256
203
|
"plugin": [
|
|
257
|
-
["opencode-codex-memory@0.6.
|
|
204
|
+
["opencode-codex-memory@0.6.5", { "disable_on_external_context": true, "min_rollout_idle_hours": 2 }]
|
|
258
205
|
]
|
|
259
206
|
}
|
|
260
207
|
```
|
|
@@ -288,6 +235,11 @@ on your own `memorize-extract`/`memorize` agent definition, if you overrode
|
|
|
288
235
|
one → the provider's default model. Note that the first two pass the model
|
|
289
236
|
explicitly, so they win over an agent-level `model`.
|
|
290
237
|
|
|
238
|
+
Reasoning effort follows Codex (`low` for extraction, `medium` for
|
|
239
|
+
consolidation) when the model lists that name. Otherwise the plugin picks the
|
|
240
|
+
nearest OpenCode variant on `none < minimal < low < medium < high < xhigh < max`
|
|
241
|
+
(ties go higher). No listed efforts → no pin, host default.
|
|
242
|
+
|
|
291
243
|
> Note: `dedicated_tools` defaults to `true` here (Codex defaults it to `false`).
|
|
292
244
|
> This is the one intentional default difference — the tools are a core part of a
|
|
293
245
|
> standalone memory plugin. Everything else matches Codex's defaults.
|
|
@@ -316,7 +268,7 @@ Off by default; no changes to Codex's own config are required.
|
|
|
316
268
|
{
|
|
317
269
|
"plugin": [
|
|
318
270
|
[
|
|
319
|
-
"opencode-codex-memory@0.6.
|
|
271
|
+
"opencode-codex-memory@0.6.5",
|
|
320
272
|
{ "codex_interop": { "import": true, "export": true } }
|
|
321
273
|
]
|
|
322
274
|
]
|
|
@@ -373,7 +325,7 @@ from the project memories Claude already keeps on your machine. **One-way only**
|
|
|
373
325
|
```json
|
|
374
326
|
{
|
|
375
327
|
"plugin": [
|
|
376
|
-
["opencode-codex-memory@0.6.
|
|
328
|
+
["opencode-codex-memory@0.6.5", { "claude_import": { "enabled": true } }]
|
|
377
329
|
]
|
|
378
330
|
}
|
|
379
331
|
```
|
|
@@ -400,7 +352,7 @@ Claude names each project with an opaque id (a folder under
|
|
|
400
352
|
{
|
|
401
353
|
"plugin": [
|
|
402
354
|
[
|
|
403
|
-
"opencode-codex-memory@0.6.
|
|
355
|
+
"opencode-codex-memory@0.6.5",
|
|
404
356
|
{
|
|
405
357
|
"claude_import": {
|
|
406
358
|
"enabled": true,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - AssistantMessage.info.structured: structured extraction result
|
|
11
11
|
* - client._client.get: experimental routes (no experimental.* namespace on V1)
|
|
12
12
|
* - mcp.status: present on host client, weakly typed in plugin package
|
|
13
|
+
* - provider.list model.variants omitted from V1 OpenAPI types
|
|
13
14
|
*/
|
|
14
15
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
15
16
|
export type HostHttpGet = (opts: {
|
|
@@ -45,6 +46,13 @@ export interface SessionCreateBody {
|
|
|
45
46
|
title?: string;
|
|
46
47
|
metadata?: Record<string, unknown>;
|
|
47
48
|
}
|
|
49
|
+
/** GET /provider. V1 SDK types omit model.variants; parse the payload loosely. */
|
|
50
|
+
export declare function hostProviderList(client: PluginInput["client"] | null | undefined, opts?: {
|
|
51
|
+
signal?: AbortSignal;
|
|
52
|
+
}): Promise<{
|
|
53
|
+
error?: unknown;
|
|
54
|
+
data?: unknown;
|
|
55
|
+
}>;
|
|
48
56
|
/** session.create with metadata (SDK body type is incomplete). */
|
|
49
57
|
export declare function hostSessionCreate(client: PluginInput["client"], opts: {
|
|
50
58
|
directory: string;
|
package/dist/src/host-client.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - AssistantMessage.info.structured: structured extraction result
|
|
11
11
|
* - client._client.get: experimental routes (no experimental.* namespace on V1)
|
|
12
12
|
* - mcp.status: present on host client, weakly typed in plugin package
|
|
13
|
+
* - provider.list model.variants omitted from V1 OpenAPI types
|
|
13
14
|
*/
|
|
14
15
|
/** Hey-api transport on PluginInput.client for routes the V1 surface lags on. */
|
|
15
16
|
export function pluginHttpGet(client) {
|
|
@@ -64,6 +65,17 @@ export async function withHostTimeout(promise, ms, label, abort) {
|
|
|
64
65
|
clearTimeout(timer);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
68
|
+
/** GET /provider. V1 SDK types omit model.variants; parse the payload loosely. */
|
|
69
|
+
export async function hostProviderList(client, opts) {
|
|
70
|
+
const list = client?.provider?.list;
|
|
71
|
+
if (typeof list === "function") {
|
|
72
|
+
return (await list(opts?.signal ? { signal: opts.signal } : undefined));
|
|
73
|
+
}
|
|
74
|
+
const get = pluginHttpGet(client);
|
|
75
|
+
if (!get)
|
|
76
|
+
return { error: { message: "provider.list unavailable" } };
|
|
77
|
+
return get({ url: "/provider", ...(opts?.signal ? { signal: opts.signal } : {}) });
|
|
78
|
+
}
|
|
67
79
|
/** session.create with metadata (SDK body type is incomplete). */
|
|
68
80
|
export async function hostSessionCreate(client, opts) {
|
|
69
81
|
return client.session.create({
|
package/dist/src/index.js
CHANGED
|
@@ -326,6 +326,10 @@ async function classifyExternalContextTool(toolName) {
|
|
|
326
326
|
}
|
|
327
327
|
return false;
|
|
328
328
|
}
|
|
329
|
+
const TITLE_GENERATOR_MARKER = "You are a title generator. You output ONLY a thread title.";
|
|
330
|
+
function isTitleGenerationPrompt(system) {
|
|
331
|
+
return system.some((block) => block.includes(TITLE_GENERATOR_MARKER));
|
|
332
|
+
}
|
|
329
333
|
/**
|
|
330
334
|
* Registers the memorize / memorize-extract sub-agents through the config
|
|
331
335
|
* hook so installing the plugin requires no manual agent setup. Definitions
|
|
@@ -383,9 +387,13 @@ function buildHooks() {
|
|
|
383
387
|
if (!pluginOptions.use_memories)
|
|
384
388
|
return;
|
|
385
389
|
// OpenCode also invokes this hook while generating agent definitions,
|
|
386
|
-
// without a session
|
|
390
|
+
// without a session, and while naming a session (hidden `title` agent,
|
|
391
|
+
// same sessionID; hook has no agent field). Memory belongs only in
|
|
392
|
+
// real conversation prompts.
|
|
387
393
|
if (!input.sessionID || isMemorySubSession(input.sessionID))
|
|
388
394
|
return;
|
|
395
|
+
if (isTitleGenerationPrompt(output.system))
|
|
396
|
+
return;
|
|
389
397
|
ensureMemoryLayout();
|
|
390
398
|
const memoryPrompt = buildMemorySystemPrompt(pluginOptions.dedicated_tools);
|
|
391
399
|
if (memoryPrompt) {
|
package/dist/src/llm.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare function setSubSessionCreateTimeoutForTest(ms?: number): void;
|
|
|
11
11
|
/** Test seam. */
|
|
12
12
|
export declare function setConfigGetTimeoutForTest(ms?: number): void;
|
|
13
13
|
/** Test seam. */
|
|
14
|
+
export declare function setProviderListTimeoutForTest(ms?: number): void;
|
|
15
|
+
/** Test seam. */
|
|
14
16
|
export declare function setStaleDeleteBatchTimeoutForTest(ms?: number): void;
|
|
15
17
|
export declare function isMemorySubSession(sessionId: string): boolean;
|
|
16
18
|
export declare function resolveExtractionModel(configured?: string): Promise<string | undefined>;
|
package/dist/src/llm.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { memoryRoot } from "./paths.js";
|
|
4
|
-
import { hostListSessionsGlobal, hostSessionCreate, hostSessionDeletionConfirmed, hostSessionPrompt, hostStructuredOutput, ignoreLateRejection, pluginHttpGet, withHostTimeout, } from "./host-client.js";
|
|
4
|
+
import { hostListSessionsGlobal, hostProviderList, hostSessionCreate, hostSessionDeletionConfirmed, hostSessionPrompt, hostStructuredOutput, ignoreLateRejection, pluginHttpGet, withHostTimeout, } from "./host-client.js";
|
|
5
|
+
import { catalogVariantKeys, nearestReasoningVariant } from "./reasoning-variant.js";
|
|
5
6
|
import { isPluginShuttingDown, pluginShutdownSignal } from "./lifecycle.js";
|
|
6
7
|
import { SCAN_LIMIT } from "./store.js";
|
|
7
8
|
import { isProviderCapacityError, ProviderCapacityError } from "./ratelimit.js";
|
|
@@ -12,6 +13,8 @@ export function setPluginInput(input) {
|
|
|
12
13
|
inputGeneration++;
|
|
13
14
|
configModels = null;
|
|
14
15
|
configModelsInFlight = null;
|
|
16
|
+
providerCatalog = null;
|
|
17
|
+
providerCatalogInFlight = null;
|
|
15
18
|
}
|
|
16
19
|
export function getPluginInput() {
|
|
17
20
|
return inputRef;
|
|
@@ -27,10 +30,12 @@ const SUBSESSION_CONFIRM_TIMEOUT_MS = 1_000;
|
|
|
27
30
|
const SUBSESSION_DELETE_TIMEOUT_MS = 10_000;
|
|
28
31
|
const SUBSESSION_CREATE_TIMEOUT_MS = 10_000;
|
|
29
32
|
const CONFIG_GET_TIMEOUT_MS = 5_000;
|
|
33
|
+
const PROVIDER_LIST_TIMEOUT_MS = 5_000;
|
|
30
34
|
const SUBSESSION_DELETE_CONCURRENCY = 8;
|
|
31
35
|
const SUBSESSION_DELETE_BATCH_TIMEOUT_MS = 30_000;
|
|
32
36
|
let createTimeoutMs = SUBSESSION_CREATE_TIMEOUT_MS;
|
|
33
37
|
let configGetTimeoutMs = CONFIG_GET_TIMEOUT_MS;
|
|
38
|
+
let providerListTimeoutMs = PROVIDER_LIST_TIMEOUT_MS;
|
|
34
39
|
let staleDeleteBatchTimeoutMs = SUBSESSION_DELETE_BATCH_TIMEOUT_MS;
|
|
35
40
|
/** Test seam. */
|
|
36
41
|
export function setSubSessionCreateTimeoutForTest(ms) {
|
|
@@ -41,6 +46,10 @@ export function setConfigGetTimeoutForTest(ms) {
|
|
|
41
46
|
configGetTimeoutMs = ms ?? CONFIG_GET_TIMEOUT_MS;
|
|
42
47
|
}
|
|
43
48
|
/** Test seam. */
|
|
49
|
+
export function setProviderListTimeoutForTest(ms) {
|
|
50
|
+
providerListTimeoutMs = ms ?? PROVIDER_LIST_TIMEOUT_MS;
|
|
51
|
+
}
|
|
52
|
+
/** Test seam. */
|
|
44
53
|
export function setStaleDeleteBatchTimeoutForTest(ms) {
|
|
45
54
|
staleDeleteBatchTimeoutMs = ms ?? SUBSESSION_DELETE_BATCH_TIMEOUT_MS;
|
|
46
55
|
}
|
|
@@ -142,6 +151,53 @@ function parseModelRef(ref) {
|
|
|
142
151
|
return null;
|
|
143
152
|
return { providerID: ref.slice(0, slash), modelID: ref.slice(slash + 1) };
|
|
144
153
|
}
|
|
154
|
+
const EXTRACT_VARIANT = "low";
|
|
155
|
+
const CONSOLIDATE_VARIANT = "medium";
|
|
156
|
+
let providerCatalog = null;
|
|
157
|
+
let providerCatalogInFlight = null;
|
|
158
|
+
async function getProviderCatalog() {
|
|
159
|
+
if (providerCatalog)
|
|
160
|
+
return providerCatalog.data;
|
|
161
|
+
if (providerCatalogInFlight)
|
|
162
|
+
return providerCatalogInFlight;
|
|
163
|
+
const input = getPluginInput();
|
|
164
|
+
if (!input)
|
|
165
|
+
return null;
|
|
166
|
+
const generation = inputGeneration;
|
|
167
|
+
const request = (async () => {
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
try {
|
|
170
|
+
const res = await withHostTimeout(hostProviderList(input.client, { signal: controller.signal }), providerListTimeoutMs, "provider.list", controller);
|
|
171
|
+
if (res.error || res.data == null || generation !== inputGeneration)
|
|
172
|
+
return null;
|
|
173
|
+
providerCatalog = { data: res.data };
|
|
174
|
+
return res.data;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
providerCatalogInFlight = request;
|
|
181
|
+
try {
|
|
182
|
+
return await request;
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
if (providerCatalogInFlight === request)
|
|
186
|
+
providerCatalogInFlight = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function resolveReasoningVariant(preferred, modelRef) {
|
|
190
|
+
const parsed = modelRef ? parseModelRef(modelRef) : null;
|
|
191
|
+
if (!parsed)
|
|
192
|
+
return preferred;
|
|
193
|
+
const catalog = await getProviderCatalog();
|
|
194
|
+
if (catalog == null)
|
|
195
|
+
return preferred;
|
|
196
|
+
const keys = catalogVariantKeys(catalog, parsed.providerID, parsed.modelID);
|
|
197
|
+
if (keys === undefined)
|
|
198
|
+
return preferred;
|
|
199
|
+
return nearestReasoningVariant(preferred, keys);
|
|
200
|
+
}
|
|
145
201
|
/**
|
|
146
202
|
* Thrown when a sub-agent prompt exceeds its budget. A distinct type (rather
|
|
147
203
|
* than matching on the message text) is what tells the catch below that the
|
|
@@ -338,9 +394,9 @@ export async function extractViaSubagent(sessionId, transcript, opts = {}) {
|
|
|
338
394
|
// call (toolChoice: required) — which is why memorize-extract must allow
|
|
339
395
|
// that one otherwise-denied tool.
|
|
340
396
|
format: { type: "json_schema", schema: EXTRACTION_SCHEMA },
|
|
341
|
-
// Codex extraction ReasoningEffort::Low. Host maps variant → reasoningEffort
|
|
342
|
-
//
|
|
343
|
-
variant:
|
|
397
|
+
// Codex extraction ReasoningEffort::Low. Host maps variant → reasoningEffort.
|
|
398
|
+
// If the model has no `low`, pick the nearest listed effort (host-only).
|
|
399
|
+
variant: await resolveReasoningVariant(EXTRACT_VARIANT, model),
|
|
344
400
|
});
|
|
345
401
|
// The captured JSON lands on AssistantMessage.structured (schema
|
|
346
402
|
// v1/session.ts; absent from the generated SDK type — see host-client.ts).
|
|
@@ -376,8 +432,8 @@ export async function consolidateViaSubagent(memoryRoot, diffFileName, model, si
|
|
|
376
432
|
model: resolved,
|
|
377
433
|
timeoutMs: CONSOLIDATION_TIMEOUT_MS,
|
|
378
434
|
signal,
|
|
379
|
-
// Codex consolidation ReasoningEffort::Medium.
|
|
380
|
-
variant:
|
|
435
|
+
// Codex consolidation ReasoningEffort::Medium. Nearest listed effort if missing.
|
|
436
|
+
variant: await resolveReasoningVariant(CONSOLIDATE_VARIANT, resolved),
|
|
381
437
|
});
|
|
382
438
|
}
|
|
383
439
|
catch (err) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const REASONING_VARIANT_LADDER: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
2
|
+
export type ReasoningVariant = (typeof REASONING_VARIANT_LADDER)[number];
|
|
3
|
+
export declare function nearestReasoningVariant(preferred: string, available: Iterable<string>): string | undefined;
|
|
4
|
+
export declare function enabledVariantKeys(variants: unknown): string[];
|
|
5
|
+
export declare function catalogVariantKeys(data: unknown, providerID: string, modelID: string): string[] | undefined;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const REASONING_VARIANT_LADDER = ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
2
|
+
export function nearestReasoningVariant(preferred, available) {
|
|
3
|
+
const have = new Set(available);
|
|
4
|
+
if (have.has(preferred))
|
|
5
|
+
return preferred;
|
|
6
|
+
const prefIdx = REASONING_VARIANT_LADDER.indexOf(preferred);
|
|
7
|
+
if (prefIdx < 0)
|
|
8
|
+
return undefined;
|
|
9
|
+
let best;
|
|
10
|
+
let bestDist = Infinity;
|
|
11
|
+
let bestIdx = -1;
|
|
12
|
+
for (let idx = 0; idx < REASONING_VARIANT_LADDER.length; idx++) {
|
|
13
|
+
const name = REASONING_VARIANT_LADDER[idx];
|
|
14
|
+
if (!have.has(name))
|
|
15
|
+
continue;
|
|
16
|
+
const dist = Math.abs(idx - prefIdx);
|
|
17
|
+
if (dist < bestDist || (dist === bestDist && idx > bestIdx)) {
|
|
18
|
+
best = name;
|
|
19
|
+
bestDist = dist;
|
|
20
|
+
bestIdx = idx;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return best;
|
|
24
|
+
}
|
|
25
|
+
export function enabledVariantKeys(variants) {
|
|
26
|
+
if (!variants || typeof variants !== "object" || Array.isArray(variants))
|
|
27
|
+
return [];
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const [name, value] of Object.entries(variants)) {
|
|
30
|
+
if (value && typeof value === "object" && !Array.isArray(value) && value.disabled === true) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
out.push(name);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
export function catalogVariantKeys(data, providerID, modelID) {
|
|
38
|
+
if (!data || typeof data !== "object")
|
|
39
|
+
return undefined;
|
|
40
|
+
const root = data;
|
|
41
|
+
const all = Array.isArray(root.all) ? root.all : Array.isArray(root.providers) ? root.providers : undefined;
|
|
42
|
+
if (!all)
|
|
43
|
+
return undefined;
|
|
44
|
+
const provider = all.find((item) => item && typeof item === "object" && item.id === providerID);
|
|
45
|
+
if (!provider?.models || typeof provider.models !== "object")
|
|
46
|
+
return undefined;
|
|
47
|
+
const model = provider.models[modelID];
|
|
48
|
+
if (!model || typeof model !== "object" || Array.isArray(model))
|
|
49
|
+
return undefined;
|
|
50
|
+
if (!Object.prototype.hasOwnProperty.call(model, "variants"))
|
|
51
|
+
return undefined;
|
|
52
|
+
return enabledVariantKeys(model.variants);
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-codex-memory",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Persistent memory plugin for opencode — ports codex's two-phase memory system (extraction → consolidation → injection → citation feedback)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|