@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/src/commands/login.ts
CHANGED
|
@@ -129,6 +129,8 @@ const PROVIDER_POPULARITY_ORDER: string[] = [
|
|
|
129
129
|
'xai',
|
|
130
130
|
'groq',
|
|
131
131
|
'deepseek',
|
|
132
|
+
'opencode',
|
|
133
|
+
'opencode-go',
|
|
132
134
|
'mistral',
|
|
133
135
|
'openrouter',
|
|
134
136
|
'fireworks-ai',
|
|
@@ -137,12 +139,12 @@ const PROVIDER_POPULARITY_ORDER: string[] = [
|
|
|
137
139
|
'azure',
|
|
138
140
|
'google-vertex',
|
|
139
141
|
'google-vertex-anthropic',
|
|
140
|
-
'cohere',
|
|
142
|
+
// 'cohere',
|
|
141
143
|
'cerebras',
|
|
142
|
-
'perplexity',
|
|
144
|
+
// 'perplexity',
|
|
143
145
|
'cloudflare-workers-ai',
|
|
144
|
-
'novita-ai',
|
|
145
|
-
'huggingface',
|
|
146
|
+
// 'novita-ai',
|
|
147
|
+
// 'huggingface',
|
|
146
148
|
'deepinfra',
|
|
147
149
|
'github-models',
|
|
148
150
|
'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
|
//
|
|
@@ -18,8 +17,6 @@
|
|
|
18
17
|
|
|
19
18
|
import type { Plugin } from '@opencode-ai/plugin'
|
|
20
19
|
import crypto from 'node:crypto'
|
|
21
|
-
import fs from 'node:fs'
|
|
22
|
-
import path from 'node:path'
|
|
23
20
|
import * as errore from 'errore'
|
|
24
21
|
import {
|
|
25
22
|
createPluginLogger,
|
|
@@ -29,7 +26,6 @@ import {
|
|
|
29
26
|
import { setDataDir } from './config.js'
|
|
30
27
|
import { initSentry, notifyError } from './sentry.js'
|
|
31
28
|
import { execAsync } from './exec-async.js'
|
|
32
|
-
import { condenseMemoryMd } from './condense-memory.js'
|
|
33
29
|
import {
|
|
34
30
|
ONBOARDING_TUTORIAL_INSTRUCTIONS,
|
|
35
31
|
TUTORIAL_WELCOME_TEXT,
|
|
@@ -49,7 +45,6 @@ type GitState = {
|
|
|
49
45
|
// All per-session mutable state in one place. One Map entry, one delete.
|
|
50
46
|
type SessionState = {
|
|
51
47
|
gitState: GitState | undefined
|
|
52
|
-
memoryInjected: boolean
|
|
53
48
|
lastMemoryReminderAssistantMessageId: string | undefined
|
|
54
49
|
tutorialInjected: boolean
|
|
55
50
|
// Last directory observed via session.get(). Refreshed on each real user
|
|
@@ -60,17 +55,6 @@ type SessionState = {
|
|
|
60
55
|
announcedDirectory: string | undefined
|
|
61
56
|
}
|
|
62
57
|
|
|
63
|
-
function createSessionState(): SessionState {
|
|
64
|
-
return {
|
|
65
|
-
gitState: undefined,
|
|
66
|
-
memoryInjected: false,
|
|
67
|
-
lastMemoryReminderAssistantMessageId: undefined,
|
|
68
|
-
tutorialInjected: false,
|
|
69
|
-
resolvedDirectory: undefined,
|
|
70
|
-
announcedDirectory: undefined,
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
58
|
// Minimal type for the opencode plugin client (v1 SDK style with path objects).
|
|
75
59
|
type PluginClient = {
|
|
76
60
|
session: {
|
|
@@ -147,10 +131,6 @@ type AssistantMessageInfo = {
|
|
|
147
131
|
tokens?: AssistantTokenUsage
|
|
148
132
|
}
|
|
149
133
|
|
|
150
|
-
function getOutputTokenTotal(tokens: AssistantTokenUsage): number {
|
|
151
|
-
return Math.max(0, tokens.output + tokens.reasoning)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
134
|
export function shouldInjectMemoryReminderFromLatestAssistant({
|
|
155
135
|
lastMemoryReminderAssistantMessageId,
|
|
156
136
|
latestAssistantMessage,
|
|
@@ -175,7 +155,10 @@ export function shouldInjectMemoryReminderFromLatestAssistant({
|
|
|
175
155
|
if (lastMemoryReminderAssistantMessageId === latestAssistantMessage.id) {
|
|
176
156
|
return { inject: false }
|
|
177
157
|
}
|
|
178
|
-
const outputTokens =
|
|
158
|
+
const outputTokens = Math.max(
|
|
159
|
+
0,
|
|
160
|
+
latestAssistantMessage.tokens.output + latestAssistantMessage.tokens.reasoning,
|
|
161
|
+
)
|
|
179
162
|
if (outputTokens < threshold) {
|
|
180
163
|
return { inject: false }
|
|
181
164
|
}
|
|
@@ -311,7 +294,13 @@ const contextAwarenessPlugin: Plugin = async ({ directory, client }) => {
|
|
|
311
294
|
if (existing) {
|
|
312
295
|
return existing
|
|
313
296
|
}
|
|
314
|
-
const state =
|
|
297
|
+
const state: SessionState = {
|
|
298
|
+
gitState: undefined,
|
|
299
|
+
lastMemoryReminderAssistantMessageId: undefined,
|
|
300
|
+
tutorialInjected: false,
|
|
301
|
+
resolvedDirectory: undefined,
|
|
302
|
+
announcedDirectory: undefined,
|
|
303
|
+
}
|
|
315
304
|
sessions.set(sessionID, state)
|
|
316
305
|
return state
|
|
317
306
|
}
|
|
@@ -412,26 +401,6 @@ const contextAwarenessPlugin: Plugin = async ({ directory, client }) => {
|
|
|
412
401
|
})
|
|
413
402
|
}
|
|
414
403
|
|
|
415
|
-
// -- MEMORY.md injection --
|
|
416
|
-
if (!state.memoryInjected) {
|
|
417
|
-
state.memoryInjected = true
|
|
418
|
-
const memoryPath = path.join(effectiveDirectory, 'MEMORY.md')
|
|
419
|
-
const memoryContent = await fs.promises
|
|
420
|
-
.readFile(memoryPath, 'utf-8')
|
|
421
|
-
.catch(() => null)
|
|
422
|
-
if (memoryContent) {
|
|
423
|
-
const condensed = condenseMemoryMd(memoryContent)
|
|
424
|
-
output.parts.push({
|
|
425
|
-
id: `prt_${crypto.randomUUID()}`,
|
|
426
|
-
sessionID,
|
|
427
|
-
messageID,
|
|
428
|
-
type: 'text' as const,
|
|
429
|
-
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>`,
|
|
430
|
-
synthetic: true,
|
|
431
|
-
})
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
404
|
const memoryReminder = shouldInjectMemoryReminderFromLatestAssistant({
|
|
436
405
|
lastMemoryReminderAssistantMessageId:
|
|
437
406
|
state.lastMemoryReminderAssistantMessageId,
|
|
@@ -5,12 +5,14 @@
|
|
|
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
|
|
|
12
13
|
export { ipcToolsPlugin } from './ipc-tools-plugin.js'
|
|
13
14
|
export { contextAwarenessPlugin } from './context-awareness-plugin.js'
|
|
15
|
+
export { memoryOverviewPlugin } from './memory-overview-plugin.js'
|
|
14
16
|
export { interruptOpencodeSessionOnUserMessage } from './opencode-interrupt-plugin.js'
|
|
15
17
|
export { systemPromptDriftPlugin } from './system-prompt-drift-plugin.js'
|
|
16
18
|
export { anthropicAuthPlugin } from './anthropic-auth-plugin.js'
|
|
@@ -0,0 +1,161 @@
|
|
|
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
|
+
|
|
6
|
+
import crypto from 'node:crypto'
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import path from 'node:path'
|
|
9
|
+
import type { Plugin } from '@opencode-ai/plugin'
|
|
10
|
+
import * as errore from 'errore'
|
|
11
|
+
import {
|
|
12
|
+
createPluginLogger,
|
|
13
|
+
formatPluginErrorWithStack,
|
|
14
|
+
setPluginLogFilePath,
|
|
15
|
+
} from './plugin-logger.js'
|
|
16
|
+
import { condenseMemoryMd } from './condense-memory.js'
|
|
17
|
+
import { initSentry, notifyError } from './sentry.js'
|
|
18
|
+
|
|
19
|
+
const logger = createPluginLogger('OPENCODE')
|
|
20
|
+
|
|
21
|
+
type SessionState = {
|
|
22
|
+
hasFrozenOverview: boolean
|
|
23
|
+
frozenOverviewText: string | null
|
|
24
|
+
injected: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function createSessionState(): SessionState {
|
|
28
|
+
return {
|
|
29
|
+
hasFrozenOverview: false,
|
|
30
|
+
frozenOverviewText: null,
|
|
31
|
+
injected: false,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildMemoryOverviewReminder({ condensed }: { condensed: string }): string {
|
|
36
|
+
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>`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function freezeMemoryOverview({
|
|
40
|
+
directory,
|
|
41
|
+
state,
|
|
42
|
+
}: {
|
|
43
|
+
directory: string
|
|
44
|
+
state: SessionState
|
|
45
|
+
}): Promise<string | null> {
|
|
46
|
+
if (state.hasFrozenOverview) {
|
|
47
|
+
return state.frozenOverviewText
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const memoryPath = path.join(directory, 'MEMORY.md')
|
|
51
|
+
const memoryContentResult = await fs.promises.readFile(memoryPath, 'utf-8').catch(() => {
|
|
52
|
+
return null
|
|
53
|
+
})
|
|
54
|
+
if (!memoryContentResult) {
|
|
55
|
+
state.hasFrozenOverview = true
|
|
56
|
+
state.frozenOverviewText = null
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const condensed = condenseMemoryMd(memoryContentResult)
|
|
61
|
+
state.hasFrozenOverview = true
|
|
62
|
+
state.frozenOverviewText = buildMemoryOverviewReminder({ condensed })
|
|
63
|
+
return state.frozenOverviewText
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const memoryOverviewPlugin: Plugin = async ({ directory }) => {
|
|
67
|
+
initSentry()
|
|
68
|
+
|
|
69
|
+
const dataDir = process.env.KIMAKI_DATA_DIR
|
|
70
|
+
if (dataDir) {
|
|
71
|
+
setPluginLogFilePath(dataDir)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sessions = new Map<string, SessionState>()
|
|
75
|
+
|
|
76
|
+
function getOrCreateSessionState({ sessionID }: { sessionID: string }): SessionState {
|
|
77
|
+
const existing = sessions.get(sessionID)
|
|
78
|
+
if (existing) {
|
|
79
|
+
return existing
|
|
80
|
+
}
|
|
81
|
+
const state = createSessionState()
|
|
82
|
+
sessions.set(sessionID, state)
|
|
83
|
+
return state
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
'chat.message': async (input, output) => {
|
|
88
|
+
const result = await errore.tryAsync({
|
|
89
|
+
try: async () => {
|
|
90
|
+
const state = getOrCreateSessionState({ sessionID: input.sessionID })
|
|
91
|
+
if (state.injected) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const firstPart = output.parts.find((part) => {
|
|
96
|
+
if (part.type !== 'text') {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
return part.synthetic !== true
|
|
100
|
+
})
|
|
101
|
+
if (!firstPart || firstPart.type !== 'text' || firstPart.text.trim().length === 0) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const overviewText = await freezeMemoryOverview({ directory, state })
|
|
106
|
+
state.injected = true
|
|
107
|
+
if (!overviewText) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
output.parts.push({
|
|
112
|
+
id: `prt_${crypto.randomUUID()}`,
|
|
113
|
+
sessionID: input.sessionID,
|
|
114
|
+
messageID: firstPart.messageID,
|
|
115
|
+
type: 'text' as const,
|
|
116
|
+
text: overviewText,
|
|
117
|
+
synthetic: true,
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
catch: (error) => {
|
|
121
|
+
return new Error('memory overview chat.message hook failed', {
|
|
122
|
+
cause: error,
|
|
123
|
+
})
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
if (!(result instanceof Error)) {
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
logger.warn(
|
|
130
|
+
`[memory-overview-plugin] ${formatPluginErrorWithStack(result)}`,
|
|
131
|
+
)
|
|
132
|
+
void notifyError(result, 'memory overview plugin chat.message hook failed')
|
|
133
|
+
},
|
|
134
|
+
event: async ({ event }) => {
|
|
135
|
+
const result = await errore.tryAsync({
|
|
136
|
+
try: async () => {
|
|
137
|
+
if (event.type !== 'session.deleted') {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
const id = event.properties?.info?.id
|
|
141
|
+
if (!id) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
sessions.delete(id)
|
|
145
|
+
},
|
|
146
|
+
catch: (error) => {
|
|
147
|
+
return new Error('memory overview event hook failed', {
|
|
148
|
+
cause: error,
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
})
|
|
152
|
+
if (!(result instanceof Error)) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
logger.warn(`[memory-overview-plugin] ${formatPluginErrorWithStack(result)}`)
|
|
156
|
+
void notifyError(result, 'memory overview plugin event hook failed')
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { memoryOverviewPlugin }
|