Add timestamp to messages

This commit is contained in:
Mario Zechner 2025-10-26 00:43:43 +02:00
parent ef09efaac9
commit 55dc0b6e08
24 changed files with 388 additions and 220 deletions

View file

@ -9,6 +9,7 @@ async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, options: Opti
{
role: "user",
content: "What is 15 + 27? Think step by step. Then list 50 first names.",
timestamp: Date.now(),
},
],
};
@ -29,7 +30,11 @@ async function testAbortSignal<TApi extends Api>(llm: Model<TApi>, options: Opti
expect(msg.content.length).toBeGreaterThan(0);
context.messages.push(msg);
context.messages.push({ role: "user", content: "Please continue, but only generate 5 names." });
context.messages.push({
role: "user",
content: "Please continue, but only generate 5 names.",
timestamp: Date.now(),
});
const followUp = await complete(llm, context, options);
expect(followUp.stopReason).toBe("stop");
@ -42,7 +47,7 @@ async function testImmediateAbort<TApi extends Api>(llm: Model<TApi>, options: O
controller.abort();
const context: Context = {
messages: [{ role: "user", content: "Hello" }],
messages: [{ role: "user", content: "Hello", timestamp: Date.now() }],
};
const response = await complete(llm, context, { ...options, signal: controller.signal });

View file

@ -27,6 +27,7 @@ async function calculateTest<TApi extends Api>(model: Model<TApi>, options: Opti
1. Calculate 3485 * 4234 and 88823 * 3482 in parallel
2. Calculate the sum of the two results using the calculator tool
3. Output ONLY the final sum as a single integer number, nothing else.`,
timestamp: Date.now(),
};
// Calculate expected results (using integers)
@ -176,6 +177,7 @@ async function abortTest<TApi extends Api>(model: Model<TApi>, options: OptionsF
const userPrompt: UserMessage = {
role: "user",
content: "Calculate 100 * 200, then 300 * 400, then 500 * 600, then sum all three results.",
timestamp: Date.now(),
};
// Create abort controller

View file

@ -8,6 +8,7 @@ async function testEmptyMessage<TApi extends Api>(llm: Model<TApi>, options: Opt
const emptyMessage: UserMessage = {
role: "user",
content: [],
timestamp: Date.now(),
};
const context: Context = {
@ -34,6 +35,7 @@ async function testEmptyStringMessage<TApi extends Api>(llm: Model<TApi>, option
{
role: "user",
content: "",
timestamp: Date.now(),
},
],
};
@ -58,6 +60,7 @@ async function testWhitespaceOnlyMessage<TApi extends Api>(llm: Model<TApi>, opt
{
role: "user",
content: " \n\t ",
timestamp: Date.now(),
},
],
};
@ -92,6 +95,7 @@ async function testEmptyAssistantMessage<TApi extends Api>(llm: Model<TApi>, opt
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "stop",
timestamp: Date.now(),
};
const context: Context = {
@ -99,11 +103,13 @@ async function testEmptyAssistantMessage<TApi extends Api>(llm: Model<TApi>, opt
{
role: "user",
content: "Hello, how are you?",
timestamp: Date.now(),
},
emptyAssistant,
{
role: "user",
content: "Please respond this time.",
timestamp: Date.now(),
},
],
};

View file

@ -49,6 +49,7 @@ const providerContexts = {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
} satisfies AssistantMessage,
toolResult: {
role: "toolResult" as const,
@ -56,6 +57,7 @@ const providerContexts = {
toolName: "get_weather",
output: "Weather in Tokyo: 18°C, partly cloudy",
isError: false,
timestamp: Date.now(),
} satisfies ToolResultMessage,
facts: {
calculation: 391,
@ -98,6 +100,7 @@ const providerContexts = {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
} satisfies AssistantMessage,
toolResult: {
role: "toolResult" as const,
@ -105,6 +108,7 @@ const providerContexts = {
toolName: "get_weather",
output: "Weather in Berlin: 22°C, sunny",
isError: false,
timestamp: Date.now(),
} satisfies ToolResultMessage,
facts: {
calculation: 456,
@ -146,6 +150,7 @@ const providerContexts = {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
} satisfies AssistantMessage,
toolResult: {
role: "toolResult" as const,
@ -153,6 +158,7 @@ const providerContexts = {
toolName: "get_weather",
output: "Weather in London: 15°C, rainy",
isError: false,
timestamp: Date.now(),
} satisfies ToolResultMessage,
facts: {
calculation: 525,
@ -196,6 +202,7 @@ const providerContexts = {
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
} satisfies AssistantMessage,
toolResult: {
role: "toolResult" as const,
@ -203,6 +210,7 @@ const providerContexts = {
toolName: "get_weather",
output: "Weather in Sydney: 25°C, clear",
isError: false,
timestamp: Date.now(),
} satisfies ToolResultMessage,
facts: {
calculation: 486,
@ -239,6 +247,7 @@ const providerContexts = {
},
stopReason: "error",
errorMessage: "Request was aborted",
timestamp: Date.now(),
} satisfies AssistantMessage,
toolResult: null,
facts: {
@ -263,6 +272,7 @@ async function testProviderHandoff<TApi extends Api>(
{
role: "user",
content: "Please do some calculations, tell me about capitals, and check the weather.",
timestamp: Date.now(),
},
sourceContext.message,
];
@ -281,6 +291,7 @@ async function testProviderHandoff<TApi extends Api>(
3) What was the temperature?
4) What capital city was mentioned?
Please include the specific numbers and names.`,
timestamp: Date.now(),
});
const context: Context = {

View file

@ -32,7 +32,7 @@ const calculatorTool: Tool<typeof calculatorSchema> = {
async function basicTextGeneration<TApi extends Api>(model: Model<TApi>, options?: OptionsForApi<TApi>) {
const context: Context = {
systemPrompt: "You are a helpful assistant. Be concise.",
messages: [{ role: "user", content: "Reply with exactly: 'Hello test successful'" }],
messages: [{ role: "user", content: "Reply with exactly: 'Hello test successful'", timestamp: Date.now() }],
};
const response = await complete(model, context, options);
@ -44,7 +44,7 @@ async function basicTextGeneration<TApi extends Api>(model: Model<TApi>, options
expect(response.content.map((b) => (b.type === "text" ? b.text : "")).join("")).toContain("Hello test successful");
context.messages.push(response);
context.messages.push({ role: "user", content: "Now say 'Goodbye test successful'" });
context.messages.push({ role: "user", content: "Now say 'Goodbye test successful'", timestamp: Date.now() });
const secondResponse = await complete(model, context, options);
@ -65,6 +65,7 @@ async function handleToolCall<TApi extends Api>(model: Model<TApi>, options?: Op
{
role: "user",
content: "Calculate 15 + 27 using the calculator tool.",
timestamp: Date.now(),
},
],
tools: [calculatorTool],
@ -141,7 +142,7 @@ async function handleStreaming<TApi extends Api>(model: Model<TApi>, options?: O
let textCompleted = false;
const context: Context = {
messages: [{ role: "user", content: "Count from 1 to 3" }],
messages: [{ role: "user", content: "Count from 1 to 3", timestamp: Date.now() }],
};
const s = stream(model, context, options);
@ -174,6 +175,7 @@ async function handleThinking<TApi extends Api>(model: Model<TApi>, options?: Op
{
role: "user",
content: `Think long and hard about ${(Math.random() * 255) | 0} + 27. Think step by step. Then output the result.`,
timestamp: Date.now(),
},
],
};
@ -228,6 +230,7 @@ async function handleImage<TApi extends Api>(model: Model<TApi>, options?: Optio
},
imageContent,
],
timestamp: Date.now(),
},
],
};
@ -251,6 +254,7 @@ async function multiTurn<TApi extends Api>(model: Model<TApi>, options?: Options
{
role: "user",
content: "Think about this briefly, then calculate 42 * 17 and 453 + 434 using the calculator tool.",
timestamp: Date.now(),
},
],
tools: [calculatorTool],
@ -303,6 +307,7 @@ async function multiTurn<TApi extends Api>(model: Model<TApi>, options?: Options
toolName: block.name,
output: `${result}`,
isError: false,
timestamp: Date.now(),
});
}
}

