@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.
Files changed (85) hide show
  1. package/README.md +51 -16
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +12 -8
  4. package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +2 -18
  5. package/dist/runtime/components/MeistrariChatEmbed.vue +22 -4
  6. package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +2 -18
  7. package/dist/runtime/components/chat/message-input.d.vue.ts +30 -4
  8. package/dist/runtime/components/chat/message-input.vue +268 -24
  9. package/dist/runtime/components/chat/message-input.vue.d.ts +30 -4
  10. package/dist/runtime/composables/useChat.d.ts +3 -5
  11. package/dist/runtime/composables/useChat.js +18 -4
  12. package/dist/runtime/composables/useChatApi.js +4 -0
  13. package/dist/runtime/composables/useConversations.js +8 -2
  14. package/dist/runtime/composables/useEmbedConfig.d.ts +7 -1
  15. package/dist/runtime/composables/useEmbedConfig.js +8 -0
  16. package/dist/runtime/composables/useFeatureFlags.d.ts +1 -2
  17. package/dist/runtime/composables/useTelaAgentMetadata.d.ts +8 -0
  18. package/dist/runtime/composables/useTelaAgentMetadata.js +65 -0
  19. package/dist/runtime/composables/useWorkspaceSettings.d.ts +1 -19
  20. package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
  21. package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
  22. package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +3 -23
  23. package/dist/runtime/embed/components/ChatEmbed.vue +34 -9
  24. package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +3 -23
  25. package/dist/runtime/embed/components/ChatEmbedInner.vue +36 -12
  26. package/dist/runtime/plugins/markstream.d.ts +1 -1
  27. package/dist/runtime/plugins/markstream.js +1 -1
  28. package/dist/runtime/server/api/conversations/[id]/cancel.post.d.ts +1 -1
  29. package/dist/runtime/server/api/conversations/[id]/cancel.post.js +37 -17
  30. package/dist/runtime/server/api/conversations/[id]/clear.post.js +28 -13
  31. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +11 -13
  32. package/dist/runtime/server/api/conversations/[id]/files.get.js +5 -7
  33. package/dist/runtime/server/api/conversations/[id]/index.delete.js +13 -12
  34. package/dist/runtime/server/api/conversations/[id]/index.get.js +6 -13
  35. package/dist/runtime/server/api/conversations/[id]/index.patch.js +11 -10
  36. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +46 -80
  37. package/dist/runtime/server/api/conversations/[id]/messages/index.get.d.ts +1 -14
  38. package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +81 -187
  39. package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +68 -86
  40. package/dist/runtime/server/api/conversations/[id]/usage.get.js +4 -8
  41. package/dist/runtime/server/api/conversations/generate-title.post.js +4 -3
  42. package/dist/runtime/server/api/conversations/index.get.js +5 -4
  43. package/dist/runtime/server/api/conversations/index.post.js +5 -4
  44. package/dist/runtime/server/api/tela/agents/[id].get.d.ts +6 -0
  45. package/dist/runtime/server/api/tela/agents/[id].get.js +25 -0
  46. package/dist/runtime/server/api/workspace/settings.patch.js +1 -1
  47. package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
  48. package/dist/runtime/server/db/schema/conversations.js +3 -1
  49. package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
  50. package/dist/runtime/server/utils/chat-context.d.ts +5 -0
  51. package/dist/runtime/server/utils/chat-context.js +20 -0
  52. package/dist/runtime/server/utils/conversation-agent-turn.d.ts +39 -0
  53. package/dist/runtime/server/utils/conversation-agent-turn.js +170 -0
  54. package/dist/runtime/server/utils/conversation-message-files.d.ts +10 -0
  55. package/dist/runtime/server/utils/conversation-message-files.js +77 -0
  56. package/dist/runtime/server/utils/conversation-message-sync.d.ts +31 -0
  57. package/dist/runtime/server/utils/conversation-message-sync.js +132 -0
  58. package/dist/runtime/server/utils/conversation-scope.d.ts +2 -0
  59. package/dist/runtime/server/utils/conversation-scope.js +12 -0
  60. package/dist/runtime/server/utils/tela-agent-api.d.ts +29 -0
  61. package/dist/runtime/server/utils/tela-agent-api.js +107 -0
  62. package/dist/runtime/server/utils/tela-agent-session.d.ts +48 -0
  63. package/dist/runtime/server/utils/tela-agent-session.js +350 -0
  64. package/dist/runtime/types/embed.d.ts +29 -0
  65. package/dist/runtime/types/embed.js +0 -0
  66. package/dist/runtime/types/tela-agent.d.ts +243 -0
  67. package/dist/runtime/types/tela-agent.js +98 -0
  68. package/dist/runtime/types/workspace-settings-data.d.ts +19 -0
  69. package/dist/runtime/types/workspace-settings-data.js +0 -0
  70. package/dist/runtime/types/workspace-settings.d.ts +1 -1
  71. package/dist/runtime/utils/agent-input-readiness.d.ts +14 -0
  72. package/dist/runtime/utils/agent-input-readiness.js +15 -0
  73. package/dist/runtime/utils/features.d.ts +9 -0
  74. package/dist/runtime/utils/features.js +5 -4
  75. package/dist/runtime/utils/file.js +46 -7
  76. package/dist/runtime/utils/markdown-nodes.d.ts +1 -0
  77. package/dist/runtime/utils/markdown-nodes.js +6 -0
  78. package/dist/runtime/utils/tela-chat.d.ts +6 -0
  79. package/dist/runtime/utils/tela-chat.js +12 -1
  80. package/drizzle/0014_faulty_lake.sql +2 -0
  81. package/drizzle/meta/0014_snapshot.json +732 -0
  82. package/drizzle/meta/_journal.json +7 -0
  83. package/package.json +1 -1
  84. package/dist/runtime/types/feature-flags.d.ts +0 -7
  85. package/dist/runtime/types/feature-flags.js +0 -6
