@meistrari/chat-nuxt 1.3.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 (98) 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 +5 -16
  5. package/dist/runtime/components/MeistrariChatEmbed.vue +28 -5
  6. package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +5 -16
  7. package/dist/runtime/components/app-markdown-render.d.vue.ts +3 -1
  8. package/dist/runtime/components/app-markdown-render.vue +15 -4
  9. package/dist/runtime/components/app-markdown-render.vue.d.ts +3 -1
  10. package/dist/runtime/components/chat/message-bubble.vue +10 -2
  11. package/dist/runtime/components/chat/message-input.d.vue.ts +30 -4
  12. package/dist/runtime/components/chat/message-input.vue +268 -24
  13. package/dist/runtime/components/chat/message-input.vue.d.ts +30 -4
  14. package/dist/runtime/composables/useChat.d.ts +3 -5
  15. package/dist/runtime/composables/useChat.js +24 -3
  16. package/dist/runtime/composables/useChatAction.d.ts +10 -0
  17. package/dist/runtime/composables/useChatAction.js +12 -0
  18. package/dist/runtime/composables/useChatApi.js +4 -0
  19. package/dist/runtime/composables/useConversations.js +8 -2
  20. package/dist/runtime/composables/useEmbedConfig.d.ts +12 -2
  21. package/dist/runtime/composables/useEmbedConfig.js +24 -3
  22. package/dist/runtime/composables/useFeatureFlags.d.ts +1 -2
  23. package/dist/runtime/composables/useTelaAgentMetadata.d.ts +8 -0
  24. package/dist/runtime/composables/useTelaAgentMetadata.js +65 -0
  25. package/dist/runtime/composables/useWorkspaceSettings.d.ts +1 -19
  26. package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
  27. package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
  28. package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +6 -21
  29. package/dist/runtime/embed/components/ChatEmbed.vue +44 -11
  30. package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +6 -21
  31. package/dist/runtime/embed/components/ChatEmbedInner.vue +36 -12
  32. package/dist/runtime/plugins/markstream.d.ts +1 -1
  33. package/dist/runtime/plugins/markstream.js +1 -1
  34. package/dist/runtime/server/api/conversations/[id]/cancel.post.d.ts +1 -1
  35. package/dist/runtime/server/api/conversations/[id]/cancel.post.js +37 -17
  36. package/dist/runtime/server/api/conversations/[id]/clear.post.js +28 -13
  37. package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +11 -13
  38. package/dist/runtime/server/api/conversations/[id]/files.get.js +5 -7
  39. package/dist/runtime/server/api/conversations/[id]/index.delete.js +13 -12
  40. package/dist/runtime/server/api/conversations/[id]/index.get.js +6 -13
  41. package/dist/runtime/server/api/conversations/[id]/index.patch.js +11 -10
  42. package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +46 -80
  43. package/dist/runtime/server/api/conversations/[id]/messages/index.get.d.ts +1 -14
  44. package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +81 -187
  45. package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +68 -83
  46. package/dist/runtime/server/api/conversations/[id]/usage.get.js +4 -8
  47. package/dist/runtime/server/api/conversations/generate-title.post.js +4 -3
  48. package/dist/runtime/server/api/conversations/index.get.js +5 -4
  49. package/dist/runtime/server/api/conversations/index.post.js +5 -4
  50. package/dist/runtime/server/api/tela/agents/[id].get.d.ts +6 -0
  51. package/dist/runtime/server/api/tela/agents/[id].get.js +25 -0
  52. package/dist/runtime/server/api/workspace/settings.patch.js +1 -1
  53. package/dist/runtime/server/db/index.js +62 -0
  54. package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
  55. package/dist/runtime/server/db/schema/conversations.js +3 -1
  56. package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
  57. package/dist/runtime/server/utils/agent-api.d.ts +1 -1
  58. package/dist/runtime/server/utils/agent-api.js +2 -5
  59. package/dist/runtime/server/utils/chat-context.d.ts +5 -0
  60. package/dist/runtime/server/utils/chat-context.js +20 -0
  61. package/dist/runtime/server/utils/chat-workspace-settings.d.ts +2 -1
  62. package/dist/runtime/server/utils/chat-workspace-settings.js +10 -1
  63. package/dist/runtime/server/utils/conversation-agent-turn.d.ts +39 -0
  64. package/dist/runtime/server/utils/conversation-agent-turn.js +170 -0
  65. package/dist/runtime/server/utils/conversation-message-files.d.ts +10 -0
  66. package/dist/runtime/server/utils/conversation-message-files.js +77 -0
  67. package/dist/runtime/server/utils/conversation-message-sync.d.ts +31 -0
  68. package/dist/runtime/server/utils/conversation-message-sync.js +132 -0
  69. package/dist/runtime/server/utils/conversation-scope.d.ts +2 -0
  70. package/dist/runtime/server/utils/conversation-scope.js +12 -0
  71. package/dist/runtime/server/utils/tela-agent-api.d.ts +29 -0
  72. package/dist/runtime/server/utils/tela-agent-api.js +107 -0
  73. package/dist/runtime/server/utils/tela-agent-session.d.ts +48 -0
  74. package/dist/runtime/server/utils/tela-agent-session.js +350 -0
  75. package/dist/runtime/types/embed.d.ts +29 -0
  76. package/dist/runtime/types/embed.js +0 -0
  77. package/dist/runtime/types/tela-agent.d.ts +243 -0
  78. package/dist/runtime/types/tela-agent.js +98 -0
  79. package/dist/runtime/types/workspace-settings-data.d.ts +19 -0
  80. package/dist/runtime/types/workspace-settings-data.js +0 -0
  81. package/dist/runtime/types/workspace-settings.d.ts +1 -1
  82. package/dist/runtime/utils/agent-input-readiness.d.ts +14 -0
  83. package/dist/runtime/utils/agent-input-readiness.js +15 -0
  84. package/dist/runtime/utils/custom-markdown-components.d.ts +9 -0
  85. package/dist/runtime/utils/custom-markdown-components.js +54 -0
  86. package/dist/runtime/utils/features.d.ts +9 -0
  87. package/dist/runtime/utils/features.js +5 -4
  88. package/dist/runtime/utils/file.js +46 -7
  89. package/dist/runtime/utils/markdown-nodes.d.ts +1 -0
  90. package/dist/runtime/utils/markdown-nodes.js +6 -0
  91. package/dist/runtime/utils/tela-chat.d.ts +6 -0
  92. package/dist/runtime/utils/tela-chat.js +12 -1
  93. package/drizzle/0014_faulty_lake.sql +2 -0
  94. package/drizzle/meta/0014_snapshot.json +732 -0
  95. package/drizzle/meta/_journal.json +7 -0
  96. package/package.json +1 -1
  97. package/dist/runtime/types/feature-flags.d.ts +0 -7
  98. package/dist/runtime/types/feature-flags.js +0 -6