View file

@ -33,6 +33,7 @@ describe("Tool Call Without Result Tests", () => {
context.messages.push({
role: "user",
content: "Please calculate 25 * 18 using the calculate tool.",
timestamp: Date.now(),
});
// Step 3: Get the assistant's response (should contain a tool call)
@ -54,6 +55,7 @@ describe("Tool Call Without Result Tests", () => {
context.messages.push({
role: "user",
content: "Never mind, just tell me what is 2+2?",
timestamp: Date.now(),
});
// Step 5: The fix should filter out the orphaned tool call, and the request should succeed

View file

@ -22,6 +22,7 @@ async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, option
{
role: "user",
content: "Use the test tool",
timestamp: Date.now(),
},
{
role: "assistant",
@ -44,6 +45,7 @@ async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, option
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
},
],
tools: [
@ -72,6 +74,7 @@ async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, option
- Mathematical symbols:
- Special quotes: "curly" 'quotes'`,
isError: false,
timestamp: Date.now(),
};
context.messages.push(toolResult);
@ -80,6 +83,7 @@ async function testEmojiInToolResults<TApi extends Api>(llm: Model<TApi>, option
context.messages.push({
role: "user",
content: "Summarize the tool result briefly.",
timestamp: Date.now(),
});
// This should not throw a surrogate pair error
@ -97,6 +101,7 @@ async function testRealWorldLinkedInData<TApi extends Api>(llm: Model<TApi>, opt
{
role: "user",
content: "Use the linkedin tool to get comments",
timestamp: Date.now(),
},
{
role: "assistant",
@ -119,6 +124,7 @@ async function testRealWorldLinkedInData<TApi extends Api>(llm: Model<TApi>, opt
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
},
],
tools: [
@ -151,6 +157,7 @@ Unanswered Comments: 2
]
}`,
isError: false,
timestamp: Date.now(),
};
context.messages.push(toolResult);
@ -158,6 +165,7 @@ Unanswered Comments: 2
context.messages.push({
role: "user",
content: "How many comments are there?",
timestamp: Date.now(),
});
// This should not throw a surrogate pair error
@ -175,6 +183,7 @@ async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TApi>, opt
{
role: "user",
content: "Use the test tool",
timestamp: Date.now(),
},
{
role: "assistant",
@ -197,6 +206,7 @@ async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TApi>, opt
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: "toolUse",
timestamp: Date.now(),
},
],
tools: [
@ -218,6 +228,7 @@ async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TApi>, opt
toolName: "test_tool",
output: `Text with unpaired surrogate: ${unpairedSurrogate} <- should be sanitized`,
isError: false,
timestamp: Date.now(),
};
context.messages.push(toolResult);
@ -225,6 +236,7 @@ async function testUnpairedHighSurrogate<TApi extends Api>(llm: Model<TApi>, opt
context.messages.push({
role: "user",
content: "What did the tool return?",
timestamp: Date.now(),
});
// This should not throw a surrogate pair error