This commit is contained in:
seb
2026-10-05 22:27:23 +02:00
parent af0ef29e22
commit 18699eb40b
7 changed files with 61 additions and 50 deletions

View File

@@ -303,7 +303,6 @@ export class ConversationManager {
throw err;
}
this.broadcast('message', userMsg);
this.broadcast('state', { state: 'thinking' });
emitCompanyEvent('modelconversation', { companyId: this.companyId });
@@ -343,7 +342,6 @@ export class ConversationManager {
throw err;
}
this.broadcast('message', toolMsg);
this.broadcast('state', { state: 'working' });
emitCompanyEvent('modelconversation', { companyId: this.companyId });
@@ -361,7 +359,6 @@ export class ConversationManager {
* @param {number} googleAuthId
*/
async runGeneration(googleAuthId) {
let usage = null;
let error = null;
try {
@@ -397,33 +394,23 @@ export class ConversationManager {
...history.map(toLlmMessage),
];
let working = false;
let text = '';
let currentReasoning = '';
let savedToolCallsTurn = null;
const markWorking = () => {
if (working) return;
working = true;
this.broadcast('state', { state: 'working' });
};
this.orbiter = new LLMOrbiter(new OpenRouterProvider({ apiKey }), {
onStatus: (status) => {
if (status === 'working') markWorking();
this.orbiter = new LLMOrbiter(new OpenRouterProvider({ apiKey, apiUrl: env.openrouterApiUrl }), {
onStatus: (status, meta) => {
this.broadcast('state', { state: status, ...(meta || {}) });
},
onReasoning: (reasoning) => {
markWorking();
currentReasoning = reasoning;
this.broadcast('reasoning', { text: reasoning });
},
onContent: (chunk) => {
markWorking();
text += chunk;
this.broadcast('delta', { delta: chunk, text });
},
onToolCall: async ({ name, args, id, callItem, tool_calls, reasoning_details, reasoning, content }) => {
markWorking();
try {
const effectiveToolCalls = tool_calls || (callItem ? [callItem] : [{
id: id || randomUUID(),
@@ -449,12 +436,11 @@ export class ConversationManager {
...(effectiveReasoning ? { reasoning: effectiveReasoning } : {}),
};
const callMsg = await db.addMessage(googleAuthId, this.conversationId, {
await db.addMessage(googleAuthId, this.conversationId, {
role: 'assistant',
message: JSON.stringify(assistantPayload),
});
text = '';
this.broadcast('message', callMsg);
emitCompanyEvent('modelconversation', { companyId: this.companyId });
}
} catch (err) {
@@ -462,15 +448,23 @@ export class ConversationManager {
}
},
onToolResult: async ({ name, result, id, callItem }) => {
const rawArgs = callItem?.function?.arguments || '';
this.broadcast('state', {
state: 'toolcall',
id: id || callItem?.id || '',
name,
arguments: rawArgs,
content: rawArgs,
result,
});
try {
const resMsg = await db.addMessage(googleAuthId, this.conversationId, {
await db.addMessage(googleAuthId, this.conversationId, {
role: 'tool',
message: JSON.stringify({
content: typeof result === 'string' ? result : JSON.stringify(result),
tool_call_id: id || callItem?.id || '',
}),
});
this.broadcast('message', resMsg);
emitCompanyEvent('modelconversation', { companyId: this.companyId });
} catch (err) {
console.error(`[ConversationManager ${this.conversationId}] failed to save tool result message:`, err);
@@ -478,21 +472,37 @@ export class ConversationManager {
},
onToolError: async ({ name, error, id, callItem }) => {
console.error(`[ConversationManager ${this.conversationId}] Tool "${name}" errored:`, error);
const rawArgs = callItem?.function?.arguments || '';
this.broadcast('state', {
state: 'toolcall',
id: id || callItem?.id || '',
name,
arguments: rawArgs,
content: rawArgs,
error: String(error),
});
try {
const errMsg = await db.addMessage(googleAuthId, this.conversationId, {
await db.addMessage(googleAuthId, this.conversationId, {
role: 'tool',
message: JSON.stringify({
content: `Tool error [${name}]: ${error}`,
tool_call_id: id || callItem?.id || '',
}),
});
this.broadcast('message', errMsg);
emitCompanyEvent('modelconversation', { companyId: this.companyId });
} catch (err) {
console.error(`[ConversationManager ${this.conversationId}] failed to save tool error message:`, err);
}
},
onUsage: (u) => {
if (u) {
this.broadcast('usage', {
input: u.input ?? 0,
cached: u.cached ?? 0,
output: u.output ?? 0,
...(u.cost != null ? { cost: u.cost } : {}),
});
}
// A call that consumed 0 input tokens AND produced 0 output means the
// provider returned nothing at all — not a real generation. LLMOrbiter
// treats "no content and no tool calls" as "keep looping", so several of
@@ -577,7 +587,7 @@ export class ConversationManager {
}
if (content || finalReasoningDetails || finalReasoning) {
const assistantMsg = await db.addMessage(googleAuthId, this.conversationId, {
await db.addMessage(googleAuthId, this.conversationId, {
message: JSON.stringify({
content: content || '',
...(finalReasoningDetails ? { reasoning_details: finalReasoningDetails } : {}),
@@ -585,15 +595,7 @@ export class ConversationManager {
}),
role: 'assistant',
});
this.broadcast('message', assistantMsg);
}
usage = {
input: result.usage.input,
cached: result.usage.cached,
output: result.usage.output,
cost: result.usage.cost,
};
} catch (err) {
if (this.destroyed) return;
error = err.message;
@@ -609,7 +611,7 @@ export class ConversationManager {
console.error(`[ConversationManager ${this.conversationId}] failed to reset state:`, err);
}
emitCompanyEvent('modelconversation', { companyId: this.companyId });
this.broadcast('state', error ? { state: 'done', error } : { state: 'done', usage });
this.broadcast('state', error ? { state: 'done', error } : { state: 'done' });
}
destroy() {