All checks were successful
Deploy / deploy (push) Successful in 2m47s
- route.ts: replace @anthropic-ai/sdk with groq-sdk, rewrite chat loop - voice-tool-schemas.ts: convert from Anthropic format to OpenAI/Groq function tools - voice-history.ts: extend HistoryMessage type to include tool role, simplify cache stubs No prompt caching (Groq does not support it), tool calling preserved.
89 lines
3.1 KiB
TypeScript
89 lines
3.1 KiB
TypeScript
/**
|
||
* История диалога per-agent per-day. Файлы в /data/voice-history/{agent}-{date}.json.
|
||
* /data — это volume контейнера (на хосте /opt/digital-home/smart-home-tablet-data/).
|
||
*
|
||
* Fallback: если /data не существует (локальная разработка) — пишем в /tmp/voice-history.
|
||
*
|
||
* Формат обновлён под Groq/OpenAI: role может быть 'user' | 'assistant' | 'tool'.
|
||
*/
|
||
import { promises as fs } from 'node:fs'
|
||
import { existsSync } from 'node:fs'
|
||
import path from 'node:path'
|
||
|
||
const PRIMARY_DIR = process.env.VOICE_HISTORY_DIR || '/data/voice-history'
|
||
const DATA_DIR = (() => {
|
||
const parent = path.dirname(PRIMARY_DIR)
|
||
return existsSync(parent) ? PRIMARY_DIR : '/tmp/voice-history'
|
||
})()
|
||
const MAX_HISTORY = parseInt(process.env.VOICE_MAX_HISTORY || '40', 10)
|
||
|
||
export type HistoryMessage = {
|
||
role: 'user' | 'assistant' | 'tool'
|
||
content: any
|
||
tool_calls?: any[]
|
||
tool_call_id?: string
|
||
}
|
||
|
||
function todayIso(): string {
|
||
return new Date().toISOString().slice(0, 10)
|
||
}
|
||
|
||
function historyPath(agent: string): string {
|
||
return path.join(DATA_DIR, `${agent}-${todayIso()}.json`)
|
||
}
|
||
|
||
export async function loadHistory(agent: string): Promise<HistoryMessage[]> {
|
||
try {
|
||
const raw = await fs.readFile(historyPath(agent), 'utf-8')
|
||
const parsed = JSON.parse(raw)
|
||
return Array.isArray(parsed) ? parsed : []
|
||
} catch (e: any) {
|
||
if (e?.code === 'ENOENT') return []
|
||
console.warn('[voice/history] read failed:', e?.message || e)
|
||
return []
|
||
}
|
||
}
|
||
|
||
export async function saveHistory(agent: string, history: HistoryMessage[]): Promise<void> {
|
||
try {
|
||
await fs.mkdir(DATA_DIR, { recursive: true })
|
||
const trimmed = history.slice(-MAX_HISTORY)
|
||
await fs.writeFile(historyPath(agent), JSON.stringify(trimmed, null, 2), 'utf-8')
|
||
} catch (e: any) {
|
||
console.warn('[voice/history] write failed:', e?.message || e)
|
||
}
|
||
}
|
||
|
||
export async function resetHistory(agent: string): Promise<void> {
|
||
try {
|
||
await fs.unlink(historyPath(agent))
|
||
} catch (e: any) {
|
||
if (e?.code !== 'ENOENT') console.warn('[voice/history] reset failed:', e?.message || e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Заглушка для обратной совместимости — убирает cache_control из блоков.
|
||
* В Groq-режиме cache_control не используется, функция — no-op.
|
||
*/
|
||
export function stripCacheControl(content: any): any {
|
||
if (Array.isArray(content)) {
|
||
return content.map((b) => {
|
||
if (b && typeof b === 'object' && 'cache_control' in b) {
|
||
const { cache_control: _ignore, ...rest } = b
|
||
return rest
|
||
}
|
||
return b
|
||
})
|
||
}
|
||
return content
|
||
}
|
||
|
||
/**
|
||
* Заглушка для обратной совместимости.
|
||
* В Groq-режиме prompt-кеширование не используется — просто возвращаем историю как есть.
|
||
*/
|
||
export function buildMessagesWithCache(history: HistoryMessage[], _cacheTailUncached = 2): HistoryMessage[] {
|
||
return history.map((m) => ({ role: m.role, content: m.content }))
|
||
}
|