@meistrari/chat-nuxt 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -16
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -8
- package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +2 -18
- package/dist/runtime/components/MeistrariChatEmbed.vue +22 -4
- package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +2 -18
- package/dist/runtime/components/chat/message-input.d.vue.ts +30 -4
- package/dist/runtime/components/chat/message-input.vue +268 -24
- package/dist/runtime/components/chat/message-input.vue.d.ts +30 -4
- package/dist/runtime/composables/useChat.d.ts +3 -5
- package/dist/runtime/composables/useChat.js +18 -4
- package/dist/runtime/composables/useChatApi.js +4 -0
- package/dist/runtime/composables/useConversations.js +8 -2
- package/dist/runtime/composables/useEmbedConfig.d.ts +7 -1
- package/dist/runtime/composables/useEmbedConfig.js +8 -0
- package/dist/runtime/composables/useFeatureFlags.d.ts +1 -2
- package/dist/runtime/composables/useTelaAgentMetadata.d.ts +8 -0
- package/dist/runtime/composables/useTelaAgentMetadata.js +65 -0
- package/dist/runtime/composables/useWorkspaceSettings.d.ts +1 -19
- package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
- package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
- package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +3 -23
- package/dist/runtime/embed/components/ChatEmbed.vue +34 -9
- package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +3 -23
- package/dist/runtime/embed/components/ChatEmbedInner.vue +36 -12
- package/dist/runtime/plugins/markstream.d.ts +1 -1
- package/dist/runtime/plugins/markstream.js +1 -1
- package/dist/runtime/server/api/conversations/[id]/cancel.post.d.ts +1 -1
- package/dist/runtime/server/api/conversations/[id]/cancel.post.js +37 -17
- package/dist/runtime/server/api/conversations/[id]/clear.post.js +28 -13
- package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +11 -13
- package/dist/runtime/server/api/conversations/[id]/files.get.js +5 -7
- package/dist/runtime/server/api/conversations/[id]/index.delete.js +13 -12
- package/dist/runtime/server/api/conversations/[id]/index.get.js +6 -13
- package/dist/runtime/server/api/conversations/[id]/index.patch.js +11 -10
- package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +46 -80
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.d.ts +1 -14
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +81 -187
- package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +68 -86
- package/dist/runtime/server/api/conversations/[id]/usage.get.js +4 -8
- package/dist/runtime/server/api/conversations/generate-title.post.js +4 -3
- package/dist/runtime/server/api/conversations/index.get.js +5 -4
- package/dist/runtime/server/api/conversations/index.post.js +5 -4
- package/dist/runtime/server/api/tela/agents/[id].get.d.ts +6 -0
- package/dist/runtime/server/api/tela/agents/[id].get.js +25 -0
- package/dist/runtime/server/api/workspace/settings.patch.js +1 -1
- package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
- package/dist/runtime/server/db/schema/conversations.js +3 -1
- package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
- package/dist/runtime/server/utils/chat-context.d.ts +5 -0
- package/dist/runtime/server/utils/chat-context.js +20 -0
- package/dist/runtime/server/utils/conversation-agent-turn.d.ts +39 -0
- package/dist/runtime/server/utils/conversation-agent-turn.js +170 -0
- package/dist/runtime/server/utils/conversation-message-files.d.ts +10 -0
- package/dist/runtime/server/utils/conversation-message-files.js +77 -0
- package/dist/runtime/server/utils/conversation-message-sync.d.ts +31 -0
- package/dist/runtime/server/utils/conversation-message-sync.js +132 -0
- package/dist/runtime/server/utils/conversation-scope.d.ts +2 -0
- package/dist/runtime/server/utils/conversation-scope.js +12 -0
- package/dist/runtime/server/utils/tela-agent-api.d.ts +29 -0
- package/dist/runtime/server/utils/tela-agent-api.js +107 -0
- package/dist/runtime/server/utils/tela-agent-session.d.ts +48 -0
- package/dist/runtime/server/utils/tela-agent-session.js +350 -0
- package/dist/runtime/types/embed.d.ts +29 -0
- package/dist/runtime/types/embed.js +0 -0
- package/dist/runtime/types/tela-agent.d.ts +243 -0
- package/dist/runtime/types/tela-agent.js +98 -0
- package/dist/runtime/types/workspace-settings-data.d.ts +19 -0
- package/dist/runtime/types/workspace-settings-data.js +0 -0
- package/dist/runtime/types/workspace-settings.d.ts +1 -1
- package/dist/runtime/utils/agent-input-readiness.d.ts +14 -0
- package/dist/runtime/utils/agent-input-readiness.js +15 -0
- package/dist/runtime/utils/features.d.ts +9 -0
- package/dist/runtime/utils/features.js +5 -4
- package/dist/runtime/utils/file.js +46 -7
- package/dist/runtime/utils/markdown-nodes.d.ts +1 -0
- package/dist/runtime/utils/markdown-nodes.js +6 -0
- package/dist/runtime/utils/tela-chat.d.ts +6 -0
- package/dist/runtime/utils/tela-chat.js +12 -1
- package/drizzle/0014_faulty_lake.sql +2 -0
- package/drizzle/meta/0014_snapshot.json +732 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/dist/runtime/types/feature-flags.d.ts +0 -7
- package/dist/runtime/types/feature-flags.js +0 -6
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { schema } from "#chat-runtime/server/db";
|
|
4
|
+
import { buildMessageWithContext, renderSystemPrompt, truncateTitle } from "#chat-runtime/server/utils/chat-message-builder";
|
|
5
|
+
import {
|
|
6
|
+
getConversationAppliedSettings,
|
|
7
|
+
getWorkspaceSettingsUpdatedAt,
|
|
8
|
+
parseWorkspaceSettingsSnapshot,
|
|
9
|
+
resolveWorkspaceSettings,
|
|
10
|
+
serializeResolvedWorkspaceSettings
|
|
11
|
+
} from "#chat-runtime/server/utils/chat-workspace-settings";
|
|
12
|
+
import { logger } from "#chat-runtime/server/utils/logger";
|
|
13
|
+
import { getTelaAgent, runTelaAgent } from "#chat-runtime/server/utils/tela-agent-api";
|
|
14
|
+
import { toTelaAgentAttachments, toTelaAgentInputSchema } from "#chat-runtime/server/utils/tela-agent-session";
|
|
15
|
+
import { resolveWorkspaceCredentials } from "#chat-runtime/server/utils/workspace";
|
|
16
|
+
import { resolveChatNuxtFeatures } from "#chat-runtime/utils/features";
|
|
17
|
+
import { mergeFileUrls } from "#chat-runtime/utils/file";
|
|
18
|
+
import { isValidModel } from "#chat-runtime/types/chat";
|
|
19
|
+
function getFileVaultRef(file) {
|
|
20
|
+
return file.vaultReference?.trim() || (file.url.startsWith("vault://") ? file.url : null);
|
|
21
|
+
}
|
|
22
|
+
function filterMappedFiles(files, inputSchema) {
|
|
23
|
+
const mappedVaultRefs = new Set(
|
|
24
|
+
inputSchema.filter((input) => input.type === "file").map((input) => input.vaultRef)
|
|
25
|
+
);
|
|
26
|
+
if (mappedVaultRefs.size === 0)
|
|
27
|
+
return files ?? [];
|
|
28
|
+
return (files ?? []).filter((file) => {
|
|
29
|
+
const vaultRef = getFileVaultRef(file);
|
|
30
|
+
return !vaultRef || !mappedVaultRefs.has(vaultRef);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export async function buildTelaAgentTurnPayload(event, conversation, content, files, agentInputs) {
|
|
34
|
+
let inputSchema = agentInputs ?? [];
|
|
35
|
+
let attachmentFiles = filterMappedFiles(files, inputSchema);
|
|
36
|
+
if (inputSchema.length === 0 && conversation.telaAgentId && files?.length) {
|
|
37
|
+
const agentResult = await getTelaAgent(event, conversation.telaAgentId);
|
|
38
|
+
if (agentResult.success) {
|
|
39
|
+
const fileInputCount = agentResult.data.inputSchema.filter((input) => input.type === "file" && input.name.trim()).length;
|
|
40
|
+
inputSchema = toTelaAgentInputSchema(files, agentResult.data.inputSchema);
|
|
41
|
+
if (inputSchema.length > 0) {
|
|
42
|
+
attachmentFiles = [];
|
|
43
|
+
} else if (fileInputCount > 1) {
|
|
44
|
+
logger.warn({
|
|
45
|
+
agentId: conversation.telaAgentId,
|
|
46
|
+
fileInputCount
|
|
47
|
+
}, "Tela agent file input auto-mapping skipped");
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
logger.warn({ agentId: conversation.telaAgentId, error: agentResult.error }, "Tela agent metadata fetch failed");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const attachments = toTelaAgentAttachments(attachmentFiles);
|
|
54
|
+
return {
|
|
55
|
+
message: content.trim(),
|
|
56
|
+
...conversation.threadSessionId ? { sessionId: conversation.threadSessionId } : {},
|
|
57
|
+
...inputSchema.length > 0 ? { inputSchema } : {},
|
|
58
|
+
...attachments.length > 0 ? { attachments } : {}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function queueTelaAgentTurn(event, db, options) {
|
|
62
|
+
const needsNewThread = options.conversation.threadSessionId === null;
|
|
63
|
+
const logContext = {
|
|
64
|
+
conversationId: options.conversationId,
|
|
65
|
+
messageId: options.assistantMessageId
|
|
66
|
+
};
|
|
67
|
+
const telaAgentId = options.conversation.telaAgentId;
|
|
68
|
+
if (!telaAgentId) {
|
|
69
|
+
logger.error(logContext, "Tela agent turn queued without agent id");
|
|
70
|
+
void db.update(schema.messages).set({
|
|
71
|
+
content: "Erro: Agente Tela n\xE3o configurado",
|
|
72
|
+
status: "failed"
|
|
73
|
+
}).where(eq(schema.messages.id, options.assistantMessageId));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
runTelaAgent(event, telaAgentId, options.payload).then(async (result) => {
|
|
77
|
+
if (result.success) {
|
|
78
|
+
await db.update(schema.conversations).set({
|
|
79
|
+
...needsNewThread ? { title: truncateTitle(options.titleContent) } : {},
|
|
80
|
+
threadSessionId: result.sessionId,
|
|
81
|
+
model: null,
|
|
82
|
+
appliedSettingsAt: null,
|
|
83
|
+
appliedSettingsSnapshot: null,
|
|
84
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
85
|
+
}).where(eq(schema.conversations.id, options.conversationId));
|
|
86
|
+
} else {
|
|
87
|
+
logger.warn({ ...logContext, error: result.error }, options.operation === "retry" ? "Tela agent retry failed" : "Tela agent run failed");
|
|
88
|
+
await db.update(schema.messages).set({
|
|
89
|
+
content: `Erro: ${result.error}`,
|
|
90
|
+
status: "failed"
|
|
91
|
+
}).where(eq(schema.messages.id, options.assistantMessageId));
|
|
92
|
+
}
|
|
93
|
+
}).catch(async (error) => {
|
|
94
|
+
logger.error({ ...logContext, error: error.message }, options.operation === "retry" ? "Tela agent retry threw exception" : "Tela agent run threw exception");
|
|
95
|
+
await db.update(schema.messages).set({
|
|
96
|
+
content: "Erro: Falha ao conectar ao agente",
|
|
97
|
+
status: "failed"
|
|
98
|
+
}).where(eq(schema.messages.id, options.assistantMessageId));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
export async function prepareDefaultAgentTurn(event, db, context, options) {
|
|
102
|
+
const explicitSettings = !options.conversation.appliedSettingsSnapshot && options.workspaceSettingsSnapshot !== void 0 ? parseWorkspaceSettingsSnapshot(options.workspaceSettingsSnapshot) : void 0;
|
|
103
|
+
const trustedSettings = options.conversation.appliedSettingsSnapshot || explicitSettings ? null : await resolveWorkspaceSettings(event, context, db);
|
|
104
|
+
const appliedSettings = getConversationAppliedSettings(options.conversation, trustedSettings, explicitSettings);
|
|
105
|
+
const settingsTimestamp = getWorkspaceSettingsUpdatedAt(appliedSettings);
|
|
106
|
+
const snapshotSettings = explicitSettings ?? trustedSettings;
|
|
107
|
+
if (!options.conversation.appliedSettingsSnapshot && snapshotSettings) {
|
|
108
|
+
await db.update(schema.conversations).set({
|
|
109
|
+
appliedSettingsAt: getWorkspaceSettingsUpdatedAt(snapshotSettings),
|
|
110
|
+
appliedSettingsSnapshot: serializeResolvedWorkspaceSettings(snapshotSettings),
|
|
111
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
112
|
+
}).where(and(
|
|
113
|
+
eq(schema.conversations.id, options.conversationId),
|
|
114
|
+
isNull(schema.conversations.appliedSettingsSnapshot)
|
|
115
|
+
));
|
|
116
|
+
}
|
|
117
|
+
const needsNewThread = options.conversation.threadSessionId === null;
|
|
118
|
+
const effectiveModel = options.requestedModel ?? (isValidModel(options.conversation.model) ? options.conversation.model : void 0);
|
|
119
|
+
const content = options.content.trim();
|
|
120
|
+
const knowledgeSources = appliedSettings.knowledgeSources ?? null;
|
|
121
|
+
const hasKnowledgeSources = knowledgeSources !== null && knowledgeSources.length > 0;
|
|
122
|
+
const externalSkillRefs = appliedSettings.externalSkills?.map((skill) => skill.ref) ?? [];
|
|
123
|
+
const skills = externalSkillRefs.length > 0 ? externalSkillRefs : void 0;
|
|
124
|
+
const fileUrls = mergeFileUrls(appliedSettings.contextFiles, options.files);
|
|
125
|
+
const { vaultUrl } = useRuntimeConfig();
|
|
126
|
+
const messageWithContext = buildMessageWithContext(
|
|
127
|
+
content,
|
|
128
|
+
appliedSettings.systemMessage ?? null,
|
|
129
|
+
knowledgeSources,
|
|
130
|
+
needsNewThread,
|
|
131
|
+
needsNewThread ? renderSystemPrompt(vaultUrl) : ""
|
|
132
|
+
);
|
|
133
|
+
return {
|
|
134
|
+
needsNewThread,
|
|
135
|
+
effectiveModel,
|
|
136
|
+
settingsTimestamp,
|
|
137
|
+
messageTitle: truncateTitle(options.content),
|
|
138
|
+
createPayload: {
|
|
139
|
+
message: messageWithContext,
|
|
140
|
+
fileUrls,
|
|
141
|
+
model: effectiveModel,
|
|
142
|
+
customTools: appliedSettings.canvasTools?.length ? { canvas: appliedSettings.canvasTools } : void 0,
|
|
143
|
+
tools: hasKnowledgeSources ? { workstationQueryTool: true } : void 0,
|
|
144
|
+
skills
|
|
145
|
+
},
|
|
146
|
+
continuePayload: {
|
|
147
|
+
content,
|
|
148
|
+
fileUrls,
|
|
149
|
+
model: effectiveModel
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export async function resolveDefaultAgentEnvironmentVariables(event, context, logContext, failureMessage) {
|
|
154
|
+
if (!resolveChatNuxtFeatures(useRuntimeConfig()).credentials)
|
|
155
|
+
return {};
|
|
156
|
+
try {
|
|
157
|
+
return await resolveWorkspaceCredentials(event, context.workspaceId);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
logger.warn({ ...logContext, error }, failureMessage);
|
|
160
|
+
return {};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export function withEnvironmentVariables(payload, environmentVariables) {
|
|
164
|
+
if (Object.keys(environmentVariables).length === 0)
|
|
165
|
+
return payload;
|
|
166
|
+
return {
|
|
167
|
+
...payload,
|
|
168
|
+
environmentVariables
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { useDb } from '#chat-runtime/server/db';
|
|
2
|
+
import type { MessageFile } from '#chat-runtime/types/chat';
|
|
3
|
+
type DbInstance = ReturnType<typeof useDb>;
|
|
4
|
+
type MessageUpdateWithFiles = {
|
|
5
|
+
content?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
files?: MessageFile[];
|
|
8
|
+
};
|
|
9
|
+
export declare function attachNewAssistantFilesToMessageUpdate(db: DbInstance, conversationId: string, updateData: MessageUpdateWithFiles, authToken: string): Promise<void>;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { and, eq, isNotNull } from "drizzle-orm";
|
|
2
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
|
+
import { APIKeyAuthStrategy, VaultFile } from "@meistrari/vault-sdk";
|
|
4
|
+
import { schema } from "#chat-runtime/server/db";
|
|
5
|
+
import { logger } from "#chat-runtime/server/utils/logger";
|
|
6
|
+
import { detectFilesInText } from "#chat-runtime/utils/file";
|
|
7
|
+
async function getExistingMessageFileUrls(db, conversationId) {
|
|
8
|
+
const messagesWithFiles = await db.select({ files: schema.messages.files }).from(schema.messages).where(and(
|
|
9
|
+
eq(schema.messages.conversationId, conversationId),
|
|
10
|
+
isNotNull(schema.messages.files)
|
|
11
|
+
));
|
|
12
|
+
const urls = /* @__PURE__ */ new Set();
|
|
13
|
+
for (const message of messagesWithFiles) {
|
|
14
|
+
if (Array.isArray(message.files)) {
|
|
15
|
+
for (const file of message.files) {
|
|
16
|
+
if (file?.url) {
|
|
17
|
+
urls.add(file.url);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return urls;
|
|
23
|
+
}
|
|
24
|
+
async function extractAssistantFiles(content, authToken) {
|
|
25
|
+
const detected = detectFilesInText(content);
|
|
26
|
+
const vaultFiles = detected.filter((file) => file.type === "vault");
|
|
27
|
+
if (vaultFiles.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
const { vaultUrl } = useRuntimeConfig();
|
|
30
|
+
return Promise.all(
|
|
31
|
+
vaultFiles.map(async (file) => {
|
|
32
|
+
let name = file.name;
|
|
33
|
+
let mimeType = file.mimeType !== "unknown" ? file.mimeType : void 0;
|
|
34
|
+
let size;
|
|
35
|
+
if (vaultUrl) {
|
|
36
|
+
try {
|
|
37
|
+
const authStrategy = new APIKeyAuthStrategy(authToken);
|
|
38
|
+
const vaultFile = await VaultFile.fromVaultReference({
|
|
39
|
+
reference: file.reference,
|
|
40
|
+
config: { authStrategy, vaultUrl }
|
|
41
|
+
});
|
|
42
|
+
await vaultFile.populateMetadata();
|
|
43
|
+
if (vaultFile.metadata?.originalFileName) {
|
|
44
|
+
name = vaultFile.metadata.originalFileName;
|
|
45
|
+
}
|
|
46
|
+
if (vaultFile.metadata?.mimeType) {
|
|
47
|
+
mimeType = vaultFile.metadata.mimeType;
|
|
48
|
+
}
|
|
49
|
+
if (vaultFile.metadata?.size) {
|
|
50
|
+
size = vaultFile.metadata.size;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
logger.debug({ reference: file.reference }, "Vault metadata fetch failed for assistant file");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
url: file.reference,
|
|
58
|
+
name,
|
|
59
|
+
mimeType,
|
|
60
|
+
source: "assistant",
|
|
61
|
+
size
|
|
62
|
+
};
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
export async function attachNewAssistantFilesToMessageUpdate(db, conversationId, updateData, authToken) {
|
|
67
|
+
if (!updateData.content || updateData.status !== "completed")
|
|
68
|
+
return;
|
|
69
|
+
const assistantFiles = await extractAssistantFiles(updateData.content, authToken);
|
|
70
|
+
if (assistantFiles.length === 0)
|
|
71
|
+
return;
|
|
72
|
+
const existingUrls = await getExistingMessageFileUrls(db, conversationId);
|
|
73
|
+
const newFiles = assistantFiles.filter((file) => !existingUrls.has(file.url));
|
|
74
|
+
if (newFiles.length > 0) {
|
|
75
|
+
updateData.files = newFiles;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { useDb } from '#chat-runtime/server/db';
|
|
2
|
+
import type { AgentApiSession, MessageStatus, ReasoningItem } from '#chat-runtime/types/agent';
|
|
3
|
+
import type { ChatRuntimeContext } from '#chat-runtime/server/utils/chat-context';
|
|
4
|
+
import type { Message, MessageFile } from '#chat-runtime/types/chat';
|
|
5
|
+
import type { TelaAgentUsageValues } from '#chat-runtime/server/utils/tela-agent-session';
|
|
6
|
+
type DbInstance = ReturnType<typeof useDb>;
|
|
7
|
+
export type MessageUpdate = {
|
|
8
|
+
reasoningData?: ReasoningItem[];
|
|
9
|
+
externalUuid?: string;
|
|
10
|
+
content?: string;
|
|
11
|
+
status?: MessageStatus;
|
|
12
|
+
ttft?: string;
|
|
13
|
+
updatedAt?: Date;
|
|
14
|
+
files?: MessageFile[];
|
|
15
|
+
};
|
|
16
|
+
type MessageUpdateResponse = {
|
|
17
|
+
messages: Message[];
|
|
18
|
+
newFiles?: MessageFile[];
|
|
19
|
+
};
|
|
20
|
+
export declare function mapAgentApiSessionToMessageUpdate(agentSession: AgentApiSession, options: {
|
|
21
|
+
pendingMessage: Pick<Message, 'ttft' | 'createdAt'>;
|
|
22
|
+
lastAssistantMessage?: Pick<Message, 'externalUuid'> | null;
|
|
23
|
+
userMessage?: Pick<Message, 'createdAt'> | null;
|
|
24
|
+
now?: Date;
|
|
25
|
+
}): MessageUpdate;
|
|
26
|
+
export declare function hasMessageUpdate(updateData: MessageUpdate): boolean;
|
|
27
|
+
export declare function isTerminalMessageUpdate(updateData: MessageUpdate): boolean;
|
|
28
|
+
export declare function buildMessageUpdateResponse(messages: Message[], messageId: string, updateData: MessageUpdate): MessageUpdateResponse;
|
|
29
|
+
export declare function trackTelaAgentConversationUsage(db: DbInstance, context: ChatRuntimeContext, conversationId: string, sessionId: string, usage: TelaAgentUsageValues | null): void;
|
|
30
|
+
export declare function trackAgentApiConversationUsage(db: DbInstance, context: ChatRuntimeContext, conversationId: string, sessionId: string, agentSession: AgentApiSession): void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { schema } from "#chat-runtime/server/db";
|
|
2
|
+
import { extractContentFromSession } from "#chat-runtime/server/utils/agent-api";
|
|
3
|
+
import { logger } from "#chat-runtime/server/utils/logger";
|
|
4
|
+
function toMilliseconds(timestamp) {
|
|
5
|
+
if (typeof timestamp !== "number" || !Number.isFinite(timestamp))
|
|
6
|
+
return null;
|
|
7
|
+
return timestamp < 1e10 ? timestamp * 1e3 : timestamp;
|
|
8
|
+
}
|
|
9
|
+
export function mapAgentApiSessionToMessageUpdate(agentSession, options) {
|
|
10
|
+
const updateData = {};
|
|
11
|
+
const lastSyncedUuid = options.lastAssistantMessage?.externalUuid;
|
|
12
|
+
const lastSyncedIndex = lastSyncedUuid ? agentSession.reasoning?.findIndex((item) => item.uuid === lastSyncedUuid) ?? -1 : -1;
|
|
13
|
+
const newReasoning = agentSession.reasoning?.slice(lastSyncedIndex + 1);
|
|
14
|
+
const hasNewReasoning = newReasoning && newReasoning.length > 0;
|
|
15
|
+
const sessionUpdatedAtMs = toMilliseconds(agentSession.updatedAt);
|
|
16
|
+
const terminalSessionBelongsToPendingMessage = hasNewReasoning || !options.lastAssistantMessage || sessionUpdatedAtMs !== null && sessionUpdatedAtMs >= options.pendingMessage.createdAt.getTime();
|
|
17
|
+
if (hasNewReasoning) {
|
|
18
|
+
updateData.reasoningData = newReasoning;
|
|
19
|
+
const lastReasoningItem = newReasoning.at(-1);
|
|
20
|
+
if (lastReasoningItem?.uuid) {
|
|
21
|
+
updateData.externalUuid = lastReasoningItem.uuid;
|
|
22
|
+
}
|
|
23
|
+
if (!options.pendingMessage.ttft && options.userMessage) {
|
|
24
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
25
|
+
updateData.ttft = String(now.getTime() - options.userMessage.createdAt.getTime());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (agentSession.status === "completed" || agentSession.status === "waiting_messages") {
|
|
29
|
+
if (terminalSessionBelongsToPendingMessage) {
|
|
30
|
+
updateData.content = extractContentFromSession(agentSession);
|
|
31
|
+
updateData.status = "completed";
|
|
32
|
+
updateData.updatedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
33
|
+
}
|
|
34
|
+
} else if (agentSession.status === "failed") {
|
|
35
|
+
const errorContent = agentSession.error || "Agent execution failed";
|
|
36
|
+
updateData.content = `Error: ${errorContent}`;
|
|
37
|
+
updateData.status = "failed";
|
|
38
|
+
updateData.updatedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
39
|
+
}
|
|
40
|
+
return updateData;
|
|
41
|
+
}
|
|
42
|
+
export function hasMessageUpdate(updateData) {
|
|
43
|
+
return Object.keys(updateData).length > 0;
|
|
44
|
+
}
|
|
45
|
+
export function isTerminalMessageUpdate(updateData) {
|
|
46
|
+
return updateData.status === "completed" || updateData.status === "failed";
|
|
47
|
+
}
|
|
48
|
+
export function buildMessageUpdateResponse(messages, messageId, updateData) {
|
|
49
|
+
const updatedMessages = messages.map(
|
|
50
|
+
(message) => message.id === messageId ? { ...message, ...updateData } : message
|
|
51
|
+
);
|
|
52
|
+
if (updateData.files && updateData.files.length > 0) {
|
|
53
|
+
return {
|
|
54
|
+
messages: updatedMessages,
|
|
55
|
+
newFiles: updateData.files
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { messages: updatedMessages };
|
|
59
|
+
}
|
|
60
|
+
export function trackTelaAgentConversationUsage(db, context, conversationId, sessionId, usage) {
|
|
61
|
+
if (!usage)
|
|
62
|
+
return;
|
|
63
|
+
void db.insert(schema.conversationUsage).values({
|
|
64
|
+
workspaceId: context.workspaceId,
|
|
65
|
+
conversationId,
|
|
66
|
+
...usage,
|
|
67
|
+
userId: context.user.id,
|
|
68
|
+
userEmail: context.user.email ?? null,
|
|
69
|
+
sessionId
|
|
70
|
+
}).onConflictDoUpdate({
|
|
71
|
+
target: schema.conversationUsage.conversationId,
|
|
72
|
+
set: {
|
|
73
|
+
...usage,
|
|
74
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
75
|
+
}
|
|
76
|
+
}).catch((error) => {
|
|
77
|
+
logger.warn({ conversationId, error }, "Failed to track conversation usage");
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function trackAgentApiConversationUsage(db, context, conversationId, sessionId, agentSession) {
|
|
81
|
+
const usage = agentSession.result?.usage;
|
|
82
|
+
if (!usage)
|
|
83
|
+
return;
|
|
84
|
+
const breakdown = agentSession.result?.usageBreakdown;
|
|
85
|
+
const serverToolUse = breakdown?.serverToolUse;
|
|
86
|
+
const webSearchRequests = typeof serverToolUse?.web_search_requests === "number" ? serverToolUse.web_search_requests : null;
|
|
87
|
+
const webFetchRequests = typeof serverToolUse?.web_fetch_requests === "number" ? serverToolUse.web_fetch_requests : null;
|
|
88
|
+
void db.insert(schema.conversationUsage).values({
|
|
89
|
+
workspaceId: context.workspaceId,
|
|
90
|
+
conversationId,
|
|
91
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
92
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
93
|
+
totalTokens: usage.totalTokens ?? 0,
|
|
94
|
+
promptCost: String(usage.promptCost ?? 0),
|
|
95
|
+
completionCost: String(usage.completionCost ?? 0),
|
|
96
|
+
totalCost: String(usage.totalCost ?? 0),
|
|
97
|
+
durationMs: usage.duration ?? null,
|
|
98
|
+
turns: usage.turns ?? null,
|
|
99
|
+
userId: context.user.id,
|
|
100
|
+
userEmail: context.user.email ?? null,
|
|
101
|
+
model: agentSession.model ?? null,
|
|
102
|
+
sessionId,
|
|
103
|
+
cacheReadInputTokens: breakdown?.cacheReadInputTokens ?? null,
|
|
104
|
+
cacheCreationInputTokens: breakdown?.cacheCreationInputTokens ?? null,
|
|
105
|
+
modelUsage: breakdown?.modelUsage ?? null,
|
|
106
|
+
durationApiMs: breakdown?.durationApiMs ?? null,
|
|
107
|
+
webSearchRequests,
|
|
108
|
+
webFetchRequests
|
|
109
|
+
}).onConflictDoUpdate({
|
|
110
|
+
target: schema.conversationUsage.conversationId,
|
|
111
|
+
set: {
|
|
112
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
113
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
114
|
+
totalTokens: usage.totalTokens ?? 0,
|
|
115
|
+
promptCost: String(usage.promptCost ?? 0),
|
|
116
|
+
completionCost: String(usage.completionCost ?? 0),
|
|
117
|
+
totalCost: String(usage.totalCost ?? 0),
|
|
118
|
+
durationMs: usage.duration ?? null,
|
|
119
|
+
turns: usage.turns ?? null,
|
|
120
|
+
model: agentSession.model ?? null,
|
|
121
|
+
cacheReadInputTokens: breakdown?.cacheReadInputTokens ?? null,
|
|
122
|
+
cacheCreationInputTokens: breakdown?.cacheCreationInputTokens ?? null,
|
|
123
|
+
modelUsage: breakdown?.modelUsage ?? null,
|
|
124
|
+
durationApiMs: breakdown?.durationApiMs ?? null,
|
|
125
|
+
webSearchRequests,
|
|
126
|
+
webFetchRequests,
|
|
127
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
128
|
+
}
|
|
129
|
+
}).catch((error) => {
|
|
130
|
+
logger.warn({ conversationId, error }, "Failed to track conversation usage");
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
2
|
+
import { schema } from "#chat-runtime/server/db";
|
|
3
|
+
export function conversationScopeConditions(context, conversationId) {
|
|
4
|
+
const conditions = [
|
|
5
|
+
eq(schema.conversations.workspaceId, context.workspaceId),
|
|
6
|
+
context.telaAgentId ? eq(schema.conversations.telaAgentId, context.telaAgentId) : isNull(schema.conversations.telaAgentId)
|
|
7
|
+
];
|
|
8
|
+
if (conversationId) {
|
|
9
|
+
conditions.unshift(eq(schema.conversations.id, conversationId));
|
|
10
|
+
}
|
|
11
|
+
return and(...conditions);
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { TelaAgent, TelaAgentRunPayload, TelaAgentSession } from '#chat-runtime/types/tela-agent';
|
|
3
|
+
export declare function runTelaAgent(event: H3Event, agentId: string, payload: TelaAgentRunPayload): Promise<{
|
|
4
|
+
success: true;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
} | {
|
|
7
|
+
success: false;
|
|
8
|
+
error: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function getTelaAgent(event: H3Event, agentId: string): Promise<{
|
|
11
|
+
success: true;
|
|
12
|
+
data: TelaAgent;
|
|
13
|
+
} | {
|
|
14
|
+
success: false;
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function getTelaAgentSession(event: H3Event, sessionId: string): Promise<{
|
|
18
|
+
success: true;
|
|
19
|
+
data: TelaAgentSession;
|
|
20
|
+
} | {
|
|
21
|
+
success: false;
|
|
22
|
+
error: string;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function endTelaAgentSession(event: H3Event, sessionId: string): Promise<{
|
|
25
|
+
success: true;
|
|
26
|
+
} | {
|
|
27
|
+
success: false;
|
|
28
|
+
error: string;
|
|
29
|
+
}>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
|
+
import {
|
|
3
|
+
telaAgentApiResponseSchema,
|
|
4
|
+
telaAgentEndSessionApiResponseSchema,
|
|
5
|
+
telaAgentRunApiResponseSchema,
|
|
6
|
+
telaAgentSessionApiResponseSchema
|
|
7
|
+
} from "#chat-runtime/types/tela-agent";
|
|
8
|
+
import { getAgentDataToken } from "#chat-runtime/server/utils/agent-api";
|
|
9
|
+
import { logger } from "#chat-runtime/server/utils/logger";
|
|
10
|
+
function getTelaErrorMessage(body, fallback) {
|
|
11
|
+
if (body && typeof body === "object") {
|
|
12
|
+
const record = body;
|
|
13
|
+
if (typeof record.error === "string")
|
|
14
|
+
return record.error;
|
|
15
|
+
if (typeof record.message === "string")
|
|
16
|
+
return record.message;
|
|
17
|
+
}
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
async function readResponseBody(response) {
|
|
21
|
+
const text = await response.text();
|
|
22
|
+
if (!text)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
} catch {
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function telaAgentFetch(event, path, init) {
|
|
31
|
+
const { telaApiUrl } = useRuntimeConfig();
|
|
32
|
+
const token = getAgentDataToken(event);
|
|
33
|
+
const url = `${telaApiUrl}${path}`;
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
...init,
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"x-data-token": token,
|
|
39
|
+
"Authorization": `Bearer ${token}`,
|
|
40
|
+
...init.headers
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const body = await readResponseBody(response);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const error = getTelaErrorMessage(body, `Tela API error: ${response.status} - ${response.statusText}`);
|
|
46
|
+
logger.warn({ status: response.status, statusText: response.statusText, path }, "Tela agent API request failed");
|
|
47
|
+
return { success: false, error };
|
|
48
|
+
}
|
|
49
|
+
return { success: true, body };
|
|
50
|
+
}
|
|
51
|
+
export async function runTelaAgent(event, agentId, payload) {
|
|
52
|
+
const encodedAgentId = encodeURIComponent(agentId);
|
|
53
|
+
const result = await telaAgentFetch(event, `/agent/${encodedAgentId}/run`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify(payload)
|
|
56
|
+
});
|
|
57
|
+
if (!result.success)
|
|
58
|
+
return result;
|
|
59
|
+
const parsed = telaAgentRunApiResponseSchema.safeParse(result.body);
|
|
60
|
+
if (!parsed.success) {
|
|
61
|
+
logger.warn({ issues: parsed.error.issues }, "Tela agent run response parse error");
|
|
62
|
+
return { success: false, error: "Invalid response from Tela API" };
|
|
63
|
+
}
|
|
64
|
+
return { success: true, sessionId: parsed.data.data.sessionId };
|
|
65
|
+
}
|
|
66
|
+
export async function getTelaAgent(event, agentId) {
|
|
67
|
+
const encodedAgentId = encodeURIComponent(agentId);
|
|
68
|
+
const result = await telaAgentFetch(event, `/agent/${encodedAgentId}`, {
|
|
69
|
+
method: "GET"
|
|
70
|
+
});
|
|
71
|
+
if (!result.success)
|
|
72
|
+
return result;
|
|
73
|
+
const parsed = telaAgentApiResponseSchema.safeParse(result.body);
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
logger.warn({ issues: parsed.error.issues, agentId }, "Tela agent metadata response parse error");
|
|
76
|
+
return { success: false, error: "Invalid agent metadata response from Tela API" };
|
|
77
|
+
}
|
|
78
|
+
return { success: true, data: parsed.data.data };
|
|
79
|
+
}
|
|
80
|
+
export async function getTelaAgentSession(event, sessionId) {
|
|
81
|
+
const encodedSessionId = encodeURIComponent(sessionId);
|
|
82
|
+
const result = await telaAgentFetch(event, `/agent/sessions/${encodedSessionId}`, {
|
|
83
|
+
method: "GET"
|
|
84
|
+
});
|
|
85
|
+
if (!result.success)
|
|
86
|
+
return result;
|
|
87
|
+
const parsed = telaAgentSessionApiResponseSchema.safeParse(result.body);
|
|
88
|
+
if (!parsed.success) {
|
|
89
|
+
logger.warn({ issues: parsed.error.issues, sessionId }, "Tela agent session response parse error");
|
|
90
|
+
return { success: false, error: "Invalid session response from Tela API" };
|
|
91
|
+
}
|
|
92
|
+
return { success: true, data: parsed.data.data };
|
|
93
|
+
}
|
|
94
|
+
export async function endTelaAgentSession(event, sessionId) {
|
|
95
|
+
const encodedSessionId = encodeURIComponent(sessionId);
|
|
96
|
+
const result = await telaAgentFetch(event, `/agent/sessions/${encodedSessionId}`, {
|
|
97
|
+
method: "DELETE"
|
|
98
|
+
});
|
|
99
|
+
if (!result.success)
|
|
100
|
+
return result;
|
|
101
|
+
const parsed = telaAgentEndSessionApiResponseSchema.safeParse(result.body);
|
|
102
|
+
if (!parsed.success || !parsed.data.data.success) {
|
|
103
|
+
logger.warn({ sessionId }, "Tela agent end session response parse error");
|
|
104
|
+
return { success: false, error: "Invalid end-session response from Tela API" };
|
|
105
|
+
}
|
|
106
|
+
return { success: true };
|
|
107
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { MessageStatus, ReasoningItem } from '#chat-runtime/types/agent';
|
|
2
|
+
import type { FileUploadInput, MessageFile } from '#chat-runtime/types/chat';
|
|
3
|
+
import type { TelaAgentExecutionInput, TelaAgentInputVariable, TelaAgentSession, TelaAgentStep } from '#chat-runtime/types/tela-agent';
|
|
4
|
+
export type TelaAgentMessageUpdate = {
|
|
5
|
+
reasoningData?: ReasoningItem[];
|
|
6
|
+
externalUuid?: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
status?: MessageStatus;
|
|
9
|
+
ttft?: string;
|
|
10
|
+
updatedAt?: Date;
|
|
11
|
+
files?: MessageFile[];
|
|
12
|
+
};
|
|
13
|
+
export type TelaAgentUsageValues = {
|
|
14
|
+
promptTokens: number;
|
|
15
|
+
completionTokens: number;
|
|
16
|
+
totalTokens: number;
|
|
17
|
+
promptCost: string;
|
|
18
|
+
completionCost: string;
|
|
19
|
+
totalCost: string;
|
|
20
|
+
durationMs: number | null;
|
|
21
|
+
turns: number | null;
|
|
22
|
+
model: string | null;
|
|
23
|
+
cacheReadInputTokens: number | null;
|
|
24
|
+
cacheCreationInputTokens: number | null;
|
|
25
|
+
modelUsage: null;
|
|
26
|
+
durationApiMs: null;
|
|
27
|
+
webSearchRequests: null;
|
|
28
|
+
webFetchRequests: null;
|
|
29
|
+
};
|
|
30
|
+
type TelaAgentFileInput = FileUploadInput | MessageFile;
|
|
31
|
+
export declare function toTelaAgentInputSchema(files: TelaAgentFileInput[] | undefined, inputVariables: TelaAgentInputVariable[] | null | undefined): TelaAgentExecutionInput[];
|
|
32
|
+
export declare function toTelaAgentAttachments(files: TelaAgentFileInput[] | undefined): {
|
|
33
|
+
vaultRef: string;
|
|
34
|
+
filename: any;
|
|
35
|
+
}[];
|
|
36
|
+
export declare function getTelaAgentStepSyncId(step: TelaAgentStep): string;
|
|
37
|
+
export declare function normalizeTelaAgentReasoningStep(sessionId: string, step: TelaAgentStep): ReasoningItem | null;
|
|
38
|
+
export declare function getNewTelaAgentSteps(session: TelaAgentSession, lastSyncedStepId: string | null | undefined): TelaAgentStep[];
|
|
39
|
+
export declare function extractTelaAgentAssistantContent(session: TelaAgentSession, steps?: TelaAgentStep[]): string;
|
|
40
|
+
export declare function mapTelaAgentSessionToMessageUpdate(session: TelaAgentSession, options?: {
|
|
41
|
+
lastSyncedStepId?: string | null;
|
|
42
|
+
existingReasoningData?: ReasoningItem[] | null;
|
|
43
|
+
pendingMessageTtft?: string | null;
|
|
44
|
+
userMessageCreatedAt?: Date | null;
|
|
45
|
+
now?: Date;
|
|
46
|
+
}): TelaAgentMessageUpdate;
|
|
47
|
+
export declare function mapTelaAgentUsage(session: TelaAgentSession): TelaAgentUsageValues | null;
|
|
48
|
+
export {};
|