opencode-smart-title 1.0.0
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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +366 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +22 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +144 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/logger.d.ts +12 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +55 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/model-selector.d.ts +36 -0
- package/dist/lib/model-selector.d.ts.map +1 -0
- package/dist/lib/model-selector.js +133 -0
- package/dist/lib/model-selector.js.map +1 -0
- package/dist/prompt.d.ts +5 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +26 -0
- package/dist/prompt.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Smart Title Plugin
|
|
2
|
+
|
|
3
|
+
Auto-generates meaningful session titles for your OpenCode conversations using AI.
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
- Watches your conversation and generates short, descriptive titles
|
|
8
|
+
- Updates automatically when the session becomes idle (you stop typing)
|
|
9
|
+
- Uses OpenCode's unified auth - no API keys needed
|
|
10
|
+
- Works with any authenticated AI provider
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @tarquinen/opencode-smart-title
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Add to `~/.config/opencode/opencode.json`:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"plugin": ["@tarquinen/opencode-smart-title"]
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Configuration
|
|
27
|
+
|
|
28
|
+
The plugin supports both global and project-level configuration:
|
|
29
|
+
|
|
30
|
+
- **Global:** `~/.config/opencode/smart-title.jsonc` - Applies to all sessions
|
|
31
|
+
- **Project:** `.opencode/smart-title.jsonc` - Overrides global config
|
|
32
|
+
|
|
33
|
+
The plugin creates a default global config on first run.
|
|
34
|
+
|
|
35
|
+
```jsonc
|
|
36
|
+
{
|
|
37
|
+
// Enable or disable the plugin
|
|
38
|
+
"enabled": true,
|
|
39
|
+
|
|
40
|
+
// Enable debug logging
|
|
41
|
+
"debug": false,
|
|
42
|
+
|
|
43
|
+
// Optional: Use a specific model (otherwise uses smart fallbacks)
|
|
44
|
+
// "model": "anthropic/claude-haiku-4-5",
|
|
45
|
+
|
|
46
|
+
// Update title every N idle events (1 = every time you pause)
|
|
47
|
+
"updateThreshold": 1
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Title Plugin for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* Automatically generates meaningful session titles based on conversation content.
|
|
5
|
+
* Uses OpenCode auth provider for unified authentication across all AI providers.
|
|
6
|
+
*
|
|
7
|
+
* Configuration: ~/.config/opencode/smart-title.jsonc
|
|
8
|
+
* Logs: ~/.config/opencode/logs/smart-title/YYYY-MM-DD.log
|
|
9
|
+
*
|
|
10
|
+
* NOTE: ai package is lazily imported to avoid loading the 2.8MB package during
|
|
11
|
+
* plugin initialization. The package is only loaded when title generation is needed.
|
|
12
|
+
*/
|
|
13
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
14
|
+
/**
|
|
15
|
+
* Smart Title Plugin
|
|
16
|
+
* Automatically updates session titles using AI and smart context selection
|
|
17
|
+
*/
|
|
18
|
+
declare const SmartTitlePlugin: Plugin;
|
|
19
|
+
export default SmartTitlePlugin;
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAwZjD;;;GAGG;AACH,QAAA,MAAM,gBAAgB,EAAE,MAwEvB,CAAA;AAED,eAAe,gBAAgB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Title Plugin for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* Automatically generates meaningful session titles based on conversation content.
|
|
5
|
+
* Uses OpenCode auth provider for unified authentication across all AI providers.
|
|
6
|
+
*
|
|
7
|
+
* Configuration: ~/.config/opencode/smart-title.jsonc
|
|
8
|
+
* Logs: ~/.config/opencode/logs/smart-title/YYYY-MM-DD.log
|
|
9
|
+
*
|
|
10
|
+
* NOTE: ai package is lazily imported to avoid loading the 2.8MB package during
|
|
11
|
+
* plugin initialization. The package is only loaded when title generation is needed.
|
|
12
|
+
*/
|
|
13
|
+
import { getConfig } from "./lib/config.js";
|
|
14
|
+
import { Logger } from "./lib/logger.js";
|
|
15
|
+
import { selectModel } from "./lib/model-selector.js";
|
|
16
|
+
import { TITLE_PROMPT } from "./prompt.js";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
/**
|
|
20
|
+
* Checks if a session is a subagent (child session)
|
|
21
|
+
* Subagent sessions should skip title generation
|
|
22
|
+
*/
|
|
23
|
+
async function isSubagentSession(client, sessionID, logger) {
|
|
24
|
+
try {
|
|
25
|
+
const result = await client.session.get({ path: { id: sessionID } });
|
|
26
|
+
if (result.data?.parentID) {
|
|
27
|
+
logger.debug("subagent-check", "Detected subagent session, skipping title generation", {
|
|
28
|
+
sessionID,
|
|
29
|
+
parentID: result.data.parentID
|
|
30
|
+
});
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
logger.error("subagent-check", "Failed to check if session is subagent", {
|
|
37
|
+
sessionID,
|
|
38
|
+
error: error.message
|
|
39
|
+
});
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Track idle event count per session for threshold-based updates
|
|
44
|
+
const sessionIdleCount = new Map();
|
|
45
|
+
/**
|
|
46
|
+
* Extract only text content from message parts, excluding synthetic content
|
|
47
|
+
*/
|
|
48
|
+
function extractTextOnly(parts) {
|
|
49
|
+
// Only extract text parts, exclude synthetic content
|
|
50
|
+
const textParts = parts.filter(part => part.type === "text" && !part.synthetic);
|
|
51
|
+
return textParts
|
|
52
|
+
.map(part => part.text || '')
|
|
53
|
+
.join("\n")
|
|
54
|
+
.trim();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract smart context from conversation
|
|
58
|
+
* Returns first and last assistant messages per turn to minimize token usage
|
|
59
|
+
*/
|
|
60
|
+
async function extractSmartContext(client, sessionId, logger) {
|
|
61
|
+
logger.debug('context-extraction', 'Fetching session messages', { sessionId });
|
|
62
|
+
// Get all messages
|
|
63
|
+
const { data: messages } = await client.session.messages({
|
|
64
|
+
path: { id: sessionId }
|
|
65
|
+
});
|
|
66
|
+
logger.debug('context-extraction', 'Messages fetched', {
|
|
67
|
+
sessionId,
|
|
68
|
+
totalMessages: messages.length
|
|
69
|
+
});
|
|
70
|
+
// Filter out system messages
|
|
71
|
+
const conversationMessages = messages.filter((msg) => msg.info.role === "user" || msg.info.role === "assistant");
|
|
72
|
+
logger.debug('context-extraction', 'Filtered conversation messages', {
|
|
73
|
+
sessionId,
|
|
74
|
+
conversationMessages: conversationMessages.length
|
|
75
|
+
});
|
|
76
|
+
// Group into turns
|
|
77
|
+
const turns = [];
|
|
78
|
+
let currentTurn = null;
|
|
79
|
+
let assistantMessagesInTurn = [];
|
|
80
|
+
for (const msg of conversationMessages) {
|
|
81
|
+
if (msg.info.role === "user") {
|
|
82
|
+
// Save previous turn if exists
|
|
83
|
+
if (currentTurn && assistantMessagesInTurn.length > 0) {
|
|
84
|
+
currentTurn.assistant = {
|
|
85
|
+
first: assistantMessagesInTurn[0].text,
|
|
86
|
+
last: assistantMessagesInTurn[assistantMessagesInTurn.length - 1].text,
|
|
87
|
+
time: assistantMessagesInTurn[0].time
|
|
88
|
+
};
|
|
89
|
+
turns.push(currentTurn);
|
|
90
|
+
}
|
|
91
|
+
// Start new turn
|
|
92
|
+
const userText = extractTextOnly(msg.parts);
|
|
93
|
+
currentTurn = {
|
|
94
|
+
user: {
|
|
95
|
+
text: userText,
|
|
96
|
+
time: msg.info.time.created
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
assistantMessagesInTurn = [];
|
|
100
|
+
}
|
|
101
|
+
else if (msg.info.role === "assistant") {
|
|
102
|
+
// Collect assistant messages for this turn
|
|
103
|
+
const assistantText = extractTextOnly(msg.parts);
|
|
104
|
+
if (assistantText.length > 0) {
|
|
105
|
+
assistantMessagesInTurn.push({
|
|
106
|
+
text: assistantText,
|
|
107
|
+
time: msg.info.time.created
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Don't forget the last turn (might not have assistant response yet)
|
|
113
|
+
if (currentTurn) {
|
|
114
|
+
if (assistantMessagesInTurn.length > 0) {
|
|
115
|
+
currentTurn.assistant = {
|
|
116
|
+
first: assistantMessagesInTurn[0].text,
|
|
117
|
+
last: assistantMessagesInTurn[assistantMessagesInTurn.length - 1].text,
|
|
118
|
+
time: assistantMessagesInTurn[0].time
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Include the turn even if it doesn't have an assistant response yet
|
|
122
|
+
// This ensures the triggering user message is included in the context
|
|
123
|
+
turns.push(currentTurn);
|
|
124
|
+
}
|
|
125
|
+
logger.debug('context-extraction', 'Extracted conversation turns', {
|
|
126
|
+
sessionId,
|
|
127
|
+
turnCount: turns.length
|
|
128
|
+
});
|
|
129
|
+
return turns;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Truncate text to specified length with ellipsis
|
|
133
|
+
*/
|
|
134
|
+
function truncate(text, maxLength) {
|
|
135
|
+
if (text.length <= maxLength)
|
|
136
|
+
return text;
|
|
137
|
+
return text.substring(0, maxLength) + "...";
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Format conversation context for title generation
|
|
141
|
+
*/
|
|
142
|
+
function formatContextForTitle(turns) {
|
|
143
|
+
const formatted = [];
|
|
144
|
+
for (const turn of turns) {
|
|
145
|
+
// Add user message
|
|
146
|
+
formatted.push(`User: ${turn.user.text}`);
|
|
147
|
+
formatted.push(""); // Empty line for readability
|
|
148
|
+
// Add assistant messages if they exist
|
|
149
|
+
if (turn.assistant) {
|
|
150
|
+
if (turn.assistant.first === turn.assistant.last) {
|
|
151
|
+
// Only one message - don't duplicate
|
|
152
|
+
formatted.push(`Assistant: ${turn.assistant.first}`);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
// Multiple messages - show first and last
|
|
156
|
+
formatted.push(`Assistant (initial): ${turn.assistant.first}`);
|
|
157
|
+
formatted.push(`Assistant (final): ${turn.assistant.last}`);
|
|
158
|
+
}
|
|
159
|
+
formatted.push(""); // Empty line between turns
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return formatted.join("\n");
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Clean AI-generated title
|
|
166
|
+
*/
|
|
167
|
+
function cleanTitle(raw) {
|
|
168
|
+
// Remove thinking tags
|
|
169
|
+
let cleaned = raw.replace(/<think>[\s\S]*?<\/think>\s*/g, "");
|
|
170
|
+
// Get first non-empty line
|
|
171
|
+
const lines = cleaned.split("\n").map(line => line.trim());
|
|
172
|
+
cleaned = lines.find(line => line.length > 0) || "Untitled";
|
|
173
|
+
// Truncate if too long
|
|
174
|
+
if (cleaned.length > 100) {
|
|
175
|
+
cleaned = cleaned.substring(0, 97) + "...";
|
|
176
|
+
}
|
|
177
|
+
return cleaned;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Generate title from conversation context using AI
|
|
181
|
+
*/
|
|
182
|
+
async function generateTitleFromContext(context, configModel, logger, client) {
|
|
183
|
+
try {
|
|
184
|
+
logger.debug('title-generation', 'Selecting model', { configModel });
|
|
185
|
+
const { model, modelInfo, source, reason, failedModel } = await selectModel(logger, configModel);
|
|
186
|
+
logger.info('title-generation', 'Model selected', {
|
|
187
|
+
providerID: modelInfo.providerID,
|
|
188
|
+
modelID: modelInfo.modelID,
|
|
189
|
+
source,
|
|
190
|
+
reason
|
|
191
|
+
});
|
|
192
|
+
// Show toast if we had to fallback from a configured model
|
|
193
|
+
if (failedModel) {
|
|
194
|
+
try {
|
|
195
|
+
await client.tui.showToast({
|
|
196
|
+
body: {
|
|
197
|
+
title: "Smart Title: Model fallback",
|
|
198
|
+
message: `${failedModel.providerID}/${failedModel.modelID} failed\nUsing ${modelInfo.providerID}/${modelInfo.modelID}`,
|
|
199
|
+
variant: "info",
|
|
200
|
+
duration: 5000
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
logger.info('title-generation', 'Toast notification shown for model fallback', {
|
|
204
|
+
failedModel,
|
|
205
|
+
selectedModel: modelInfo
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
catch (toastError) {
|
|
209
|
+
logger.error('title-generation', 'Failed to show toast notification', {
|
|
210
|
+
error: toastError.message
|
|
211
|
+
});
|
|
212
|
+
// Don't fail the whole operation if toast fails
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
logger.debug('title-generation', 'Generating title', {
|
|
216
|
+
contextLength: context.length
|
|
217
|
+
});
|
|
218
|
+
// Lazy import - only load the 2.8MB ai package when actually needed
|
|
219
|
+
const { generateText } = await import('ai');
|
|
220
|
+
const result = await generateText({
|
|
221
|
+
model,
|
|
222
|
+
messages: [
|
|
223
|
+
{
|
|
224
|
+
role: 'user',
|
|
225
|
+
content: `${TITLE_PROMPT}\n\n<conversation>\n${context}\n</conversation>\n\nOutput the title now:`
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
const title = cleanTitle(result.text);
|
|
230
|
+
logger.info('title-generation', 'Title generated successfully', {
|
|
231
|
+
title,
|
|
232
|
+
titleLength: title.length,
|
|
233
|
+
rawLength: result.text.length
|
|
234
|
+
});
|
|
235
|
+
return title;
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
logger.error('title-generation', 'Failed to generate title', {
|
|
239
|
+
error: error.message,
|
|
240
|
+
stack: error.stack
|
|
241
|
+
});
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Update session title with smart context
|
|
247
|
+
*/
|
|
248
|
+
async function updateSessionTitle(client, sessionId, logger, config) {
|
|
249
|
+
try {
|
|
250
|
+
logger.info('update-title', 'Title update triggered', { sessionId });
|
|
251
|
+
// Extract smart context
|
|
252
|
+
const turns = await extractSmartContext(client, sessionId, logger);
|
|
253
|
+
// Need at least one turn to generate title
|
|
254
|
+
if (turns.length === 0) {
|
|
255
|
+
logger.warn('update-title', 'No conversation turns found', { sessionId });
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
logger.info('update-title', 'Context extracted', {
|
|
259
|
+
sessionId,
|
|
260
|
+
turnCount: turns.length
|
|
261
|
+
});
|
|
262
|
+
// Log truncated context for debugging
|
|
263
|
+
for (const turn of turns) {
|
|
264
|
+
logger.debug('update-title', 'Turn context', {
|
|
265
|
+
user: truncate(turn.user.text, 100),
|
|
266
|
+
hasAssistant: !!turn.assistant
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
// Format context
|
|
270
|
+
const context = formatContextForTitle(turns);
|
|
271
|
+
// Generate title
|
|
272
|
+
const newTitle = await generateTitleFromContext(context, config.model, logger, client);
|
|
273
|
+
if (!newTitle) {
|
|
274
|
+
logger.warn('update-title', 'Title generation returned null', { sessionId });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
logger.info('update-title', 'Updating session with new title', {
|
|
278
|
+
sessionId,
|
|
279
|
+
title: newTitle
|
|
280
|
+
});
|
|
281
|
+
// Update session
|
|
282
|
+
await client.session.update({
|
|
283
|
+
path: { id: sessionId },
|
|
284
|
+
body: { title: newTitle }
|
|
285
|
+
});
|
|
286
|
+
logger.info('update-title', 'Session title updated successfully', {
|
|
287
|
+
sessionId,
|
|
288
|
+
title: newTitle
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
logger.error('update-title', 'Failed to update session title', {
|
|
293
|
+
sessionId,
|
|
294
|
+
error: error.message,
|
|
295
|
+
stack: error.stack
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Smart Title Plugin
|
|
301
|
+
* Automatically updates session titles using AI and smart context selection
|
|
302
|
+
*/
|
|
303
|
+
const SmartTitlePlugin = async (ctx) => {
|
|
304
|
+
const config = getConfig(ctx);
|
|
305
|
+
// Exit early if plugin is disabled
|
|
306
|
+
if (!config.enabled) {
|
|
307
|
+
return {};
|
|
308
|
+
}
|
|
309
|
+
const logger = new Logger(config.debug);
|
|
310
|
+
const { client } = ctx;
|
|
311
|
+
logger.info('plugin', 'Smart Title plugin initialized', {
|
|
312
|
+
enabled: config.enabled,
|
|
313
|
+
debug: config.debug,
|
|
314
|
+
model: config.model,
|
|
315
|
+
updateThreshold: config.updateThreshold,
|
|
316
|
+
globalConfigFile: join(homedir(), ".config", "opencode", "smart-title.jsonc"),
|
|
317
|
+
projectConfigFile: ctx.directory ? join(ctx.directory, ".opencode", "smart-title.jsonc") : "N/A",
|
|
318
|
+
logDirectory: join(homedir(), ".config", "opencode", "logs", "smart-title")
|
|
319
|
+
});
|
|
320
|
+
return {
|
|
321
|
+
event: async ({ event }) => {
|
|
322
|
+
// @ts-ignore - session.status is not yet in the SDK types
|
|
323
|
+
if (event.type === "session.status" && event.properties.status.type === "idle") {
|
|
324
|
+
// @ts-ignore
|
|
325
|
+
const sessionId = event.properties.sessionID;
|
|
326
|
+
logger.debug('event', 'Session became idle', { sessionId });
|
|
327
|
+
// Skip if this is a subagent session
|
|
328
|
+
if (await isSubagentSession(client, sessionId, logger)) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
// Increment idle count for this session
|
|
332
|
+
const currentCount = (sessionIdleCount.get(sessionId) || 0) + 1;
|
|
333
|
+
sessionIdleCount.set(sessionId, currentCount);
|
|
334
|
+
logger.debug('event', 'Idle count updated', {
|
|
335
|
+
sessionId,
|
|
336
|
+
currentCount,
|
|
337
|
+
threshold: config.updateThreshold
|
|
338
|
+
});
|
|
339
|
+
// Only update title if we've reached the threshold
|
|
340
|
+
if (currentCount % config.updateThreshold !== 0) {
|
|
341
|
+
logger.debug('event', 'Threshold not reached, skipping title update', {
|
|
342
|
+
sessionId,
|
|
343
|
+
currentCount,
|
|
344
|
+
threshold: config.updateThreshold
|
|
345
|
+
});
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
logger.info('event', 'Threshold reached, triggering title update for idle session', {
|
|
349
|
+
sessionId,
|
|
350
|
+
currentCount,
|
|
351
|
+
threshold: config.updateThreshold
|
|
352
|
+
});
|
|
353
|
+
// Fire and forget - don't block the event handler
|
|
354
|
+
updateSessionTitle(client, sessionId, logger, config).catch((error) => {
|
|
355
|
+
logger.error('event', 'Title update failed', {
|
|
356
|
+
sessionId,
|
|
357
|
+
error: error.message,
|
|
358
|
+
stack: error.stack
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
export default SmartTitlePlugin;
|
|
366
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
2
|
+
export interface PluginConfig {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
debug: boolean;
|
|
5
|
+
model?: string;
|
|
6
|
+
updateThreshold: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Loads configuration with support for both global and project-level configs
|
|
10
|
+
*
|
|
11
|
+
* Config resolution order:
|
|
12
|
+
* 1. Start with default config
|
|
13
|
+
* 2. Merge with global config (~/.config/opencode/smart-title.jsonc)
|
|
14
|
+
* 3. Merge with project config (.opencode/smart-title.jsonc) if found
|
|
15
|
+
*
|
|
16
|
+
* Project config overrides global config, which overrides defaults.
|
|
17
|
+
*
|
|
18
|
+
* @param ctx - Plugin input context (optional). If provided, will search for project-level config.
|
|
19
|
+
* @returns Merged configuration
|
|
20
|
+
*/
|
|
21
|
+
export declare function getConfig(ctx?: PluginInput): PluginConfig;
|
|
22
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../lib/config.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEtD,MAAM,WAAW,YAAY;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,EAAE,MAAM,CAAA;CAC1B;AAuGD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,WAAW,GAAG,YAAY,CA+BzD"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// lib/config.ts
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { parse } from 'jsonc-parser';
|
|
6
|
+
const defaultConfig = {
|
|
7
|
+
enabled: true,
|
|
8
|
+
debug: false,
|
|
9
|
+
updateThreshold: 1
|
|
10
|
+
};
|
|
11
|
+
const GLOBAL_CONFIG_DIR = join(homedir(), '.config', 'opencode');
|
|
12
|
+
const GLOBAL_CONFIG_PATH_JSONC = join(GLOBAL_CONFIG_DIR, 'smart-title.jsonc');
|
|
13
|
+
const GLOBAL_CONFIG_PATH_JSON = join(GLOBAL_CONFIG_DIR, 'smart-title.json');
|
|
14
|
+
/**
|
|
15
|
+
* Searches for .opencode directory starting from current directory and going up
|
|
16
|
+
* Returns the path to .opencode directory if found, null otherwise
|
|
17
|
+
*/
|
|
18
|
+
function findOpencodeDir(startDir) {
|
|
19
|
+
let current = startDir;
|
|
20
|
+
while (current !== '/') {
|
|
21
|
+
const candidate = join(current, '.opencode');
|
|
22
|
+
if (existsSync(candidate) && statSync(candidate).isDirectory()) {
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
const parent = dirname(current);
|
|
26
|
+
if (parent === current)
|
|
27
|
+
break; // Reached root
|
|
28
|
+
current = parent;
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Determines which config file to use (prefers .jsonc, falls back to .json)
|
|
34
|
+
* Checks both project-level and global configs
|
|
35
|
+
*/
|
|
36
|
+
function getConfigPaths(ctx) {
|
|
37
|
+
// Global config paths
|
|
38
|
+
let globalPath = null;
|
|
39
|
+
if (existsSync(GLOBAL_CONFIG_PATH_JSONC)) {
|
|
40
|
+
globalPath = GLOBAL_CONFIG_PATH_JSONC;
|
|
41
|
+
}
|
|
42
|
+
else if (existsSync(GLOBAL_CONFIG_PATH_JSON)) {
|
|
43
|
+
globalPath = GLOBAL_CONFIG_PATH_JSON;
|
|
44
|
+
}
|
|
45
|
+
// Project config paths (if context provided)
|
|
46
|
+
let projectPath = null;
|
|
47
|
+
if (ctx?.directory) {
|
|
48
|
+
const opencodeDir = findOpencodeDir(ctx.directory);
|
|
49
|
+
if (opencodeDir) {
|
|
50
|
+
const projectJsonc = join(opencodeDir, 'smart-title.jsonc');
|
|
51
|
+
const projectJson = join(opencodeDir, 'smart-title.json');
|
|
52
|
+
if (existsSync(projectJsonc)) {
|
|
53
|
+
projectPath = projectJsonc;
|
|
54
|
+
}
|
|
55
|
+
else if (existsSync(projectJson)) {
|
|
56
|
+
projectPath = projectJson;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { global: globalPath, project: projectPath };
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Creates the default configuration file with helpful comments
|
|
64
|
+
*/
|
|
65
|
+
function createDefaultConfig() {
|
|
66
|
+
// Ensure the directory exists
|
|
67
|
+
if (!existsSync(GLOBAL_CONFIG_DIR)) {
|
|
68
|
+
mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
const configContent = `{
|
|
71
|
+
// Enable or disable the Smart Title plugin
|
|
72
|
+
"enabled": true,
|
|
73
|
+
|
|
74
|
+
// Enable debug logging to ~/.config/opencode/logs/smart-title/YYYY-MM-DD.log
|
|
75
|
+
"debug": false,
|
|
76
|
+
|
|
77
|
+
// Optional: Specify a model to use for title generation
|
|
78
|
+
// Format: "provider/model" (same as agent model config in opencode.jsonc)
|
|
79
|
+
// If not specified, will use intelligent fallbacks from authenticated providers
|
|
80
|
+
// Examples: "anthropic/claude-haiku-4-5", "openai/gpt-5-mini"
|
|
81
|
+
// "model": "anthropic/claude-haiku-4-5",
|
|
82
|
+
|
|
83
|
+
// Update title every N idle events (default: 1)
|
|
84
|
+
"updateThreshold": 1
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
writeFileSync(GLOBAL_CONFIG_PATH_JSONC, configContent, 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Loads a single config file and parses it
|
|
91
|
+
*/
|
|
92
|
+
function loadConfigFile(configPath) {
|
|
93
|
+
try {
|
|
94
|
+
const fileContent = readFileSync(configPath, 'utf-8');
|
|
95
|
+
return parse(fileContent);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Loads configuration with support for both global and project-level configs
|
|
103
|
+
*
|
|
104
|
+
* Config resolution order:
|
|
105
|
+
* 1. Start with default config
|
|
106
|
+
* 2. Merge with global config (~/.config/opencode/smart-title.jsonc)
|
|
107
|
+
* 3. Merge with project config (.opencode/smart-title.jsonc) if found
|
|
108
|
+
*
|
|
109
|
+
* Project config overrides global config, which overrides defaults.
|
|
110
|
+
*
|
|
111
|
+
* @param ctx - Plugin input context (optional). If provided, will search for project-level config.
|
|
112
|
+
* @returns Merged configuration
|
|
113
|
+
*/
|
|
114
|
+
export function getConfig(ctx) {
|
|
115
|
+
let config = { ...defaultConfig };
|
|
116
|
+
const configPaths = getConfigPaths(ctx);
|
|
117
|
+
if (configPaths.global) {
|
|
118
|
+
const globalConfig = loadConfigFile(configPaths.global);
|
|
119
|
+
if (globalConfig) {
|
|
120
|
+
config = {
|
|
121
|
+
enabled: globalConfig.enabled ?? config.enabled,
|
|
122
|
+
debug: globalConfig.debug ?? config.debug,
|
|
123
|
+
model: globalConfig.model ?? config.model,
|
|
124
|
+
updateThreshold: globalConfig.updateThreshold ?? config.updateThreshold
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
createDefaultConfig();
|
|
130
|
+
}
|
|
131
|
+
if (configPaths.project) {
|
|
132
|
+
const projectConfig = loadConfigFile(configPaths.project);
|
|
133
|
+
if (projectConfig) {
|
|
134
|
+
config = {
|
|
135
|
+
enabled: projectConfig.enabled ?? config.enabled,
|
|
136
|
+
debug: projectConfig.debug ?? config.debug,
|
|
137
|
+
model: projectConfig.model ?? config.model,
|
|
138
|
+
updateThreshold: projectConfig.updateThreshold ?? config.updateThreshold
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return config;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../lib/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private logDir;
|
|
3
|
+
private enabled;
|
|
4
|
+
constructor(enabled: boolean);
|
|
5
|
+
private ensureLogDir;
|
|
6
|
+
private write;
|
|
7
|
+
info(component: string, message: string, data?: any): Promise<void>;
|
|
8
|
+
debug(component: string, message: string, data?: any): Promise<void>;
|
|
9
|
+
warn(component: string, message: string, data?: any): Promise<void>;
|
|
10
|
+
error(component: string, message: string, data?: any): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../lib/logger.ts"],"names":[],"mappings":"AAMA,qBAAa,MAAM;IACf,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,OAAO;YAQd,YAAY;YAMZ,KAAK;IAwBnB,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAInD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAIpD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAInD,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;CAGvD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// lib/logger.ts
|
|
2
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import { homedir } from "os";
|
|
6
|
+
export class Logger {
|
|
7
|
+
logDir;
|
|
8
|
+
enabled;
|
|
9
|
+
constructor(enabled) {
|
|
10
|
+
this.enabled = enabled;
|
|
11
|
+
// Always save logs to ~/.config/opencode/logs/smart-title/ regardless of installation method
|
|
12
|
+
// This ensures users can find logs in a consistent location
|
|
13
|
+
const opencodeConfigDir = join(homedir(), ".config", "opencode");
|
|
14
|
+
this.logDir = join(opencodeConfigDir, "logs", "smart-title");
|
|
15
|
+
}
|
|
16
|
+
async ensureLogDir() {
|
|
17
|
+
if (!existsSync(this.logDir)) {
|
|
18
|
+
await mkdir(this.logDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async write(level, component, message, data) {
|
|
22
|
+
if (!this.enabled)
|
|
23
|
+
return;
|
|
24
|
+
try {
|
|
25
|
+
await this.ensureLogDir();
|
|
26
|
+
const timestamp = new Date().toISOString();
|
|
27
|
+
const logEntry = {
|
|
28
|
+
timestamp,
|
|
29
|
+
level,
|
|
30
|
+
component,
|
|
31
|
+
message,
|
|
32
|
+
...(data && { data })
|
|
33
|
+
};
|
|
34
|
+
const logFile = join(this.logDir, `${new Date().toISOString().split('T')[0]}.log`);
|
|
35
|
+
const logLine = JSON.stringify(logEntry) + "\n";
|
|
36
|
+
await writeFile(logFile, logLine, { flag: "a" });
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
// Silently fail - don't break the plugin if logging fails
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
info(component, message, data) {
|
|
43
|
+
return this.write("INFO", component, message, data);
|
|
44
|
+
}
|
|
45
|
+
debug(component, message, data) {
|
|
46
|
+
return this.write("DEBUG", component, message, data);
|
|
47
|
+
}
|
|
48
|
+
warn(component, message, data) {
|
|
49
|
+
return this.write("WARN", component, message, data);
|
|
50
|
+
}
|
|
51
|
+
error(component, message, data) {
|
|
52
|
+
return this.write("ERROR", component, message, data);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../lib/logger.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAChB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAA;AAE5B,MAAM,OAAO,MAAM;IACP,MAAM,CAAQ;IACd,OAAO,CAAS;IAExB,YAAY,OAAgB;QACxB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,6FAA6F;QAC7F,4DAA4D;QAC5D,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC,CAAA;QAChE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,EAAE,aAAa,CAAC,CAAA;IAChE,CAAC;IAEO,KAAK,CAAC,YAAY;QACtB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QACjD,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,SAAiB,EAAE,OAAe,EAAE,IAAU;QAC7E,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAM;QAEzB,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA;YAEzB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAC1C,MAAM,QAAQ,GAAG;gBACb,SAAS;gBACT,KAAK;gBACL,SAAS;gBACT,OAAO;gBACP,GAAG,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC;aACxB,CAAA;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;YAClF,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAA;YAE/C,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAA;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,0DAA0D;QAC9D,CAAC;IACL,CAAC;IAED,IAAI,CAAC,SAAiB,EAAE,OAAe,EAAE,IAAU;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACvD,CAAC;IAED,KAAK,CAAC,SAAiB,EAAE,OAAe,EAAE,IAAU;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACxD,CAAC;IAED,IAAI,CAAC,SAAiB,EAAE,OAAe,EAAE,IAAU;QAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACvD,CAAC;IAED,KAAK,CAAC,SAAiB,EAAE,OAAe,EAAE,IAAU;QAChD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAA;IACxD,CAAC;CACJ"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Selection and Fallback Logic for Smart Title
|
|
3
|
+
*
|
|
4
|
+
* This module handles intelligent model selection for title generation.
|
|
5
|
+
* It tries models in order from a predefined fallback list.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: OpencodeAI is lazily imported to avoid loading the 812KB package during
|
|
8
|
+
* plugin initialization. The package is only loaded when model selection is needed.
|
|
9
|
+
*/
|
|
10
|
+
import type { LanguageModel } from 'ai';
|
|
11
|
+
import type { Logger } from './logger';
|
|
12
|
+
export interface ModelInfo {
|
|
13
|
+
providerID: string;
|
|
14
|
+
modelID: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const FALLBACK_MODELS: Record<string, string>;
|
|
17
|
+
export interface ModelSelectionResult {
|
|
18
|
+
model: LanguageModel;
|
|
19
|
+
modelInfo: ModelInfo;
|
|
20
|
+
source: 'config' | 'fallback';
|
|
21
|
+
reason?: string;
|
|
22
|
+
failedModel?: ModelInfo;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Main model selection function with intelligent fallback logic
|
|
26
|
+
*
|
|
27
|
+
* Selection hierarchy:
|
|
28
|
+
* 1. Try the config-specified model (if provided)
|
|
29
|
+
* 2. Try fallback models from authenticated providers (in priority order)
|
|
30
|
+
*
|
|
31
|
+
* @param logger - Logger instance for debug output
|
|
32
|
+
* @param configModel - Model string in "provider/model" format (e.g., "anthropic/claude-haiku-4-5")
|
|
33
|
+
* @returns Selected model with metadata about the selection
|
|
34
|
+
*/
|
|
35
|
+
export declare function selectModel(logger?: Logger, configModel?: string): Promise<ModelSelectionResult>;
|
|
36
|
+
//# sourceMappingURL=model-selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-selector.d.ts","sourceRoot":"","sources":["../../lib/model-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,WAAW,SAAS;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASlD,CAAC;AAaF,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,SAAS,CAAC;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,WAAW,CAC7B,MAAM,CAAC,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,oBAAoB,CAAC,CAmG/B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model Selection and Fallback Logic for Smart Title
|
|
3
|
+
*
|
|
4
|
+
* This module handles intelligent model selection for title generation.
|
|
5
|
+
* It tries models in order from a predefined fallback list.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: OpencodeAI is lazily imported to avoid loading the 812KB package during
|
|
8
|
+
* plugin initialization. The package is only loaded when model selection is needed.
|
|
9
|
+
*/
|
|
10
|
+
export const FALLBACK_MODELS = {
|
|
11
|
+
openai: 'gpt-5-mini',
|
|
12
|
+
anthropic: 'claude-haiku-4-5',
|
|
13
|
+
google: 'gemini-2.5-flash',
|
|
14
|
+
deepseek: 'deepseek-chat',
|
|
15
|
+
xai: 'grok-4-fast',
|
|
16
|
+
alibaba: 'qwen3-coder-flash',
|
|
17
|
+
zai: 'glm-4.5-flash',
|
|
18
|
+
opencode: 'big-pickle'
|
|
19
|
+
};
|
|
20
|
+
const PROVIDER_PRIORITY = [
|
|
21
|
+
'openai',
|
|
22
|
+
'anthropic',
|
|
23
|
+
'google',
|
|
24
|
+
'deepseek',
|
|
25
|
+
'xai',
|
|
26
|
+
'alibaba',
|
|
27
|
+
'zai',
|
|
28
|
+
'opencode'
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Main model selection function with intelligent fallback logic
|
|
32
|
+
*
|
|
33
|
+
* Selection hierarchy:
|
|
34
|
+
* 1. Try the config-specified model (if provided)
|
|
35
|
+
* 2. Try fallback models from authenticated providers (in priority order)
|
|
36
|
+
*
|
|
37
|
+
* @param logger - Logger instance for debug output
|
|
38
|
+
* @param configModel - Model string in "provider/model" format (e.g., "anthropic/claude-haiku-4-5")
|
|
39
|
+
* @returns Selected model with metadata about the selection
|
|
40
|
+
*/
|
|
41
|
+
export async function selectModel(logger, configModel) {
|
|
42
|
+
logger?.info('model-selector', 'Model selection started', { configModel });
|
|
43
|
+
// Lazy import - only load the 812KB auth provider package when actually needed
|
|
44
|
+
const { OpencodeAI } = await import('@tarquinen/opencode-auth-provider');
|
|
45
|
+
const opencodeAI = new OpencodeAI();
|
|
46
|
+
let failedModelInfo;
|
|
47
|
+
if (configModel) {
|
|
48
|
+
const parts = configModel.split('/');
|
|
49
|
+
if (parts.length !== 2) {
|
|
50
|
+
logger?.warn('model-selector', '✗ Invalid config model format, expected "provider/model"', {
|
|
51
|
+
configModel
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const [providerID, modelID] = parts;
|
|
56
|
+
logger?.debug('model-selector', 'Attempting to use config-specified model', {
|
|
57
|
+
providerID,
|
|
58
|
+
modelID
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const model = await opencodeAI.getLanguageModel(providerID, modelID);
|
|
62
|
+
logger?.info('model-selector', '✓ Successfully using config-specified model', {
|
|
63
|
+
providerID,
|
|
64
|
+
modelID
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
model,
|
|
68
|
+
modelInfo: { providerID, modelID },
|
|
69
|
+
source: 'config',
|
|
70
|
+
reason: 'Using model specified in smart-title.jsonc config'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
logger?.warn('model-selector', '✗ Failed to use config-specified model, falling back', {
|
|
75
|
+
providerID,
|
|
76
|
+
modelID,
|
|
77
|
+
error: error.message
|
|
78
|
+
});
|
|
79
|
+
// Track the failed model
|
|
80
|
+
failedModelInfo = { providerID, modelID };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
logger?.debug('model-selector', 'Fetching available authenticated providers');
|
|
85
|
+
const providers = await opencodeAI.listProviders();
|
|
86
|
+
const availableProviderIDs = Object.keys(providers);
|
|
87
|
+
logger?.info('model-selector', 'Available authenticated providers', {
|
|
88
|
+
providerCount: availableProviderIDs.length,
|
|
89
|
+
providerIDs: availableProviderIDs,
|
|
90
|
+
providers: Object.entries(providers).map(([id, info]) => ({
|
|
91
|
+
id,
|
|
92
|
+
source: info.source,
|
|
93
|
+
name: info.info.name
|
|
94
|
+
}))
|
|
95
|
+
});
|
|
96
|
+
logger?.debug('model-selector', 'Attempting fallback models from providers', {
|
|
97
|
+
priorityOrder: PROVIDER_PRIORITY
|
|
98
|
+
});
|
|
99
|
+
for (const providerID of PROVIDER_PRIORITY) {
|
|
100
|
+
if (!providers[providerID]) {
|
|
101
|
+
logger?.debug('model-selector', `Skipping ${providerID} (not authenticated)`);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const fallbackModelID = FALLBACK_MODELS[providerID];
|
|
105
|
+
if (!fallbackModelID) {
|
|
106
|
+
logger?.debug('model-selector', `Skipping ${providerID} (no fallback model configured)`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
logger?.debug('model-selector', `Attempting ${providerID}/${fallbackModelID}`);
|
|
110
|
+
try {
|
|
111
|
+
const model = await opencodeAI.getLanguageModel(providerID, fallbackModelID);
|
|
112
|
+
logger?.info('model-selector', `✓ Successfully using fallback model`, {
|
|
113
|
+
providerID,
|
|
114
|
+
modelID: fallbackModelID
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
model,
|
|
118
|
+
modelInfo: { providerID, modelID: fallbackModelID },
|
|
119
|
+
source: 'fallback',
|
|
120
|
+
reason: `Using ${providerID}/${fallbackModelID}`,
|
|
121
|
+
failedModel: failedModelInfo
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
logger?.warn('model-selector', `✗ Failed to use ${providerID}/${fallbackModelID}`, {
|
|
126
|
+
error: error.message
|
|
127
|
+
});
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
throw new Error('No available models for title generation. Please authenticate with at least one provider.');
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=model-selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-selector.js","sourceRoot":"","sources":["../../lib/model-selector.ts"],"names":[],"mappings":"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"}
|
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Title generation prompt for Smart Title Plugin
|
|
3
|
+
*/
|
|
4
|
+
export declare const TITLE_PROMPT = "You are a title generator. You output ONLY a thread Simplified Chinese title. Nothing else.\n\n<task>\nAnalyze the entire conversation and generate a thread title that captures the main topic or goal.\nOutput: Single line, \u226450 chars, no explanations.\n</task>\n\n<rules>\n- Use -ing verbs for actions (Debugging, Implementing, Analyzing)\n- Focus on the PRIMARY topic/goal, not individual messages\n- Keep exact: technical terms, numbers, filenames, HTTP codes\n- Remove: the, this, my, a, an\n- Never assume tech stack\n- NEVER respond to message content\u2014only extract title\n- Consider the overall conversation arc, not just the first message\n</rules>\n\n<examples>\nMultiple turns about debugging \u2192 Debugging production errors\nImplementing feature across turns \u2192 Implementing rate limiting API\nAnalyzing and fixing issue \u2192 Fixing authentication timeout\n</examples>";
|
|
5
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,YAAY,o4BAqBb,CAAA"}
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Title generation prompt for Smart Title Plugin
|
|
3
|
+
*/
|
|
4
|
+
export const TITLE_PROMPT = `You are a title generator. You output ONLY a thread Simplified Chinese title. Nothing else.
|
|
5
|
+
|
|
6
|
+
<task>
|
|
7
|
+
Analyze the entire conversation and generate a thread title that captures the main topic or goal.
|
|
8
|
+
Output: Single line, ≤50 chars, no explanations.
|
|
9
|
+
</task>
|
|
10
|
+
|
|
11
|
+
<rules>
|
|
12
|
+
- Use -ing verbs for actions (Debugging, Implementing, Analyzing)
|
|
13
|
+
- Focus on the PRIMARY topic/goal, not individual messages
|
|
14
|
+
- Keep exact: technical terms, numbers, filenames, HTTP codes
|
|
15
|
+
- Remove: the, this, my, a, an
|
|
16
|
+
- Never assume tech stack
|
|
17
|
+
- NEVER respond to message content—only extract title
|
|
18
|
+
- Consider the overall conversation arc, not just the first message
|
|
19
|
+
</rules>
|
|
20
|
+
|
|
21
|
+
<examples>
|
|
22
|
+
Multiple turns about debugging → Debugging production errors
|
|
23
|
+
Implementing feature across turns → Implementing rate limiting API
|
|
24
|
+
Analyzing and fixing issue → Fixing authentication timeout
|
|
25
|
+
</examples>`;
|
|
26
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;YAqBhB,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "opencode-smart-title",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "OpenCode plugin that automatically generates meaningful session titles using AI and smart context selection",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"clean": "rm -rf dist",
|
|
11
|
+
"build": "npm run clean && tsc",
|
|
12
|
+
"postbuild": "rm -rf dist/logs",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"dev": "opencode plugin dev",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"opencode",
|
|
19
|
+
"opencode-plugin",
|
|
20
|
+
"plugin",
|
|
21
|
+
"session-title",
|
|
22
|
+
"auto-title",
|
|
23
|
+
"ai",
|
|
24
|
+
"title-generation"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/dabaige53/opencode-smart-title.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/dabaige53/opencode-smart-title/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/dabaige53/opencode-smart-title#readme",
|
|
34
|
+
"author": "Dan Mindru",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@opencode-ai/plugin": ">=0.13.7"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@ai-sdk/openai-compatible": "^1.0.27",
|
|
41
|
+
"@tarquinen/opencode-auth-provider": "^0.1.7",
|
|
42
|
+
"ai": "^5.0.98",
|
|
43
|
+
"jsonc-parser": "^3.3.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@opencode-ai/plugin": ">=0.13.7",
|
|
47
|
+
"@types/node": "^24.10.1",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"files": [
|
|
51
|
+
"dist/",
|
|
52
|
+
"README.md",
|
|
53
|
+
"LICENSE"
|
|
54
|
+
]
|
|
55
|
+
}
|