@meistrari/chat-nuxt 1.12.0 → 1.13.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/dist/module.json +1 -1
- package/dist/runtime/components/chat/reasoning-message.vue +4 -1
- package/dist/runtime/composables/useVerbConjugation.d.ts +1 -0
- package/dist/runtime/composables/useVerbConjugation.js +14 -1
- package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +2 -0
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +26 -4
- package/dist/runtime/server/db/schema/messages.d.ts +34 -0
- package/dist/runtime/server/db/schema/messages.js +3 -1
- package/dist/runtime/server/utils/conversation-message-sync.d.ts +1 -1
- package/dist/runtime/server/utils/conversation-message-sync.js +2 -2
- package/dist/runtime/server/utils/tela-agent-api.d.ts +3 -1
- package/dist/runtime/server/utils/tela-agent-api.js +285 -26
- package/dist/runtime/server/utils/tela-agent-session.d.ts +14 -4
- package/dist/runtime/server/utils/tela-agent-session.js +57 -22
- package/dist/runtime/types/tela-agent.d.ts +155 -43
- package/dist/runtime/types/tela-agent.js +49 -11
- package/drizzle/0017_typical_dexter_bennett.sql +2 -0
- package/drizzle/meta/0017_snapshot.json +899 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -19,7 +19,7 @@ const props = defineProps({
|
|
|
19
19
|
isPreviewMode: { type: Boolean, required: false },
|
|
20
20
|
completed: { type: Boolean, required: false }
|
|
21
21
|
});
|
|
22
|
-
const { conjugateText } = useVerbConjugation();
|
|
22
|
+
const { conjugateText, resolveReadFileStepTitle } = useVerbConjugation();
|
|
23
23
|
const { settings: workspaceSettings } = useWorkspaceSettings();
|
|
24
24
|
const canvasToolNames = computed(() => {
|
|
25
25
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -193,6 +193,9 @@ const stepTitle = computed(() => {
|
|
|
193
193
|
const verb = stepWasExecuted.value ? "Leu" : "Lendo";
|
|
194
194
|
if (typeof filePath === "string") {
|
|
195
195
|
const fileName = filePath.split("/").pop() || "arquivo";
|
|
196
|
+
const readTitle = resolveReadFileStepTitle(fileName, stepWasExecuted.value);
|
|
197
|
+
if (readTitle)
|
|
198
|
+
return readTitle;
|
|
196
199
|
title = `${verb}: ${fileName.length > 40 ? `${fileName.slice(0, 40)}...` : fileName}`;
|
|
197
200
|
} else {
|
|
198
201
|
title = `${verb} arquivo`;
|
|
@@ -3,4 +3,5 @@ export declare function useVerbConjugation(): {
|
|
|
3
3
|
isEnglish: (text: string) => boolean;
|
|
4
4
|
translateEnglishText: (text: string, toPast: boolean) => string;
|
|
5
5
|
conjugatePortugueseVerb: (text: string) => string;
|
|
6
|
+
resolveReadFileStepTitle: (fileName: string, wasExecuted: boolean) => string | null;
|
|
6
7
|
};
|
|
@@ -186,6 +186,12 @@ const gerundToPast = new Map(
|
|
|
186
186
|
const infinitiveToPast = new Map(
|
|
187
187
|
ptValues.map((v) => [v.infinitive, v.past])
|
|
188
188
|
);
|
|
189
|
+
const readFileStepLabels = {
|
|
190
|
+
"instructions.md": "instru\xE7\xF5es",
|
|
191
|
+
"output-format.json": "estrutura de sa\xEDda esperada",
|
|
192
|
+
"input-list.json": "lista de arquivos de entrada",
|
|
193
|
+
"result.json": "resultado"
|
|
194
|
+
};
|
|
189
195
|
export function useVerbConjugation() {
|
|
190
196
|
function isEnglish(text) {
|
|
191
197
|
const firstWord = text.trim().split(/\s+/)[0]?.toLowerCase() || "";
|
|
@@ -247,10 +253,17 @@ export function useVerbConjugation() {
|
|
|
247
253
|
}
|
|
248
254
|
return conjugatePortugueseVerb(text);
|
|
249
255
|
}
|
|
256
|
+
function resolveReadFileStepTitle(fileName, wasExecuted) {
|
|
257
|
+
const label = readFileStepLabels[fileName];
|
|
258
|
+
if (!label)
|
|
259
|
+
return null;
|
|
260
|
+
return conjugateText(`Lendo ${label}`, wasExecuted);
|
|
261
|
+
}
|
|
250
262
|
return {
|
|
251
263
|
conjugateText,
|
|
252
264
|
isEnglish,
|
|
253
265
|
translateEnglishText,
|
|
254
|
-
conjugatePortugueseVerb
|
|
266
|
+
conjugatePortugueseVerb,
|
|
267
|
+
resolveReadFileStepTitle
|
|
255
268
|
};
|
|
256
269
|
}
|
|
@@ -64,6 +64,8 @@ export default defineEventHandler(async (event) => {
|
|
|
64
64
|
reasoningData: null,
|
|
65
65
|
files: null,
|
|
66
66
|
ttft: null,
|
|
67
|
+
agentSessionCursor: null,
|
|
68
|
+
agentSessionId: null,
|
|
67
69
|
updatedAt: null
|
|
68
70
|
}).where(eq(schema.messages.id, messageId));
|
|
69
71
|
logger.info({ conversationId, messageId }, "Message retry initiated");
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
hasPendingMessageExceededGraceWindow,
|
|
12
12
|
isTerminalMessageUpdate,
|
|
13
13
|
mapAgentApiSessionToMessageUpdate,
|
|
14
|
+
PENDING_RESPONSE_GRACE_MS,
|
|
14
15
|
SESSION_STATUS_CHECK_ERROR,
|
|
15
16
|
THREAD_SESSION_START_ERROR,
|
|
16
17
|
trackAgentApiConversationUsage,
|
|
@@ -18,8 +19,9 @@ import {
|
|
|
18
19
|
} from "#chat-runtime/server/utils/conversation-message-sync";
|
|
19
20
|
import { conversationScopeConditions } from "#chat-runtime/server/utils/conversation-scope";
|
|
20
21
|
import { getTelaAgentSession } from "#chat-runtime/server/utils/tela-agent-api";
|
|
21
|
-
import { mapTelaAgentSessionToMessageUpdate, mapTelaAgentUsage } from "#chat-runtime/server/utils/tela-agent-session";
|
|
22
|
+
import { mapTelaAgentSessionToMessageUpdate, mapTelaAgentUsage, resolveTelaAgentRequestCursor } from "#chat-runtime/server/utils/tela-agent-session";
|
|
22
23
|
import { logger } from "#chat-runtime/server/utils/logger";
|
|
24
|
+
const TELA_AGENT_PROGRESS_RECOVERY_MS = 5 * 6e4;
|
|
23
25
|
export default defineEventHandler(async (event) => {
|
|
24
26
|
const context = resolveChatRuntimeContext(event);
|
|
25
27
|
const token = getAgentDataToken(event);
|
|
@@ -70,18 +72,38 @@ export default defineEventHandler(async (event) => {
|
|
|
70
72
|
}
|
|
71
73
|
logger.debug({ conversationId, messageId: pendingMessage.id, sessionId: conversation.threadSessionId }, "Polling pending message");
|
|
72
74
|
if (conversation.telaAgentId) {
|
|
73
|
-
const
|
|
75
|
+
const agentSessionCursor = resolveTelaAgentRequestCursor({
|
|
76
|
+
pendingMessage,
|
|
77
|
+
lastTerminalMessage: lastTerminalAssistantMessage,
|
|
78
|
+
threadSessionId: conversation.threadSessionId
|
|
79
|
+
});
|
|
80
|
+
const sessionResult2 = await getTelaAgentSession(event, conversation.threadSessionId, {
|
|
81
|
+
cursor: agentSessionCursor
|
|
82
|
+
});
|
|
74
83
|
if (!sessionResult2.success) {
|
|
75
|
-
|
|
84
|
+
const lastProgressAt = pendingMessage.updatedAt ?? pendingMessage.createdAt;
|
|
85
|
+
const hasTelaAgentProgress = typeof pendingMessage.agentSessionCursor === "number" || !!pendingMessage.externalUuid || Array.isArray(pendingMessage.reasoningData) && pendingMessage.reasoningData.length > 0;
|
|
86
|
+
const recoveryMs = hasTelaAgentProgress ? TELA_AGENT_PROGRESS_RECOVERY_MS : PENDING_RESPONSE_GRACE_MS;
|
|
87
|
+
if (!hasPendingMessageExceededGraceWindow({ createdAt: lastProgressAt }, /* @__PURE__ */ new Date(), recoveryMs)) {
|
|
88
|
+
logger.debug({ conversationId, messageId: pendingMessage.id, sessionId: conversation.threadSessionId, error: sessionResult2.error }, "Tela agent session status check failed within grace window");
|
|
89
|
+
return { messages };
|
|
90
|
+
}
|
|
91
|
+
const updateData3 = buildFailedPendingMessageUpdate(SESSION_STATUS_CHECK_ERROR);
|
|
92
|
+
logger.warn({ conversationId, messageId: pendingMessage.id, sessionId: conversation.threadSessionId, error: sessionResult2.error }, "Pending message failed because Tela agent session status check did not recover");
|
|
93
|
+
await db.update(schema.messages).set(updateData3).where(eq(schema.messages.id, pendingMessage.id));
|
|
94
|
+
return buildMessageUpdateResponse(messages, pendingMessage.id, updateData3);
|
|
76
95
|
}
|
|
77
96
|
const agentSession2 = sessionResult2.data;
|
|
78
97
|
const userMessage2 = messages.findLast((message) => message.role === "user");
|
|
79
98
|
const updateData2 = mapTelaAgentSessionToMessageUpdate(agentSession2, {
|
|
80
|
-
lastSyncedStepId: pendingMessage.externalUuid ?? lastTerminalAssistantMessage?.externalUuid,
|
|
81
99
|
existingReasoningData: Array.isArray(pendingMessage.reasoningData) ? pendingMessage.reasoningData : null,
|
|
82
100
|
pendingMessageTtft: pendingMessage.ttft,
|
|
83
101
|
userMessageCreatedAt: userMessage2?.createdAt ?? null
|
|
84
102
|
});
|
|
103
|
+
if (typeof agentSessionCursor === "number" && pendingMessage.agentSessionCursor !== agentSessionCursor && updateData2.agentSessionCursor === void 0) {
|
|
104
|
+
updateData2.agentSessionCursor = agentSessionCursor;
|
|
105
|
+
updateData2.agentSessionId = conversation.threadSessionId;
|
|
106
|
+
}
|
|
85
107
|
await attachNewAssistantFilesToMessageUpdate(db, conversationId, updateData2, token);
|
|
86
108
|
if (!hasMessageUpdate(updateData2)) {
|
|
87
109
|
return { messages };
|
|
@@ -148,6 +148,40 @@ export declare const messages: import("drizzle-orm/pg-core").PgTableWithColumns<
|
|
|
148
148
|
identity: undefined;
|
|
149
149
|
generated: undefined;
|
|
150
150
|
}, {}, {}>;
|
|
151
|
+
agentSessionCursor: import("drizzle-orm/pg-core").PgColumn<{
|
|
152
|
+
name: "agent_session_cursor";
|
|
153
|
+
tableName: "messages";
|
|
154
|
+
dataType: "number";
|
|
155
|
+
columnType: "PgInteger";
|
|
156
|
+
data: number;
|
|
157
|
+
driverParam: string | number;
|
|
158
|
+
notNull: false;
|
|
159
|
+
hasDefault: false;
|
|
160
|
+
isPrimaryKey: false;
|
|
161
|
+
isAutoincrement: false;
|
|
162
|
+
hasRuntimeDefault: false;
|
|
163
|
+
enumValues: undefined;
|
|
164
|
+
baseColumn: never;
|
|
165
|
+
identity: undefined;
|
|
166
|
+
generated: undefined;
|
|
167
|
+
}, {}, {}>;
|
|
168
|
+
agentSessionId: import("drizzle-orm/pg-core").PgColumn<{
|
|
169
|
+
name: "agent_session_id";
|
|
170
|
+
tableName: "messages";
|
|
171
|
+
dataType: "string";
|
|
172
|
+
columnType: "PgText";
|
|
173
|
+
data: string;
|
|
174
|
+
driverParam: string;
|
|
175
|
+
notNull: false;
|
|
176
|
+
hasDefault: false;
|
|
177
|
+
isPrimaryKey: false;
|
|
178
|
+
isAutoincrement: false;
|
|
179
|
+
hasRuntimeDefault: false;
|
|
180
|
+
enumValues: [string, ...string[]];
|
|
181
|
+
baseColumn: never;
|
|
182
|
+
identity: undefined;
|
|
183
|
+
generated: undefined;
|
|
184
|
+
}, {}, {}>;
|
|
151
185
|
ttft: import("drizzle-orm/pg-core").PgColumn<{
|
|
152
186
|
name: "ttft";
|
|
153
187
|
tableName: "messages";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { uuid, text, timestamp, index, jsonb } from "drizzle-orm/pg-core";
|
|
1
|
+
import { uuid, text, timestamp, index, integer, jsonb } from "drizzle-orm/pg-core";
|
|
2
2
|
import { chatSchema } from "./_schema.js";
|
|
3
3
|
import { conversations } from "./conversations.js";
|
|
4
4
|
export const messages = chatSchema.table("messages", {
|
|
@@ -10,6 +10,8 @@ export const messages = chatSchema.table("messages", {
|
|
|
10
10
|
reasoningData: jsonb("reasoning_data"),
|
|
11
11
|
files: jsonb("files").$type(),
|
|
12
12
|
externalUuid: text("external_uuid"),
|
|
13
|
+
agentSessionCursor: integer("agent_session_cursor"),
|
|
14
|
+
agentSessionId: text("agent_session_id"),
|
|
13
15
|
ttft: text("ttft"),
|
|
14
16
|
createdBy: text("created_by"),
|
|
15
17
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
@@ -20,7 +20,7 @@ type MessageUpdateResponse = {
|
|
|
20
20
|
export declare const PENDING_RESPONSE_GRACE_MS = 30000;
|
|
21
21
|
export declare const THREAD_SESSION_START_ERROR = "Erro: N\u00E3o foi poss\u00EDvel iniciar a sess\u00E3o de resposta. Tente enviar a mensagem novamente.";
|
|
22
22
|
export declare const SESSION_STATUS_CHECK_ERROR = "Erro: N\u00E3o foi poss\u00EDvel verificar o andamento da resposta. Tente enviar a mensagem novamente.";
|
|
23
|
-
export declare function hasPendingMessageExceededGraceWindow(pendingMessage: Pick<Message, 'createdAt'>, now?: Date): boolean;
|
|
23
|
+
export declare function hasPendingMessageExceededGraceWindow(pendingMessage: Pick<Message, 'createdAt'>, now?: Date, graceMs?: number): boolean;
|
|
24
24
|
export declare function buildFailedPendingMessageUpdate(content: string, now?: Date): MessageUpdate;
|
|
25
25
|
export declare function mapAgentApiSessionToMessageUpdate(agentSession: AgentApiSession, options: {
|
|
26
26
|
pendingMessage: Pick<Message, 'ttft' | 'createdAt' | 'updatedAt'>;
|
|
@@ -24,8 +24,8 @@ function resolveTerminalContentForPendingMessage(agentSession, newReasoning, las
|
|
|
24
24
|
return null;
|
|
25
25
|
return sessionContent || reasoningContent;
|
|
26
26
|
}
|
|
27
|
-
export function hasPendingMessageExceededGraceWindow(pendingMessage, now = /* @__PURE__ */ new Date()) {
|
|
28
|
-
return now.getTime() - pendingMessage.createdAt.getTime() >=
|
|
27
|
+
export function hasPendingMessageExceededGraceWindow(pendingMessage, now = /* @__PURE__ */ new Date(), graceMs = PENDING_RESPONSE_GRACE_MS) {
|
|
28
|
+
return now.getTime() - pendingMessage.createdAt.getTime() >= graceMs;
|
|
29
29
|
}
|
|
30
30
|
export function buildFailedPendingMessageUpdate(content, now = /* @__PURE__ */ new Date()) {
|
|
31
31
|
return {
|
|
@@ -14,7 +14,9 @@ export declare function getTelaAgent(event: H3Event, agentId: string): Promise<{
|
|
|
14
14
|
success: false;
|
|
15
15
|
error: string;
|
|
16
16
|
}>;
|
|
17
|
-
export declare function getTelaAgentSession(event: H3Event, sessionId: string
|
|
17
|
+
export declare function getTelaAgentSession(event: H3Event, sessionId: string, options?: {
|
|
18
|
+
cursor?: number | null;
|
|
19
|
+
}): Promise<{
|
|
18
20
|
success: true;
|
|
19
21
|
data: TelaAgentSession;
|
|
20
22
|
} | {
|
|
@@ -3,10 +3,14 @@ import {
|
|
|
3
3
|
telaAgentApiResponseSchema,
|
|
4
4
|
telaAgentEndSessionApiResponseSchema,
|
|
5
5
|
telaAgentRunApiResponseSchema,
|
|
6
|
-
|
|
6
|
+
telaAgentV4SessionStreamEventSchema
|
|
7
7
|
} from "#chat-runtime/types/tela-agent";
|
|
8
8
|
import { getAgentDataToken } from "#chat-runtime/server/utils/agent-api";
|
|
9
9
|
import { logger } from "#chat-runtime/server/utils/logger";
|
|
10
|
+
const TELA_SESSION_STREAM_FIRST_EVENT_TIMEOUT_MS = 3e3;
|
|
11
|
+
const TELA_SESSION_STREAM_IDLE_TIMEOUT_MS = 250;
|
|
12
|
+
const TELA_AGENT_V4_SESSION_EVENT_KINDS = /* @__PURE__ */ new Set(["status", "steps", "result", "error"]);
|
|
13
|
+
const TELA_AGENT_METADATA_MAX_ATTEMPTS = 3;
|
|
10
14
|
function getTelaErrorMessage(body, fallback) {
|
|
11
15
|
if (body && typeof body === "object") {
|
|
12
16
|
const record = body;
|
|
@@ -27,26 +31,105 @@ async function readResponseBody(response) {
|
|
|
27
31
|
return text;
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
|
-
|
|
34
|
+
function getErrorMessage(error) {
|
|
35
|
+
return error instanceof Error ? error.message : "Unknown error";
|
|
36
|
+
}
|
|
37
|
+
async function telaAgentFetch(event, path, init, options = {}) {
|
|
31
38
|
const { telaApiUrl } = useRuntimeConfig();
|
|
32
39
|
const token = getAgentDataToken(event);
|
|
33
40
|
const url = `${telaApiUrl}${path}`;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const maxAttempts = options.maxAttempts ?? 1;
|
|
42
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
43
|
+
let response;
|
|
44
|
+
let body;
|
|
45
|
+
try {
|
|
46
|
+
response = await fetch(url, {
|
|
47
|
+
...init,
|
|
48
|
+
headers: {
|
|
49
|
+
"Content-Type": "application/json",
|
|
50
|
+
"x-data-token": token,
|
|
51
|
+
"Authorization": `Bearer ${token}`,
|
|
52
|
+
...init.headers
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
body = await readResponseBody(response);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const message = getErrorMessage(error);
|
|
58
|
+
logger.warn({ error: message, path }, "Tela agent API request threw");
|
|
59
|
+
return { success: false, error: `Tela API request failed: ${message}` };
|
|
41
60
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const error = getTelaErrorMessage(body, `Tela API error: ${response.status} - ${response.statusText}`);
|
|
63
|
+
if (attempt < maxAttempts && options.retryStatusCodes?.includes(response.status)) {
|
|
64
|
+
logger.warn({ status: response.status, statusText: response.statusText, path, attempt }, "Tela agent API request failed, retrying");
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
logger.warn({ status: response.status, statusText: response.statusText, path }, "Tela agent API request failed");
|
|
68
|
+
return { success: false, error };
|
|
69
|
+
}
|
|
70
|
+
return { success: true, body };
|
|
71
|
+
}
|
|
72
|
+
return { success: false, error: "Tela API request failed" };
|
|
73
|
+
}
|
|
74
|
+
function getTelaAgentV4SessionEventKind(event) {
|
|
75
|
+
if (!event || typeof event !== "object" || !("kind" in event))
|
|
76
|
+
return null;
|
|
77
|
+
const kind = event.kind;
|
|
78
|
+
return typeof kind === "string" ? kind : null;
|
|
79
|
+
}
|
|
80
|
+
function parseTelaAgentV4SessionSseFrame(frame) {
|
|
81
|
+
let data = "";
|
|
82
|
+
let hasData = false;
|
|
83
|
+
let lineStart = 0;
|
|
84
|
+
while (lineStart <= frame.length) {
|
|
85
|
+
const lineEnd = frame.indexOf("\n", lineStart);
|
|
86
|
+
const line = lineEnd === -1 ? frame.slice(lineStart) : frame.slice(lineStart, lineEnd);
|
|
87
|
+
lineStart = lineEnd === -1 ? frame.length + 1 : lineEnd + 1;
|
|
88
|
+
if (!line || line.startsWith(":"))
|
|
89
|
+
continue;
|
|
90
|
+
const separatorIndex = line.indexOf(":");
|
|
91
|
+
const field = separatorIndex === -1 ? line : line.slice(0, separatorIndex);
|
|
92
|
+
let value = separatorIndex === -1 ? "" : line.slice(separatorIndex + 1);
|
|
93
|
+
if (value.startsWith(" "))
|
|
94
|
+
value = value.slice(1);
|
|
95
|
+
if (field === "data") {
|
|
96
|
+
data = hasData ? `${data}
|
|
97
|
+
${value}` : value;
|
|
98
|
+
hasData = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!hasData || !data || data === "[DONE]" || data === "Stream complete")
|
|
102
|
+
return void 0;
|
|
103
|
+
let rawEvent;
|
|
104
|
+
try {
|
|
105
|
+
rawEvent = JSON.parse(data);
|
|
106
|
+
} catch {
|
|
107
|
+
return void 0;
|
|
108
|
+
}
|
|
109
|
+
const eventKind = getTelaAgentV4SessionEventKind(rawEvent);
|
|
110
|
+
if (!eventKind || !TELA_AGENT_V4_SESSION_EVENT_KINDS.has(eventKind))
|
|
111
|
+
return void 0;
|
|
112
|
+
const parsed = telaAgentV4SessionStreamEventSchema.safeParse(rawEvent);
|
|
113
|
+
if (!parsed.success)
|
|
114
|
+
return void 0;
|
|
115
|
+
return parsed.data;
|
|
116
|
+
}
|
|
117
|
+
async function readTelaSessionStreamChunk(reader, timeoutMs) {
|
|
118
|
+
let timeout;
|
|
119
|
+
try {
|
|
120
|
+
return await Promise.race([
|
|
121
|
+
reader.read().then(
|
|
122
|
+
(result) => ({ type: "read", result }),
|
|
123
|
+
(error) => ({ type: "error", error })
|
|
124
|
+
),
|
|
125
|
+
new Promise((resolve) => {
|
|
126
|
+
timeout = setTimeout(() => resolve({ type: "timeout" }), timeoutMs);
|
|
127
|
+
})
|
|
128
|
+
]);
|
|
129
|
+
} finally {
|
|
130
|
+
if (timeout)
|
|
131
|
+
clearTimeout(timeout);
|
|
48
132
|
}
|
|
49
|
-
return { success: true, body };
|
|
50
133
|
}
|
|
51
134
|
export async function runTelaAgent(event, agentId, payload) {
|
|
52
135
|
const encodedAgentId = encodeURIComponent(agentId);
|
|
@@ -67,6 +150,9 @@ export async function getTelaAgent(event, agentId) {
|
|
|
67
150
|
const encodedAgentId = encodeURIComponent(agentId);
|
|
68
151
|
const result = await telaAgentFetch(event, `/agent/${encodedAgentId}`, {
|
|
69
152
|
method: "GET"
|
|
153
|
+
}, {
|
|
154
|
+
maxAttempts: TELA_AGENT_METADATA_MAX_ATTEMPTS,
|
|
155
|
+
retryStatusCodes: [502]
|
|
70
156
|
});
|
|
71
157
|
if (!result.success)
|
|
72
158
|
return result;
|
|
@@ -77,19 +163,192 @@ export async function getTelaAgent(event, agentId) {
|
|
|
77
163
|
}
|
|
78
164
|
return { success: true, data: parsed.data.data };
|
|
79
165
|
}
|
|
80
|
-
export async function getTelaAgentSession(event, sessionId) {
|
|
166
|
+
export async function getTelaAgentSession(event, sessionId, options = {}) {
|
|
167
|
+
const { telaApiUrl } = useRuntimeConfig();
|
|
168
|
+
const token = getAgentDataToken(event);
|
|
81
169
|
const encodedSessionId = encodeURIComponent(sessionId);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
170
|
+
const params = new URLSearchParams();
|
|
171
|
+
if (typeof options.cursor === "number")
|
|
172
|
+
params.set("cursor", String(options.cursor));
|
|
173
|
+
const queryString = params.toString();
|
|
174
|
+
const path = `/agent/sessions/${encodedSessionId}${queryString ? `?${queryString}` : ""}`;
|
|
175
|
+
const url = `${telaApiUrl}${path}`;
|
|
176
|
+
const abortController = new AbortController();
|
|
177
|
+
let completed = false;
|
|
178
|
+
let response = null;
|
|
179
|
+
let reader = null;
|
|
180
|
+
const abortOnResponseClose = () => {
|
|
181
|
+
if (!completed)
|
|
182
|
+
abortController.abort();
|
|
183
|
+
};
|
|
184
|
+
event.node?.res?.once?.("close", abortOnResponseClose);
|
|
185
|
+
try {
|
|
186
|
+
let buildSnapshot = function() {
|
|
187
|
+
if (!status)
|
|
188
|
+
return null;
|
|
189
|
+
return {
|
|
190
|
+
sessionId,
|
|
191
|
+
status,
|
|
192
|
+
steps,
|
|
193
|
+
...nextCursor !== void 0 ? { nextCursor } : {},
|
|
194
|
+
...result ? { result } : {},
|
|
195
|
+
...error ? { error } : {},
|
|
196
|
+
...createdAt !== void 0 ? { createdAt } : {},
|
|
197
|
+
...updatedAt !== void 0 ? { updatedAt } : {}
|
|
198
|
+
};
|
|
199
|
+
}, applyStreamEvent = function(streamEvent) {
|
|
200
|
+
if (streamEvent.kind === "error") {
|
|
201
|
+
status = "failed";
|
|
202
|
+
error = streamEvent.error;
|
|
203
|
+
return buildSnapshot();
|
|
204
|
+
}
|
|
205
|
+
status = streamEvent.status;
|
|
206
|
+
if (streamEvent.kind === "status") {
|
|
207
|
+
error = streamEvent.error;
|
|
208
|
+
createdAt = streamEvent.createdAt;
|
|
209
|
+
updatedAt = streamEvent.updatedAt;
|
|
210
|
+
if (status === "failed" || status === "cancelled")
|
|
211
|
+
return buildSnapshot();
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
createdAt = streamEvent.createdAt;
|
|
215
|
+
updatedAt = streamEvent.updatedAt;
|
|
216
|
+
nextCursor = streamEvent.nextCursor;
|
|
217
|
+
if (streamEvent.kind === "steps") {
|
|
218
|
+
for (const step of streamEvent.steps) {
|
|
219
|
+
const key = step.eventId ?? step.id;
|
|
220
|
+
if (!key)
|
|
221
|
+
continue;
|
|
222
|
+
const existingIndex = stepIndexByKey.get(key);
|
|
223
|
+
if (existingIndex === void 0) {
|
|
224
|
+
stepIndexByKey.set(key, steps.length);
|
|
225
|
+
steps.push(step);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
steps[existingIndex] = {
|
|
229
|
+
...steps[existingIndex],
|
|
230
|
+
...step
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (status === "failed" || status === "cancelled")
|
|
234
|
+
return buildSnapshot();
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
result = streamEvent.result;
|
|
238
|
+
error = void 0;
|
|
239
|
+
return buildSnapshot();
|
|
240
|
+
}, drainBuffer = function() {
|
|
241
|
+
while (true) {
|
|
242
|
+
const frameBoundary = buffer.indexOf("\n\n");
|
|
243
|
+
if (frameBoundary === -1)
|
|
244
|
+
return null;
|
|
245
|
+
const frame = buffer.slice(0, frameBoundary);
|
|
246
|
+
buffer = buffer.slice(frameBoundary + 2);
|
|
247
|
+
const streamEvent = parseTelaAgentV4SessionSseFrame(frame);
|
|
248
|
+
if (!streamEvent)
|
|
249
|
+
continue;
|
|
250
|
+
const terminalSnapshot = applyStreamEvent(streamEvent);
|
|
251
|
+
if (terminalSnapshot)
|
|
252
|
+
return terminalSnapshot;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
try {
|
|
256
|
+
response = await fetch(url, {
|
|
257
|
+
method: "GET",
|
|
258
|
+
signal: abortController.signal,
|
|
259
|
+
headers: {
|
|
260
|
+
"Accept": "text/event-stream",
|
|
261
|
+
"x-data-token": token,
|
|
262
|
+
"Authorization": `Bearer ${token}`
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
} catch (error2) {
|
|
266
|
+
const message = getErrorMessage(error2);
|
|
267
|
+
logger.warn({ error: message, path }, "Tela agent API request threw");
|
|
268
|
+
return { success: false, error: `Tela API request failed: ${message}` };
|
|
269
|
+
}
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
let body;
|
|
272
|
+
try {
|
|
273
|
+
body = await readResponseBody(response);
|
|
274
|
+
} catch (error3) {
|
|
275
|
+
const message = getErrorMessage(error3);
|
|
276
|
+
logger.warn({ error: message, path }, "Tela agent API error response read failed");
|
|
277
|
+
return { success: false, error: `Tela API request failed: ${message}` };
|
|
278
|
+
}
|
|
279
|
+
const error2 = getTelaErrorMessage(body, `Tela API error: ${response.status} - ${response.statusText}`);
|
|
280
|
+
logger.warn({ status: response.status, statusText: response.statusText, path }, "Tela agent API request failed");
|
|
281
|
+
return { success: false, error: error2 };
|
|
282
|
+
}
|
|
283
|
+
if (!response.body)
|
|
284
|
+
return { success: false, error: "Tela session stream response body is missing" };
|
|
285
|
+
reader = response.body.getReader();
|
|
286
|
+
const decoder = new TextDecoder();
|
|
287
|
+
const steps = [];
|
|
288
|
+
const stepIndexByKey = /* @__PURE__ */ new Map();
|
|
289
|
+
let status = null;
|
|
290
|
+
let nextCursor;
|
|
291
|
+
let result;
|
|
292
|
+
let error;
|
|
293
|
+
let createdAt;
|
|
294
|
+
let updatedAt;
|
|
295
|
+
let buffer = "";
|
|
296
|
+
while (true) {
|
|
297
|
+
const snapshot = buildSnapshot();
|
|
298
|
+
const readResult = await readTelaSessionStreamChunk(
|
|
299
|
+
reader,
|
|
300
|
+
snapshot ? TELA_SESSION_STREAM_IDLE_TIMEOUT_MS : TELA_SESSION_STREAM_FIRST_EVENT_TIMEOUT_MS
|
|
301
|
+
);
|
|
302
|
+
if (readResult.type === "timeout") {
|
|
303
|
+
if (snapshot)
|
|
304
|
+
return { success: true, data: snapshot };
|
|
305
|
+
return { success: false, error: "Tela session stream timed out before a session event" };
|
|
306
|
+
}
|
|
307
|
+
if (readResult.type === "error") {
|
|
308
|
+
const message = getErrorMessage(readResult.error);
|
|
309
|
+
const activeSnapshot = buildSnapshot();
|
|
310
|
+
if (activeSnapshot) {
|
|
311
|
+
logger.warn({ error: message, path, sessionId }, "Tela agent session stream failed after active snapshot");
|
|
312
|
+
return { success: true, data: activeSnapshot };
|
|
313
|
+
}
|
|
314
|
+
logger.warn({ error: message, path }, "Tela agent API request threw");
|
|
315
|
+
return { success: false, error: `Tela API request failed: ${message}` };
|
|
316
|
+
}
|
|
317
|
+
const { done, value } = readResult.result;
|
|
318
|
+
if (done) {
|
|
319
|
+
buffer += decoder.decode().replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
320
|
+
if (buffer.trim()) {
|
|
321
|
+
const streamEvent = parseTelaAgentV4SessionSseFrame(buffer);
|
|
322
|
+
if (streamEvent) {
|
|
323
|
+
const terminalSnapshot2 = applyStreamEvent(streamEvent);
|
|
324
|
+
if (terminalSnapshot2)
|
|
325
|
+
return { success: true, data: terminalSnapshot2 };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const snapshot2 = buildSnapshot();
|
|
329
|
+
if (snapshot2)
|
|
330
|
+
return { success: true, data: snapshot2 };
|
|
331
|
+
return { success: false, error: "Tela session stream ended without a session event" };
|
|
332
|
+
}
|
|
333
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
334
|
+
const terminalSnapshot = drainBuffer();
|
|
335
|
+
if (terminalSnapshot)
|
|
336
|
+
return { success: true, data: terminalSnapshot };
|
|
337
|
+
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const message = getErrorMessage(error);
|
|
340
|
+
logger.warn({ error: message, sessionId }, "Tela agent session response parse error");
|
|
90
341
|
return { success: false, error: "Invalid session response from Tela API" };
|
|
342
|
+
} finally {
|
|
343
|
+
completed = true;
|
|
344
|
+
event.node?.res?.off?.("close", abortOnResponseClose);
|
|
345
|
+
abortController.abort();
|
|
346
|
+
await reader?.cancel().catch(() => void 0);
|
|
347
|
+
try {
|
|
348
|
+
reader?.releaseLock();
|
|
349
|
+
} catch {
|
|
350
|
+
}
|
|
91
351
|
}
|
|
92
|
-
return { success: true, data: parsed.data.data };
|
|
93
352
|
}
|
|
94
353
|
export async function endTelaAgentSession(event, sessionId) {
|
|
95
354
|
const encodedSessionId = encodeURIComponent(sessionId);
|
|
@@ -4,6 +4,8 @@ import type { TelaAgentExecutionInput, TelaAgentInputVariable, TelaAgentSession,
|
|
|
4
4
|
export type TelaAgentMessageUpdate = {
|
|
5
5
|
reasoningData?: ReasoningItem[];
|
|
6
6
|
externalUuid?: string;
|
|
7
|
+
agentSessionCursor?: number;
|
|
8
|
+
agentSessionId?: string;
|
|
7
9
|
content?: string;
|
|
8
10
|
status?: MessageStatus;
|
|
9
11
|
ttft?: string;
|
|
@@ -33,12 +35,20 @@ export declare function toTelaAgentAttachments(files: TelaAgentFileInput[] | und
|
|
|
33
35
|
vaultRef: string;
|
|
34
36
|
filename: any;
|
|
35
37
|
}[];
|
|
36
|
-
export declare function getTelaAgentStepSyncId(step: TelaAgentStep): string;
|
|
38
|
+
export declare function getTelaAgentStepSyncId(step: TelaAgentStep): string | null;
|
|
37
39
|
export declare function normalizeTelaAgentReasoningStep(sessionId: string, step: TelaAgentStep): ReasoningItem | null;
|
|
38
|
-
export declare function
|
|
39
|
-
export declare function
|
|
40
|
+
export declare function extractTelaAgentAssistantContent(session: TelaAgentSession): string;
|
|
41
|
+
export declare function resolveTelaAgentRequestCursor(options: {
|
|
42
|
+
pendingMessage: {
|
|
43
|
+
agentSessionCursor?: number | null;
|
|
44
|
+
};
|
|
45
|
+
lastTerminalMessage?: {
|
|
46
|
+
agentSessionCursor?: number | null;
|
|
47
|
+
agentSessionId?: string | null;
|
|
48
|
+
} | null;
|
|
49
|
+
threadSessionId: string;
|
|
50
|
+
}): number | undefined;
|
|
40
51
|
export declare function mapTelaAgentSessionToMessageUpdate(session: TelaAgentSession, options?: {
|
|
41
|
-
lastSyncedStepId?: string | null;
|
|
42
52
|
existingReasoningData?: ReasoningItem[] | null;
|
|
43
53
|
pendingMessageTtft?: string | null;
|
|
44
54
|
userMessageCreatedAt?: Date | null;
|