Adding TurnId to Tool call and API responses and error logs. (#3039)
Co-authored-by: Scott Densmore <scottdensmore@mac.com>
This commit is contained in:
parent
6c12f9e0d9
commit
063481faa4
|
@ -196,10 +196,12 @@ export async function main() {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
const prompt_id = Math.random().toString(16).slice(2);
|
||||
logUserPrompt(config, {
|
||||
'event.name': 'user_prompt',
|
||||
'event.timestamp': new Date().toISOString(),
|
||||
prompt: input,
|
||||
prompt_id,
|
||||
prompt_length: input.length,
|
||||
});
|
||||
|
||||
|
@ -210,7 +212,7 @@ export async function main() {
|
|||
settings,
|
||||
);
|
||||
|
||||
await runNonInteractive(nonInteractiveConfig, input);
|
||||
await runNonInteractive(nonInteractiveConfig, input, prompt_id);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -81,15 +81,18 @@ describe('runNonInteractive', () => {
|
|||
})();
|
||||
mockChat.sendMessageStream.mockResolvedValue(inputStream);
|
||||
|
||||
await runNonInteractive(mockConfig, 'Test input');
|
||||
await runNonInteractive(mockConfig, 'Test input', 'prompt-id-1');
|
||||
|
||||
expect(mockChat.sendMessageStream).toHaveBeenCalledWith({
|
||||
expect(mockChat.sendMessageStream).toHaveBeenCalledWith(
|
||||
{
|
||||
message: [{ text: 'Test input' }],
|
||||
config: {
|
||||
abortSignal: expect.any(AbortSignal),
|
||||
tools: [{ functionDeclarations: [] }],
|
||||
},
|
||||
});
|
||||
},
|
||||
expect.any(String),
|
||||
);
|
||||
expect(mockProcessStdoutWrite).toHaveBeenCalledWith('Hello');
|
||||
expect(mockProcessStdoutWrite).toHaveBeenCalledWith(' World');
|
||||
expect(mockProcessStdoutWrite).toHaveBeenCalledWith('\n');
|
||||
|
@ -131,7 +134,7 @@ describe('runNonInteractive', () => {
|
|||
.mockResolvedValueOnce(stream1)
|
||||
.mockResolvedValueOnce(stream2);
|
||||
|
||||
await runNonInteractive(mockConfig, 'Use a tool');
|
||||
await runNonInteractive(mockConfig, 'Use a tool', 'prompt-id-2');
|
||||
|
||||
expect(mockChat.sendMessageStream).toHaveBeenCalledTimes(2);
|
||||
expect(mockCoreExecuteToolCall).toHaveBeenCalledWith(
|
||||
|
@ -144,6 +147,7 @@ describe('runNonInteractive', () => {
|
|||
expect.objectContaining({
|
||||
message: [toolResponsePart],
|
||||
}),
|
||||
expect.any(String),
|
||||
);
|
||||
expect(mockProcessStdoutWrite).toHaveBeenCalledWith('Final answer');
|
||||
});
|
||||
|
@ -190,7 +194,7 @@ describe('runNonInteractive', () => {
|
|||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await runNonInteractive(mockConfig, 'Trigger tool error');
|
||||
await runNonInteractive(mockConfig, 'Trigger tool error', 'prompt-id-3');
|
||||
|
||||
expect(mockCoreExecuteToolCall).toHaveBeenCalled();
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
|
@ -200,6 +204,7 @@ describe('runNonInteractive', () => {
|
|||
expect.objectContaining({
|
||||
message: [errorResponsePart],
|
||||
}),
|
||||
expect.any(String),
|
||||
);
|
||||
expect(mockProcessStdoutWrite).toHaveBeenCalledWith(
|
||||
'Could not complete request.',
|
||||
|
@ -213,7 +218,7 @@ describe('runNonInteractive', () => {
|
|||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await runNonInteractive(mockConfig, 'Initial fail');
|
||||
await runNonInteractive(mockConfig, 'Initial fail', 'prompt-id-4');
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'[API Error: API connection failed]',
|
||||
|
@ -265,7 +270,11 @@ describe('runNonInteractive', () => {
|
|||
.spyOn(console, 'error')
|
||||
.mockImplementation(() => {});
|
||||
|
||||
await runNonInteractive(mockConfig, 'Trigger tool not found');
|
||||
await runNonInteractive(
|
||||
mockConfig,
|
||||
'Trigger tool not found',
|
||||
'prompt-id-5',
|
||||
);
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
'Error executing tool nonExistentTool: Tool "nonExistentTool" not found in registry.',
|
||||
|
@ -278,6 +287,7 @@ describe('runNonInteractive', () => {
|
|||
expect.objectContaining({
|
||||
message: [errorResponsePart],
|
||||
}),
|
||||
expect.any(String),
|
||||
);
|
||||
|
||||
expect(mockProcessStdoutWrite).toHaveBeenCalledWith(
|
||||
|
|
|
@ -46,6 +46,7 @@ function getResponseText(response: GenerateContentResponse): string | null {
|
|||
export async function runNonInteractive(
|
||||
config: Config,
|
||||
input: string,
|
||||
prompt_id: string,
|
||||
): Promise<void> {
|
||||
await config.initialize();
|
||||
// Handle EPIPE errors when the output is piped to a command that closes early.
|
||||
|
@ -67,7 +68,8 @@ export async function runNonInteractive(
|
|||
while (true) {
|
||||
const functionCalls: FunctionCall[] = [];
|
||||
|
||||
const responseStream = await chat.sendMessageStream({
|
||||
const responseStream = await chat.sendMessageStream(
|
||||
{
|
||||
message: currentMessages[0]?.parts || [], // Ensure parts are always provided
|
||||
config: {
|
||||
abortSignal: abortController.signal,
|
||||
|
@ -75,7 +77,9 @@ export async function runNonInteractive(
|
|||
{ functionDeclarations: toolRegistry.getFunctionDeclarations() },
|
||||
],
|
||||
},
|
||||
});
|
||||
},
|
||||
prompt_id,
|
||||
);
|
||||
|
||||
for await (const resp of responseStream) {
|
||||
if (abortController.signal.aborted) {
|
||||
|
@ -101,6 +105,7 @@ export async function runNonInteractive(
|
|||
name: fc.name as string,
|
||||
args: (fc.args ?? {}) as Record<string, unknown>,
|
||||
isClientInitiated: false,
|
||||
prompt_id,
|
||||
};
|
||||
|
||||
const toolResponse = await executeToolCall(
|
||||
|
|
|
@ -27,7 +27,11 @@ const renderWithMockedStats = (metrics: SessionMetrics) => {
|
|||
sessionStartTime: new Date(),
|
||||
metrics,
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 5,
|
||||
},
|
||||
|
||||
getPromptCount: () => 5,
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
return render(<ModelStatsDisplay />);
|
||||
|
|
|
@ -26,7 +26,11 @@ const renderWithMockedStats = (metrics: SessionMetrics) => {
|
|||
sessionStartTime: new Date(),
|
||||
metrics,
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 5,
|
||||
},
|
||||
|
||||
getPromptCount: () => 5,
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
return render(<SessionSummaryDisplay duration="1h 23m 45s" />);
|
||||
|
|
|
@ -27,7 +27,11 @@ const renderWithMockedStats = (metrics: SessionMetrics) => {
|
|||
sessionStartTime: new Date(),
|
||||
metrics,
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 5,
|
||||
},
|
||||
|
||||
getPromptCount: () => 5,
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
return render(<StatsDisplay duration="1s" />);
|
||||
|
@ -288,7 +292,11 @@ describe('<StatsDisplay />', () => {
|
|||
sessionStartTime: new Date(),
|
||||
metrics: zeroMetrics,
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 5,
|
||||
},
|
||||
|
||||
getPromptCount: () => 5,
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
const { lastFrame } = render(
|
||||
|
|
|
@ -27,7 +27,11 @@ const renderWithMockedStats = (metrics: SessionMetrics) => {
|
|||
sessionStartTime: new Date(),
|
||||
metrics,
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 5,
|
||||
},
|
||||
|
||||
getPromptCount: () => 5,
|
||||
startNewPrompt: vi.fn(),
|
||||
});
|
||||
|
||||
return render(<ToolStatsDisplay />);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useState,
|
||||
useMemo,
|
||||
|
@ -26,6 +27,7 @@ export interface SessionStatsState {
|
|||
sessionStartTime: Date;
|
||||
metrics: SessionMetrics;
|
||||
lastPromptTokenCount: number;
|
||||
promptCount: number;
|
||||
}
|
||||
|
||||
export interface ComputedSessionStats {
|
||||
|
@ -46,6 +48,8 @@ export interface ComputedSessionStats {
|
|||
// and the functions to update it.
|
||||
interface SessionStatsContextValue {
|
||||
stats: SessionStatsState;
|
||||
startNewPrompt: () => void;
|
||||
getPromptCount: () => number;
|
||||
}
|
||||
|
||||
// --- Context Definition ---
|
||||
|
@ -63,6 +67,7 @@ export const SessionStatsProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
sessionStartTime: new Date(),
|
||||
metrics: uiTelemetryService.getMetrics(),
|
||||
lastPromptTokenCount: 0,
|
||||
promptCount: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -92,11 +97,25 @@ export const SessionStatsProvider: React.FC<{ children: React.ReactNode }> = ({
|
|||
};
|
||||
}, []);
|
||||
|
||||
const startNewPrompt = useCallback(() => {
|
||||
setStats((prevState) => ({
|
||||
...prevState,
|
||||
promptCount: prevState.promptCount + 1,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const getPromptCount = useCallback(
|
||||
() => stats.promptCount,
|
||||
[stats.promptCount],
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
stats,
|
||||
startNewPrompt,
|
||||
getPromptCount,
|
||||
}),
|
||||
[stats],
|
||||
[stats, startNewPrompt, getPromptCount],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -159,7 +159,7 @@ describe('useSlashCommandProcessor', () => {
|
|||
stats: {
|
||||
sessionStartTime: new Date('2025-01-01T00:00:00.000Z'),
|
||||
cumulative: {
|
||||
turnCount: 0,
|
||||
promptCount: 0,
|
||||
promptTokenCount: 0,
|
||||
candidatesTokenCount: 0,
|
||||
totalTokenCount: 0,
|
||||
|
@ -1311,7 +1311,10 @@ describe('useSlashCommandProcessor', () => {
|
|||
hook.rerender();
|
||||
});
|
||||
expect(hook.result.current.pendingHistoryItems).toEqual([]);
|
||||
expect(mockGeminiClient.tryCompressChat).toHaveBeenCalledWith(true);
|
||||
expect(mockGeminiClient.tryCompressChat).toHaveBeenCalledWith(
|
||||
'Prompt Id not set',
|
||||
true,
|
||||
);
|
||||
expect(mockAddItem).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
|
|
|
@ -880,7 +880,8 @@ export const useSlashCommandProcessor = (
|
|||
try {
|
||||
const compressed = await config!
|
||||
.getGeminiClient()!
|
||||
.tryCompressChat(true);
|
||||
// TODO: Set Prompt id for CompressChat from SlashCommandProcessor.
|
||||
.tryCompressChat('Prompt Id not set', true);
|
||||
if (compressed) {
|
||||
addMessage({
|
||||
type: MessageType.COMPRESSION,
|
||||
|
|
|
@ -109,12 +109,13 @@ vi.mock('./useLogger.js', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
const mockStartNewTurn = vi.fn();
|
||||
const mockStartNewPrompt = vi.fn();
|
||||
const mockAddUsage = vi.fn();
|
||||
vi.mock('../contexts/SessionContext.js', () => ({
|
||||
useSessionStats: vi.fn(() => ({
|
||||
startNewTurn: mockStartNewTurn,
|
||||
startNewPrompt: mockStartNewPrompt,
|
||||
addUsage: mockAddUsage,
|
||||
getPromptCount: vi.fn(() => 5),
|
||||
})),
|
||||
}));
|
||||
|
||||
|
@ -301,6 +302,9 @@ describe('useGeminiStream', () => {
|
|||
getUsageStatisticsEnabled: () => true,
|
||||
getDebugMode: () => false,
|
||||
addHistory: vi.fn(),
|
||||
getSessionId() {
|
||||
return 'test-session-id';
|
||||
},
|
||||
setQuotaErrorOccurred: vi.fn(),
|
||||
getQuotaErrorOccurred: vi.fn(() => false),
|
||||
} as unknown as Config;
|
||||
|
@ -426,6 +430,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'tool1',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-1',
|
||||
},
|
||||
status: 'success',
|
||||
responseSubmittedToGemini: false,
|
||||
|
@ -444,7 +449,12 @@ describe('useGeminiStream', () => {
|
|||
endTime: Date.now(),
|
||||
} as TrackedCompletedToolCall,
|
||||
{
|
||||
request: { callId: 'call2', name: 'tool2', args: {} },
|
||||
request: {
|
||||
callId: 'call2',
|
||||
name: 'tool2',
|
||||
args: {},
|
||||
prompt_id: 'prompt-id-1',
|
||||
},
|
||||
status: 'executing',
|
||||
responseSubmittedToGemini: false,
|
||||
tool: {
|
||||
|
@ -481,6 +491,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'tool1',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-2',
|
||||
},
|
||||
status: 'success',
|
||||
responseSubmittedToGemini: false,
|
||||
|
@ -492,6 +503,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'tool2',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-2',
|
||||
},
|
||||
status: 'error',
|
||||
responseSubmittedToGemini: false,
|
||||
|
@ -546,6 +558,7 @@ describe('useGeminiStream', () => {
|
|||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
expectedMergedResponse,
|
||||
expect.any(AbortSignal),
|
||||
'prompt-id-2',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -557,6 +570,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'testTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-3',
|
||||
},
|
||||
status: 'cancelled',
|
||||
response: { callId: '1', responseParts: [{ text: 'cancelled' }] },
|
||||
|
@ -618,6 +632,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'toolA',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-7',
|
||||
},
|
||||
tool: {
|
||||
name: 'toolA',
|
||||
|
@ -641,6 +656,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'toolB',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-8',
|
||||
},
|
||||
tool: {
|
||||
name: 'toolB',
|
||||
|
@ -731,6 +747,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'tool1',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-4',
|
||||
},
|
||||
status: 'executing',
|
||||
responseSubmittedToGemini: false,
|
||||
|
@ -824,6 +841,7 @@ describe('useGeminiStream', () => {
|
|||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
toolCallResponseParts,
|
||||
expect.any(AbortSignal),
|
||||
'prompt-id-4',
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1036,6 +1054,7 @@ describe('useGeminiStream', () => {
|
|||
name: 'save_memory',
|
||||
args: { fact: 'test' },
|
||||
isClientInitiated: true,
|
||||
prompt_id: 'prompt-id-6',
|
||||
},
|
||||
status: 'success',
|
||||
responseSubmittedToGemini: false,
|
||||
|
|
|
@ -53,6 +53,7 @@ import {
|
|||
TrackedCompletedToolCall,
|
||||
TrackedCancelledToolCall,
|
||||
} from './useReactToolScheduler.js';
|
||||
import { useSessionStats } from '../contexts/SessionContext.js';
|
||||
|
||||
export function mergePartListUnions(list: PartListUnion[]): PartListUnion {
|
||||
const resultParts: PartListUnion = [];
|
||||
|
@ -101,6 +102,7 @@ export const useGeminiStream = (
|
|||
const [pendingHistoryItemRef, setPendingHistoryItem] =
|
||||
useStateAndRef<HistoryItemWithoutId | null>(null);
|
||||
const processedMemoryToolsRef = useRef<Set<string>>(new Set());
|
||||
const { startNewPrompt, getPromptCount } = useSessionStats();
|
||||
const logger = useLogger();
|
||||
const gitService = useMemo(() => {
|
||||
if (!config.getProjectRoot()) {
|
||||
|
@ -203,6 +205,7 @@ export const useGeminiStream = (
|
|||
query: PartListUnion,
|
||||
userMessageTimestamp: number,
|
||||
abortSignal: AbortSignal,
|
||||
prompt_id: string,
|
||||
): Promise<{
|
||||
queryToSend: PartListUnion | null;
|
||||
shouldProceed: boolean;
|
||||
|
@ -220,7 +223,7 @@ export const useGeminiStream = (
|
|||
const trimmedQuery = query.trim();
|
||||
logUserPrompt(
|
||||
config,
|
||||
new UserPromptEvent(trimmedQuery.length, trimmedQuery),
|
||||
new UserPromptEvent(trimmedQuery.length, prompt_id, trimmedQuery),
|
||||
);
|
||||
onDebugMessage(`User query: '${trimmedQuery}'`);
|
||||
await logger?.logMessage(MessageSenderType.USER, trimmedQuery);
|
||||
|
@ -236,6 +239,7 @@ export const useGeminiStream = (
|
|||
name: toolName,
|
||||
args: toolArgs,
|
||||
isClientInitiated: true,
|
||||
prompt_id,
|
||||
};
|
||||
scheduleToolCalls([toolCallRequest], abortSignal);
|
||||
}
|
||||
|
@ -485,7 +489,11 @@ export const useGeminiStream = (
|
|||
);
|
||||
|
||||
const submitQuery = useCallback(
|
||||
async (query: PartListUnion, options?: { isContinuation: boolean }) => {
|
||||
async (
|
||||
query: PartListUnion,
|
||||
options?: { isContinuation: boolean },
|
||||
prompt_id?: string,
|
||||
) => {
|
||||
if (
|
||||
(streamingState === StreamingState.Responding ||
|
||||
streamingState === StreamingState.WaitingForConfirmation) &&
|
||||
|
@ -506,21 +514,34 @@ export const useGeminiStream = (
|
|||
const abortSignal = abortControllerRef.current.signal;
|
||||
turnCancelledRef.current = false;
|
||||
|
||||
if (!prompt_id) {
|
||||
prompt_id = config.getSessionId() + '########' + getPromptCount();
|
||||
}
|
||||
|
||||
const { queryToSend, shouldProceed } = await prepareQueryForGemini(
|
||||
query,
|
||||
userMessageTimestamp,
|
||||
abortSignal,
|
||||
prompt_id!,
|
||||
);
|
||||
|
||||
if (!shouldProceed || queryToSend === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!options?.isContinuation) {
|
||||
startNewPrompt();
|
||||
}
|
||||
|
||||
setIsResponding(true);
|
||||
setInitError(null);
|
||||
|
||||
try {
|
||||
const stream = geminiClient.sendMessageStream(queryToSend, abortSignal);
|
||||
const stream = geminiClient.sendMessageStream(
|
||||
queryToSend,
|
||||
abortSignal,
|
||||
prompt_id!,
|
||||
);
|
||||
const processingStatus = await processGeminiStreamEvents(
|
||||
stream,
|
||||
userMessageTimestamp,
|
||||
|
@ -570,6 +591,8 @@ export const useGeminiStream = (
|
|||
geminiClient,
|
||||
onAuthError,
|
||||
config,
|
||||
startNewPrompt,
|
||||
getPromptCount,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -676,6 +699,10 @@ export const useGeminiStream = (
|
|||
(toolCall) => toolCall.request.callId,
|
||||
);
|
||||
|
||||
const prompt_ids = geminiTools.map(
|
||||
(toolCall) => toolCall.request.prompt_id,
|
||||
);
|
||||
|
||||
markToolsAsSubmitted(callIdsToMarkAsSubmitted);
|
||||
|
||||
// Don't continue if model was switched due to quota error
|
||||
|
@ -683,9 +710,13 @@ export const useGeminiStream = (
|
|||
return;
|
||||
}
|
||||
|
||||
submitQuery(mergePartListUnions(responsesToSend), {
|
||||
submitQuery(
|
||||
mergePartListUnions(responsesToSend),
|
||||
{
|
||||
isContinuation: true,
|
||||
});
|
||||
},
|
||||
prompt_ids[0],
|
||||
);
|
||||
},
|
||||
[
|
||||
isResponding,
|
||||
|
|
|
@ -450,7 +450,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
});
|
||||
|
||||
const initialChat = client.getChat();
|
||||
const result = await client.tryCompressChat();
|
||||
const result = await client.tryCompressChat('prompt-id-2');
|
||||
const newChat = client.getChat();
|
||||
|
||||
expect(tokenLimit).toHaveBeenCalled();
|
||||
|
@ -476,7 +476,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
});
|
||||
|
||||
const initialChat = client.getChat();
|
||||
const result = await client.tryCompressChat();
|
||||
const result = await client.tryCompressChat('prompt-id-3');
|
||||
const newChat = client.getChat();
|
||||
|
||||
expect(tokenLimit).toHaveBeenCalled();
|
||||
|
@ -507,7 +507,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
});
|
||||
|
||||
const initialChat = client.getChat();
|
||||
const result = await client.tryCompressChat(true); // force = true
|
||||
const result = await client.tryCompressChat('prompt-id-1', true); // force = true
|
||||
const newChat = client.getChat();
|
||||
|
||||
expect(mockSendMessage).toHaveBeenCalled();
|
||||
|
@ -545,6 +545,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
const stream = client.sendMessageStream(
|
||||
[{ text: 'Hi' }],
|
||||
new AbortController().signal,
|
||||
'prompt-id-1',
|
||||
);
|
||||
|
||||
// Consume the stream manually to get the final return value.
|
||||
|
@ -597,6 +598,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
const stream = client.sendMessageStream(
|
||||
[{ text: 'Start conversation' }],
|
||||
signal,
|
||||
'prompt-id-2',
|
||||
);
|
||||
|
||||
// Count how many stream events we get
|
||||
|
@ -697,6 +699,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
const stream = client.sendMessageStream(
|
||||
[{ text: 'Start conversation' }],
|
||||
signal,
|
||||
'prompt-id-3',
|
||||
Number.MAX_SAFE_INTEGER, // Bypass the MAX_TURNS protection
|
||||
);
|
||||
|
||||
|
@ -806,7 +809,7 @@ describe('Gemini Client (client.ts)', () => {
|
|||
client['contentGenerator'] = mockGenerator as ContentGenerator;
|
||||
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
||||
|
||||
const result = await client.tryCompressChat(true);
|
||||
const result = await client.tryCompressChat('prompt-id-4', true);
|
||||
|
||||
expect(mockCountTokens).toHaveBeenCalledTimes(2);
|
||||
expect(mockCountTokens).toHaveBeenNthCalledWith(1, {
|
||||
|
|
|
@ -261,23 +261,25 @@ export class GeminiClient {
|
|||
async *sendMessageStream(
|
||||
request: PartListUnion,
|
||||
signal: AbortSignal,
|
||||
prompt_id: string,
|
||||
turns: number = this.MAX_TURNS,
|
||||
originalModel?: string,
|
||||
): AsyncGenerator<ServerGeminiStreamEvent, Turn> {
|
||||
// Ensure turns never exceeds MAX_TURNS to prevent infinite loops
|
||||
const boundedTurns = Math.min(turns, this.MAX_TURNS);
|
||||
if (!boundedTurns) {
|
||||
return new Turn(this.getChat());
|
||||
return new Turn(this.getChat(), prompt_id);
|
||||
}
|
||||
|
||||
// Track the original model from the first call to detect model switching
|
||||
const initialModel = originalModel || this.config.getModel();
|
||||
|
||||
const compressed = await this.tryCompressChat();
|
||||
const compressed = await this.tryCompressChat(prompt_id);
|
||||
|
||||
if (compressed) {
|
||||
yield { type: GeminiEventType.ChatCompressed, value: compressed };
|
||||
}
|
||||
const turn = new Turn(this.getChat());
|
||||
const turn = new Turn(this.getChat(), prompt_id);
|
||||
const resultStream = turn.run(request, signal);
|
||||
for await (const event of resultStream) {
|
||||
yield event;
|
||||
|
@ -303,6 +305,7 @@ export class GeminiClient {
|
|||
yield* this.sendMessageStream(
|
||||
nextRequest,
|
||||
signal,
|
||||
prompt_id,
|
||||
boundedTurns - 1,
|
||||
initialModel,
|
||||
);
|
||||
|
@ -492,6 +495,7 @@ export class GeminiClient {
|
|||
}
|
||||
|
||||
async tryCompressChat(
|
||||
prompt_id: string,
|
||||
force: boolean = false,
|
||||
): Promise<ChatCompressionInfo | null> {
|
||||
const curatedHistory = this.getChat().getHistory(true);
|
||||
|
@ -538,14 +542,17 @@ export class GeminiClient {
|
|||
|
||||
this.getChat().setHistory(historyToCompress);
|
||||
|
||||
const { text: summary } = await this.getChat().sendMessage({
|
||||
const { text: summary } = await this.getChat().sendMessage(
|
||||
{
|
||||
message: {
|
||||
text: 'First, reason in your scratchpad. Then, generate the <state_snapshot>.',
|
||||
},
|
||||
config: {
|
||||
systemInstruction: { text: getCompressionPrompt() },
|
||||
},
|
||||
});
|
||||
},
|
||||
prompt_id,
|
||||
);
|
||||
this.chat = await this.startChat([
|
||||
{
|
||||
role: 'user',
|
||||
|
|
|
@ -139,6 +139,7 @@ describe('CoreToolScheduler', () => {
|
|||
name: 'mockTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-1',
|
||||
};
|
||||
|
||||
abortController.abort();
|
||||
|
@ -206,6 +207,7 @@ describe('CoreToolScheduler with payload', () => {
|
|||
name: 'mockModifiableTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-2',
|
||||
};
|
||||
|
||||
await scheduler.schedule([request], abortController.signal);
|
||||
|
|
|
@ -77,7 +77,7 @@ describe('GeminiChat', () => {
|
|||
} as unknown as GenerateContentResponse;
|
||||
vi.mocked(mockModelsModule.generateContent).mockResolvedValue(response);
|
||||
|
||||
await chat.sendMessage({ message: 'hello' });
|
||||
await chat.sendMessage({ message: 'hello' }, 'prompt-id-1');
|
||||
|
||||
expect(mockModelsModule.generateContent).toHaveBeenCalledWith({
|
||||
model: 'gemini-pro',
|
||||
|
@ -109,7 +109,7 @@ describe('GeminiChat', () => {
|
|||
response,
|
||||
);
|
||||
|
||||
await chat.sendMessageStream({ message: 'hello' });
|
||||
await chat.sendMessageStream({ message: 'hello' }, 'prompt-id-1');
|
||||
|
||||
expect(mockModelsModule.generateContentStream).toHaveBeenCalledWith({
|
||||
model: 'gemini-pro',
|
||||
|
|
|
@ -151,13 +151,18 @@ export class GeminiChat {
|
|||
private async _logApiRequest(
|
||||
contents: Content[],
|
||||
model: string,
|
||||
prompt_id: string,
|
||||
): Promise<void> {
|
||||
const requestText = this._getRequestTextFromContents(contents);
|
||||
logApiRequest(this.config, new ApiRequestEvent(model, requestText));
|
||||
logApiRequest(
|
||||
this.config,
|
||||
new ApiRequestEvent(model, prompt_id, requestText),
|
||||
);
|
||||
}
|
||||
|
||||
private async _logApiResponse(
|
||||
durationMs: number,
|
||||
prompt_id: string,
|
||||
usageMetadata?: GenerateContentResponseUsageMetadata,
|
||||
responseText?: string,
|
||||
): Promise<void> {
|
||||
|
@ -166,13 +171,18 @@ export class GeminiChat {
|
|||
new ApiResponseEvent(
|
||||
this.config.getModel(),
|
||||
durationMs,
|
||||
prompt_id,
|
||||
usageMetadata,
|
||||
responseText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private _logApiError(durationMs: number, error: unknown): void {
|
||||
private _logApiError(
|
||||
durationMs: number,
|
||||
error: unknown,
|
||||
prompt_id: string,
|
||||
): void {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const errorType = error instanceof Error ? error.name : 'unknown';
|
||||
|
||||
|
@ -182,6 +192,7 @@ export class GeminiChat {
|
|||
this.config.getModel(),
|
||||
errorMessage,
|
||||
durationMs,
|
||||
prompt_id,
|
||||
errorType,
|
||||
),
|
||||
);
|
||||
|
@ -255,12 +266,13 @@ export class GeminiChat {
|
|||
*/
|
||||
async sendMessage(
|
||||
params: SendMessageParameters,
|
||||
prompt_id: string,
|
||||
): Promise<GenerateContentResponse> {
|
||||
await this.sendPromise;
|
||||
const userContent = createUserContent(params.message);
|
||||
const requestContents = this.getHistory(true).concat(userContent);
|
||||
|
||||
this._logApiRequest(requestContents, this.config.getModel());
|
||||
this._logApiRequest(requestContents, this.config.getModel(), prompt_id);
|
||||
|
||||
const startTime = Date.now();
|
||||
let response: GenerateContentResponse;
|
||||
|
@ -301,6 +313,7 @@ export class GeminiChat {
|
|||
const durationMs = Date.now() - startTime;
|
||||
await this._logApiResponse(
|
||||
durationMs,
|
||||
prompt_id,
|
||||
response.usageMetadata,
|
||||
getStructuredResponse(response),
|
||||
);
|
||||
|
@ -332,7 +345,7 @@ export class GeminiChat {
|
|||
return response;
|
||||
} catch (error) {
|
||||
const durationMs = Date.now() - startTime;
|
||||
this._logApiError(durationMs, error);
|
||||
this._logApiError(durationMs, error, prompt_id);
|
||||
this.sendPromise = Promise.resolve();
|
||||
throw error;
|
||||
}
|
||||
|
@ -362,11 +375,12 @@ export class GeminiChat {
|
|||
*/
|
||||
async sendMessageStream(
|
||||
params: SendMessageParameters,
|
||||
prompt_id: string,
|
||||
): Promise<AsyncGenerator<GenerateContentResponse>> {
|
||||
await this.sendPromise;
|
||||
const userContent = createUserContent(params.message);
|
||||
const requestContents = this.getHistory(true).concat(userContent);
|
||||
this._logApiRequest(requestContents, this.config.getModel());
|
||||
this._logApiRequest(requestContents, this.config.getModel(), prompt_id);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
|
@ -420,11 +434,12 @@ export class GeminiChat {
|
|||
streamResponse,
|
||||
userContent,
|
||||
startTime,
|
||||
prompt_id,
|
||||
);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const durationMs = Date.now() - startTime;
|
||||
this._logApiError(durationMs, error);
|
||||
this._logApiError(durationMs, error, prompt_id);
|
||||
this.sendPromise = Promise.resolve();
|
||||
throw error;
|
||||
}
|
||||
|
@ -496,6 +511,7 @@ export class GeminiChat {
|
|||
streamResponse: AsyncGenerator<GenerateContentResponse>,
|
||||
inputContent: Content,
|
||||
startTime: number,
|
||||
prompt_id: string,
|
||||
) {
|
||||
const outputContent: Content[] = [];
|
||||
const chunks: GenerateContentResponse[] = [];
|
||||
|
@ -519,7 +535,7 @@ export class GeminiChat {
|
|||
} catch (error) {
|
||||
errorOccurred = true;
|
||||
const durationMs = Date.now() - startTime;
|
||||
this._logApiError(durationMs, error);
|
||||
this._logApiError(durationMs, error, prompt_id);
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -534,6 +550,7 @@ export class GeminiChat {
|
|||
const fullText = getStructuredResponseFromParts(allParts);
|
||||
await this._logApiResponse(
|
||||
durationMs,
|
||||
prompt_id,
|
||||
this.getFinalUsageMetadata(chunks),
|
||||
fullText,
|
||||
);
|
||||
|
|
|
@ -67,6 +67,7 @@ describe('executeToolCall', () => {
|
|||
name: 'testTool',
|
||||
args: { param1: 'value1' },
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-1',
|
||||
};
|
||||
const toolResult: ToolResult = {
|
||||
llmContent: 'Tool executed successfully',
|
||||
|
@ -105,6 +106,7 @@ describe('executeToolCall', () => {
|
|||
name: 'nonExistentTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-2',
|
||||
};
|
||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(undefined);
|
||||
|
||||
|
@ -140,6 +142,7 @@ describe('executeToolCall', () => {
|
|||
name: 'testTool',
|
||||
args: { param1: 'value1' },
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-3',
|
||||
};
|
||||
const executionError = new Error('Tool execution failed');
|
||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
||||
|
@ -172,6 +175,7 @@ describe('executeToolCall', () => {
|
|||
name: 'testTool',
|
||||
args: { param1: 'value1' },
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-4',
|
||||
};
|
||||
const cancellationError = new Error('Operation cancelled');
|
||||
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
||||
|
@ -215,6 +219,7 @@ describe('executeToolCall', () => {
|
|||
name: 'testTool',
|
||||
args: {},
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-5',
|
||||
};
|
||||
const imageDataPart: Part = {
|
||||
inlineData: { mimeType: 'image/png', data: 'base64data' },
|
||||
|
|
|
@ -40,6 +40,7 @@ export async function executeToolCall(
|
|||
duration_ms: durationMs,
|
||||
success: false,
|
||||
error: error.message,
|
||||
prompt_id: toolCallRequest.prompt_id,
|
||||
});
|
||||
// Ensure the response structure matches what the API expects for an error
|
||||
return {
|
||||
|
@ -75,6 +76,7 @@ export async function executeToolCall(
|
|||
function_args: toolCallRequest.args,
|
||||
duration_ms: durationMs,
|
||||
success: true,
|
||||
prompt_id: toolCallRequest.prompt_id,
|
||||
});
|
||||
|
||||
const response = convertToFunctionResponse(
|
||||
|
@ -100,6 +102,7 @@ export async function executeToolCall(
|
|||
duration_ms: durationMs,
|
||||
success: false,
|
||||
error: error.message,
|
||||
prompt_id: toolCallRequest.prompt_id,
|
||||
});
|
||||
return {
|
||||
callId: toolCallRequest.callId,
|
||||
|
|
|
@ -55,7 +55,7 @@ describe('Turn', () => {
|
|||
sendMessageStream: mockSendMessageStream,
|
||||
getHistory: mockGetHistory,
|
||||
};
|
||||
turn = new Turn(mockChatInstance as unknown as GeminiChat);
|
||||
turn = new Turn(mockChatInstance as unknown as GeminiChat, 'prompt-id-1');
|
||||
mockGetHistory.mockReturnValue([]);
|
||||
mockSendMessageStream.mockResolvedValue((async function* () {})());
|
||||
});
|
||||
|
@ -92,10 +92,13 @@ describe('Turn', () => {
|
|||
events.push(event);
|
||||
}
|
||||
|
||||
expect(mockSendMessageStream).toHaveBeenCalledWith({
|
||||
expect(mockSendMessageStream).toHaveBeenCalledWith(
|
||||
{
|
||||
message: reqParts,
|
||||
config: { abortSignal: expect.any(AbortSignal) },
|
||||
});
|
||||
},
|
||||
'prompt-id-1',
|
||||
);
|
||||
|
||||
expect(events).toEqual([
|
||||
{ type: GeminiEventType.Content, value: 'Hello' },
|
||||
|
|
|
@ -64,6 +64,7 @@ export interface ToolCallRequestInfo {
|
|||
name: string;
|
||||
args: Record<string, unknown>;
|
||||
isClientInitiated: boolean;
|
||||
prompt_id: string;
|
||||
}
|
||||
|
||||
export interface ToolCallResponseInfo {
|
||||
|
@ -143,7 +144,10 @@ export class Turn {
|
|||
readonly pendingToolCalls: ToolCallRequestInfo[];
|
||||
private debugResponses: GenerateContentResponse[];
|
||||
|
||||
constructor(private readonly chat: GeminiChat) {
|
||||
constructor(
|
||||
private readonly chat: GeminiChat,
|
||||
private readonly prompt_id: string,
|
||||
) {
|
||||
this.pendingToolCalls = [];
|
||||
this.debugResponses = [];
|
||||
}
|
||||
|
@ -153,12 +157,15 @@ export class Turn {
|
|||
signal: AbortSignal,
|
||||
): AsyncGenerator<ServerGeminiStreamEvent> {
|
||||
try {
|
||||
const responseStream = await this.chat.sendMessageStream({
|
||||
const responseStream = await this.chat.sendMessageStream(
|
||||
{
|
||||
message: req,
|
||||
config: {
|
||||
abortSignal: signal,
|
||||
},
|
||||
});
|
||||
},
|
||||
this.prompt_id,
|
||||
);
|
||||
|
||||
for await (const resp of responseStream) {
|
||||
if (signal?.aborted) {
|
||||
|
@ -252,6 +259,7 @@ export class Turn {
|
|||
name,
|
||||
args,
|
||||
isClientInitiated: false,
|
||||
prompt_id: this.prompt_id,
|
||||
};
|
||||
|
||||
this.pendingToolCalls.push(toolCallRequest);
|
||||
|
|
|
@ -265,6 +265,10 @@ export class ClearcutLogger {
|
|||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_USER_PROMPT_LENGTH,
|
||||
value: JSON.stringify(event.prompt_length),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
||||
value: JSON.stringify(event.prompt_id),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(this.createLogEvent(new_prompt_event_name, data));
|
||||
|
@ -279,6 +283,10 @@ export class ClearcutLogger {
|
|||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_NAME,
|
||||
value: JSON.stringify(event.function_name),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
||||
value: JSON.stringify(event.prompt_id),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_DECISION,
|
||||
value: JSON.stringify(event.decision),
|
||||
|
@ -313,6 +321,10 @@ export class ClearcutLogger {
|
|||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_REQUEST_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
||||
value: JSON.stringify(event.prompt_id),
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(this.createLogEvent(api_request_event_name, data));
|
||||
|
@ -327,6 +339,10 @@ export class ClearcutLogger {
|
|||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
||||
value: JSON.stringify(event.prompt_id),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_RESPONSE_STATUS_CODE,
|
||||
value: JSON.stringify(event.status_code),
|
||||
|
@ -378,6 +394,10 @@ export class ClearcutLogger {
|
|||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_ERROR_MODEL,
|
||||
value: JSON.stringify(event.model),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROMPT_ID,
|
||||
value: JSON.stringify(event.prompt_id),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_API_ERROR_TYPE,
|
||||
value: JSON.stringify(event.error_type),
|
||||
|
|
|
@ -137,6 +137,13 @@ export enum EventMetadataKey {
|
|||
|
||||
// Logs the end of a session.
|
||||
GEMINI_CLI_END_SESSION_ID = 34,
|
||||
|
||||
// ==========================================================================
|
||||
// Shared Keys
|
||||
// ===========================================================================
|
||||
|
||||
// Logs the Prompt Id
|
||||
GEMINI_CLI_PROMPT_ID = 35,
|
||||
}
|
||||
|
||||
export function getEventMetadataKey(
|
||||
|
|
|
@ -127,7 +127,7 @@ describe('loggers', () => {
|
|||
} as unknown as Config;
|
||||
|
||||
it('should log a user prompt', () => {
|
||||
const event = new UserPromptEvent(11, 'test-prompt');
|
||||
const event = new UserPromptEvent(11, 'prompt-id-8', 'test-prompt');
|
||||
|
||||
logUserPrompt(mockConfig, event);
|
||||
|
||||
|
@ -201,6 +201,7 @@ describe('loggers', () => {
|
|||
const event = new ApiResponseEvent(
|
||||
'test-model',
|
||||
100,
|
||||
'prompt-id-1',
|
||||
usageData,
|
||||
'test-response',
|
||||
);
|
||||
|
@ -224,6 +225,7 @@ describe('loggers', () => {
|
|||
tool_token_count: 2,
|
||||
total_token_count: 0,
|
||||
response_text: 'test-response',
|
||||
prompt_id: 'prompt-id-1',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -260,6 +262,7 @@ describe('loggers', () => {
|
|||
const event = new ApiResponseEvent(
|
||||
'test-model',
|
||||
100,
|
||||
'prompt-id-1',
|
||||
usageData,
|
||||
'test-response',
|
||||
'test-error',
|
||||
|
@ -296,7 +299,11 @@ describe('loggers', () => {
|
|||
} as Config;
|
||||
|
||||
it('should log an API request with request_text', () => {
|
||||
const event = new ApiRequestEvent('test-model', 'This is a test request');
|
||||
const event = new ApiRequestEvent(
|
||||
'test-model',
|
||||
'prompt-id-7',
|
||||
'This is a test request',
|
||||
);
|
||||
|
||||
logApiRequest(mockConfig, event);
|
||||
|
||||
|
@ -308,12 +315,13 @@ describe('loggers', () => {
|
|||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
model: 'test-model',
|
||||
request_text: 'This is a test request',
|
||||
prompt_id: 'prompt-id-7',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should log an API request without request_text', () => {
|
||||
const event = new ApiRequestEvent('test-model');
|
||||
const event = new ApiRequestEvent('test-model', 'prompt-id-6');
|
||||
|
||||
logApiRequest(mockConfig, event);
|
||||
|
||||
|
@ -324,6 +332,7 @@ describe('loggers', () => {
|
|||
'event.name': EVENT_API_REQUEST,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
model: 'test-model',
|
||||
prompt_id: 'prompt-id-6',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -394,6 +403,7 @@ describe('loggers', () => {
|
|||
},
|
||||
callId: 'test-call-id',
|
||||
isClientInitiated: true,
|
||||
prompt_id: 'prompt-id-1',
|
||||
},
|
||||
response: {
|
||||
callId: 'test-call-id',
|
||||
|
@ -427,6 +437,7 @@ describe('loggers', () => {
|
|||
duration_ms: 100,
|
||||
success: true,
|
||||
decision: ToolCallDecision.ACCEPT,
|
||||
prompt_id: 'prompt-id-1',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -455,6 +466,7 @@ describe('loggers', () => {
|
|||
},
|
||||
callId: 'test-call-id',
|
||||
isClientInitiated: true,
|
||||
prompt_id: 'prompt-id-2',
|
||||
},
|
||||
response: {
|
||||
callId: 'test-call-id',
|
||||
|
@ -487,6 +499,7 @@ describe('loggers', () => {
|
|||
duration_ms: 100,
|
||||
success: false,
|
||||
decision: ToolCallDecision.REJECT,
|
||||
prompt_id: 'prompt-id-2',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -516,6 +529,7 @@ describe('loggers', () => {
|
|||
},
|
||||
callId: 'test-call-id',
|
||||
isClientInitiated: true,
|
||||
prompt_id: 'prompt-id-3',
|
||||
},
|
||||
response: {
|
||||
callId: 'test-call-id',
|
||||
|
@ -549,6 +563,7 @@ describe('loggers', () => {
|
|||
duration_ms: 100,
|
||||
success: true,
|
||||
decision: ToolCallDecision.MODIFY,
|
||||
prompt_id: 'prompt-id-3',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -578,6 +593,7 @@ describe('loggers', () => {
|
|||
},
|
||||
callId: 'test-call-id',
|
||||
isClientInitiated: true,
|
||||
prompt_id: 'prompt-id-4',
|
||||
},
|
||||
response: {
|
||||
callId: 'test-call-id',
|
||||
|
@ -609,6 +625,7 @@ describe('loggers', () => {
|
|||
),
|
||||
duration_ms: 100,
|
||||
success: true,
|
||||
prompt_id: 'prompt-id-4',
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -638,6 +655,7 @@ describe('loggers', () => {
|
|||
},
|
||||
callId: 'test-call-id',
|
||||
isClientInitiated: true,
|
||||
prompt_id: 'prompt-id-5',
|
||||
},
|
||||
response: {
|
||||
callId: 'test-call-id',
|
||||
|
@ -675,6 +693,7 @@ describe('loggers', () => {
|
|||
'error.message': 'test-error',
|
||||
error_type: 'test-error-type',
|
||||
'error.type': 'test-error-type',
|
||||
prompt_id: 'prompt-id-5',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -95,12 +95,14 @@ export class UserPromptEvent {
|
|||
'event.name': 'user_prompt';
|
||||
'event.timestamp': string; // ISO 8601
|
||||
prompt_length: number;
|
||||
prompt_id: string;
|
||||
prompt?: string;
|
||||
|
||||
constructor(prompt_length: number, prompt?: string) {
|
||||
constructor(prompt_length: number, prompt_Id: string, prompt?: string) {
|
||||
this['event.name'] = 'user_prompt';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.prompt_length = prompt_length;
|
||||
this.prompt_id = prompt_Id;
|
||||
this.prompt = prompt;
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +117,7 @@ export class ToolCallEvent {
|
|||
decision?: ToolCallDecision;
|
||||
error?: string;
|
||||
error_type?: string;
|
||||
prompt_id: string;
|
||||
|
||||
constructor(call: CompletedToolCall) {
|
||||
this['event.name'] = 'tool_call';
|
||||
|
@ -128,6 +131,7 @@ export class ToolCallEvent {
|
|||
: undefined;
|
||||
this.error = call.response.error?.message;
|
||||
this.error_type = call.response.error?.name;
|
||||
this.prompt_id = call.request.prompt_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,12 +139,14 @@ export class ApiRequestEvent {
|
|||
'event.name': 'api_request';
|
||||
'event.timestamp': string; // ISO 8601
|
||||
model: string;
|
||||
prompt_id: string;
|
||||
request_text?: string;
|
||||
|
||||
constructor(model: string, request_text?: string) {
|
||||
constructor(model: string, prompt_id: string, request_text?: string) {
|
||||
this['event.name'] = 'api_request';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.model = model;
|
||||
this.prompt_id = prompt_id;
|
||||
this.request_text = request_text;
|
||||
}
|
||||
}
|
||||
|
@ -153,11 +159,13 @@ export class ApiErrorEvent {
|
|||
error_type?: string;
|
||||
status_code?: number | string;
|
||||
duration_ms: number;
|
||||
prompt_id: string;
|
||||
|
||||
constructor(
|
||||
model: string,
|
||||
error: string,
|
||||
duration_ms: number,
|
||||
prompt_id: string,
|
||||
error_type?: string,
|
||||
status_code?: number | string,
|
||||
) {
|
||||
|
@ -168,6 +176,7 @@ export class ApiErrorEvent {
|
|||
this.error_type = error_type;
|
||||
this.status_code = status_code;
|
||||
this.duration_ms = duration_ms;
|
||||
this.prompt_id = prompt_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,10 +194,12 @@ export class ApiResponseEvent {
|
|||
tool_token_count: number;
|
||||
total_token_count: number;
|
||||
response_text?: string;
|
||||
prompt_id: string;
|
||||
|
||||
constructor(
|
||||
model: string,
|
||||
duration_ms: number,
|
||||
prompt_id: string,
|
||||
usage_data?: GenerateContentResponseUsageMetadata,
|
||||
response_text?: string,
|
||||
error?: string,
|
||||
|
@ -206,6 +217,7 @@ export class ApiResponseEvent {
|
|||
this.total_token_count = usage_data?.totalTokenCount ?? 0;
|
||||
this.response_text = response_text;
|
||||
this.error = error;
|
||||
this.prompt_id = prompt_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ const createFakeCompletedToolCall = (
|
|||
name,
|
||||
args: { foo: 'bar' },
|
||||
isClientInitiated: false,
|
||||
prompt_id: 'prompt-id-1',
|
||||
};
|
||||
|
||||
if (success) {
|
||||
|
|
Loading…
Reference in New Issue