Skip to content

Commit 41480e8

Browse files
authored
Merge pull request #6 from Tarquinen/fix/improve-tool-summary-and-model-import
Fix tool summary display, path shortening, and model import reliability
2 parents b5304e3 + c924db1 commit 41480e8

File tree

6 files changed

+108
-30
lines changed

6 files changed

+108
-30
lines changed

index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const plugin: Plugin = (async (ctx) => {
5656
const stateManager = new StateManager()
5757
const toolParametersCache = new Map<string, any>() // callID -> parameters
5858
const modelCache = new Map<string, { providerID: string; modelID: string }>() // sessionID -> model info
59-
const janitor = new Janitor(ctx.client, stateManager, logger, toolParametersCache, config.protectedTools, modelCache, config.model, config.showModelErrorToasts, config.pruningMode, config.pruning_summary)
59+
const janitor = new Janitor(ctx.client, stateManager, logger, toolParametersCache, config.protectedTools, modelCache, config.model, config.showModelErrorToasts, config.pruningMode, config.pruning_summary, ctx.directory)
6060

6161
const cacheToolParameters = (messages: any[], component: string) => {
6262
for (const message of messages) {

lib/deduplicator.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -151,16 +151,24 @@ export function extractParameterKey(metadata: { tool: string, parameters?: any }
151151
}
152152

153153
// ===== Directory/Search Tools =====
154-
if (tool === "list" && parameters.path) {
155-
return parameters.path
156-
}
157-
if (tool === "glob" && parameters.pattern) {
158-
const pathInfo = parameters.path ? ` in ${parameters.path}` : ""
159-
return `"${parameters.pattern}"${pathInfo}`
154+
if (tool === "list") {
155+
// path is optional, defaults to current directory
156+
return parameters.path || '(current directory)'
157+
}
158+
if (tool === "glob") {
159+
// pattern is required for glob
160+
if (parameters.pattern) {
161+
const pathInfo = parameters.path ? ` in ${parameters.path}` : ""
162+
return `"${parameters.pattern}"${pathInfo}`
163+
}
164+
return '(unknown pattern)'
160165
}
161-
if (tool === "grep" && parameters.pattern) {
162-
const pathInfo = parameters.path ? ` in ${parameters.path}` : ""
163-
return `"${parameters.pattern}"${pathInfo}`
166+
if (tool === "grep") {
167+
if (parameters.pattern) {
168+
const pathInfo = parameters.path ? ` in ${parameters.path}` : ""
169+
return `"${parameters.pattern}"${pathInfo}`
170+
}
171+
return '(unknown pattern)'
164172
}
165173

166174
// ===== Execution Tools =====
@@ -205,5 +213,10 @@ export function extractParameterKey(metadata: { tool: string, parameters?: any }
205213

206214
// ===== Fallback =====
207215
// For unknown tools, custom tools, or tools without extractable keys
208-
return JSON.stringify(parameters).substring(0, 50)
216+
// Check if parameters is empty or only has empty values
217+
const paramStr = JSON.stringify(parameters)
218+
if (paramStr === '{}' || paramStr === '[]' || paramStr === 'null') {
219+
return '' // Return empty to trigger (default) fallback in UI
220+
}
221+
return paramStr.substring(0, 50)
209222
}

lib/janitor.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ export class Janitor {
1717
private configModel?: string, // Format: "provider/model"
1818
private showModelErrorToasts: boolean = true, // Whether to show toast for model errors
1919
private pruningMode: "auto" | "smart" = "smart", // Pruning strategy
20-
private pruningSummary: "off" | "minimal" | "detailed" = "detailed" // UI summary display mode
20+
private pruningSummary: "off" | "minimal" | "detailed" = "detailed", // UI summary display mode
21+
private workingDirectory?: string // Current working directory for relative path display
2122
) { }
2223

2324
/**
@@ -490,6 +491,23 @@ export class Janitor {
490491
return `${nodeModulesMatch[1]}/${nodeModulesMatch[2]}`
491492
}
492493

494+
// Strip working directory to show relative paths
495+
if (this.workingDirectory) {
496+
// Try to match against the absolute working directory first
497+
if (path.startsWith(this.workingDirectory + '/')) {
498+
return path.slice(this.workingDirectory.length + 1)
499+
}
500+
501+
// Also try matching against ~ version of working directory
502+
const workingDirWithTilde = this.workingDirectory.startsWith(homeDir)
503+
? '~' + this.workingDirectory.slice(homeDir.length)
504+
: null
505+
506+
if (workingDirWithTilde && path.startsWith(workingDirWithTilde + '/')) {
507+
return path.slice(workingDirWithTilde.length + 1)
508+
}
509+
}
510+
493511
return path
494512
}
495513

@@ -553,6 +571,8 @@ export class Janitor {
553571
/**
554572
* Build a summary of tools by grouping them
555573
* Uses shared extractParameterKey logic for consistent parameter extraction
574+
*
575+
* Note: prunedIds may be in original case (from LLM) but toolMetadata uses lowercase keys
556576
*/
557577
private buildToolsSummary(prunedIds: string[], toolMetadata: Map<string, { tool: string, parameters?: any }>): Map<string, string[]> {
558578
const toolsSummary = new Map<string, string[]>()
@@ -564,7 +584,9 @@ export class Janitor {
564584
}
565585

566586
for (const prunedId of prunedIds) {
567-
const metadata = toolMetadata.get(prunedId)
587+
// Normalize ID to lowercase for lookup (toolMetadata uses lowercase keys)
588+
const normalizedId = prunedId.toLowerCase()
589+
const metadata = toolMetadata.get(normalizedId)
568590
if (metadata) {
569591
const toolName = metadata.tool
570592
if (!toolsSummary.has(toolName)) {
@@ -577,6 +599,10 @@ export class Janitor {
577599
// Apply path shortening and truncation for display
578600
const displayKey = truncate(this.shortenPath(paramKey), 80)
579601
toolsSummary.get(toolName)!.push(displayKey)
602+
} else {
603+
// For tools with no extractable parameter key, add a placeholder
604+
// This ensures the tool still shows up in the summary
605+
toolsSummary.get(toolName)!.push('(default)')
580606
}
581607
}
582608
}
@@ -736,17 +762,20 @@ export class Janitor {
736762
for (const param of params) {
737763
message += ` ${param}\n`
738764
}
739-
} else {
740-
// For tools with no specific params (like batch), just show the tool name and count
741-
const count = llmPrunedIds.filter(id => {
742-
const m = toolMetadata.get(id)
743-
return m && m.tool === toolName
744-
}).length
745-
if (count > 0) {
746-
message += ` ${toolName} (${count})\n`
747-
}
748765
}
749766
}
767+
768+
// Handle any tools that weren't found in metadata (edge case)
769+
const foundToolNames = new Set(toolsSummary.keys())
770+
const missingTools = llmPrunedIds.filter(id => {
771+
const normalizedId = id.toLowerCase()
772+
const metadata = toolMetadata.get(normalizedId)
773+
return !metadata || !foundToolNames.has(metadata.tool)
774+
})
775+
776+
if (missingTools.length > 0) {
777+
message += ` (${missingTools.length} tool${missingTools.length > 1 ? 's' : ''} with unknown metadata)\n`
778+
}
750779
}
751780

752781
await this.sendIgnoredMessage(sessionID, message.trim())

lib/model-selector.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,42 @@ function shouldSkipProvider(providerID: string): boolean {
6565
return SKIP_PROVIDERS.some(skip => normalized.includes(skip.toLowerCase()));
6666
}
6767

68+
/**
69+
* Attempts to import OpencodeAI with retry logic to handle plugin initialization timing issues.
70+
* Some providers (like openai via @openhax/codex) may not be fully initialized on first attempt.
71+
*/
72+
async function importOpencodeAI(logger?: Logger, maxRetries: number = 3, delayMs: number = 100): Promise<any> {
73+
let lastError: Error | undefined;
74+
75+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
76+
try {
77+
const { OpencodeAI } = await import('@tarquinen/opencode-auth-provider');
78+
return new OpencodeAI();
79+
} catch (error: any) {
80+
lastError = error;
81+
82+
// Check if this is the specific initialization error we're handling
83+
if (error.message?.includes('before initialization')) {
84+
logger?.debug('model-selector', `Import attempt ${attempt}/${maxRetries} failed, will retry`, {
85+
error: error.message
86+
});
87+
88+
if (attempt < maxRetries) {
89+
// Wait before retrying, with exponential backoff
90+
await new Promise(resolve => setTimeout(resolve, delayMs * attempt));
91+
continue;
92+
}
93+
}
94+
95+
// For other errors, don't retry
96+
throw error;
97+
}
98+
}
99+
100+
// All retries exhausted
101+
throw lastError;
102+
}
103+
68104
/**
69105
* Main model selection function with intelligent fallback logic
70106
*
@@ -85,9 +121,9 @@ export async function selectModel(
85121
): Promise<ModelSelectionResult> {
86122
logger?.info('model-selector', 'Model selection started', { currentModel, configModel });
87123

88-
// Lazy import - only load the 812KB auth provider package when actually needed
89-
const { OpencodeAI } = await import('@tarquinen/opencode-auth-provider');
90-
const opencodeAI = new OpencodeAI();
124+
// Lazy import with retry logic - handles plugin initialization timing issues
125+
// Some providers (like openai via @openhax/codex) may not be ready on first attempt
126+
const opencodeAI = await importOpencodeAI(logger);
91127

92128
let failedModelInfo: ModelInfo | undefined;
93129

@@ -178,10 +214,10 @@ export async function selectModel(
178214
logger?.info('model-selector', 'Available authenticated providers', {
179215
providerCount: availableProviderIDs.length,
180216
providerIDs: availableProviderIDs,
181-
providers: Object.entries(providers).map(([id, info]) => ({
217+
providers: Object.entries(providers).map(([id, info]: [string, any]) => ({
182218
id,
183219
source: info.source,
184-
name: info.info.name
220+
name: info.info?.name
185221
}))
186222
});
187223

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/package.json",
33
"name": "@tarquinen/opencode-dcp",
4-
"version": "0.3.5",
4+
"version": "0.3.6",
55
"type": "module",
66
"description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context",
77
"main": "./dist/index.js",

0 commit comments

Comments
 (0)