fix: gate dotenv loading + improve api logging

This commit is contained in:
Hannes Rudolph 2025-12-29 18:59:12 -07:00
parent 95c0030c6a
commit e5b9558c1f
5 changed files with 47 additions and 23 deletions

View file

@ -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<string, unknown>
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,

View file

@ -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" }],
})
})
})

View file

@ -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

View file

@ -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
}
}

View file

@ -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"