ui: Linting & Formatting scripts (#26819)

This commit is contained in:
Aleksander Grygier
2026-08-10 08:38:37 +02:00
committed by GitHub
parent 1e396e72a8
commit 92d1bb0c99
538 changed files with 8806 additions and 6036 deletions
+183 -116
View File
@@ -20,25 +20,14 @@
* @see mcpStore in stores/mcp.svelte.ts for MCP operations
*/
import { ChatService } from '$lib/services';
import { config } from '$lib/stores/settings.svelte';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { modelsStore } from '$lib/stores/models.svelte';
import { toolsStore } from '$lib/stores/tools.svelte';
import { permissionsStore } from '$lib/stores/permissions.svelte';
import { BuiltInTool, ToolSource, ToolPermissionDecision } from '$lib/enums';
import { SvelteMap } from 'svelte/reactivity';
import { ToolsService } from '$lib/services/tools.service';
import { SandboxService } from '$lib/services/sandbox.service';
import { isAbortError } from '$lib/utils';
import { DEFAULT_AGENTIC_CONFIG, NEWLINE } from '$lib/constants';
import {
IMAGE_MIME_TO_EXTENSION,
DATA_URI_BASE64_REGEX,
MCP_ATTACHMENT_NAME_PREFIX,
DEFAULT_IMAGE_EXTENSION
DEFAULT_IMAGE_EXTENSION,
IMAGE_MIME_TO_EXTENSION,
MCP_ATTACHMENT_NAME_PREFIX
} from '$lib/constants';
import { BuiltInTool, ToolPermissionDecision, ToolSource } from '$lib/enums';
import {
AttachmentType,
ContentPartType,
@@ -46,49 +35,60 @@ import {
MimeTypePrefix,
ToolCallType
} from '$lib/enums';
import { ChatService } from '$lib/services';
import { SandboxService } from '$lib/services/sandbox.service';
import { ToolsService } from '$lib/services/tools.service';
import { conversationsStore } from '$lib/stores/conversations.svelte';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { modelsStore } from '$lib/stores/models.svelte';
import { permissionsStore } from '$lib/stores/permissions.svelte';
import { config } from '$lib/stores/settings.svelte';
import { toolsStore } from '$lib/stores/tools.svelte';
import type {
AgenticConfig,
AgenticFlowParams,
AgenticFlowResult,
AgenticSession,
AgenticConfig,
SettingsConfigType,
McpServerOverride,
MCPToolCall
MCPToolCall,
SettingsConfigType
} from '$lib/types';
import type {
AgenticMessage,
AgenticToolCallList,
AgenticFlowCallbacks,
AgenticFlowOptions,
AgenticMessage,
AgenticToolCallList,
SteeringMessage
} from '$lib/types/agentic';
import type {
ApiChatCompletionToolCall,
ApiChatMessageData,
ApiChatMessageContentPart
ApiChatMessageContentPart,
ApiChatMessageData
} from '$lib/types/api';
import type {
ChatMessageAgenticTimings,
ChatMessageAgenticTurnStats,
ChatMessagePromptProgress,
ChatMessageTimings,
ChatMessageAgenticTimings,
ChatMessageToolCallTiming,
ChatMessageAgenticTurnStats
ChatMessageToolCallTiming
} from '$lib/types/chat';
import type {
DatabaseMessage,
DatabaseMessageExtra,
DatabaseMessageExtraImageFile
} from '$lib/types/database';
import { isAbortError } from '$lib/utils';
import { SvelteMap } from 'svelte/reactivity';
function createDefaultSession(): AgenticSession {
return {
isRunning: false,
currentTurn: 0,
totalToolCalls: 0,
executingToolCallId: null,
isRunning: false,
lastError: null,
streamingToolCall: null,
pendingPermissionRequest: null,
executingToolCallId: null
streamingToolCall: null,
totalToolCalls: 0
};
}
@@ -100,36 +100,39 @@ function toAgenticMessages(messages: ApiChatMessageData[]): AgenticMessage[] {
message.tool_calls.length > 0
) {
return {
role: MessageRole.ASSISTANT,
content: message.content,
reasoning_content: message.reasoning_content,
role: MessageRole.ASSISTANT,
tool_calls: message.tool_calls.map((call, index) => ({
id: call.id ?? `call_${index}`,
type: (call.type as ToolCallType.FUNCTION) ?? ToolCallType.FUNCTION,
function: {
name: call.function?.name ?? '',
arguments: call.function?.arguments ?? ''
}
arguments: call.function?.arguments ?? '',
name: call.function?.name ?? ''
},
id: call.id ?? `call_${index}`,
type: (call.type as ToolCallType.FUNCTION) ?? ToolCallType.FUNCTION
}))
} satisfies AgenticMessage;
}
if (message.role === MessageRole.ASSISTANT) {
return {
role: MessageRole.ASSISTANT,
content: message.content,
reasoning_content: message.reasoning_content
reasoning_content: message.reasoning_content,
role: MessageRole.ASSISTANT
} satisfies AgenticMessage;
}
if (message.role === MessageRole.TOOL && message.tool_call_id) {
return {
content: typeof message.content === 'string' ? message.content : '',
role: MessageRole.TOOL,
tool_call_id: message.tool_call_id,
content: typeof message.content === 'string' ? message.content : ''
tool_call_id: message.tool_call_id
} satisfies AgenticMessage;
}
return {
role: message.role as MessageRole.SYSTEM | MessageRole.USER,
content: message.content
content: message.content,
role: message.role as MessageRole.SYSTEM | MessageRole.USER
} satisfies AgenticMessage;
});
}
@@ -159,20 +162,24 @@ class AgenticStore {
for (const session of this._sessions.values()) {
if (session.isRunning) return true;
}
return false;
}
getSession(conversationId: string): AgenticSession {
let session = this._sessions.get(conversationId);
if (!session) {
session = createDefaultSession();
this._sessions.set(conversationId, session);
}
return session;
}
private updateSession(conversationId: string, update: Partial<AgenticSession>): void {
const session = this.getSession(conversationId);
this._sessions.set(conversationId, { ...session, ...update });
}
@@ -182,9 +189,11 @@ class AgenticStore {
getActiveSessions(): Array<{ conversationId: string; session: AgenticSession }> {
const active: Array<{ conversationId: string; session: AgenticSession }> = [];
for (const [conversationId, session] of this._sessions.entries()) {
if (session.isRunning) active.push({ conversationId, session });
}
return active;
}
@@ -224,6 +233,7 @@ class AgenticStore {
resolveContinue(conversationId: string, shouldContinue: boolean): void {
const resolver = this._continueResolvers.get(conversationId);
if (resolver) {
this._continueResolvers.delete(conversationId);
resolver(shouldContinue);
@@ -232,6 +242,7 @@ class AgenticStore {
resolvePermission(conversationId: string, decision: ToolPermissionDecision): void {
const resolver = this._permissionResolvers.get(conversationId);
if (resolver) {
this._permissionResolvers.delete(conversationId);
resolver(decision);
@@ -279,8 +290,11 @@ class AgenticStore {
*/
consumePendingSteeringMessage(conversationId: string): SteeringMessage | null {
const msg = this._steeringMessages.get(conversationId);
if (!msg) return null;
this._steeringMessages.delete(conversationId);
return msg;
}
@@ -291,6 +305,7 @@ class AgenticStore {
toolsStore.builtinTools.length > 0 ||
toolsStore.frontendTools.length > 0 ||
toolsStore.customTools.length > 0;
return {
enabled: hasTools && DEFAULT_AGENTIC_CONFIG.enabled,
maxTurns
@@ -299,8 +314,11 @@ class AgenticStore {
private parseToolArguments(args: string | Record<string, unknown>): Record<string, unknown> {
if (typeof args === 'object') return args;
const trimmed = args.trim();
if (trimmed === '') return {};
return JSON.parse(trimmed) as Record<string, unknown>;
}
@@ -311,21 +329,24 @@ class AgenticStore {
signal?: AbortSignal
): Promise<ToolPermissionDecision> {
const permissionKey = toolsStore.getPermissionKey(toolName);
if (permissionKey && permissionsStore.hasTool(permissionKey)) {
return ToolPermissionDecision.ONCE;
}
this._pendingPermissions.set(conversationId, { toolName, serverLabel });
this._pendingPermissions.set(conversationId, { serverLabel, toolName });
return new Promise<ToolPermissionDecision>((resolve) => {
if (signal?.aborted) {
this._pendingPermissions.set(conversationId, null);
resolve(ToolPermissionDecision.DENY);
return;
}
this._permissionResolvers.set(conversationId, (decision) => {
this._pendingPermissions.set(conversationId, null);
if (decision === ToolPermissionDecision.ALWAYS && permissionKey) {
permissionsStore.allowTool(permissionKey);
} else if (decision === ToolPermissionDecision.ALWAYS_SERVER) {
@@ -337,8 +358,10 @@ class AgenticStore {
)
.map((t) => toolsStore.getPermissionKey(t.definition.function.name)!)
.filter((k): k is string => k !== null);
permissionsStore.allowTools(serverToolKeys);
}
resolve(decision);
});
@@ -346,6 +369,7 @@ class AgenticStore {
'abort',
() => {
const resolver = this._permissionResolvers.get(conversationId);
if (resolver) {
this._permissionResolvers.delete(conversationId);
this._pendingPermissions.set(conversationId, null);
@@ -364,6 +388,7 @@ class AgenticStore {
if (signal?.aborted) {
this._pendingContinueRequests.set(conversationId, false);
resolve(false);
return;
}
@@ -376,6 +401,7 @@ class AgenticStore {
'abort',
() => {
const resolver = this._continueResolvers.get(conversationId);
if (resolver) {
this._continueResolvers.delete(conversationId);
this._pendingContinueRequests.set(conversationId, false);
@@ -388,7 +414,7 @@ class AgenticStore {
}
async runAgenticFlow(params: AgenticFlowParams): Promise<AgenticFlowResult> {
const { conversationId, messages, options = {}, callbacks, signal, perChatOverrides } = params;
const { callbacks, conversationId, messages, options = {}, perChatOverrides, signal } = params;
// Clear any pending permissions/continue requests for this conversation when starting a new flow
this._pendingPermissions.set(conversationId, null);
@@ -403,9 +429,11 @@ class AgenticStore {
}
const agenticConfig = this.getConfig(config(), perChatOverrides);
if (!agenticConfig.enabled) return { handled: false };
const hasMcpServers = mcpStore.hasEnabledServers(perChatOverrides);
if (hasMcpServers) {
const initialized = await mcpStore.ensureInitialized(perChatOverrides);
@@ -415,6 +443,7 @@ class AgenticStore {
}
const tools = toolsStore.getEnabledToolsForLLM();
if (tools.length === 0) {
return { handled: false };
}
@@ -428,42 +457,48 @@ class AgenticStore {
return ChatService.convertDbMessageToApiChatMessageData(
msg as DatabaseMessage & { extra?: DatabaseMessageExtra[] }
);
return msg as ApiChatMessageData;
})
)
).filter((msg: { role: ChatRole; content: string | ApiChatMessageContentPart[] }) => {
if (msg.role === MessageRole.SYSTEM) {
const content = typeof msg.content === 'string' ? msg.content : '';
return content.trim().length > 0;
}
return true;
});
this.updateSession(conversationId, {
isRunning: true,
currentTurn: 0,
totalToolCalls: 0,
lastError: null
isRunning: true,
lastError: null,
totalToolCalls: 0
});
if (hasMcpServers) mcpStore.acquireConnection();
try {
await this.executeAgenticLoop({
agenticConfig,
callbacks,
conversationId,
messages: normalizedMessages,
options,
tools,
agenticConfig,
callbacks,
signal
signal,
tools
});
return { handled: true };
} catch (error) {
const normalizedError = error instanceof Error ? error : new Error(String(error));
this.updateSession(conversationId, { lastError: normalizedError });
callbacks.onError?.(normalizedError);
return { handled: true, error: normalizedError };
return { error: normalizedError, handled: true };
} finally {
this.updateSession(conversationId, { isRunning: false });
@@ -486,40 +521,40 @@ class AgenticStore {
callbacks: AgenticFlowCallbacks;
signal?: AbortSignal;
}): Promise<void> {
const { conversationId, messages, options, tools, agenticConfig, callbacks, signal } = params;
const { agenticConfig, callbacks, conversationId, messages, options, signal, tools } = params;
const {
onChunk,
onReasoningChunk,
onToolCallsStreaming,
onAttachments,
onModel,
onCompletionId,
onAssistantTurnComplete,
createToolResultMessage,
updateToolResultMessage,
createAssistantMessage,
createToolResultMessage,
onAssistantTurnComplete,
onAttachments,
onChunk,
onCompletionId,
onFlowComplete,
onModel,
onReasoningChunk,
onTimings,
onTurnComplete
onToolCallsStreaming,
onTurnComplete,
updateToolResultMessage
} = callbacks;
const sessionMessages: AgenticMessage[] = toAgenticMessages(messages);
let capturedTimings: ChatMessageTimings | undefined;
let totalToolCallCount = 0;
const agenticTimings: ChatMessageAgenticTimings = {
turns: 0,
llm: { predicted_ms: 0, predicted_n: 0, prompt_ms: 0, prompt_n: 0 },
perTurn: [],
toolCalls: [],
toolCallsCount: 0,
toolsMs: 0,
toolCalls: [],
perTurn: [],
llm: { predicted_n: 0, predicted_ms: 0, prompt_n: 0, prompt_ms: 0 }
turns: 0
};
const maxTurns = agenticConfig.maxTurns;
const effectiveModel = options.model || modelsStore.models[0]?.model || '';
let turn = 0;
while (true) {
if (turn >= maxTurns) {
// Turn limit reached - ask user whether to continue
@@ -530,6 +565,7 @@ class AgenticStore {
if (!shouldContinue || signal?.aborted) {
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -542,6 +578,7 @@ class AgenticStore {
if (signal?.aborted) {
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -558,10 +595,10 @@ class AgenticStore {
let turnTimings: ChatMessageTimings | undefined;
const turnStats: ChatMessageAgenticTurnStats = {
turn: turn + 1,
llm: { predicted_n: 0, predicted_ms: 0, prompt_n: 0, prompt_ms: 0 },
llm: { predicted_ms: 0, predicted_n: 0, prompt_ms: 0, prompt_n: 0 },
toolCalls: [],
toolsMs: 0
toolsMs: 0,
turn: turn + 1
};
try {
@@ -569,16 +606,30 @@ class AgenticStore {
sessionMessages as ApiChatMessageData[],
{
...options,
stream: true,
tools: tools.length > 0 ? tools : undefined,
onChunk: (chunk: string) => {
turnContent += chunk;
onChunk?.(chunk);
},
onComplete: () => {
/* Completion handled after sendMessage resolves */
},
onCompletionId,
onError: (error: Error) => {
throw error;
},
onModel,
onReasoningChunk: (chunk: string) => {
turnReasoningContent += chunk;
onReasoningChunk?.(chunk);
},
onTimings: (timings?: ChatMessageTimings, progress?: ChatMessagePromptProgress) => {
onTimings?.(timings, progress);
if (timings) {
capturedTimings = timings;
turnTimings = timings;
}
},
onToolCallChunk: (serialized: string) => {
try {
turnToolCalls = JSON.parse(serialized) as ApiChatCompletionToolCall[];
@@ -589,6 +640,7 @@ class AgenticStore {
const name = turnToolCalls[0].function.name || '';
const args = turnToolCalls[0].function.arguments || '';
const argsLengthBucket = Math.floor(args.length / 100);
if (
name !== lastStreamingToolCallName ||
argsLengthBucket !== lastStreamingToolCallArgsLength
@@ -596,7 +648,7 @@ class AgenticStore {
lastStreamingToolCallName = name;
lastStreamingToolCallArgsLength = argsLengthBucket;
this.updateSession(conversationId, {
streamingToolCall: { name, arguments: args }
streamingToolCall: { arguments: args, name }
});
}
}
@@ -604,21 +656,8 @@ class AgenticStore {
/* Ignore parse errors during streaming */
}
},
onModel,
onCompletionId,
onTimings: (timings?: ChatMessageTimings, progress?: ChatMessagePromptProgress) => {
onTimings?.(timings, progress);
if (timings) {
capturedTimings = timings;
turnTimings = timings;
}
},
onComplete: () => {
/* Completion handled after sendMessage resolves */
},
onError: (error: Error) => {
throw error;
}
stream: true,
tools: tools.length > 0 ? tools : undefined
},
conversationId,
signal
@@ -646,9 +685,12 @@ class AgenticStore {
undefined
);
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
const normalizedError = error instanceof Error ? error : new Error('LLM stream error');
// preserve partial output as is, the outer error dialog informs the user separately
await onAssistantTurnComplete?.(
turnContent,
@@ -657,6 +699,7 @@ class AgenticStore {
undefined
);
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
throw normalizedError;
}
@@ -672,6 +715,7 @@ class AgenticStore {
undefined
);
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -686,6 +730,7 @@ class AgenticStore {
turnToolCalls.length > 0 ? this.normalizeToolCalls(turnToolCalls) : undefined
);
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -711,6 +756,7 @@ class AgenticStore {
// Normalize and save assistant turn with tool calls
const normalizedCalls = this.normalizeToolCalls(turnToolCalls);
if (normalizedCalls.length === 0) {
await onAssistantTurnComplete?.(
turnContent,
@@ -719,6 +765,7 @@ class AgenticStore {
undefined
);
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -735,9 +782,9 @@ class AgenticStore {
// Add assistant message to session history
sessionMessages.push({
role: MessageRole.ASSISTANT,
content: turnContent || undefined,
reasoning_content: turnReasoningContent || undefined,
role: MessageRole.ASSISTANT,
tool_calls: normalizedCalls
});
@@ -747,6 +794,7 @@ class AgenticStore {
if (signal?.aborted) {
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -758,21 +806,23 @@ class AgenticStore {
for (let j = i; j < normalizedCalls.length; j++) {
const remainingCall = normalizedCalls[j];
const interruptedContent = 'Tool execution was interrupted by a new user message.';
if (createToolResultMessage) {
await createToolResultMessage(remainingCall.id, interruptedContent);
}
sessionMessages.push({
content: interruptedContent,
role: MessageRole.TOOL,
tool_call_id: remainingCall.id,
content: interruptedContent
tool_call_id: remainingCall.id
});
}
break;
}
const toolName = toolCall.function.name;
const serverLabel = toolsStore.getToolServerLabel(toolName);
// Ask for permission before executing the tool
const permission = await this.requestPermission(
conversationId,
@@ -786,6 +836,7 @@ class AgenticStore {
if (signal?.aborted) {
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -815,14 +866,17 @@ class AgenticStore {
const args = this.parseToolArguments(toolCall.function.arguments);
const cwd = conversationsStore.activeConversation?.cwd;
const msg = await createToolResultMessage(toolCall.id, '', undefined, cwd);
createdToolResultMessageId = msg.id;
let accumulated = '';
for await (const ev of ToolsService.streamTool(toolName, args, signal, cwd)) {
if (ev.chunk !== null) {
accumulated += ev.chunk;
await updateToolResultMessage(msg.id, accumulated);
}
if (ev.done) {
if (ev.error) {
accumulated = accumulated
@@ -831,6 +885,7 @@ class AgenticStore {
await updateToolResultMessage(msg.id, accumulated);
toolSuccess = false;
}
break;
}
}
@@ -852,8 +907,8 @@ class AgenticStore {
if (executionResult.isError) toolSuccess = false;
} else {
const mcpCall: MCPToolCall = {
id: toolCall.id,
function: { name: toolName, arguments: toolCall.function.arguments }
function: { arguments: toolCall.function.arguments, name: toolName },
id: toolCall.id
};
const executionResult = await mcpStore.executeTool(mcpCall, signal);
@@ -863,14 +918,17 @@ class AgenticStore {
if (isAbortError(error)) {
this.updateSession(conversationId, { executingToolCallId: null });
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
// Carry the partial stream contents already mirrored to the UI -
// they show up as live output even if the stream broke off mid-run.
result = result
? `${result}\nError: ${error instanceof Error ? error.message : String(error)}`
: `Error: ${error instanceof Error ? error.message : String(error)}`;
toolSuccess = false;
if (createdToolResultMessageId && updateToolResultMessage) {
await updateToolResultMessage(createdToolResultMessageId, result);
}
@@ -881,8 +939,8 @@ class AgenticStore {
const toolDurationMs = performance.now() - toolStartTime;
const toolTiming: ChatMessageToolCallTiming = {
name: toolCall.function.name,
duration_ms: Math.round(toolDurationMs),
name: toolCall.function.name,
success: toolSuccess
};
@@ -894,10 +952,11 @@ class AgenticStore {
if (signal?.aborted) {
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
const { cleanedResult, attachments } = this.extractBase64Attachments(result);
const { attachments, cleanedResult } = this.extractBase64Attachments(result);
// For streaming tools the result message was created empty
// at the start of execution and updated in place as chunks
@@ -906,8 +965,10 @@ class AgenticStore {
// the final accumulator (rare, since chunks usually don't
// carry image data URIs) and emit the attachments callback.
let toolResultMessage: DatabaseMessage | undefined;
if (createdToolResultMessageId) {
toolResultMessage = { id: createdToolResultMessageId } as DatabaseMessage;
if (attachments.length > 0 && updateToolResultMessage) {
await updateToolResultMessage(createdToolResultMessageId, cleanedResult, attachments);
}
@@ -925,16 +986,17 @@ class AgenticStore {
// Build content parts for session history (including images for vision models)
const contentParts: ApiChatMessageContentPart[] = [
{ type: ContentPartType.TEXT, text: cleanedResult }
{ text: cleanedResult, type: ContentPartType.TEXT }
];
for (const attachment of attachments) {
if (attachment.type === AttachmentType.IMAGE) {
if (modelsStore.modelSupportsVision(effectiveModel)) {
contentParts.push({
type: ContentPartType.IMAGE_URL,
image_url: {
url: (attachment as DatabaseMessageExtraImageFile).base64Url
}
},
type: ContentPartType.IMAGE_URL
});
} else {
console.info(
@@ -945,9 +1007,9 @@ class AgenticStore {
}
sessionMessages.push({
content: contentParts.length === 1 ? cleanedResult : contentParts,
role: MessageRole.TOOL,
tool_call_id: toolCall.id,
content: contentParts.length === 1 ? cleanedResult : contentParts
tool_call_id: toolCall.id
});
}
@@ -955,6 +1017,7 @@ class AgenticStore {
agenticTimings.perTurn!.push(turnStats);
const intermediateTimings = this.buildFinalTimings(capturedTimings, agenticTimings);
if (intermediateTimings) onTurnComplete?.(intermediateTimings);
}
@@ -964,6 +1027,7 @@ class AgenticStore {
'[AgenticStore] Steering message detected after tool execution, exiting agentic flow'
);
onFlowComplete?.(this.buildFinalTimings(capturedTimings, agenticTimings));
return;
}
@@ -976,25 +1040,27 @@ class AgenticStore {
agenticTimings: ChatMessageAgenticTimings
): ChatMessageTimings | undefined {
if (agenticTimings.toolCallsCount === 0) return capturedTimings;
return {
predicted_n: capturedTimings?.predicted_n,
predicted_ms: capturedTimings?.predicted_ms,
prompt_n: capturedTimings?.prompt_n,
prompt_ms: capturedTimings?.prompt_ms,
agentic: agenticTimings,
cache_n: capturedTimings?.cache_n,
agentic: agenticTimings
predicted_ms: capturedTimings?.predicted_ms,
predicted_n: capturedTimings?.predicted_n,
prompt_ms: capturedTimings?.prompt_ms,
prompt_n: capturedTimings?.prompt_n
};
}
private normalizeToolCalls(toolCalls: ApiChatCompletionToolCall[]): AgenticToolCallList {
if (!toolCalls) return [];
return toolCalls.map((call, index) => ({
id: call?.id ?? `tool_${index}`,
type: (call?.type as ToolCallType.FUNCTION) ?? ToolCallType.FUNCTION,
function: {
name: call?.function?.name ?? '',
arguments: call?.function?.arguments ?? ''
}
arguments: call?.function?.arguments ?? '',
name: call?.function?.name ?? ''
},
id: call?.id ?? `tool_${index}`,
type: (call?.type as ToolCallType.FUNCTION) ?? ToolCallType.FUNCTION
}));
}
@@ -1003,17 +1069,18 @@ class AgenticStore {
attachments: DatabaseMessageExtra[];
} {
if (!result.trim()) {
return { cleanedResult: result, attachments: [] };
return { attachments: [], cleanedResult: result };
}
const lines = result.split(NEWLINE);
const attachments: DatabaseMessageExtra[] = [];
let attachmentIndex = 0;
const cleanedLines = lines.map((line) => {
const trimmedLine = line.trim();
const match = trimmedLine.match(DATA_URI_BASE64_REGEX);
if (!match) {
return line;
}
@@ -1029,7 +1096,7 @@ class AgenticStore {
const name = this.buildAttachmentName(mimeType, attachmentIndex);
if (mimeType.startsWith(MimeTypePrefix.IMAGE)) {
attachments.push({ type: AttachmentType.IMAGE, name, base64Url: trimmedLine });
attachments.push({ base64Url: trimmedLine, name, type: AttachmentType.IMAGE });
return `[Attachment saved: ${name}]`;
}
@@ -1037,7 +1104,7 @@ class AgenticStore {
return line;
});
return { cleanedResult: cleanedLines.join(NEWLINE), attachments };
return { attachments, cleanedResult: cleanedLines.join(NEWLINE) };
}
private buildAttachmentName(mimeType: string, index: number): string {