diff --git a/packages/kilo-telemetry/src/telemetry.ts b/packages/kilo-telemetry/src/telemetry.ts index 94a8d946ce8..2e037e3aa6b 100644 --- a/packages/kilo-telemetry/src/telemetry.ts +++ b/packages/kilo-telemetry/src/telemetry.ts @@ -154,6 +154,9 @@ export namespace Telemetry { // LLM export function trackLlmCompletion(properties: { taskId?: string + mode?: "review" + feature?: "code_reviews" + command?: "local-review" | "local-review-uncommitted" apiProvider: string modelId: string inputTokens?: number diff --git a/packages/opencode/src/kilocode/session/processor.ts b/packages/opencode/src/kilocode/session/processor.ts index 24029d3f16e..1632585bb1d 100644 --- a/packages/opencode/src/kilocode/session/processor.ts +++ b/packages/opencode/src/kilocode/session/processor.ts @@ -8,12 +8,37 @@ import * as Log from "@opencode-ai/core/util/log" import { Effect } from "effect" import { Flag } from "@opencode-ai/core/flag/flag" +export type ReviewTelemetry = { + mode: "review" + feature: "code_reviews" + command: "local-review" | "local-review-uncommitted" +} + export namespace KiloSessionProcessor { const log = Log.create({ service: "session.processor.kilo" }) export const OUTPUT_LENGTH_WARNING = "The model hit its output limit, so this response may be incomplete." export const REASONING_LENGTH_WARNING = "The model hit its output limit while reasoning and produced no actionable output. Try disabling reasoning or increasing the output limit." + export function reviewTelemetry(command: string): ReviewTelemetry | undefined { + if (command === "local-review" || command === "local-review-uncommitted") { + return { mode: "review", feature: "code_reviews", command } + } + } + + export function extractReviewTelemetry(parts: MessageV2.Part[]): ReviewTelemetry | undefined { + for (const part of parts) { + if (part.type !== "text") continue + const meta: Record | undefined = part.metadata + if (!meta) continue + if (meta.mode !== "review") continue + if (meta.feature !== "code_reviews") continue + const command = meta.command + if (command !== "local-review" && command !== "local-review-uncommitted") continue + return { mode: "review", feature: "code_reviews", command } + } + } + /** * Track LLM completion telemetry for a finished step. * Only fires if at least one token bucket is non-zero. @@ -24,11 +49,13 @@ export namespace KiloSessionProcessor { tokens: { input: number; output: number; cache: { read: number; write: number } } cost: number elapsed: number + telemetry?: ReviewTelemetry }) { const { tokens } = input if (tokens.input > 0 || tokens.output > 0 || tokens.cache.write > 0 || tokens.cache.read > 0) { Telemetry.trackLlmCompletion({ taskId: input.sessionID, + ...(input.telemetry ?? {}), apiProvider: input.model.providerID, modelId: input.model.id, inputTokens: tokens.input, diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index f1bf6f3b2e7..e20706edffa 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -17,7 +17,7 @@ import { SessionStatus } from "./status" import { SessionSummary } from "./summary" import type { Provider } from "@/provider/provider" import { Question } from "@/question" -import { KiloSessionProcessor } from "@/kilocode/session/processor" // kilocode_change +import { KiloSessionProcessor, type ReviewTelemetry } from "@/kilocode/session/processor" // kilocode_change import { Suggestion } from "@/kilocode/suggestion" // kilocode_change import { NotFoundError } from "@/storage/storage" // kilocode_change import { errorMessage } from "@/util/error" @@ -53,6 +53,7 @@ type Input = { assistantMessage: MessageV2.Assistant sessionID: SessionID model: Provider.Model + telemetry?: ReviewTelemetry // kilocode_change } export interface Interface { @@ -126,6 +127,7 @@ export const layer: Layer.Layer< needsCompaction: false, currentText: undefined, reasoningMap: {}, + telemetry: input.telemetry, // kilocode_change stepStart: 0, // kilocode_change step: { reasoning: false, text: false, tool: false }, // kilocode_change } @@ -430,6 +432,7 @@ export const layer: Layer.Layer< tokens: usage.tokens, cost: usage.cost, elapsed: Math.round(performance.now() - ctx.stepStart), + telemetry: ctx.telemetry, }) // kilocode_change end ctx.assistantMessage.finish = value.finishReason diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 9055b1aec5b..824b23a37b8 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -5,6 +5,7 @@ import { KiloSessionPrompt } from "@/kilocode/session/prompt" // kilocode_change import { KiloSessionPromptQueue } from "@/kilocode/session/prompt-queue" // kilocode_change import { KiloSession } from "@/kilocode/session" // kilocode_change import { KiloCostPropagation } from "@/kilocode/session/cost-propagation" // kilocode_change +import { KiloSessionProcessor } from "@/kilocode/session/processor" // kilocode_change import { Suggestion } from "@/kilocode/suggestion" // kilocode_change import { Question } from "@/question" // kilocode_change import z from "zod" @@ -1373,6 +1374,12 @@ NOTE: At any point in time through this workflow you should feel free to ask the if (!lastUser) throw new Error("No user message found in stream. This should never happen.") + // kilocode_change start - carry local review command marker into LLM telemetry + const telemetry = KiloSessionProcessor.extractReviewTelemetry( + msgs.findLast((m) => m.info.role === "user" && m.info.id === lastUser.id)?.parts ?? [], + ) + // kilocode_change end + const lastAssistantMsg = msgs.findLast( (msg) => msg.info.role === "assistant" && msg.info.id === lastAssistant?.id, ) @@ -1511,6 +1518,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the assistantMessage: msg, sessionID, model, + telemetry, // kilocode_change }) const outcome: "break" | "continue" = yield* Effect.gen(function* () { @@ -1760,6 +1768,15 @@ NOTE: At any point in time through this workflow you should feel free to ask the } const templateParts = yield* resolvePromptParts(template) + // kilocode_change start - mark local review commands for completion telemetry + const telemetry = KiloSessionProcessor.reviewTelemetry(input.command) + if (telemetry) { + for (const part of templateParts) { + if (part.type !== "text") continue + part.metadata = { ...part.metadata, ...telemetry } + } + } + // kilocode_change end const isSubtask = (agent.mode === "subagent" && cmd.subtask !== false) || cmd.subtask === true const parts = isSubtask ? [