mirror of https://github.com/ghostfolio/ghostfolio
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
98 lines
2.3 KiB
98 lines
2.3 KiB
/**
|
|
* Shared helpers for evals — authenticates + calls the agent endpoint,
|
|
* parses the UI message stream, and extracts tool calls + text.
|
|
*/
|
|
|
|
const API_BASE = process.env.API_BASE || 'http://localhost:3333';
|
|
|
|
export interface ToolResultEntry {
|
|
toolName: string;
|
|
result: unknown;
|
|
}
|
|
|
|
export interface AgentResponse {
|
|
text: string;
|
|
toolCalls: string[];
|
|
toolResults: ToolResultEntry[];
|
|
}
|
|
|
|
export async function getAuthToken(): Promise<string> {
|
|
const accessToken = process.env.TEST_USER_ACCESS_TOKEN;
|
|
|
|
if (!accessToken) {
|
|
throw new Error('TEST_USER_ACCESS_TOKEN not set in env');
|
|
}
|
|
|
|
const res = await fetch(`${API_BASE}/api/v1/auth/anonymous/${accessToken}`);
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`Auth failed: ${res.status}`);
|
|
}
|
|
|
|
const data = (await res.json()) as { authToken: string };
|
|
return data.authToken;
|
|
}
|
|
|
|
export async function callAgent(prompt: string): Promise<AgentResponse> {
|
|
const jwt = await getAuthToken();
|
|
|
|
const res = await fetch(`${API_BASE}/api/v1/agent/chat`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${jwt}`
|
|
},
|
|
body: JSON.stringify({
|
|
messages: [
|
|
{
|
|
id: crypto.randomUUID(),
|
|
role: 'user' as const,
|
|
parts: [{ type: 'text', text: prompt }]
|
|
}
|
|
]
|
|
})
|
|
});
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`Agent call failed: ${res.status} ${await res.text()}`);
|
|
}
|
|
|
|
const body = await res.text();
|
|
return parseUIMessageStream(body);
|
|
}
|
|
|
|
function parseUIMessageStream(raw: string): AgentResponse {
|
|
const lines = raw.split('\n');
|
|
let text = '';
|
|
const toolCalls: string[] = [];
|
|
const toolResults: ToolResultEntry[] = [];
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
|
|
if (!trimmed.startsWith('data: ')) continue;
|
|
|
|
const data = trimmed.slice(6);
|
|
|
|
if (data === '[DONE]') continue;
|
|
|
|
try {
|
|
const evt = JSON.parse(data);
|
|
|
|
if (evt.type === 'text-delta') {
|
|
text += evt.delta;
|
|
} else if (evt.type === 'tool-input-start') {
|
|
toolCalls.push(evt.toolName);
|
|
} else if (evt.type === 'tool-result') {
|
|
toolResults.push({
|
|
toolName: evt.toolName,
|
|
result: evt.result
|
|
});
|
|
}
|
|
} catch {
|
|
// skip unparseable lines
|
|
}
|
|
}
|
|
|
|
return { text, toolCalls, toolResults };
|
|
}
|
|
|