@jc01rho/opencode-smart-title 0.3.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 +74 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +124 -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/context.d.ts +7 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +93 -0
- package/dist/lib/context.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/lib/session.d.ts +5 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +43 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/title.d.ts +9 -0
- package/dist/lib/title.d.ts.map +1 -0
- package/dist/lib/title.js +201 -0
- package/dist/lib/title.js.map +1 -0
- package/dist/lib/types.d.ts +65 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +5 -0
- package/dist/lib/types.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 +58 -0
|
@@ -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":"AAAA;;;;;;;;GAQG;AAUH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACnD,MAAM,EAAE,YAAY;IACpB,SAAS,EAAE,kBAAkB;IAC7B,MAAM,EAAE,kBAAkB;IAC1B,QAAQ,EAAE,eAAe;IACzB,GAAG,EAAE,aAAa;IAClB,OAAO,EAAE,mBAAmB;IAC5B,GAAG,EAAE,eAAe;IACpB,QAAQ,EAAE,YAAY;CACzB,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACtB,QAAQ;IACR,WAAW;IACX,QAAQ;IACR,UAAU;IACV,KAAK;IACL,SAAS;IACT,KAAK;IACL,UAAU;CACb,CAAC;AAUF;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC7B,MAAe,EACf,WAAoB;IAEpB,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,yBAAyB,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;IAE3E,+EAA+E;IAC/E,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,mCAAmC,CAAC,CAAC;IACzE,MAAM,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC;IAEpC,IAAI,eAAsC,CAAC;IAE3C,IAAI,WAAW,EAAE,CAAC;QACd,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACpC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,0DAA0D,EAAE;gBACvF,WAAW;aACd,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACJ,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,KAAK,CAAA;YACnC,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,0CAA0C,EAAE;gBACxE,UAAU;gBACV,OAAO;aACV,CAAC,CAAC;YAEH,IAAI,CAAC;gBACD,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;gBACrE,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,6CAA6C,EAAE;oBAC1E,UAAU;oBACV,OAAO;iBACV,CAAC,CAAC;gBACH,OAAO;oBACH,KAAK;oBACL,SAAS,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE;oBAClC,MAAM,EAAE,QAAQ;oBAChB,MAAM,EAAE,mDAAmD;iBAC9D,CAAC;YACN,CAAC;YAAC,OAAO,KAAU,EAAE,CAAC;gBAClB,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,sDAAsD,EAAE;oBACnF,UAAU;oBACV,OAAO;oBACP,KAAK,EAAE,KAAK,CAAC,OAAO;iBACvB,CAAC,CAAC;gBACH,yBAAyB;gBACzB,eAAe,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;YAC9C,CAAC;QACL,CAAC;IACL,CAAC;IAED,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,4CAA4C,CAAC,CAAC;IAC9E,MAAM,SAAS,GAAG,MAAM,UAAU,CAAC,aAAa,EAAE,CAAC;IACnD,MAAM,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACpD,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,mCAAmC,EAAE;QAChE,aAAa,EAAE,oBAAoB,CAAC,MAAM;QAC1C,WAAW,EAAE,oBAAoB;QACjC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,EAAE;YACF,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;SACvB,CAAC,CAAC;KACN,CAAC,CAAC;IAEH,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,2CAA2C,EAAE;QACzE,aAAa,EAAE,iBAAiB;KACnC,CAAC,CAAC;IAEH,KAAK,MAAM,UAAU,IAAI,iBAAiB,EAAE,CAAC;QACzC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YACzB,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,YAAY,UAAU,sBAAsB,CAAC,CAAC;YAC9E,SAAS;QACb,CAAC;QAED,MAAM,eAAe,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;QACpD,IAAI,CAAC,eAAe,EAAE,CAAC;YACnB,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,YAAY,UAAU,iCAAiC,CAAC,CAAC;YACzF,SAAS;QACb,CAAC;QAED,MAAM,EAAE,KAAK,CAAC,gBAAgB,EAAE,cAAc,UAAU,IAAI,eAAe,EAAE,CAAC,CAAC;QAE/E,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,gBAAgB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAC7E,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,qCAAqC,EAAE;gBAClE,UAAU;gBACV,OAAO,EAAE,eAAe;aAC3B,CAAC,CAAC;YACH,OAAO;gBACH,KAAK;gBACL,SAAS,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE;gBACnD,MAAM,EAAE,UAAU;gBAClB,MAAM,EAAE,SAAS,UAAU,IAAI,eAAe,EAAE;gBAChD,WAAW,EAAE,eAAe;aAC/B,CAAC;QACN,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,mBAAmB,UAAU,IAAI,eAAe,EAAE,EAAE;gBAC/E,KAAK,EAAE,KAAK,CAAC,OAAO;aACvB,CAAC,CAAC;YACH,SAAS;QACb,CAAC;IACL,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,2FAA2F,CAAC,CAAC;AACjH,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OpenCodeClient } from "./types.js";
|
|
2
|
+
import type { Logger } from "./logger.js";
|
|
3
|
+
export declare function isSubagentSession(client: OpenCodeClient, sessionID: string, logger: Logger): Promise<boolean>;
|
|
4
|
+
export declare const sessionIdleCount: Map<string, number>;
|
|
5
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAKzC,wBAAsB,iBAAiB,CACnC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC,CAgDlB;AAED,eAAO,MAAM,gBAAgB,qBAA4B,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const subagentSessionCache = new Map();
|
|
2
|
+
const subagentSessionChecksInFlight = new Map();
|
|
3
|
+
export async function isSubagentSession(client, sessionID, logger) {
|
|
4
|
+
try {
|
|
5
|
+
const cached = subagentSessionCache.get(sessionID);
|
|
6
|
+
if (typeof cached === "boolean") {
|
|
7
|
+
return cached;
|
|
8
|
+
}
|
|
9
|
+
const existingCheck = subagentSessionChecksInFlight.get(sessionID);
|
|
10
|
+
if (existingCheck) {
|
|
11
|
+
return await existingCheck;
|
|
12
|
+
}
|
|
13
|
+
const checkPromise = (async () => {
|
|
14
|
+
const result = await client.session.get({ path: { id: sessionID } });
|
|
15
|
+
const isSubagent = Boolean(result.data?.parentID);
|
|
16
|
+
subagentSessionCache.set(sessionID, isSubagent);
|
|
17
|
+
if (isSubagent) {
|
|
18
|
+
logger.debug("subagent-check", "Detected subagent session, skipping title generation", {
|
|
19
|
+
sessionID,
|
|
20
|
+
parentID: result.data.parentID
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return isSubagent;
|
|
24
|
+
})();
|
|
25
|
+
subagentSessionChecksInFlight.set(sessionID, checkPromise);
|
|
26
|
+
const isSubagent = await checkPromise;
|
|
27
|
+
subagentSessionChecksInFlight.delete(sessionID);
|
|
28
|
+
if (isSubagent) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
subagentSessionChecksInFlight.delete(sessionID);
|
|
35
|
+
logger.error("subagent-check", "Failed to check if session is subagent", {
|
|
36
|
+
sessionID,
|
|
37
|
+
error: error.message
|
|
38
|
+
});
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export const sessionIdleCount = new Map();
|
|
43
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../lib/session.ts"],"names":[],"mappings":"AAGA,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAmB,CAAA;AACvD,MAAM,6BAA6B,GAAG,IAAI,GAAG,EAA4B,CAAA;AAEzE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACnC,MAAsB,EACtB,SAAiB,EACjB,MAAc;IAEd,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAElD,IAAI,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,MAAM,CAAA;QACjB,CAAC;QAED,MAAM,aAAa,GAAG,6BAA6B,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAElE,IAAI,aAAa,EAAE,CAAC;YAChB,OAAO,MAAM,aAAa,CAAA;QAC9B,CAAC;QAED,MAAM,YAAY,GAAG,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC,CAAA;YACpE,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;YAEjD,oBAAoB,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;YAE/C,IAAI,UAAU,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,sDAAsD,EAAE;oBACnF,SAAS;oBACT,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;iBACjC,CAAC,CAAA;YACN,CAAC;YAED,OAAO,UAAU,CAAA;QACrB,CAAC,CAAC,EAAE,CAAA;QAEJ,6BAA6B,CAAC,GAAG,CAAC,SAAS,EAAE,YAAY,CAAC,CAAA;QAE1D,MAAM,UAAU,GAAG,MAAM,YAAY,CAAA;QACrC,6BAA6B,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAE/C,IAAI,UAAU,EAAE,CAAC;YACb,OAAO,IAAI,CAAA;QACf,CAAC;QAED,OAAO,KAAK,CAAA;IAChB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QAClB,6BAA6B,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,wCAAwC,EAAE;YACrE,SAAS;YACT,KAAK,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAA;QACF,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAA"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OpenCodeClient } from "./types.js";
|
|
2
|
+
import type { Logger } from "./logger.js";
|
|
3
|
+
import type { PluginConfig } from "./config.js";
|
|
4
|
+
export type TerminalStatus = "idle" | "running";
|
|
5
|
+
export declare function updateTerminalTitle(directory: string | undefined, status: TerminalStatus, logger: Logger): void;
|
|
6
|
+
export declare function cleanTitle(raw: string): string;
|
|
7
|
+
export declare function generateTitleFromContext(context: string, configModel: string | undefined, logger: Logger, client: OpenCodeClient): Promise<string | null>;
|
|
8
|
+
export declare function updateSessionTitle(client: OpenCodeClient, sessionId: string, logger: Logger, config: PluginConfig): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=title.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title.d.ts","sourceRoot":"","sources":["../../lib/title.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM/C,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,CAAA;AA+C/C,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,MAAM,GACf,IAAI,CAoCN;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAW9C;AAED,wBAAsB,wBAAwB,CAC1C,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,cAAc,GACvB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsExB;AAED,wBAAsB,kBAAkB,CACpC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC,CA0Ef"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { extractSmartContext, formatContextForTitle, truncate } from "./context.js";
|
|
2
|
+
import { selectModel } from "./model-selector.js";
|
|
3
|
+
import { TITLE_PROMPT } from "../prompt.js";
|
|
4
|
+
import { basename } from "path";
|
|
5
|
+
let lastTerminalTitle = null;
|
|
6
|
+
const inFlightSessionTitleUpdates = new Set();
|
|
7
|
+
const queuedSessionTitleUpdates = new Set();
|
|
8
|
+
function sanitizeTerminalTitle(value) {
|
|
9
|
+
return value
|
|
10
|
+
.replace(/[\u0007\u001b]/g, "")
|
|
11
|
+
.replace(/[\r\n]+/g, " ")
|
|
12
|
+
.trim();
|
|
13
|
+
}
|
|
14
|
+
function formatTerminalTitle(directory, status) {
|
|
15
|
+
const projectName = directory ? basename(directory) || "opencode" : "opencode";
|
|
16
|
+
const decoratedStatus = status === "running" ? "🟢 running" : "💤 idle";
|
|
17
|
+
return sanitizeTerminalTitle(`${projectName} : ${decoratedStatus}`);
|
|
18
|
+
}
|
|
19
|
+
function getTerminalWriter() {
|
|
20
|
+
if (process.stdout.isTTY) {
|
|
21
|
+
return process.stdout;
|
|
22
|
+
}
|
|
23
|
+
if (process.stderr.isTTY) {
|
|
24
|
+
return process.stderr;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function wrapOscSequenceForTerminal(sequence) {
|
|
29
|
+
const term = process.env.TERM ?? "";
|
|
30
|
+
const tmux = process.env.TMUX;
|
|
31
|
+
const isScreenLike = term.startsWith("screen") || term.startsWith("tmux");
|
|
32
|
+
if (!tmux && !isScreenLike) {
|
|
33
|
+
return sequence;
|
|
34
|
+
}
|
|
35
|
+
return `\u001bPtmux;${sequence.replace(/\u001b/g, "\u001b\u001b")}\u001b\\`;
|
|
36
|
+
}
|
|
37
|
+
export function updateTerminalTitle(directory, status, logger) {
|
|
38
|
+
try {
|
|
39
|
+
const writer = getTerminalWriter();
|
|
40
|
+
if (!writer) {
|
|
41
|
+
logger.debug("terminal-title", "Skipping terminal title update because no TTY writer is available", {
|
|
42
|
+
status
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const title = formatTerminalTitle(directory, status);
|
|
47
|
+
if (!title || title === lastTerminalTitle) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const osc0 = wrapOscSequenceForTerminal(`\u001b]0;${title}\u0007`);
|
|
51
|
+
const osc2 = wrapOscSequenceForTerminal(`\u001b]2;${title}\u0007`);
|
|
52
|
+
writer.write(osc0);
|
|
53
|
+
writer.write(osc2);
|
|
54
|
+
lastTerminalTitle = title;
|
|
55
|
+
logger.debug("terminal-title", "Terminal title updated", {
|
|
56
|
+
title,
|
|
57
|
+
status,
|
|
58
|
+
writer: writer === process.stdout ? "stdout" : "stderr",
|
|
59
|
+
tmux: Boolean(process.env.TMUX)
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
logger.warn("terminal-title", "Failed to update terminal title", {
|
|
64
|
+
status,
|
|
65
|
+
error: error?.message ?? String(error)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function cleanTitle(raw) {
|
|
70
|
+
let cleaned = raw.replace(/<think>[\s\S]*?<\/think>\s*/g, "");
|
|
71
|
+
const lines = cleaned.split("\n").map(line => line.trim());
|
|
72
|
+
cleaned = lines.find(line => line.length > 0) || "Untitled";
|
|
73
|
+
if (cleaned.length > 100) {
|
|
74
|
+
cleaned = cleaned.substring(0, 97) + "...";
|
|
75
|
+
}
|
|
76
|
+
return cleaned;
|
|
77
|
+
}
|
|
78
|
+
export async function generateTitleFromContext(context, configModel, logger, client) {
|
|
79
|
+
try {
|
|
80
|
+
logger.debug('title-generation', 'Selecting model', { configModel });
|
|
81
|
+
const { model, modelInfo, source, reason, failedModel } = await selectModel(logger, configModel);
|
|
82
|
+
logger.info('title-generation', 'Model selected', {
|
|
83
|
+
providerID: modelInfo.providerID,
|
|
84
|
+
modelID: modelInfo.modelID,
|
|
85
|
+
source,
|
|
86
|
+
reason
|
|
87
|
+
});
|
|
88
|
+
if (failedModel) {
|
|
89
|
+
try {
|
|
90
|
+
await client.tui.showToast({
|
|
91
|
+
body: {
|
|
92
|
+
title: "Smart Title: Model fallback",
|
|
93
|
+
message: `${failedModel.providerID}/${failedModel.modelID} failed\nUsing ${modelInfo.providerID}/${modelInfo.modelID}`,
|
|
94
|
+
variant: "info",
|
|
95
|
+
duration: 5000
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
logger.info('title-generation', 'Toast notification shown for model fallback', {
|
|
99
|
+
failedModel,
|
|
100
|
+
selectedModel: modelInfo
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (toastError) {
|
|
104
|
+
logger.error('title-generation', 'Failed to show toast notification', {
|
|
105
|
+
error: toastError.message
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
logger.debug('title-generation', 'Generating title', {
|
|
110
|
+
contextLength: context.length
|
|
111
|
+
});
|
|
112
|
+
const { generateText } = await import('ai');
|
|
113
|
+
const result = await generateText({
|
|
114
|
+
model,
|
|
115
|
+
messages: [
|
|
116
|
+
{
|
|
117
|
+
role: 'user',
|
|
118
|
+
content: `${TITLE_PROMPT}\n\n<conversation>\n${context}\n</conversation>\n\nOutput the title now:`
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
});
|
|
122
|
+
const title = cleanTitle(result.text);
|
|
123
|
+
logger.info('title-generation', 'Title generated successfully', {
|
|
124
|
+
title,
|
|
125
|
+
titleLength: title.length,
|
|
126
|
+
rawLength: result.text.length
|
|
127
|
+
});
|
|
128
|
+
return title;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
logger.error('title-generation', 'Failed to generate title', {
|
|
132
|
+
error: error.message,
|
|
133
|
+
stack: error.stack
|
|
134
|
+
});
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export async function updateSessionTitle(client, sessionId, logger, config) {
|
|
139
|
+
if (inFlightSessionTitleUpdates.has(sessionId)) {
|
|
140
|
+
queuedSessionTitleUpdates.add(sessionId);
|
|
141
|
+
logger.debug('update-title', 'Skipping duplicate session title update while one is already in flight', {
|
|
142
|
+
sessionId
|
|
143
|
+
});
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
inFlightSessionTitleUpdates.add(sessionId);
|
|
147
|
+
try {
|
|
148
|
+
logger.info('update-title', 'Title update triggered', { sessionId });
|
|
149
|
+
const turns = await extractSmartContext(client, sessionId, logger);
|
|
150
|
+
if (turns.length === 0) {
|
|
151
|
+
logger.warn('update-title', 'No conversation turns found', { sessionId });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
logger.info('update-title', 'Context extracted', {
|
|
155
|
+
sessionId,
|
|
156
|
+
turnCount: turns.length
|
|
157
|
+
});
|
|
158
|
+
for (const turn of turns) {
|
|
159
|
+
logger.debug('update-title', 'Turn context', {
|
|
160
|
+
user: truncate(turn.user.text, 100),
|
|
161
|
+
hasAssistant: !!turn.assistant
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const context = formatContextForTitle(turns);
|
|
165
|
+
logger.debug('update-title', 'Formatted context prepared but fixed title override is enabled', {
|
|
166
|
+
sessionId,
|
|
167
|
+
contextLength: context.length,
|
|
168
|
+
configuredModel: config.model
|
|
169
|
+
});
|
|
170
|
+
const newTitle = "hi";
|
|
171
|
+
logger.info('update-title', 'Updating session with new title', {
|
|
172
|
+
sessionId,
|
|
173
|
+
title: newTitle
|
|
174
|
+
});
|
|
175
|
+
await client.session.update({
|
|
176
|
+
path: { id: sessionId },
|
|
177
|
+
body: { title: newTitle }
|
|
178
|
+
});
|
|
179
|
+
logger.info('update-title', 'Session title updated successfully', {
|
|
180
|
+
sessionId,
|
|
181
|
+
title: newTitle
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
logger.error('update-title', 'Failed to update session title', {
|
|
186
|
+
sessionId,
|
|
187
|
+
error: error.message,
|
|
188
|
+
stack: error.stack
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
inFlightSessionTitleUpdates.delete(sessionId);
|
|
193
|
+
if (queuedSessionTitleUpdates.delete(sessionId)) {
|
|
194
|
+
logger.debug('update-title', 'Re-running queued session title update after in-flight completion', {
|
|
195
|
+
sessionId
|
|
196
|
+
});
|
|
197
|
+
await updateSessionTitle(client, sessionId, logger, config);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=title.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"title.js","sourceRoot":"","sources":["../../lib/title.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Smart Title Plugin
|
|
3
|
+
*/
|
|
4
|
+
export interface OpenCodeClient {
|
|
5
|
+
session: {
|
|
6
|
+
messages: (params: {
|
|
7
|
+
path: {
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
}) => Promise<any>;
|
|
11
|
+
update: (params: {
|
|
12
|
+
path: {
|
|
13
|
+
id: string;
|
|
14
|
+
};
|
|
15
|
+
body: {
|
|
16
|
+
title: string;
|
|
17
|
+
};
|
|
18
|
+
}) => Promise<any>;
|
|
19
|
+
get: (params: {
|
|
20
|
+
path: {
|
|
21
|
+
id: string;
|
|
22
|
+
};
|
|
23
|
+
}) => Promise<any>;
|
|
24
|
+
};
|
|
25
|
+
tui: {
|
|
26
|
+
showToast: (params: {
|
|
27
|
+
body: {
|
|
28
|
+
title: string;
|
|
29
|
+
message: string;
|
|
30
|
+
variant: "info" | "success" | "warning" | "error";
|
|
31
|
+
duration: number;
|
|
32
|
+
};
|
|
33
|
+
}) => Promise<any>;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface ConversationTurn {
|
|
37
|
+
user: {
|
|
38
|
+
text: string;
|
|
39
|
+
time: number;
|
|
40
|
+
};
|
|
41
|
+
assistant?: {
|
|
42
|
+
first: string;
|
|
43
|
+
last: string;
|
|
44
|
+
time: number;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface MessagePart {
|
|
48
|
+
type: string;
|
|
49
|
+
text?: string;
|
|
50
|
+
synthetic?: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface Message {
|
|
53
|
+
info: {
|
|
54
|
+
id: string;
|
|
55
|
+
role: "user" | "assistant" | "system";
|
|
56
|
+
sessionID: string;
|
|
57
|
+
time: {
|
|
58
|
+
created: number;
|
|
59
|
+
completed?: number;
|
|
60
|
+
};
|
|
61
|
+
parentID?: string;
|
|
62
|
+
};
|
|
63
|
+
parts: MessagePart[];
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE;QACL,QAAQ,EAAE,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;QAC5D,MAAM,EAAE,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAA;aAAE,CAAC;YAAC,IAAI,EAAE;gBAAE,KAAK,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;QACnF,GAAG,EAAE,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE;gBAAE,EAAE,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAC1D,CAAA;IACD,GAAG,EAAE;QACD,SAAS,EAAE,CAAC,MAAM,EAAE;YAAE,IAAI,EAAE;gBAAE,KAAK,EAAE,MAAM,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAC;gBAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;gBAAC,QAAQ,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KACzJ,CAAA;CACJ;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE;QACF,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACf,CAAA;IACD,SAAS,CAAC,EAAE;QACR,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACf,CAAA;CACJ;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,MAAM,WAAW,OAAO;IACpB,IAAI,EAAE;QACF,EAAE,EAAE,MAAM,CAAA;QACV,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;QACrC,SAAS,EAAE,MAAM,CAAA;QACjB,IAAI,EAAE;YACF,OAAO,EAAE,MAAM,CAAA;YACf,SAAS,CAAC,EAAE,MAAM,CAAA;SACrB,CAAA;QACD,QAAQ,CAAC,EAAE,MAAM,CAAA;KACpB,CAAA;IACD,KAAK,EAAE,WAAW,EAAE,CAAA;CACvB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../lib/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
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 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,i3BAqBb,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 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,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "@jc01rho/opencode-smart-title",
|
|
4
|
+
"version": "0.3.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/jc01rho/opencode-smart-title.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/jc01rho/opencode-smart-title/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/jc01rho/opencode-smart-title#readme",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"author": "Dan Mindru",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@opencode-ai/plugin": ">=0.13.7"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@ai-sdk/openai-compatible": "^1.0.27",
|
|
44
|
+
"@tarquinen/opencode-auth-provider": "^0.1.7",
|
|
45
|
+
"ai": "^5.0.98",
|
|
46
|
+
"jsonc-parser": "^3.3.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@opencode-ai/plugin": ">=0.13.7",
|
|
50
|
+
"@types/node": "^24.10.1",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist/",
|
|
55
|
+
"README.md",
|
|
56
|
+
"LICENSE"
|
|
57
|
+
]
|
|
58
|
+
}
|