fix(core-agent): stabilize legacy chat timestamps (#1858)
This commit is contained in:
@@ -236,6 +236,47 @@ describe('createChatOrchestratorRuntime', () => {
|
|||||||
expect(harness.promptProjections).toHaveLength(1)
|
expect(harness.promptProjections).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('keeps timestamp prefixes stable for legacy user messages without createdAt', async () => {
|
||||||
|
const harness = createHarness()
|
||||||
|
const legacyUserMessage = {
|
||||||
|
role: 'user' as const,
|
||||||
|
content: 'legacy prompt',
|
||||||
|
id: 'legacy-user',
|
||||||
|
}
|
||||||
|
harness.sessionMessages['session-1'] = [
|
||||||
|
{ role: 'system', content: 'system prompt', createdAt: 1, id: 'system' },
|
||||||
|
legacyUserMessage,
|
||||||
|
]
|
||||||
|
const firstMessages: Message[][] = []
|
||||||
|
const secondMessages: Message[][] = []
|
||||||
|
|
||||||
|
harness.stream.mockImplementationOnce(async (_model, _chatProvider, messages, options) => {
|
||||||
|
firstMessages.push(structuredClone(messages))
|
||||||
|
await options?.onStreamEvent?.({ type: 'finish', finishReason: 'stop' })
|
||||||
|
})
|
||||||
|
harness.now.set(new Date(2026, 3, 25, 18, 47).getTime())
|
||||||
|
|
||||||
|
await harness.runtime.ingest('first send', {
|
||||||
|
model: 'gpt-test',
|
||||||
|
chatProvider: provider,
|
||||||
|
})
|
||||||
|
|
||||||
|
harness.stream.mockImplementationOnce(async (_model, _chatProvider, messages, options) => {
|
||||||
|
secondMessages.push(structuredClone(messages))
|
||||||
|
await options?.onStreamEvent?.({ type: 'finish', finishReason: 'stop' })
|
||||||
|
})
|
||||||
|
harness.now.set(new Date(2026, 3, 25, 19, 12).getTime())
|
||||||
|
|
||||||
|
await harness.runtime.ingest('second send', {
|
||||||
|
model: 'gpt-test',
|
||||||
|
chatProvider: provider,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(firstMessages[0]?.[1]?.content).toBe('[2026-04-25 18:47] legacy prompt')
|
||||||
|
expect(secondMessages[0]?.[1]?.content).toBe('[2026-04-25 18:47] legacy prompt')
|
||||||
|
expect(legacyUserMessage.createdAt).toBe(new Date(2026, 3, 25, 18, 47).getTime())
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @example
|
* @example
|
||||||
* deps.getSystemPromptSupplement() returns tool guidance.
|
* deps.getSystemPromptSupplement() returns tool guidance.
|
||||||
|
|||||||
@@ -386,15 +386,23 @@ export function createChatOrchestratorRuntime(deps: ChatOrchestratorRuntimeDeps)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStablePromptTimestamp(message: ChatHistoryItem, fallbackCreatedAt: number) {
|
||||||
|
if (typeof message.createdAt === 'number')
|
||||||
|
return message.createdAt
|
||||||
|
|
||||||
|
message.createdAt = fallbackCreatedAt
|
||||||
|
return fallbackCreatedAt
|
||||||
|
}
|
||||||
|
|
||||||
function buildProviderMessages(sessionMessagesForSend: ChatHistoryItem[]) {
|
function buildProviderMessages(sessionMessagesForSend: ChatHistoryItem[]) {
|
||||||
const nowTs = now()
|
const nowTs = now()
|
||||||
|
|
||||||
return sessionMessagesForSend.map((msg) => {
|
return sessionMessagesForSend.map((msg) => {
|
||||||
const { context: _context, id: _id, createdAt, ...withoutContext } = msg
|
const { context: _context, id: _id, createdAt: _createdAt, ...withoutContext } = msg
|
||||||
const rawMessage = unwrapMessage(withoutContext)
|
const rawMessage = unwrapMessage(withoutContext)
|
||||||
|
|
||||||
if (rawMessage.role === 'user') {
|
if (rawMessage.role === 'user') {
|
||||||
return prependTextToContent(rawMessage, formatTimePrefix(createdAt ?? nowTs))
|
return prependTextToContent(rawMessage, formatTimePrefix(getStablePromptTimestamp(msg, nowTs)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rawMessage.role === 'assistant') {
|
if (rawMessage.role === 'assistant') {
|
||||||
|
|||||||
Reference in New Issue
Block a user