merlyn/server/utils/agents/aibitat/plugins/chat-history.js
Timothy Carambat f395083978
Automatic mode for workspace (Agent mode default) (#5143)
* Add automatic chat mode with native tool calling support

    Introduces a new automatic chat mode (now the default) that automatically invokes tools when the provider supports native tool calling. Conditionally shows/hides the @agent command based on whether native tooling is available.

    - Add supportsNativeToolCalling() to AI providers (OpenAI, Anthropic, Azure always support; others opt-in via ENV)
    - Update all locale translations with new mode descriptions
    - Enhance translator to preserve Trans component tags
    - Remove deprecated ability tags UI

* rebase translations

* WIP on image attachments. Supports initial image attachment + subsequent attachments

* persist images

* Image attachments and updates for providers

* desktop pre-change

* always show command on failure

* add back gemini streaming detection

* move provider native tooling flag to Provider func

* whoops - forgot to delete

* strip "@agent" from prompts to prevent weird replies

* translations for automatic-mode (#5145)

* translations for automatic-mode

* rebase

* translations

* lint

* fix dead translations

* change default for now to chat mode just for rollout

* remove pfp for workspace

* passthrough workspace for showAgentCommand detection and rendering

* Agent API automatic mode support

* ephemeral attachments passthrough

* support reading of pinned documents in agent context
2026-03-18 12:35:43 -07:00

104 lines
3.6 KiB
JavaScript

const { WorkspaceChats } = require("../../../../models/workspaceChats");
/**
* Plugin to save chat history to AnythingLLM DB.
*/
const chatHistory = {
name: "chat-history",
startupConfig: {
params: {},
},
plugin: function () {
return {
name: this.name,
setup: function (aibitat) {
aibitat.onMessage(async () => {
try {
const lastResponses = aibitat.chats.slice(-2);
if (lastResponses.length !== 2) return;
const [prev, last] = lastResponses;
// We need a full conversation reply with prev being from
// the USER and the last being from anyone other than the user.
if (prev.from !== "USER" || last.from === "USER") return;
// Extract attachments from user message if present
const attachments = prev.attachments || [];
// If we have a post-reply flow we should save the chat using this special flow
// so that post save cleanup and other unique properties can be run as opposed to regular chat.
if (aibitat.hasOwnProperty("_replySpecialAttributes")) {
await this._storeSpecial(aibitat, {
prompt: prev.content,
response: last.content,
attachments,
options: aibitat._replySpecialAttributes,
});
delete aibitat._replySpecialAttributes;
return;
}
await this._store(aibitat, {
prompt: prev.content,
response: last.content,
attachments,
});
} catch {}
});
},
_store: async function (
aibitat,
{ prompt, response, attachments = [] } = {}
) {
const invocation = aibitat.handlerProps.invocation;
const metrics = aibitat.provider?.getUsage?.() ?? {};
const citations = aibitat._pendingCitations ?? [];
await WorkspaceChats.new({
workspaceId: Number(invocation.workspace_id),
prompt,
response: {
text: response,
sources: citations,
type: "chat",
attachments,
metrics,
},
user: { id: invocation?.user_id || null },
threadId: invocation?.thread_id || null,
});
aibitat.clearCitations?.();
},
_storeSpecial: async function (
aibitat,
{ prompt, response, attachments = [], options = {} } = {}
) {
const invocation = aibitat.handlerProps.invocation;
const metrics = aibitat.provider?.getUsage?.() ?? {};
const citations = aibitat._pendingCitations ?? [];
const existingSources = options?.sources ?? [];
await WorkspaceChats.new({
workspaceId: Number(invocation.workspace_id),
prompt,
response: {
sources: [...existingSources, ...citations],
// when we have a _storeSpecial called the options param can include a storedResponse() function
// that will override the text property to store extra information in, depending on the special type of chat.
text: options.hasOwnProperty("storedResponse")
? options.storedResponse(response)
: response,
type: options?.saveAsType ?? "chat",
attachments,
metrics,
},
user: { id: invocation?.user_id || null },
threadId: invocation?.thread_id || null,
});
aibitat.clearCitations?.();
options?.postSave();
},
};
},
};
module.exports = { chatHistory };