|
1 | 1 | import * as fs from 'fs'; |
2 | 2 | import * as path from 'path'; |
3 | 3 | import type { ModelUsage, ChatTurn } from '../types'; |
4 | | -import type { IEcosystemAdapter } from '../ecosystemAdapter'; |
5 | | -import type { IDiscoverableEcosystem, DiscoveryResult, CandidatePath } from '../ecosystemAdapter'; |
| 4 | +import type { IEcosystemAdapter, IDiscoverableEcosystem, IAnalyzableEcosystem, DiscoveryResult, CandidatePath, UsageAnalysisAdapterContext } from '../ecosystemAdapter'; |
6 | 5 | import { OpenCodeDataAccess } from '../opencode'; |
7 | 6 | import { createEmptyContextRefs } from '../tokenEstimation'; |
| 7 | +import { createEmptySessionUsageAnalysis, applyModelTierClassification } from '../usageAnalysis'; |
8 | 8 |
|
9 | | -export class OpenCodeAdapter implements IEcosystemAdapter, IDiscoverableEcosystem { |
| 9 | +export class OpenCodeAdapter implements IEcosystemAdapter, IDiscoverableEcosystem, IAnalyzableEcosystem { |
10 | 10 | readonly id = 'opencode'; |
11 | 11 | readonly displayName = 'OpenCode'; |
12 | 12 |
|
@@ -212,4 +212,47 @@ export class OpenCodeAdapter implements IEcosystemAdapter, IDiscoverableEcosyste |
212 | 212 | } |
213 | 213 | return { turns }; |
214 | 214 | } |
| 215 | + |
| 216 | + async getSyncData(sessionFile: string): Promise<{ tokens: number; interactions: number; modelUsage: ModelUsage; timestamp: number }> { |
| 217 | + return this.openCode.getOpenCodeSessionData(sessionFile); |
| 218 | + } |
| 219 | + |
| 220 | + async analyzeUsage(sessionFile: string, ctx: UsageAnalysisAdapterContext): Promise<import('../types').SessionUsageAnalysis> { |
| 221 | + const analysis = createEmptySessionUsageAnalysis(); |
| 222 | + const messages = await this.openCode.getOpenCodeMessagesForSession(sessionFile); |
| 223 | + if (messages.length > 0) { |
| 224 | + const models: string[] = []; |
| 225 | + for (const msg of messages) { |
| 226 | + if (msg.role === 'user') { |
| 227 | + const mode = msg.agent || 'agent'; |
| 228 | + if (mode === 'build' || mode === 'agent') { analysis.modeUsage.agent++; } |
| 229 | + else if (mode === 'ask') { analysis.modeUsage.ask++; } |
| 230 | + else if (mode === 'edit') { analysis.modeUsage.edit++; } |
| 231 | + else { analysis.modeUsage.agent++; } |
| 232 | + } |
| 233 | + if (msg.role === 'assistant') { |
| 234 | + const model = msg.modelID || 'unknown'; |
| 235 | + models.push(model); |
| 236 | + const parts = await this.openCode.getOpenCodePartsForMessage(msg.id); |
| 237 | + for (const part of parts) { |
| 238 | + if (part.type === 'tool' && part.tool) { |
| 239 | + analysis.toolCalls.total++; |
| 240 | + const toolName = part.tool; |
| 241 | + analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; |
| 242 | + } |
| 243 | + } |
| 244 | + } |
| 245 | + } |
| 246 | + const uniqueModels = [...new Set(models)]; |
| 247 | + analysis.modelSwitching.uniqueModels = uniqueModels; |
| 248 | + analysis.modelSwitching.modelCount = uniqueModels.length; |
| 249 | + analysis.modelSwitching.totalRequests = models.length; |
| 250 | + let switchCount = 0; |
| 251 | + for (let i = 1; i < models.length; i++) { |
| 252 | + if (models[i] !== models[i - 1]) { switchCount++; } |
| 253 | + } |
| 254 | + analysis.modelSwitching.switchCount = switchCount; |
| 255 | + } |
| 256 | + return analysis; |
| 257 | + } |
215 | 258 | } |
0 commit comments