@@ -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, newReasoning);
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
  });
@@ -1,24 +1,21 @@
1
1
  import { createError, defineEventHandler, getRouterParam, readBody } from "h3";
2
- import { useRuntimeConfig } from "nitropack/runtime";
3
- import { eq, and, isNull } from "drizzle-orm";
2
+ import { eq } from "drizzle-orm";
4
3
  import { useDb, schema } from "#chat-runtime/server/db";
5
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
6
- import { buildMessageWithContext, renderSystemPrompt, truncateTitle } from "#chat-runtime/server/utils/chat-message-builder";
4
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
5
+ import {
6
+ buildTelaAgentTurnPayload,
7
+ prepareDefaultAgentTurn,
8
+ queueTelaAgentTurn,
9
+ resolveDefaultAgentEnvironmentVariables,
10
+ withEnvironmentVariables
11
+ } from "#chat-runtime/server/utils/conversation-agent-turn";
12
+ import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
7
13
  import { getAgentDataToken, createChatAgentSession, continueSession } from "#chat-runtime/server/utils/agent-api";
8
14
  import { logger } from "#chat-runtime/server/utils/logger";
9
- 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
15
  import { isValidModel } from "#chat-runtime/types/chat";
16
+ import { telaAgentExecutionInputSchema } from "#chat-runtime/types/tela-agent";
19
17
  export default defineEventHandler(async (event) => {
20
- const context = resolveChatContext(event);
21
- const token = getAgentDataToken(event);
18
+ const context = resolveChatRuntimeContext(event);
22
19
  const db = useDb();
23
20
  const conversationId = getRouterParam(event, "id");
24
21
  const body = await readBody(event);
@@ -34,39 +31,37 @@ export default defineEventHandler(async (event) => {
34
31
  statusMessage: "Message content is required"
35
32
  });
36
33
  }
37
- if (body.model !== void 0 && !isValidModel(body.model)) {
34
+ const [conversation] = await db.select().from(schema.conversations).where(conversationScopeConditions(context, conversationId));
35
+ if (!conversation) {
36
+ throw createError({
37
+ statusCode: 404,
38
+ statusMessage: "Conversation not found"
39
+ });
40
+ }
41
+ if (!conversation.telaAgentId && body.model !== void 0 && !isValidModel(body.model)) {
38
42
  throw createError({
39
43
  statusCode: 400,
40
44
  statusMessage: "Invalid model"
41
45
  });
42
46
  }
43
- const [conversation] = await db.select().from(schema.conversations).where(
44
- and(
45
- eq(schema.conversations.id, conversationId),
46
- eq(schema.conversations.workspaceId, context.workspaceId)
47
- )
48
- );
49
- if (!conversation) {
47
+ const agentInputs = conversation.telaAgentId && body.agentInputs !== void 0 ? telaAgentExecutionInputSchema.array().safeParse(body.agentInputs) : null;
48
+ if (agentInputs && !agentInputs.success) {
50
49
  throw createError({
51
- statusCode: 404,
52
- statusMessage: "Conversation not found"
50
+ statusCode: 400,
51
+ statusMessage: "Invalid Tela agent inputs"
53
52
  });
54
53
  }
55
- const effectiveModel = body.model ?? (isValidModel(conversation.model) ? conversation.model : void 0);
56
- const trustedSettings = conversation.appliedSettingsSnapshot ? null : await resolveWorkspaceSettings(event, context, db);
57
- const appliedSettings = getConversationAppliedSettings(conversation, trustedSettings);
54
+ const telaAgentPayload = conversation.telaAgentId ? await buildTelaAgentTurnPayload(event, conversation, body.content, body.files, agentInputs?.data) : null;
55
+ const defaultAgentTurn = conversation.telaAgentId ? null : await prepareDefaultAgentTurn(event, db, context, {
56
+ conversation,
57
+ conversationId,
58
+ content: body.content,
59
+ files: body.files,
60
+ requestedModel: body.model,
61
+ workspaceSettingsSnapshot: body.workspaceSettingsSnapshot
62
+ });
58
63
  const now = /* @__PURE__ */ new Date();
59
64
  const assistantCreatedAt = new Date(now.getTime() + 1);
60
- if (!conversation.appliedSettingsSnapshot && trustedSettings) {
61
- await db.update(schema.conversations).set({
62
- appliedSettingsAt: getWorkspaceSettingsUpdatedAt(trustedSettings),
63
- appliedSettingsSnapshot: serializeResolvedWorkspaceSettings(trustedSettings),
64
- updatedAt: now
65
- }).where(and(
66
- eq(schema.conversations.id, conversationId),
67
- isNull(schema.conversations.appliedSettingsSnapshot)
68
- ));
69
- }
70
65
  const [userMessages, assistantMessages] = await Promise.all([
71
66
  db.insert(schema.messages).values({
72
67
  conversationId,
@@ -95,49 +90,42 @@ export default defineEventHandler(async (event) => {
95
90
  }
96
91
  logger.info({ conversationId, userMessageId: userMessage.id, hasFiles: !!body.files?.length }, "Message sent");
97
92
  const assistantMessageId = assistantMessage.id;
98
- const needsNewThread = conversation.threadSessionId === null;
99
- const fileUrls = mergeFileUrls(appliedSettings.contextFiles, body.files);
100
- const messageTitle = truncateTitle(body.content);
101
- const knowledgeSources = appliedSettings.knowledgeSources ?? null;
102
- const { vaultUrl } = useRuntimeConfig();
103
- const messageWithContext = buildMessageWithContext(
104
- body.content.trim(),
105
- appliedSettings.systemMessage ?? null,
106
- knowledgeSources,
107
- needsNewThread,
108
- needsNewThread ? renderSystemPrompt(vaultUrl) : ""
109
- );
110
- const canvasTools = appliedSettings.canvasTools;
111
- const hasKnowledgeSources = knowledgeSources && knowledgeSources.length > 0;
112
- const externalSkillRefs = appliedSettings.externalSkills?.map((skill) => skill.ref) ?? [];
113
- const settingsTimestamp = getWorkspaceSettingsUpdatedAt(appliedSettings);
114
- const skills = externalSkillRefs.length > 0 ? externalSkillRefs : void 0;
115
- if (needsNewThread) {
116
- let environmentVariables = {};
117
- if (useChatNuxtFeatures().credentials) {
118
- try {
119
- environmentVariables = await resolveWorkspaceCredentials(event, context.workspaceId);
120
- } catch (error) {
121
- logger.warn({ conversationId, error }, "Failed to resolve workspace credentials, proceeding without env vars");
122
- }
123
- }
124
- const hasEnvironmentVariables = Object.keys(environmentVariables).length > 0;
125
- createChatAgentSession(token, {
126
- message: messageWithContext,
127
- fileUrls,
128
- model: effectiveModel,
129
- customTools: canvasTools?.length ? { canvas: canvasTools } : void 0,
130
- tools: hasKnowledgeSources ? { workstationQueryTool: true } : void 0,
131
- environmentVariables: hasEnvironmentVariables ? environmentVariables : void 0,
132
- skills
133
- }).then(async (result) => {
93
+ if (conversation.telaAgentId) {
94
+ queueTelaAgentTurn(event, db, {
95
+ conversation,
96
+ conversationId,
97
+ assistantMessageId,
98
+ payload: telaAgentPayload,
99
+ titleContent: body.content,
100
+ operation: "send"
101
+ });
102
+ return {
103
+ userMessage,
104
+ assistantMessage
105
+ };
106
+ }
107
+ if (!defaultAgentTurn) {
108
+ throw createError({
109
+ statusCode: 500,
110
+ statusMessage: "Failed to prepare agent turn"
111
+ });
112
+ }
113
+ const token = getAgentDataToken(event);
114
+ if (defaultAgentTurn.needsNewThread) {
115
+ const environmentVariables = await resolveDefaultAgentEnvironmentVariables(
116
+ event,
117
+ context,
118
+ { conversationId },
119
+ "Failed to resolve workspace credentials, proceeding without env vars"
120
+ );
121
+ createChatAgentSession(token, withEnvironmentVariables(defaultAgentTurn.createPayload, environmentVariables)).then(async (result) => {
134
122
  if (result.success) {
135
123
  logger.info({ conversationId, sessionId: result.sessionId }, "New agent thread created");
136
124
  await db.update(schema.conversations).set({
137
125
  threadSessionId: result.sessionId,
138
- title: messageTitle,
139
- appliedSettingsAt: settingsTimestamp,
140
- model: effectiveModel ?? null,
126
+ title: defaultAgentTurn.messageTitle,
127
+ appliedSettingsAt: defaultAgentTurn.settingsTimestamp,
128
+ model: defaultAgentTurn.effectiveModel ?? null,
141
129
  updatedAt: /* @__PURE__ */ new Date()
142
130
  }).where(eq(schema.conversations.id, conversationId));
143
131
  } else {
@@ -155,20 +143,17 @@ export default defineEventHandler(async (event) => {
155
143
  }).where(eq(schema.messages.id, assistantMessageId));
156
144
  });
157
145
  } else {
158
- continueSession(token, conversation.threadSessionId, {
159
- content: body.content.trim(),
160
- fileUrls,
161
- model: effectiveModel
162
- }).then(async (result) => {
146
+ continueSession(token, conversation.threadSessionId, defaultAgentTurn.continuePayload).then(async (result) => {
163
147
  if (!result.success) {
164
148
  logger.warn({ conversationId, sessionId: conversation.threadSessionId, error: result.error }, "Agent session continue failed");
165
149
  await db.update(schema.messages).set({
166
150
  content: `Erro: ${result.error}`,
167
151
  status: "failed"
168
152
  }).where(eq(schema.messages.id, assistantMessageId));
153
+ return;
169
154
  }
170
155
  await db.update(schema.conversations).set({
171
- ...effectiveModel ? { model: effectiveModel } : {},
156
+ ...defaultAgentTurn.effectiveModel ? { model: defaultAgentTurn.effectiveModel } : {},
172
157
  updatedAt: /* @__PURE__ */ new Date()
173
158
  }).where(eq(schema.conversations.id, conversationId));
174
159
  }).catch(async (e) => {
@@ -1,10 +1,11 @@
1
1
  import { createError, defineEventHandler, getRouterParam } from "h3";
2
2
  import { eq, and } 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
  import { getWorkspaceBilling, transformCostForBilling } from "#chat-runtime/server/utils/billing";
6
7
  export default defineEventHandler(async (event) => {
7
- const context = resolveChatContext(event);
8
+ const context = resolveChatRuntimeContext(event);
8
9
  const db = useDb();
9
10
  const id = getRouterParam(event, "id");
10
11
  if (!id) {
@@ -13,12 +14,7 @@ export default defineEventHandler(async (event) => {
13
14
  statusMessage: "Conversation ID is required"
14
15
  });
15
16
  }
16
- const [conversation] = await db.select({ id: schema.conversations.id }).from(schema.conversations).where(
17
- and(
18
- eq(schema.conversations.id, id),
19
- eq(schema.conversations.workspaceId, context.workspaceId)
20
- )
21
- );
17
+ const [conversation] = await db.select({ id: schema.conversations.id }).from(schema.conversations).where(conversationScopeConditions(context, id));
22
18
  if (!conversation) {
23
19
  throw createError({
24
20
  statusCode: 404,
@@ -3,7 +3,7 @@ import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { createTelaClient } from "@meistrari/tela-sdk-js";
4
4
  import { logger } from "#chat-runtime/server/utils/logger";
5
5
  import { requireUser } from "#chat-runtime/server/utils/auth";
6
- import { useChatNuxtFeatures } from "#chat-runtime/utils/features";
6
+ import { resolveChatNuxtFeatures } from "#chat-runtime/utils/features";
7
7
  const TITLE_CANVAS_ID = "ba06c4a0-ef05-4fb6-b4ef-5af3d63a1ba2";
8
8
  const FALLBACK_TITLE_MAX_LENGTH = 60;
9
9
  const FALLBACK_TITLE_DEFAULT = "New chat";
@@ -26,8 +26,9 @@ export default defineEventHandler(async (event) => {
26
26
  statusMessage: "userMessage and assistantResponse are required"
27
27
  });
28
28
  }
29
- const { telaApiKey } = useRuntimeConfig();
30
- const features = useChatNuxtFeatures();
29
+ const runtimeConfig = useRuntimeConfig();
30
+ const { telaApiKey } = runtimeConfig;
31
+ const features = resolveChatNuxtFeatures(runtimeConfig);
31
32
  if (!features.generateTitle || !telaApiKey) {
32
33
  const fallback = buildFallbackTitle(userMessage);
33
34
  logger.debug({ titleLength: fallback.length, reason: !features.generateTitle ? "feature-disabled" : "no-tela-api-key" }, "Title generated via fallback");
@@ -1,12 +1,13 @@
1
1
  import { defineEventHandler } from "h3";
2
- import { desc, eq } from "drizzle-orm";
2
+ import { desc } 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
  import { logger } from "#chat-runtime/server/utils/logger";
6
7
  export default defineEventHandler(async (event) => {
7
- const context = resolveChatContext(event);
8
+ const context = resolveChatRuntimeContext(event);
8
9
  const db = useDb();
9
- const conversations = await db.select().from(schema.conversations).where(eq(schema.conversations.workspaceId, context.workspaceId)).orderBy(desc(schema.conversations.updatedAt));
10
+ const conversations = await db.select().from(schema.conversations).where(conversationScopeConditions(context)).orderBy(desc(schema.conversations.updatedAt));
10
11
  logger.debug({ workspaceId: context.workspaceId, count: conversations.length }, "Conversations listed");
11
12
  return conversations;
12
13
  });
@@ -1,10 +1,10 @@
1
1
  import { createError, defineEventHandler, getRequestHeader, readBody } from "h3";
2
2
  import { useDb, schema } from "#chat-runtime/server/db";
3
- import { resolveChatContext } from "#chat-runtime/server/utils/chat-context";
3
+ import { resolveChatRuntimeContext } from "#chat-runtime/server/utils/chat-context";
4
4
  import { logger } from "#chat-runtime/server/utils/logger";
5
5
  import { isValidModel } from "#chat-runtime/types/chat";
6
6
  export default defineEventHandler(async (event) => {
7
- const context = resolveChatContext(event);
7
+ const context = resolveChatRuntimeContext(event);
8
8
  const db = useDb();
9
9
  let body = {};
10
10
  const contentLength = getRequestHeader(event, "content-length");
@@ -19,7 +19,7 @@ export default defineEventHandler(async (event) => {
19
19
  });
20
20
  }
21
21
  }
22
- if (body.model !== void 0 && body.model !== null && !isValidModel(body.model)) {
22
+ if (!context.telaAgentId && body.model !== void 0 && body.model !== null && !isValidModel(body.model)) {
23
23
  throw createError({
24
24
  statusCode: 400,
25
25
  statusMessage: "Invalid model"
@@ -29,7 +29,8 @@ export default defineEventHandler(async (event) => {
29
29
  workspaceId: context.workspaceId,
30
30
  userId: context.user.id,
31
31
  createdBy: context.user.email,
32
- ...body.model ? { model: body.model } : {}
32
+ telaAgentId: context.telaAgentId,
33
+ ...context.telaAgentId ? { model: null } : body.model ? { model: body.model } : {}
33
34
  }).returning();
34
35
  if (!conversation) {
35
36
  throw createError({
@@ -0,0 +1,6 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ id: any;
3
+ inputSchema: any;
4
+ isMultiturn: any;
5
+ }>>;
6
+ export default _default;
@@ -0,0 +1,25 @@
1
+ import { createError, defineEventHandler, getRouterParam } from "h3";
2
+ import { requireUser } from "#chat-runtime/server/utils/auth";
3
+ import { getTelaAgent } from "#chat-runtime/server/utils/tela-agent-api";
4
+ export default defineEventHandler(async (event) => {
5
+ requireUser(event);
6
+ const agentId = getRouterParam(event, "id")?.trim();
7
+ if (!agentId) {
8
+ throw createError({
9
+ statusCode: 400,
10
+ statusMessage: "Agent ID is required"
11
+ });
12
+ }
13
+ const result = await getTelaAgent(event, agentId);
14
+ if (!result.success) {
15
+ throw createError({
16
+ statusCode: 502,
17
+ statusMessage: result.error
18
+ });
19
+ }
20
+ return {
21
+ id: result.data.id,
22
+ inputSchema: result.data.inputSchema,
23
+ isMultiturn: result.data.isMultiturn ?? null
24
+ };
25
+ });
@@ -55,7 +55,7 @@ export default defineEventHandler(async (event) => {
55
55
  name: skill.name,
56
56
  description: skill.description,
57
57
  runtime: skill.runtime,
58
- allowedTools: skill.allowedTools,
58
+ allowedTools: [...skill.allowedTools],
59
59
  isPublic: skill.isPublic,
60
60
  addedByUserId: skill.addedBy?.userId || session.user.id,
61
61
  addedByUsername: skill.addedBy?.username || session.user.name || "",