This commit is contained in:
seb
2026-10-05 22:27:20 +02:00
parent 7d55b9774a
commit 4b4b03172a
5 changed files with 229 additions and 18 deletions

View File

@@ -183,17 +183,16 @@ class LLMOrbiter {
tools: toolSpecs.length ? toolSpecs : undefined,
signal: runSignal,
extraBody,
onStatus: (s) => call(onStatus, s),
onStatus: (...args) => call(onStatus, ...args),
onContent: (...args) => call(onContent, ...args),
onReasoning: (r) => call(onReasoning, r),
});
// Accumulate usage & emit event per LLM call completed
// Accumulate usage per LLM call completed
const callUsage = this._addUsage(usage, completion.usage);
if (completion.usage) {
rawUsages.push(completion.usage);
}
await call(onUsage, callUsage);
const toolCalls = completion.tool_calls;
const content = completion.content;
@@ -202,6 +201,7 @@ class LLMOrbiter {
// When there is no response (neither tool calls nor content), the loop shall continue.
if (!hasToolCalls && !hasContent) {
await call(onUsage, callUsage);
continue;
}
@@ -217,6 +217,7 @@ class LLMOrbiter {
// No tool calls → done
if (!hasToolCalls) {
finalContent = content;
await call(onUsage, callUsage);
break;
}
@@ -318,6 +319,8 @@ class LLMOrbiter {
recordToolResult(callItem, result);
}
await call(onUsage, callUsage);
if (hasDirectReturn) break;
if (pendingAppendedMessages.length > 0) {

View File

@@ -31,7 +31,7 @@ class OpenRouterProvider {
constructor({ apiKey, apiUrl, siteUrl, siteName, sessionId } = {}) {
if (!apiKey) throw new Error('OpenRouterProvider requires an apiKey');
this.apiKey = apiKey;
this.apiUrl = apiUrl || DEFAULT_API_URL;
this.apiUrl = apiUrl || process.env.OPENROUTER_API_URL || DEFAULT_API_URL;
this.siteUrl = siteUrl || '';
this.siteName = siteName || '';
this.sessionId = sessionId || PROCESS_SESSION_ID;
@@ -48,8 +48,9 @@ class OpenRouterProvider {
model,
messages,
reasoning: {
effort: 'minimal'
effort: 'max'
},
include: ["reasoning.encrypted_content"],
stream: true,
...(effectiveSessionId ? { session_id: effectiveSessionId } : {}),
...extraBody,
@@ -90,8 +91,12 @@ class OpenRouterProvider {
const decoder = new TextDecoder();
let buffer = '';
const result = {};
const state = { workingSent: false };
let firstPayload = true;
const state = {
firstEventSent: false,
reasoningSent: false,
contentSent: false,
workingSent: false,
};
while (true) {
const { done, value } = await reader.read();
@@ -103,20 +108,34 @@ class OpenRouterProvider {
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || !trimmed.startsWith('data:')) continue;
if (!trimmed) continue;
if (!state.firstEventSent) {
state.firstEventSent = true;
handlers.onStatus?.('firstevent');
}
if (!trimmed.startsWith('data:')) continue;
const payload = trimmed.slice(5).trim();
if (payload === '[DONE]') continue;
if (firstPayload) {
firstPayload = false;
handlers.onStatus?.('streaming');
}
this._processChunk(payload, result, handlers, state);
}
}
const remaining = buffer.trim();
if (remaining) {
if (!state.firstEventSent) {
state.firstEventSent = true;
handlers.onStatus?.('firstevent');
}
if (remaining.startsWith('data:')) {
const payload = remaining.slice(5).trim();
if (payload !== '[DONE]') {
this._processChunk(payload, result, handlers, state);
}
}
}
return result;
}
@@ -138,6 +157,10 @@ class OpenRouterProvider {
// Content
if (choice.delta?.content) {
if (!state.contentSent) {
state.contentSent = true;
onStatus?.('content');
}
result.content = (result.content || '') + choice.delta.content;
onContent?.(choice.delta.content, result.content);
}
@@ -181,6 +204,18 @@ class OpenRouterProvider {
}
}
const hasReasoningDelta = Boolean(
reasoningText ||
choice.delta?.reasoning ||
choice.delta?.reasoning_content ||
(Array.isArray(choice.delta?.reasoning_details) && choice.delta.reasoning_details.length > 0)
);
if (hasReasoningDelta && !state.reasoningSent) {
state.reasoningSent = true;
onStatus?.('reasoning');
}
if (reasoningText) {
result.reasoning = (result.reasoning || '') + reasoningText;
onReasoning?.(result.reasoning);