@@ -1,12 +1,12 @@
1
1
  import { createError, defineEventHandler, getRouterParam } from "h3";
2
- import { eq, and } from "drizzle-orm";
3
2
  import { useDb, schema } from "#chat-runtime/server/db";
4
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
3
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
4
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
5
5
  import { getAgentDataToken, deleteSession } from "#chat-runtime/server/utils/agent-api";
6
+ import { endTelaAgentSession } from "#chat-runtime/server/utils/tela-agent-api";
6
7
  import { logger } from "#chat-runtime/server/utils/logger";
7
8
  export default defineEventHandler(async (event) => {
8
- const context = resolveChatContext(event);
9
- const token = getAgentDataToken(event);
9
+ const context = resolveChatRuntimeContext(event);
10
10
  const db = useDb();
11
11
  const id = getRouterParam(event, "id");
12
12
  if (!id) {
@@ -15,12 +15,7 @@ export default defineEventHandler(async (event) => {
15
15
  statusMessage: "Conversation ID is required"
16
16
  });
17
17
  }
18
- const [deleted] = await db.delete(schema.conversations).where(
19
- and(
20
- eq(schema.conversations.id, id),
21
- eq(schema.conversations.workspaceId, context.workspaceId)
22
- )
23
- ).returning();
18
+ const [deleted] = await db.delete(schema.conversations).where(conversationScopeConditions(context, id)).returning();
24
19
  if (!deleted) {
25
20
  throw createError({
26
21
  statusCode: 404,
@@ -29,8 +24,14 @@ export default defineEventHandler(async (event) => {
29
24
  }
30
25
  logger.info({ conversationId: id, workspaceId: context.workspaceId }, "Conversation deleted");
31
26
  if (deleted.threadSessionId) {
32
- deleteSession(token, deleted.threadSessionId).catch(() => {
33
- });
27
+ if (deleted.telaAgentId) {
28
+ endTelaAgentSession(event, deleted.threadSessionId).catch(() => {
29
+ });
30
+ } else {
31
+ const token = getAgentDataToken(event);
32
+ deleteSession(token, deleted.threadSessionId).catch(() => {
33
+ });
34
+ }
34
35
  }
35
36
  return { success: true };
36
37
  });
@@ -1,9 +1,10 @@
1
1
  import { createError, defineEventHandler, getRouterParam } from "h3";
2
- import { eq, and, asc } from "drizzle-orm";
2
+ import { eq, asc } from "drizzle-orm";
3
3
  import { useDb, schema } from "#chat-runtime/server/db";
4
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
4
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
5
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
5
6
  export default defineEventHandler(async (event) => {
6
- const context = resolveChatContext(event);
7
+ const context = resolveChatRuntimeContext(event);
7
8
  const db = useDb();
8
9
  const id = getRouterParam(event, "id");
9
10
  if (!id) {
@@ -12,22 +13,14 @@ export default defineEventHandler(async (event) => {
12
13
  statusMessage: "Conversation ID is required"
13
14
  });
14
15
  }
15
- const [conversationResult, messages] = await Promise.all([
16
- db.select().from(schema.conversations).where(
17
- and(
18
- eq(schema.conversations.id, id),
19
- eq(schema.conversations.workspaceId, context.workspaceId)
20
- )
21
- ),
22
- db.select().from(schema.messages).where(eq(schema.messages.conversationId, id)).orderBy(asc(schema.messages.createdAt))
23
- ]);
24
- const [conversation] = conversationResult;
16
+ const [conversation] = await db.select().from(schema.conversations).where(conversationScopeConditions(context, id));
25
17
  if (!conversation) {
26
18
  throw createError({
27
19
  statusCode: 404,
28
20
  statusMessage: "Conversation not found"
29
21
  });
30
22
  }
23
+ const messages = await db.select().from(schema.messages).where(eq(schema.messages.conversationId, id)).orderBy(asc(schema.messages.createdAt));
31
24
  return {
32
25
  ...conversation,
33
26
  messages
@@ -1,11 +1,11 @@
1
1
  import { createError, defineEventHandler, getRouterParam, readBody } from "h3";
2
- import { eq, and } from "drizzle-orm";
3
2
  import { useDb, schema } from "#chat-runtime/server/db";
4
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
3
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
4
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
5
5
  import { logger } from "#chat-runtime/server/utils/logger";
6
6
  import { isValidModel } from "#chat-runtime/types/chat";
7
7
  export default defineEventHandler(async (event) => {
8
- const context = resolveChatContext(event);
8
+ const context = resolveChatRuntimeContext(event);
9
9
  const db = useDb();
10
10
  const id = getRouterParam(event, "id");
11
11
  if (!id) {
@@ -27,7 +27,13 @@ export default defineEventHandler(async (event) => {
27
27
  statusMessage: "Title cannot be empty"
28
28
  });
29
29
  }
30
- if (body.model !== void 0 && body.model !== null && !isValidModel(body.model)) {
30
+ if (context.telaAgentId && body.model !== void 0) {
31
+ throw createError({
32
+ statusCode: 400,
33
+ statusMessage: "Model cannot be updated for Tela-agent conversations"
34
+ });
35
+ }
36
+ if (!context.telaAgentId && body.model !== void 0 && body.model !== null && !isValidModel(body.model)) {
31
37
  throw createError({
32
38
  statusCode: 400,
33
39
  statusMessage: "Invalid model"
@@ -38,12 +44,7 @@ export default defineEventHandler(async (event) => {
38
44
  updateData.title = body.title.trim();
39
45
  if (body.model !== void 0)
40
46
  updateData.model = body.model;
41
- const [updated] = await db.update(schema.conversations).set(updateData).where(
42
- and(
43
- eq(schema.conversations.id, id),
44
- eq(schema.conversations.workspaceId, context.workspaceId)
45
- )
46
- ).returning();
47
+ const [updated] = await db.update(schema.conversations).set(updateData).where(conversationScopeConditions(context, id)).returning();
47
48
  if (!updated) {
48
49
  throw createError({
49
50
  statusCode: 404,
@@ -1,24 +1,19 @@
1
1
  import { createError, defineEventHandler, getRouterParam } from "h3";
2
- import { useRuntimeConfig } from "nitropack/runtime";
3
- import { eq, and, lt, desc, isNull } from "drizzle-orm";
2
+ import { eq, and, lt, desc } from "drizzle-orm";
4
3
  import { logger } from "#chat-runtime/server/utils/logger";
5
4
  import { useDb, schema } from "#chat-runtime/server/db";
6
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
7
- import { buildMessageWithContext, renderSystemPrompt, truncateTitle } from "#chat-runtime/server/utils/chat-message-builder";
8
- import { getAgentDataToken, createChatAgentSession, continueSession } from "#chat-runtime/server/utils/agent-api";
5
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
9
6
  import {
10
- getConversationAppliedSettings,
11
- getWorkspaceSettingsUpdatedAt,
12
- resolveWorkspaceSettings,
13
- serializeResolvedWorkspaceSettings
14
- } from "#chat-runtime/server/utils/chat-workspace-settings";
15
- import { resolveWorkspaceCredentials } from "#chat-runtime/server/utils/workspace";
16
- import { useChatNuxtFeatures } from "#chat-runtime/utils/features";
17
- import { mergeFileUrls } from "#chat-runtime/utils/file";
18
- import { isValidModel } from "#chat-runtime/types/chat";
7
+ buildTelaAgentTurnPayload,
8
+ prepareDefaultAgentTurn,
9
+ queueTelaAgentTurn,
10
+ resolveDefaultAgentEnvironmentVariables,
11
+ withEnvironmentVariables
12
+ } from "#chat-runtime/server/utils/conversation-agent-turn";
13
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
14
+ import { getAgentDataToken, createChatAgentSession, continueSession } from "#chat-runtime/server/utils/agent-api";
19
15
  export default defineEventHandler(async (event) => {
20
- const context = resolveChatContext(event);
21
- const token = getAgentDataToken(event);
16
+ const context = resolveChatRuntimeContext(event);
22
17
  const db = useDb();
23
18
  const conversationId = getRouterParam(event, "id");
24
19
  const messageId = getRouterParam(event, "messageId");
@@ -28,12 +23,7 @@ export default defineEventHandler(async (event) => {
28
23
  statusMessage: "Conversation ID and Message ID are required"
29
24
  });
30
25
  }
31
- const [conversation] = await db.select().from(schema.conversations).where(
32
- and(
33
- eq(schema.conversations.id, conversationId),
34
- eq(schema.conversations.workspaceId, context.workspaceId)
35
- )
36
- );
26
+ const [conversation] = await db.select().from(schema.conversations).where(conversationScopeConditions(context, conversationId));
37
27
  if (!conversation) {
38
28
  throw createError({
39
29
  statusCode: 404,
@@ -67,6 +57,7 @@ export default defineEventHandler(async (event) => {
67
57
  statusMessage: "User message not found"
68
58
  });
69
59
  }
60
+ const telaAgentPayload = conversation.telaAgentId ? await buildTelaAgentTurnPayload(event, conversation, userMessage.content, userMessage.files ?? void 0) : null;
70
61
  await db.update(schema.messages).set({
71
62
  content: "",
72
63
  status: "pending",
@@ -74,64 +65,42 @@ export default defineEventHandler(async (event) => {
74
65
  files: null
75
66
  }).where(eq(schema.messages.id, messageId));
76
67
  logger.info({ conversationId, messageId }, "Message retry initiated");
77
- const trustedSettings = conversation.appliedSettingsSnapshot ? null : await resolveWorkspaceSettings(event, context, db);
78
- const appliedSettings = getConversationAppliedSettings(conversation, trustedSettings);
79
- const needsNewThread = conversation.threadSessionId === null;
80
- const fileUrls = mergeFileUrls(appliedSettings.contextFiles, userMessage.files);
81
- const knowledgeSources = appliedSettings.knowledgeSources ?? null;
82
- const hasKnowledgeSources = knowledgeSources !== null && knowledgeSources.length > 0;
83
- const { vaultUrl } = useRuntimeConfig();
84
- const messageWithContext = buildMessageWithContext(
85
- userMessage.content.trim(),
86
- appliedSettings.systemMessage ?? null,
87
- knowledgeSources,
88
- needsNewThread,
89
- needsNewThread ? renderSystemPrompt(vaultUrl) : ""
90
- );
91
- const canvasTools = appliedSettings.canvasTools;
92
- const externalSkillRefs = appliedSettings.externalSkills?.map((skill) => skill.ref) ?? [];
93
- const skills = externalSkillRefs.length > 0 ? externalSkillRefs : void 0;
94
- const effectiveModel = isValidModel(conversation.model) ? conversation.model : void 0;
95
- const messageTitle = truncateTitle(userMessage.content);
96
- const settingsTimestamp = getWorkspaceSettingsUpdatedAt(appliedSettings);
97
- if (!conversation.appliedSettingsSnapshot && trustedSettings) {
98
- await db.update(schema.conversations).set({
99
- appliedSettingsAt: settingsTimestamp,
100
- appliedSettingsSnapshot: serializeResolvedWorkspaceSettings(trustedSettings),
101
- updatedAt: /* @__PURE__ */ new Date()
102
- }).where(and(
103
- eq(schema.conversations.id, conversationId),
104
- isNull(schema.conversations.appliedSettingsSnapshot)
105
- ));
68
+ if (conversation.telaAgentId) {
69
+ queueTelaAgentTurn(event, db, {
70
+ conversation,
71
+ conversationId,
72
+ assistantMessageId: messageId,
73
+ payload: telaAgentPayload,
74
+ titleContent: userMessage.content,
75
+ operation: "retry"
76
+ });
77
+ const [updatedMessage2] = await db.select().from(schema.messages).where(eq(schema.messages.id, messageId));
78
+ return updatedMessage2;
106
79
  }
80
+ const token = getAgentDataToken(event);
81
+ const defaultAgentTurn = await prepareDefaultAgentTurn(event, db, context, {
82
+ conversation,
83
+ conversationId,
84
+ content: userMessage.content,
85
+ files: userMessage.files
86
+ });
107
87
  const processAgent = async () => {
108
88
  try {
109
- if (needsNewThread) {
110
- let environmentVariables = {};
111
- if (useChatNuxtFeatures().credentials) {
112
- try {
113
- environmentVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
114
- } catch (error) {
115
- logger.warn({ conversationId, messageId, error }, "Failed to resolve workspace credentials on retry, proceeding without env vars");
116
- }
117
- }
118
- const hasEnvironmentVariables = Object.keys(environmentVariables).length > 0;
119
- const result = await createChatAgentSession(token, {
120
- message: messageWithContext,
121
- fileUrls,
122
- model: effectiveModel,
123
- customTools: canvasTools?.length ? { canvas: canvasTools } : void 0,
124
- tools: hasKnowledgeSources ? { workstationQueryTool: true } : void 0,
125
- environmentVariables: hasEnvironmentVariables ? environmentVariables : void 0,
126
- skills
127
- });
89
+ if (defaultAgentTurn.needsNewThread) {
90
+ const environmentVariables = await resolveDefaultAgentEnvironmentVariables(
91
+ event,
92
+ context,
93
+ { conversationId, messageId },
94
+ "Failed to resolve workspace credentials on retry, proceeding without env vars"
95
+ );
96
+ const result = await createChatAgentSession(token, withEnvironmentVariables(defaultAgentTurn.createPayload, environmentVariables));
128
97
  if (result.success) {
129
98
  logger.info({ conversationId, messageId, sessionId: result.sessionId }, "New agent thread created on retry");
130
99
  await db.update(schema.conversations).set({
131
100
  threadSessionId: result.sessionId,
132
- title: messageTitle,
133
- appliedSettingsAt: settingsTimestamp,
134
- model: effectiveModel ?? null,
101
+ title: defaultAgentTurn.messageTitle,
102
+ appliedSettingsAt: defaultAgentTurn.settingsTimestamp,
103
+ model: defaultAgentTurn.effectiveModel ?? null,
135
104
  updatedAt: /* @__PURE__ */ new Date()
136
105
  }).where(eq(schema.conversations.id, conversationId));
137
106
  } else {
@@ -142,27 +111,24 @@ export default defineEventHandler(async (event) => {
142
111
  }).where(eq(schema.messages.id, messageId));
143
112
  }
144
113
  } else {
145
- const result = await continueSession(token, conversation.threadSessionId, {
146
- content: userMessage.content.trim(),
147
- fileUrls,
148
- model: effectiveModel
149
- });
114
+ const result = await continueSession(token, conversation.threadSessionId, defaultAgentTurn.continuePayload);
150
115
  if (!result.success) {
151
116
  logger.warn({ conversationId, messageId, error: result.error }, "Agent session continue failed on retry");
152
117
  await db.update(schema.messages).set({
153
118
  content: `Erro: ${result.error}`,
154
119
  status: "failed"
155
120
  }).where(eq(schema.messages.id, messageId));
121
+ return;
156
122
  }
157
123
  await db.update(schema.conversations).set({
158
- ...effectiveModel ? { model: effectiveModel } : {},
124
+ ...defaultAgentTurn.effectiveModel ? { model: defaultAgentTurn.effectiveModel } : {},
159
125
  updatedAt: /* @__PURE__ */ new Date()
160
126
  }).where(eq(schema.conversations.id, conversationId));
161
127
  }
162
128
  } catch (error) {
163
129
  logger.error({ error }, "Failed to process agent retry");
164
130
  await db.update(schema.messages).set({
165
- content: `Erro: ${needsNewThread ? "Falha ao conectar ao agente" : "Falha ao continuar a sess\xE3o"}`,
131
+ content: `Erro: ${defaultAgentTurn.needsNewThread ? "Falha ao conectar ao agente" : "Falha ao continuar a sess\xE3o"}`,
166
132
  status: "failed"
167
133
  }).where(eq(schema.messages.id, messageId));
168
134
  }
@@ -1,15 +1,2 @@
1
- type MessageFile = {
2
- url: string;
3
- name: string;
4
- mimeType?: string;
5
- source: 'user' | 'assistant';
6
- size?: number;
7
- };
8
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
9
- messages: any;
10
- newFiles: MessageFile[];
11
- } | {
12
- messages: any;
13
- newFiles?: undefined;
14
- }>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<any>>;
15
2
  export default _default;
@@ -1,74 +1,23 @@
1
1
  import { createError, defineEventHandler, getQuery, getRouterParam } from "h3";
2
- import { useRuntimeConfig } from "nitropack/runtime";
3
- import { and, asc, eq, gt, isNotNull } from "drizzle-orm";
4
- import { APIKeyAuthStrategy, VaultFile } from "@meistrari/vault-sdk";
2
+ import { and, asc, eq, gt } from "drizzle-orm";
5
3
  import { schema, useDb } from "#chat-runtime/server/db";
6
- import { extractContentFromSession, getAgentDataToken, getSessionStatus } from "#chat-runtime/server/utils/agent-api";
7
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
8
- import { detectFilesInText } from "#chat-runtime/utils/file";
4
+ import { getAgentDataToken, getSessionStatus } from "#chat-runtime/server/utils/agent-api";
5
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
6
+ import { attachNewAssistantFilesToMessageUpdate } from "#chat-runtime/server/utils/conversation-message-files";
7
+ import {
8
+ buildMessageUpdateResponse,
9
+ hasMessageUpdate,
10
+ isTerminalMessageUpdate,
11
+ mapAgentApiSessionToMessageUpdate,
12
+ trackAgentApiConversationUsage,
13
+ trackTelaAgentConversationUsage
14
+ } from "#chat-runtime/server/utils/conversation-message-sync";
15
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
16
+ import { getTelaAgentSession } from "#chat-runtime/server/utils/tela-agent-api";
17
+ import { mapTelaAgentSessionToMessageUpdate, mapTelaAgentUsage } from "#chat-runtime/server/utils/tela-agent-session";
9
18
  import { logger } from "#chat-runtime/server/utils/logger";
10
- async function getExistingFileUrls(db, conversationId) {
11
- const messagesWithFiles = await db.select({ files: schema.messages.files }).from(schema.messages).where(and(
12
- eq(schema.messages.conversationId, conversationId),
13
- isNotNull(schema.messages.files)
14
- ));
15
- const urls = /* @__PURE__ */ new Set();
16
- for (const message of messagesWithFiles) {
17
- if (Array.isArray(message.files)) {
18
- for (const file of message.files) {
19
- if (file?.url) {
20
- urls.add(file.url);
21
- }
22
- }
23
- }
24
- }
25
- return urls;
26
- }
27
- async function extractAssistantFiles(content, authToken) {
28
- const detected = detectFilesInText(content);
29
- const vaultFiles = detected.filter((f) => f.type === "vault");
30
- if (vaultFiles.length === 0)
31
- return [];
32
- const { vaultUrl } = useRuntimeConfig();
33
- const filesWithMetadata = await Promise.all(
34
- vaultFiles.map(async (file) => {
35
- let name = file.name;
36
- let mimeType = file.mimeType !== "unknown" ? file.mimeType : void 0;
37
- let size;
38
- if (vaultUrl) {
39
- try {
40
- const authStrategy = new APIKeyAuthStrategy(authToken);
41
- const vaultFile = await VaultFile.fromVaultReference({
42
- reference: file.reference,
43
- config: { authStrategy, vaultUrl }
44
- });
45
- await vaultFile.populateMetadata();
46
- if (vaultFile.metadata?.originalFileName) {
47
- name = vaultFile.metadata.originalFileName;
48
- }
49
- if (vaultFile.metadata?.mimeType) {
50
- mimeType = vaultFile.metadata.mimeType;
51
- }
52
- if (vaultFile.metadata?.size) {
53
- size = vaultFile.metadata.size;
54
- }
55
- } catch {
56
- logger.debug({ reference: file.reference }, "Vault metadata fetch failed for assistant file");
57
- }
58
- }
59
- return {
60
- url: file.reference,
61
- name,
62
- mimeType,
63
- source: "assistant",
64
- size
65
- };
66
- })
67
- );
68
- return filesWithMetadata;
69
- }
70
19
  export default defineEventHandler(async (event) => {
71
- const context = resolveChatContext(event);
20
+ const context = resolveChatRuntimeContext(event);
72
21
  const token = getAgentDataToken(event);
73
22
  const db = useDb();
74
23
  const conversationId = getRouterParam(event, "id");
@@ -88,12 +37,7 @@ export default defineEventHandler(async (event) => {
88
37
  }
89
38
  }
90
39
  const [conversationResult, messages] = await Promise.all([
91
- db.select().from(schema.conversations).where(
92
- and(
93
- eq(schema.conversations.id, conversationId),
94
- eq(schema.conversations.workspaceId, context.workspaceId)
95
- )
96
- ),
40
+ db.select().from(schema.conversations).where(conversationScopeConditions(context, conversationId)),
97
41
  db.select().from(schema.messages).where(and(...conditions)).orderBy(asc(schema.messages.createdAt))
98
42
  ]);
99
43
  const [conversation] = conversationResult;
@@ -103,120 +47,70 @@ export default defineEventHandler(async (event) => {
103
47
  statusMessage: "Conversation not found"
104
48
  });
105
49
  }
106
- const pendingMessage = messages.find((m) => m.status === "pending");
107
- const lastAssistantMessage = messages.findLast((m) => m.role === "assistant" && m.status === "completed");
108
- if (pendingMessage && conversation.threadSessionId) {
109
- logger.debug({ conversationId, messageId: pendingMessage.id, sessionId: conversation.threadSessionId }, "Polling pending message");
110
- const sessionResult = await getSessionStatus(token, conversation.threadSessionId);
111
- if (sessionResult.success) {
112
- const agentSession = sessionResult.data;
113
- const updateData = {};
114
- const lastSyncedUuid = lastAssistantMessage?.externalUuid;
115
- const lastSyncedIndex = lastSyncedUuid ? agentSession.reasoning?.findIndex((r) => r.uuid === lastSyncedUuid) ?? -1 : -1;
116
- const newReasoning = agentSession.reasoning?.slice(lastSyncedIndex + 1);
117
- const hasNewReasoning = newReasoning && newReasoning.length > 0;
118
- if (hasNewReasoning) {
119
- updateData.reasoningData = newReasoning;
120
- const lastReasoningItem = newReasoning.at(-1);
121
- if (lastReasoningItem?.uuid) {
122
- updateData.externalUuid = lastReasoningItem.uuid;
123
- }
124
- if (!pendingMessage.ttft) {
125
- const userMessage = messages.findLast((m) => m.role === "user");
126
- if (userMessage) {
127
- const ttft = Date.now() - userMessage.createdAt.getTime();
128
- updateData.ttft = String(ttft);
129
- }
130
- }
131
- }
132
- if (agentSession.status === "completed" || agentSession.status === "waiting_messages") {
133
- if (hasNewReasoning) {
134
- const content = extractContentFromSession(agentSession);
135
- updateData.content = content;
136
- updateData.status = "completed";
137
- updateData.updatedAt = /* @__PURE__ */ new Date();
138
- logger.info({ conversationId, messageId: pendingMessage.id, status: "completed" }, "Message completed");
139
- const assistantFiles = await extractAssistantFiles(content, token);
140
- if (assistantFiles.length > 0) {
141
- const existingUrls = await getExistingFileUrls(db, conversationId);
142
- const newFiles = assistantFiles.filter((f) => !existingUrls.has(f.url));
143
- if (newFiles.length > 0) {
144
- updateData.files = newFiles;
145
- }
146
- }
147
- }
148
- } else if (agentSession.status === "failed") {
149
- const errorContent = agentSession.error || "Agent execution failed";
150
- updateData.content = `Error: ${errorContent}`;
151
- updateData.status = agentSession.status;
152
- updateData.updatedAt = /* @__PURE__ */ new Date();
153
- logger.warn({ conversationId, messageId: pendingMessage.id, status: "failed" }, "Message failed");
154
- }
155
- if (Object.keys(updateData).length > 0) {
156
- await db.update(schema.messages).set(updateData).where(eq(schema.messages.id, pendingMessage.id));
157
- if ((updateData.status === "completed" || updateData.status === "failed") && agentSession.result?.usage) {
158
- const usage = agentSession.result.usage;
159
- const breakdown = agentSession.result?.usageBreakdown;
160
- const serverToolUse = breakdown?.serverToolUse;
161
- const webSearchRequests = typeof serverToolUse?.web_search_requests === "number" ? serverToolUse.web_search_requests : null;
162
- const webFetchRequests = typeof serverToolUse?.web_fetch_requests === "number" ? serverToolUse.web_fetch_requests : null;
163
- void db.insert(schema.conversationUsage).values({
164
- workspaceId: context.workspaceId,
165
- conversationId,
166
- promptTokens: usage.promptTokens ?? 0,
167
- completionTokens: usage.completionTokens ?? 0,
168
- totalTokens: usage.totalTokens ?? 0,
169
- promptCost: String(usage.promptCost ?? 0),
170
- completionCost: String(usage.completionCost ?? 0),
171
- totalCost: String(usage.totalCost ?? 0),
172
- durationMs: usage.duration ?? null,
173
- turns: usage.turns ?? null,
174
- userId: context.user.id,
175
- userEmail: context.user.email ?? null,
176
- model: agentSession.model ?? null,
177
- sessionId: conversation.threadSessionId,
178
- cacheReadInputTokens: breakdown?.cacheReadInputTokens ?? null,
179
- cacheCreationInputTokens: breakdown?.cacheCreationInputTokens ?? null,
180
- modelUsage: breakdown?.modelUsage ?? null,
181
- durationApiMs: breakdown?.durationApiMs ?? null,
182
- webSearchRequests,
183
- webFetchRequests
184
- }).onConflictDoUpdate({
185
- target: schema.conversationUsage.conversationId,
186
- set: {
187
- promptTokens: usage.promptTokens ?? 0,
188
- completionTokens: usage.completionTokens ?? 0,
189
- totalTokens: usage.totalTokens ?? 0,
190
- promptCost: String(usage.promptCost ?? 0),
191
- completionCost: String(usage.completionCost ?? 0),
192
- totalCost: String(usage.totalCost ?? 0),
193
- durationMs: usage.duration ?? null,
194
- turns: usage.turns ?? null,
195
- model: agentSession.model ?? null,
196
- cacheReadInputTokens: breakdown?.cacheReadInputTokens ?? null,
197
- cacheCreationInputTokens: breakdown?.cacheCreationInputTokens ?? null,
198
- modelUsage: breakdown?.modelUsage ?? null,
199
- durationApiMs: breakdown?.durationApiMs ?? null,
200
- webSearchRequests,
201
- webFetchRequests,
202
- updatedAt: /* @__PURE__ */ new Date()
203
- }
204
- }).catch((error) => {
205
- console.warn("Failed to track conversation usage:", error);
206
- });
207
- }
208
- const updatedMessages = messages.map(
209
- (m) => m.id === pendingMessage.id ? { ...m, ...updateData } : m
210
- );
211
- if (updateData.files && updateData.files.length > 0) {
212
- return {
213
- messages: updatedMessages,
214
- newFiles: updateData.files
215
- };
216
- }
217
- return { messages: updatedMessages };
218
- }
50
+ const pendingMessage = messages.find((message) => message.status === "pending");
51
+ const lastAssistantMessage = messages.findLast((message) => message.role === "assistant" && message.status === "completed");
52
+ if (!pendingMessage || !conversation.threadSessionId) {
53
+ return { messages };
54
+ }
55
+ logger.debug({ conversationId, messageId: pendingMessage.id, sessionId: conversation.threadSessionId }, "Polling pending message");
56
+ if (conversation.telaAgentId) {
57
+ const sessionResult2 = await getTelaAgentSession(event, conversation.threadSessionId);
58
+ if (!sessionResult2.success) {
59
+ return { messages };
60
+ }
61
+ const agentSession2 = sessionResult2.data;
62
+ const userMessage2 = messages.findLast((message) => message.role === "user");
63
+ const updateData2 = mapTelaAgentSessionToMessageUpdate(agentSession2, {
64
+ lastSyncedStepId: pendingMessage.externalUuid ?? lastAssistantMessage?.externalUuid,
65
+ existingReasoningData: Array.isArray(pendingMessage.reasoningData) ? pendingMessage.reasoningData : null,
66
+ pendingMessageTtft: pendingMessage.ttft,
67
+ userMessageCreatedAt: userMessage2?.createdAt ?? null
68
+ });
69
+ await attachNewAssistantFilesToMessageUpdate(db, conversationId, updateData2, token);
70
+ if (!hasMessageUpdate(updateData2)) {
71
+ return { messages };
219
72
  }
73
+ await db.update(schema.messages).set(updateData2).where(eq(schema.messages.id, pendingMessage.id));
74
+ if (isTerminalMessageUpdate(updateData2)) {
75
+ trackTelaAgentConversationUsage(
76
+ db,
77
+ context,
78
+ conversationId,
79
+ conversation.threadSessionId,
80
+ mapTelaAgentUsage(agentSession2)
81
+ );
82
+ }
83
+ return buildMessageUpdateResponse(messages, pendingMessage.id, updateData2);
84
+ }
85
+ const sessionResult = await getSessionStatus(token, conversation.threadSessionId);
86
+ if (!sessionResult.success) {
87
+ return { messages };
88
+ }
89
+ const agentSession = sessionResult.data;
90
+ const userMessage = messages.findLast((message) => message.role === "user");
91
+ const updateData = mapAgentApiSessionToMessageUpdate(agentSession, {
92
+ pendingMessage,
93
+ lastAssistantMessage,
94
+ userMessage
95
+ });
96
+ if (updateData.status === "completed") {
97
+ logger.info({ conversationId, messageId: pendingMessage.id, status: "completed" }, "Message completed");
98
+ } else if (updateData.status === "failed") {
99
+ logger.warn({ conversationId, messageId: pendingMessage.id, status: "failed" }, "Message failed");
100
+ }
101
+ await attachNewAssistantFilesToMessageUpdate(db, conversationId, updateData, token);
102
+ if (!hasMessageUpdate(updateData)) {
103
+ return { messages };
104
+ }
105
+ await db.update(schema.messages).set(updateData).where(eq(schema.messages.id, pendingMessage.id));
106
+ if (isTerminalMessageUpdate(updateData)) {
107
+ trackAgentApiConversationUsage(
108
+ db,
109
+ context,
110
+ conversationId,
111
+ conversation.threadSessionId,
112
+ agentSession
113
+ );
220
114
  }
221
- return { messages };
115
+ return buildMessageUpdateResponse(messages, pendingMessage.id, updateData);
222
116
  });