Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions packages/cli/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,10 +282,10 @@ export async function startServer(
chats[chatId] = async (chunk) => {
if (!responseSoFar && chunk.model) {
logVerbose(`chat model ${chunk.model}`);
trace.itemValue("chat model", chunk.model);
trace.appendContent("\n\n");
trace?.itemValue("chat model", chunk.model);
trace?.appendContent("\n\n");
}
trace.appendToken(chunk.chunk);
trace?.appendToken(chunk.chunk);
responseSoFar += chunk.chunk ?? "";
tokensSoFar += chunk.tokens ?? 0;
partialCb?.({
Expand All @@ -296,11 +296,11 @@ export async function startServer(
});
finishReason = chunk.finishReason as any;
if (finishReason) {
trace.appendContent("\n\n");
trace.itemValue(`finish reason`, finishReason);
trace?.appendContent("\n\n");
trace?.itemValue(`finish reason`, finishReason);
delete chats[chatId];
if (chunk.error) {
trace.error(undefined, chunk.error);
trace?.error(undefined, chunk.error);
reject(chunk.error);
} else
resolve({
Expand Down
126 changes: 126 additions & 0 deletions packages/cli/test/server.trace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";

// Test for null dereference fix in server.ts
describe("server trace null dereference", () => {
let mockTrace: any;

beforeEach(() => {
mockTrace = {
itemValue: vi.fn(),
appendContent: vi.fn(),
appendToken: vi.fn(),
error: vi.fn(),
};
});

afterEach(() => {
vi.clearAllMocks();
});

it("should handle undefined trace safely in chat completion handler", () => {
// This test simulates the scenario that would cause null dereference
// before the fix was applied

// Mock the chat completion handler behavior
const mockChunk = {
model: "test-model",
chunk: "test-chunk",
finishReason: "stop",
error: null,
};

// Simulate the old unsafe behavior that would cause null dereference
const unsafeHandler = (trace: any) => {
if (mockChunk.model) {
trace.itemValue("chat model", mockChunk.model);
trace.appendContent("\n\n");
}
trace.appendToken(mockChunk.chunk);

if (mockChunk.finishReason) {
trace.appendContent("\n\n");
trace.itemValue("finish reason", mockChunk.finishReason);
}
};

// Test with null/undefined trace (this would fail before the fix)
expect(() => {
unsafeHandler(null);
}).toThrow();

expect(() => {
unsafeHandler(undefined);
}).toThrow();

// Simulate the safe behavior after the fix using optional chaining
const safeHandler = (trace: any) => {
if (mockChunk.model) {
trace?.itemValue("chat model", mockChunk.model);
trace?.appendContent("\n\n");
}
trace?.appendToken(mockChunk.chunk);

if (mockChunk.finishReason) {
trace?.appendContent("\n\n");
trace?.itemValue("finish reason", mockChunk.finishReason);
}
};

// Test with null/undefined trace (this should work after the fix)
expect(() => {
safeHandler(null);
}).not.toThrow();

expect(() => {
safeHandler(undefined);
}).not.toThrow();

// Test with real trace to ensure it still works when provided
expect(() => {
safeHandler(mockTrace);
}).not.toThrow();

// Verify the trace methods were called when trace was provided
expect(mockTrace.itemValue).toHaveBeenCalledWith("chat model", "test-model");
expect(mockTrace.appendContent).toHaveBeenCalled();
expect(mockTrace.appendToken).toHaveBeenCalledWith("test-chunk");
expect(mockTrace.itemValue).toHaveBeenCalledWith("finish reason", "stop");
});

it("should use provided trace when available", () => {
const providedTrace = {
itemValue: vi.fn(),
appendContent: vi.fn(),
appendToken: vi.fn(),
error: vi.fn(),
};

const mockChunk = {
model: "test-model",
chunk: "test-chunk",
};

// Simulate the safe handler behavior using optional chaining
const safeHandler = (trace: any) => {
if (mockChunk.model) {
trace?.itemValue("chat model", mockChunk.model);
}
trace?.appendToken(mockChunk.chunk);
};

safeHandler(providedTrace);

// Verify the provided trace was used
expect(providedTrace.itemValue).toHaveBeenCalledWith("chat model", "test-model");
expect(providedTrace.appendToken).toHaveBeenCalledWith("test-chunk");

// Test with null/undefined trace - should not throw
expect(() => {
safeHandler(null);
}).not.toThrow();

expect(() => {
safeHandler(undefined);
}).not.toThrow();
});
});