@otto-assistant/bridge 0.4.100 → 0.4.101
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/anthropic-auth-plugin.js +227 -178
- package/dist/commands/login.js +6 -4
- package/dist/context-awareness-plugin.js +8 -38
- package/dist/kimaki-opencode-plugin.js +3 -1
- package/dist/memory-overview-plugin.js +126 -0
- package/package.json +1 -1
- package/src/anthropic-auth-plugin.ts +574 -453
- package/src/commands/login.ts +6 -4
- package/src/context-awareness-plugin.ts +11 -42
- package/src/kimaki-opencode-plugin.ts +3 -1
- package/src/memory-overview-plugin.ts +161 -0
package/dist/commands/login.js
CHANGED
|
@@ -39,6 +39,8 @@ const PROVIDER_POPULARITY_ORDER = [
|
|
|
39
39
|
'xai',
|
|
40
40
|
'groq',
|
|
41
41
|
'deepseek',
|
|
42
|
+
'opencode',
|
|
43
|
+
'opencode-go',
|
|
42
44
|
'mistral',
|
|
43
45
|
'openrouter',
|
|
44
46
|
'fireworks-ai',
|
|
@@ -47,12 +49,12 @@ const PROVIDER_POPULARITY_ORDER = [
|
|
|
47
49
|
'azure',
|
|
48
50
|
'google-vertex',
|
|
49
51
|
'google-vertex-anthropic',
|
|
50
|
-
'cohere',
|
|
52
|
+
// 'cohere',
|
|
51
53
|
'cerebras',
|
|
52
|
-
'perplexity',
|
|
54
|
+
// 'perplexity',
|
|
53
55
|
'cloudflare-workers-ai',
|
|
54
|
-
'novita-ai',
|
|
55
|
-
'huggingface',
|
|
56
|
+
// 'novita-ai',
|
|
57
|
+
// 'huggingface',
|
|
56
58
|
'deepinfra',
|
|
57
59
|
'github-models',
|
|
58
60
|
'lmstudio',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// OpenCode plugin that injects synthetic message parts for context awareness:
|
|
2
2
|
// - Git branch / detached HEAD changes
|
|
3
3
|
// - Working directory (pwd) changes (e.g. after /new-worktree mid-session)
|
|
4
|
-
// - MEMORY.md table of contents on first message
|
|
5
4
|
// - MEMORY.md reminder after a large assistant reply
|
|
6
5
|
// - Onboarding tutorial instructions (when TUTORIAL_WELCOME_TEXT detected)
|
|
7
6
|
//
|
|
@@ -16,26 +15,13 @@
|
|
|
16
15
|
// Exported from kimaki-opencode-plugin.ts — each export is treated as a separate
|
|
17
16
|
// plugin by OpenCode's plugin loader.
|
|
18
17
|
import crypto from 'node:crypto';
|
|
19
|
-
import fs from 'node:fs';
|
|
20
|
-
import path from 'node:path';
|
|
21
18
|
import * as errore from 'errore';
|
|
22
19
|
import { createPluginLogger, formatPluginErrorWithStack, setPluginLogFilePath, } from './plugin-logger.js';
|
|
23
20
|
import { setDataDir } from './config.js';
|
|
24
21
|
import { initSentry, notifyError } from './sentry.js';
|
|
25
22
|
import { execAsync } from './exec-async.js';
|
|
26
|
-
import { condenseMemoryMd } from './condense-memory.js';
|
|
27
23
|
import { ONBOARDING_TUTORIAL_INSTRUCTIONS, TUTORIAL_WELCOME_TEXT, } from './onboarding-tutorial.js';
|
|
28
24
|
const logger = createPluginLogger('OPENCODE');
|
|
29
|
-
function createSessionState() {
|
|
30
|
-
return {
|
|
31
|
-
gitState: undefined,
|
|
32
|
-
memoryInjected: false,
|
|
33
|
-
lastMemoryReminderAssistantMessageId: undefined,
|
|
34
|
-
tutorialInjected: false,
|
|
35
|
-
resolvedDirectory: undefined,
|
|
36
|
-
announcedDirectory: undefined,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
25
|
// ── Pure derivation functions ────────────────────────────────────
|
|
40
26
|
// These take state + fresh input and return whether to inject.
|
|
41
27
|
// No side effects, no mutations — easy to test with fixtures.
|
|
@@ -66,9 +52,6 @@ export function shouldInjectPwd({ currentDir, previousDir, announcedDir, }) {
|
|
|
66
52
|
};
|
|
67
53
|
}
|
|
68
54
|
const MEMORY_REMINDER_OUTPUT_TOKENS = 12_000;
|
|
69
|
-
function getOutputTokenTotal(tokens) {
|
|
70
|
-
return Math.max(0, tokens.output + tokens.reasoning);
|
|
71
|
-
}
|
|
72
55
|
export function shouldInjectMemoryReminderFromLatestAssistant({ lastMemoryReminderAssistantMessageId, latestAssistantMessage, threshold = MEMORY_REMINDER_OUTPUT_TOKENS, }) {
|
|
73
56
|
if (!latestAssistantMessage) {
|
|
74
57
|
return { inject: false };
|
|
@@ -85,7 +68,7 @@ export function shouldInjectMemoryReminderFromLatestAssistant({ lastMemoryRemind
|
|
|
85
68
|
if (lastMemoryReminderAssistantMessageId === latestAssistantMessage.id) {
|
|
86
69
|
return { inject: false };
|
|
87
70
|
}
|
|
88
|
-
const outputTokens =
|
|
71
|
+
const outputTokens = Math.max(0, latestAssistantMessage.tokens.output + latestAssistantMessage.tokens.reasoning);
|
|
89
72
|
if (outputTokens < threshold) {
|
|
90
73
|
return { inject: false };
|
|
91
74
|
}
|
|
@@ -184,7 +167,13 @@ const contextAwarenessPlugin = async ({ directory, client }) => {
|
|
|
184
167
|
if (existing) {
|
|
185
168
|
return existing;
|
|
186
169
|
}
|
|
187
|
-
const state =
|
|
170
|
+
const state = {
|
|
171
|
+
gitState: undefined,
|
|
172
|
+
lastMemoryReminderAssistantMessageId: undefined,
|
|
173
|
+
tutorialInjected: false,
|
|
174
|
+
resolvedDirectory: undefined,
|
|
175
|
+
announcedDirectory: undefined,
|
|
176
|
+
};
|
|
188
177
|
sessions.set(sessionID, state);
|
|
189
178
|
return state;
|
|
190
179
|
}
|
|
@@ -274,25 +263,6 @@ const contextAwarenessPlugin = async ({ directory, client }) => {
|
|
|
274
263
|
synthetic: true,
|
|
275
264
|
});
|
|
276
265
|
}
|
|
277
|
-
// -- MEMORY.md injection --
|
|
278
|
-
if (!state.memoryInjected) {
|
|
279
|
-
state.memoryInjected = true;
|
|
280
|
-
const memoryPath = path.join(effectiveDirectory, 'MEMORY.md');
|
|
281
|
-
const memoryContent = await fs.promises
|
|
282
|
-
.readFile(memoryPath, 'utf-8')
|
|
283
|
-
.catch(() => null);
|
|
284
|
-
if (memoryContent) {
|
|
285
|
-
const condensed = condenseMemoryMd(memoryContent);
|
|
286
|
-
output.parts.push({
|
|
287
|
-
id: `prt_${crypto.randomUUID()}`,
|
|
288
|
-
sessionID,
|
|
289
|
-
messageID,
|
|
290
|
-
type: 'text',
|
|
291
|
-
text: `<system-reminder>Project memory from MEMORY.md (condensed table of contents, line numbers shown):\n${condensed}\nOnly headings are shown above — section bodies are hidden. Use Grep to search MEMORY.md for specific topics, or Read with offset and limit to read a section's content. When writing to MEMORY.md, keep titles concise (under 10 words) and content brief (2-3 sentences max). Only track non-obvious learnings that prevent future mistakes and are not already documented in code comments or AGENTS.md. Do not duplicate information that is self-evident from the code.</system-reminder>`,
|
|
292
|
-
synthetic: true,
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
266
|
const memoryReminder = shouldInjectMemoryReminderFromLatestAssistant({
|
|
297
267
|
lastMemoryReminderAssistantMessageId: state.lastMemoryReminderAssistantMessageId,
|
|
298
268
|
latestAssistantMessage,
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Plugins are split into focused modules:
|
|
7
7
|
// - ipc-tools-plugin: file upload + action buttons (IPC-based Discord tools)
|
|
8
|
-
// - context-awareness-plugin: branch, pwd, memory
|
|
8
|
+
// - context-awareness-plugin: branch, pwd, memory reminder, onboarding tutorial
|
|
9
|
+
// - memory-overview-plugin: frozen MEMORY.md heading overview per session
|
|
9
10
|
// - opencode-interrupt-plugin: interrupt queued messages at step boundaries
|
|
10
11
|
// - kitty-graphics-plugin: extract Kitty Graphics Protocol images from bash output
|
|
11
12
|
export { ipcToolsPlugin } from './ipc-tools-plugin.js';
|
|
12
13
|
export { contextAwarenessPlugin } from './context-awareness-plugin.js';
|
|
14
|
+
export { memoryOverviewPlugin } from './memory-overview-plugin.js';
|
|
13
15
|
export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js';
|
|
14
16
|
export { systemPromptDriftPlugin } from './system-prompt-drift-plugin.js';
|
|
15
17
|
export { anthropicAuthPlugin } from './anthropic-auth-plugin.js';
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// OpenCode plugin that snapshots the MEMORY.md heading overview once per
|
|
2
|
+
// session and injects that frozen snapshot on the first real user message.
|
|
3
|
+
// The snapshot is cached by session ID so later MEMORY.md edits do not change
|
|
4
|
+
// the prompt for the same session and do not invalidate OpenCode's cache.
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import * as errore from 'errore';
|
|
9
|
+
import { createPluginLogger, formatPluginErrorWithStack, setPluginLogFilePath, } from './plugin-logger.js';
|
|
10
|
+
import { condenseMemoryMd } from './condense-memory.js';
|
|
11
|
+
import { initSentry, notifyError } from './sentry.js';
|
|
12
|
+
const logger = createPluginLogger('OPENCODE');
|
|
13
|
+
function createSessionState() {
|
|
14
|
+
return {
|
|
15
|
+
hasFrozenOverview: false,
|
|
16
|
+
frozenOverviewText: null,
|
|
17
|
+
injected: false,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function buildMemoryOverviewReminder({ condensed }) {
|
|
21
|
+
return `<system-reminder>Project memory from MEMORY.md (condensed table of contents, line numbers shown):\n${condensed}\nOnly headings are shown above — section bodies are hidden. Use Grep to search MEMORY.md for specific topics, or Read with offset and limit to read a section's content. When writing to MEMORY.md, keep titles concise (under 10 words) and content brief (2-3 sentences max). Only track non-obvious learnings that prevent future mistakes and are not already documented in code comments or AGENTS.md. Do not duplicate information that is self-evident from the code.</system-reminder>`;
|
|
22
|
+
}
|
|
23
|
+
async function freezeMemoryOverview({ directory, state, }) {
|
|
24
|
+
if (state.hasFrozenOverview) {
|
|
25
|
+
return state.frozenOverviewText;
|
|
26
|
+
}
|
|
27
|
+
const memoryPath = path.join(directory, 'MEMORY.md');
|
|
28
|
+
const memoryContentResult = await fs.promises.readFile(memoryPath, 'utf-8').catch(() => {
|
|
29
|
+
return null;
|
|
30
|
+
});
|
|
31
|
+
if (!memoryContentResult) {
|
|
32
|
+
state.hasFrozenOverview = true;
|
|
33
|
+
state.frozenOverviewText = null;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const condensed = condenseMemoryMd(memoryContentResult);
|
|
37
|
+
state.hasFrozenOverview = true;
|
|
38
|
+
state.frozenOverviewText = buildMemoryOverviewReminder({ condensed });
|
|
39
|
+
return state.frozenOverviewText;
|
|
40
|
+
}
|
|
41
|
+
const memoryOverviewPlugin = async ({ directory }) => {
|
|
42
|
+
initSentry();
|
|
43
|
+
const dataDir = process.env.KIMAKI_DATA_DIR;
|
|
44
|
+
if (dataDir) {
|
|
45
|
+
setPluginLogFilePath(dataDir);
|
|
46
|
+
}
|
|
47
|
+
const sessions = new Map();
|
|
48
|
+
function getOrCreateSessionState({ sessionID }) {
|
|
49
|
+
const existing = sessions.get(sessionID);
|
|
50
|
+
if (existing) {
|
|
51
|
+
return existing;
|
|
52
|
+
}
|
|
53
|
+
const state = createSessionState();
|
|
54
|
+
sessions.set(sessionID, state);
|
|
55
|
+
return state;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
'chat.message': async (input, output) => {
|
|
59
|
+
const result = await errore.tryAsync({
|
|
60
|
+
try: async () => {
|
|
61
|
+
const state = getOrCreateSessionState({ sessionID: input.sessionID });
|
|
62
|
+
if (state.injected) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const firstPart = output.parts.find((part) => {
|
|
66
|
+
if (part.type !== 'text') {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return part.synthetic !== true;
|
|
70
|
+
});
|
|
71
|
+
if (!firstPart || firstPart.type !== 'text' || firstPart.text.trim().length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const overviewText = await freezeMemoryOverview({ directory, state });
|
|
75
|
+
state.injected = true;
|
|
76
|
+
if (!overviewText) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
output.parts.push({
|
|
80
|
+
id: `prt_${crypto.randomUUID()}`,
|
|
81
|
+
sessionID: input.sessionID,
|
|
82
|
+
messageID: firstPart.messageID,
|
|
83
|
+
type: 'text',
|
|
84
|
+
text: overviewText,
|
|
85
|
+
synthetic: true,
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
catch: (error) => {
|
|
89
|
+
return new Error('memory overview chat.message hook failed', {
|
|
90
|
+
cause: error,
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
if (!(result instanceof Error)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
logger.warn(`[memory-overview-plugin] ${formatPluginErrorWithStack(result)}`);
|
|
98
|
+
void notifyError(result, 'memory overview plugin chat.message hook failed');
|
|
99
|
+
},
|
|
100
|
+
event: async ({ event }) => {
|
|
101
|
+
const result = await errore.tryAsync({
|
|
102
|
+
try: async () => {
|
|
103
|
+
if (event.type !== 'session.deleted') {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const id = event.properties?.info?.id;
|
|
107
|
+
if (!id) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
sessions.delete(id);
|
|
111
|
+
},
|
|
112
|
+
catch: (error) => {
|
|
113
|
+
return new Error('memory overview event hook failed', {
|
|
114
|
+
cause: error,
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
if (!(result instanceof Error)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
logger.warn(`[memory-overview-plugin] ${formatPluginErrorWithStack(result)}`);
|
|
122
|
+
void notifyError(result, 'memory overview plugin event hook failed');
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
export { memoryOverviewPlugin };
|