diff --git a/src/api/logging/ApiInferenceLogger.ts b/src/api/logging/ApiInferenceLogger.ts index 4b7b04b9f9..e463ee287a 100644 --- a/src/api/logging/ApiInferenceLogger.ts +++ b/src/api/logging/ApiInferenceLogger.ts @@ -27,6 +27,13 @@ export interface ApiInferenceHandle { error: (errorPayload: unknown) => void } +function extractModelFromPayload(payload: unknown): string | undefined { + if (!payload || typeof payload !== "object") return undefined + const rec = payload as Record + const model = rec["model"] + return typeof model === "string" && model.trim().length > 0 ? model : undefined +} + /** * Configuration for payload size limiting to avoid freezing the Output Channel. */ @@ -245,11 +252,13 @@ class ApiInferenceLoggerSingleton { const requestId = context.requestId ?? generateRequestId() const startTime = Date.now() const startTimestamp = new Date().toISOString() + const resolvedModel = context.model ?? extractModelFromPayload(requestPayload) // Log the request if (this.isEnabled()) { this.logRequest({ ...context, + ...(resolvedModel ? { model: resolvedModel } : {}), requestId, timestamp: startTimestamp, payload: requestPayload, @@ -262,6 +271,7 @@ class ApiInferenceLoggerSingleton { const endTime = Date.now() this.logResponse({ ...context, + ...(resolvedModel ? { model: resolvedModel } : {}), requestId, timestamp: new Date().toISOString(), durationMs: endTime - startTime, @@ -274,6 +284,7 @@ class ApiInferenceLoggerSingleton { const endTime = Date.now() this.logError({ ...context, + ...(resolvedModel ? { model: resolvedModel } : {}), requestId, timestamp: new Date().toISOString(), durationMs: endTime - startTime, diff --git a/src/api/logging/__tests__/ApiInferenceLogger.spec.ts b/src/api/logging/__tests__/ApiInferenceLogger.spec.ts index 1b639b461c..bc32d7ca22 100644 --- a/src/api/logging/__tests__/ApiInferenceLogger.spec.ts +++ b/src/api/logging/__tests__/ApiInferenceLogger.spec.ts @@ -40,10 +40,7 @@ describe("ApiInferenceLogger", () => { ApiInferenceLogger.start({ provider: "OpenAI", operation: "createMessage" }, { model: "gpt-4" }) expect(mockSink).toHaveBeenCalledTimes(1) - expect(mockSink).toHaveBeenCalledWith( - "[API][request][OpenAI][unknown]", - expect.objectContaining({ model: "gpt-4" }), - ) + expect(mockSink).toHaveBeenCalledWith("[API][request][OpenAI][gpt-4]", { model: "gpt-4" }) }) it("should use context.model in the request label", () => { @@ -63,6 +60,21 @@ describe("ApiInferenceLogger", () => { expect.objectContaining({ test: "data" }), ) }) + + it("should fall back to payload.model when context.model is missing", () => { + ApiInferenceLogger.start( + { + provider: "OpenAI", + operation: "createMessage", + }, + { model: "gpt-4", foo: "bar" }, + ) + + expect(mockSink).toHaveBeenCalledWith( + "[API][request][OpenAI][gpt-4]", + expect.objectContaining({ model: "gpt-4", foo: "bar" }), + ) + }) }) describe("success", () => { @@ -202,13 +214,10 @@ describe("ApiInferenceLogger", () => { { model: "gpt-4", messages: [{ role: "user", content: "Hello" }] }, ) - expect(mockSink).toHaveBeenCalledWith( - "[API][request][test][unknown]", - expect.objectContaining({ - model: "gpt-4", - messages: [{ role: "user", content: "Hello" }], - }), - ) + expect(mockSink).toHaveBeenCalledWith("[API][request][test][gpt-4]", { + model: "gpt-4", + messages: [{ role: "user", content: "Hello" }], + }) }) }) diff --git a/src/api/logging/logging-fetch.ts b/src/api/logging/logging-fetch.ts index 1c72dc7dc0..37c76531cd 100644 --- a/src/api/logging/logging-fetch.ts +++ b/src/api/logging/logging-fetch.ts @@ -741,10 +741,11 @@ export function createLoggingFetch(options: LoggingFetchOptions, baseFetch: type // Tee the stream so the consumer can read one branch while we buffer the other. const [consumerStream, logStream] = response.body.tee() + // Note: returning a new Response means some Response properties (e.g. `url`) are not preserved. const responseForConsumer = new Response(consumerStream, { status: response.status, statusText: response.statusText, - headers: response.headers, + headers: new Headers(response.headers), }) void logSseResponse(logStream, options.provider, model, startedAt, maxBytes) return responseForConsumer diff --git a/src/api/providers/openai-native.ts b/src/api/providers/openai-native.ts index a99542ede3..6bf1e1c9bd 100644 --- a/src/api/providers/openai-native.ts +++ b/src/api/providers/openai-native.ts @@ -181,14 +181,8 @@ export class OpenAiNativeHandler extends BaseProvider implements SingleCompletio metadata, ) - let lastUsage: any - // Make the request (pass systemPrompt and messages for potential retry) for await (const chunk of this.executeRequest(requestBody, model, metadata, systemPrompt, messages)) { - if (chunk.type === "usage") { - lastUsage = chunk - } - yield chunk } } diff --git a/src/extension.ts b/src/extension.ts index 336067944e..dac9a0f434 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,18 +1,27 @@ import * as vscode from "vscode" import * as dotenvx from "@dotenvx/dotenvx" import * as path from "path" +import * as fs from "fs" -// Load environment variables from .env and .env.local files +// Load environment variables from local development files if present. +// In packaged production builds these files typically won't exist. try { // Specify paths to .env and .env.local files in the project root directory const envPath = path.join(__dirname, "..", ".env") const envLocalPath = path.join(__dirname, "..", ".env.local") // Load .env first, then .env.local (so .env.local can override) - dotenvx.config({ path: envPath }) - dotenvx.config({ path: envLocalPath, override: true }) + if (fs.existsSync(envPath)) { + dotenvx.config({ path: envPath }) + } + if (fs.existsSync(envLocalPath)) { + dotenvx.config({ path: envLocalPath, override: true }) + } } catch (e) { - // Silently handle environment loading errors - console.warn("Failed to load environment variables:", e) + // Don't let dotenv errors break extension activation. + // Avoid noisy logs for normal users. + if (process.env.NODE_ENV === "development") { + console.warn("Failed to load environment variables:", e) + } } import type { CloudUserInfo, AuthState } from "@roo-code/types"