@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.
- package/README.md +51 -16
- package/dist/module.json +1 -1
- package/dist/module.mjs +12 -8
- package/dist/runtime/components/MeistrariChatEmbed.d.vue.ts +5 -16
- package/dist/runtime/components/MeistrariChatEmbed.vue +28 -5
- package/dist/runtime/components/MeistrariChatEmbed.vue.d.ts +5 -16
- package/dist/runtime/components/app-markdown-render.d.vue.ts +3 -1
- package/dist/runtime/components/app-markdown-render.vue +15 -4
- package/dist/runtime/components/app-markdown-render.vue.d.ts +3 -1
- package/dist/runtime/components/chat/message-bubble.vue +10 -2
- package/dist/runtime/components/chat/message-input.d.vue.ts +30 -4
- package/dist/runtime/components/chat/message-input.vue +268 -24
- package/dist/runtime/components/chat/message-input.vue.d.ts +30 -4
- package/dist/runtime/composables/useChat.d.ts +3 -5
- package/dist/runtime/composables/useChat.js +24 -3
- package/dist/runtime/composables/useChatAction.d.ts +10 -0
- package/dist/runtime/composables/useChatAction.js +12 -0
- package/dist/runtime/composables/useChatApi.js +4 -0
- package/dist/runtime/composables/useConversations.js +8 -2
- package/dist/runtime/composables/useEmbedConfig.d.ts +12 -2
- package/dist/runtime/composables/useEmbedConfig.js +24 -3
- package/dist/runtime/composables/useFeatureFlags.d.ts +1 -2
- package/dist/runtime/composables/useTelaAgentMetadata.d.ts +8 -0
- package/dist/runtime/composables/useTelaAgentMetadata.js +65 -0
- package/dist/runtime/composables/useWorkspaceSettings.d.ts +1 -19
- package/dist/runtime/embed/components/ChatConfigurationModal.d.vue.ts +1 -1
- package/dist/runtime/embed/components/ChatConfigurationModal.vue.d.ts +1 -1
- package/dist/runtime/embed/components/ChatEmbed.d.vue.ts +6 -21
- package/dist/runtime/embed/components/ChatEmbed.vue +44 -11
- package/dist/runtime/embed/components/ChatEmbed.vue.d.ts +6 -21
- package/dist/runtime/embed/components/ChatEmbedInner.vue +36 -12
- package/dist/runtime/plugins/markstream.d.ts +1 -1
- package/dist/runtime/plugins/markstream.js +1 -1
- package/dist/runtime/server/api/conversations/[id]/cancel.post.d.ts +1 -1
- package/dist/runtime/server/api/conversations/[id]/cancel.post.js +37 -17
- package/dist/runtime/server/api/conversations/[id]/clear.post.js +28 -13
- package/dist/runtime/server/api/conversations/[id]/duplicate.post.js +11 -13
- package/dist/runtime/server/api/conversations/[id]/files.get.js +5 -7
- package/dist/runtime/server/api/conversations/[id]/index.delete.js +13 -12
- package/dist/runtime/server/api/conversations/[id]/index.get.js +6 -13
- package/dist/runtime/server/api/conversations/[id]/index.patch.js +11 -10
- package/dist/runtime/server/api/conversations/[id]/messages/[messageId]/retry.post.js +46 -80
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.d.ts +1 -14
- package/dist/runtime/server/api/conversations/[id]/messages/index.get.js +81 -187
- package/dist/runtime/server/api/conversations/[id]/messages/index.post.js +68 -83
- package/dist/runtime/server/api/conversations/[id]/usage.get.js +4 -8
- package/dist/runtime/server/api/conversations/generate-title.post.js +4 -3
- package/dist/runtime/server/api/conversations/index.get.js +5 -4
- package/dist/runtime/server/api/conversations/index.post.js +5 -4
- package/dist/runtime/server/api/tela/agents/[id].get.d.ts +6 -0
- package/dist/runtime/server/api/tela/agents/[id].get.js +25 -0
- package/dist/runtime/server/api/workspace/settings.patch.js +1 -1
- package/dist/runtime/server/db/index.js +62 -0
- package/dist/runtime/server/db/schema/conversations.d.ts +17 -0
- package/dist/runtime/server/db/schema/conversations.js +3 -1
- package/dist/runtime/server/db/schema/workspace-settings.d.ts +1 -1
- package/dist/runtime/server/utils/agent-api.d.ts +1 -1
- package/dist/runtime/server/utils/agent-api.js +2 -5
- package/dist/runtime/server/utils/chat-context.d.ts +5 -0
- package/dist/runtime/server/utils/chat-context.js +20 -0
- package/dist/runtime/server/utils/chat-workspace-settings.d.ts +2 -1
- package/dist/runtime/server/utils/chat-workspace-settings.js +10 -1
- package/dist/runtime/server/utils/conversation-agent-turn.d.ts +39 -0
- package/dist/runtime/server/utils/conversation-agent-turn.js +170 -0
- package/dist/runtime/server/utils/conversation-message-files.d.ts +10 -0
- package/dist/runtime/server/utils/conversation-message-files.js +77 -0
- package/dist/runtime/server/utils/conversation-message-sync.d.ts +31 -0
- package/dist/runtime/server/utils/conversation-message-sync.js +132 -0
- package/dist/runtime/server/utils/conversation-scope.d.ts +2 -0
- package/dist/runtime/server/utils/conversation-scope.js +12 -0
- package/dist/runtime/server/utils/tela-agent-api.d.ts +29 -0
- package/dist/runtime/server/utils/tela-agent-api.js +107 -0
- package/dist/runtime/server/utils/tela-agent-session.d.ts +48 -0
- package/dist/runtime/server/utils/tela-agent-session.js +350 -0
- package/dist/runtime/types/embed.d.ts +29 -0
- package/dist/runtime/types/embed.js +0 -0
- package/dist/runtime/types/tela-agent.d.ts +243 -0
- package/dist/runtime/types/tela-agent.js +98 -0
- package/dist/runtime/types/workspace-settings-data.d.ts +19 -0
- package/dist/runtime/types/workspace-settings-data.js +0 -0
- package/dist/runtime/types/workspace-settings.d.ts +1 -1
- package/dist/runtime/utils/agent-input-readiness.d.ts +14 -0
- package/dist/runtime/utils/agent-input-readiness.js +15 -0
- package/dist/runtime/utils/custom-markdown-components.d.ts +9 -0
- package/dist/runtime/utils/custom-markdown-components.js +54 -0
- package/dist/runtime/utils/features.d.ts +9 -0
- package/dist/runtime/utils/features.js +5 -4
- package/dist/runtime/utils/file.js +46 -7
- package/dist/runtime/utils/markdown-nodes.d.ts +1 -0
- package/dist/runtime/utils/markdown-nodes.js +6 -0
- package/dist/runtime/utils/tela-chat.d.ts +6 -0
- package/dist/runtime/utils/tela-chat.js +12 -1
- package/drizzle/0014_faulty_lake.sql +2 -0
- package/drizzle/meta/0014_snapshot.json +732 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/dist/runtime/types/feature-flags.d.ts +0 -7
- package/dist/runtime/types/feature-flags.js +0 -6
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { schema } from "#chat-runtime/server/db";
|
|
2
|
+
import { extractContentFromSession } from "#chat-runtime/server/utils/agent-api";
|
|
3
|
+
import { logger } from "#chat-runtime/server/utils/logger";
|
|
4
|
+
function toMilliseconds(timestamp) {
|
|
5
|
+
if (typeof timestamp !== "number" || !Number.isFinite(timestamp))
|
|
6
|
+
return null;
|
|
7
|
+
return timestamp < 1e10 ? timestamp * 1e3 : timestamp;
|
|
8
|
+
}
|
|
9
|
+
export function mapAgentApiSessionToMessageUpdate(agentSession, options) {
|
|
10
|
+
const updateData = {};
|
|
11
|
+
const lastSyncedUuid = options.lastAssistantMessage?.externalUuid;
|
|
12
|
+
const lastSyncedIndex = lastSyncedUuid ? agentSession.reasoning?.findIndex((item) => item.uuid === lastSyncedUuid) ?? -1 : -1;
|
|
13
|
+
const newReasoning = agentSession.reasoning?.slice(lastSyncedIndex + 1);
|
|
14
|
+
const hasNewReasoning = newReasoning && newReasoning.length > 0;
|
|
15
|
+
const sessionUpdatedAtMs = toMilliseconds(agentSession.updatedAt);
|
|
16
|
+
const terminalSessionBelongsToPendingMessage = hasNewReasoning || !options.lastAssistantMessage || sessionUpdatedAtMs !== null && sessionUpdatedAtMs >= options.pendingMessage.createdAt.getTime();
|
|
17
|
+
if (hasNewReasoning) {
|
|
18
|
+
updateData.reasoningData = newReasoning;
|
|
19
|
+
const lastReasoningItem = newReasoning.at(-1);
|
|
20
|
+
if (lastReasoningItem?.uuid) {
|
|
21
|
+
updateData.externalUuid = lastReasoningItem.uuid;
|
|
22
|
+
}
|
|
23
|
+
if (!options.pendingMessage.ttft && options.userMessage) {
|
|
24
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
25
|
+
updateData.ttft = String(now.getTime() - options.userMessage.createdAt.getTime());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (agentSession.status === "completed" || agentSession.status === "waiting_messages") {
|
|
29
|
+
if (terminalSessionBelongsToPendingMessage) {
|
|
30
|
+
updateData.content = extractContentFromSession(agentSession);
|
|
31
|
+
updateData.status = "completed";
|
|
32
|
+
updateData.updatedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
33
|
+
}
|
|
34
|
+
} else if (agentSession.status === "failed") {
|
|
35
|
+
const errorContent = agentSession.error || "Agent execution failed";
|
|
36
|
+
updateData.content = `Error: ${errorContent}`;
|
|
37
|
+
updateData.status = "failed";
|
|
38
|
+
updateData.updatedAt = options.now ?? /* @__PURE__ */ new Date();
|
|
39
|
+
}
|
|
40
|
+
return updateData;
|
|
41
|
+
}
|
|
42
|
+
export function hasMessageUpdate(updateData) {
|
|
43
|
+
return Object.keys(updateData).length > 0;
|
|
44
|
+
}
|
|
45
|
+
export function isTerminalMessageUpdate(updateData) {
|
|
46
|
+
return updateData.status === "completed" || updateData.status === "failed";
|
|
47
|
+
}
|
|
48
|
+
export function buildMessageUpdateResponse(messages, messageId, updateData) {
|
|
49
|
+
const updatedMessages = messages.map(
|
|
50
|
+
(message) => message.id === messageId ? { ...message, ...updateData } : message
|
|
51
|
+
);
|
|
52
|
+
if (updateData.files && updateData.files.length > 0) {
|
|
53
|
+
return {
|
|
54
|
+
messages: updatedMessages,
|
|
55
|
+
newFiles: updateData.files
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return { messages: updatedMessages };
|
|
59
|
+
}
|
|
60
|
+
export function trackTelaAgentConversationUsage(db, context, conversationId, sessionId, usage) {
|
|
61
|
+
if (!usage)
|
|
62
|
+
return;
|
|
63
|
+
void db.insert(schema.conversationUsage).values({
|
|
64
|
+
workspaceId: context.workspaceId,
|
|
65
|
+
conversationId,
|
|
66
|
+
...usage,
|
|
67
|
+
userId: context.user.id,
|
|
68
|
+
userEmail: context.user.email ?? null,
|
|
69
|
+
sessionId
|
|
70
|
+
}).onConflictDoUpdate({
|
|
71
|
+
target: schema.conversationUsage.conversationId,
|
|
72
|
+
set: {
|
|
73
|
+
...usage,
|
|
74
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
75
|
+
}
|
|
76
|
+
}).catch((error) => {
|
|
77
|
+
logger.warn({ conversationId, error }, "Failed to track conversation usage");
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function trackAgentApiConversationUsage(db, context, conversationId, sessionId, agentSession) {
|
|
81
|
+
const usage = agentSession.result?.usage;
|
|
82
|
+
if (!usage)
|
|
83
|
+
return;
|
|
84
|
+
const breakdown = agentSession.result?.usageBreakdown;
|
|
85
|
+
const serverToolUse = breakdown?.serverToolUse;
|
|
86
|
+
const webSearchRequests = typeof serverToolUse?.web_search_requests === "number" ? serverToolUse.web_search_requests : null;
|
|
87
|
+
const webFetchRequests = typeof serverToolUse?.web_fetch_requests === "number" ? serverToolUse.web_fetch_requests : null;
|
|
88
|
+
void db.insert(schema.conversationUsage).values({
|
|
89
|
+
workspaceId: context.workspaceId,
|
|
90
|
+
conversationId,
|
|
91
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
92
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
93
|
+
totalTokens: usage.totalTokens ?? 0,
|
|
94
|
+
promptCost: String(usage.promptCost ?? 0),
|
|
95
|
+
completionCost: String(usage.completionCost ?? 0),
|
|
96
|
+
totalCost: String(usage.totalCost ?? 0),
|
|
97
|
+
durationMs: usage.duration ?? null,
|
|
98
|
+
turns: usage.turns ?? null,
|
|
99
|
+
userId: context.user.id,
|
|
100
|
+
userEmail: context.user.email ?? null,
|
|
101
|
+
model: agentSession.model ?? null,
|
|
102
|
+
sessionId,
|
|
103
|
+
cacheReadInputTokens: breakdown?.cacheReadInputTokens ?? null,
|
|
104
|
+
cacheCreationInputTokens: breakdown?.cacheCreationInputTokens ?? null,
|
|
105
|
+
modelUsage: breakdown?.modelUsage ?? null,
|
|
106
|
+
durationApiMs: breakdown?.durationApiMs ?? null,
|
|
107
|
+
webSearchRequests,
|
|
108
|
+
webFetchRequests
|
|
109
|
+
}).onConflictDoUpdate({
|
|
110
|
+
target: schema.conversationUsage.conversationId,
|
|
111
|
+
set: {
|
|
112
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
113
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
114
|
+
totalTokens: usage.totalTokens ?? 0,
|
|
115
|
+
promptCost: String(usage.promptCost ?? 0),
|
|
116
|
+
completionCost: String(usage.completionCost ?? 0),
|
|
117
|
+
totalCost: String(usage.totalCost ?? 0),
|
|
118
|
+
durationMs: usage.duration ?? null,
|
|
119
|
+
turns: usage.turns ?? null,
|
|
120
|
+
model: agentSession.model ?? null,
|
|
121
|
+
cacheReadInputTokens: breakdown?.cacheReadInputTokens ?? null,
|
|
122
|
+
cacheCreationInputTokens: breakdown?.cacheCreationInputTokens ?? null,
|
|
123
|
+
modelUsage: breakdown?.modelUsage ?? null,
|
|
124
|
+
durationApiMs: breakdown?.durationApiMs ?? null,
|
|
125
|
+
webSearchRequests,
|
|
126
|
+
webFetchRequests,
|
|
127
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
128
|
+
}
|
|
129
|
+
}).catch((error) => {
|
|
130
|
+
logger.warn({ conversationId, error }, "Failed to track conversation usage");
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { and, eq, isNull } from "drizzle-orm";
|
|
2
|
+
import { schema } from "#chat-runtime/server/db";
|
|
3
|
+
export function conversationScopeConditions(context, conversationId) {
|
|
4
|
+
const conditions = [
|
|
5
|
+
eq(schema.conversations.workspaceId, context.workspaceId),
|
|
6
|
+
context.telaAgentId ? eq(schema.conversations.telaAgentId, context.telaAgentId) : isNull(schema.conversations.telaAgentId)
|
|
7
|
+
];
|
|
8
|
+
if (conversationId) {
|
|
9
|
+
conditions.unshift(eq(schema.conversations.id, conversationId));
|
|
10
|
+
}
|
|
11
|
+
return and(...conditions);
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { TelaAgent, TelaAgentRunPayload, TelaAgentSession } from '#chat-runtime/types/tela-agent';
|
|
3
|
+
export declare function runTelaAgent(event: H3Event, agentId: string, payload: TelaAgentRunPayload): Promise<{
|
|
4
|
+
success: true;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
} | {
|
|
7
|
+
success: false;
|
|
8
|
+
error: string;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function getTelaAgent(event: H3Event, agentId: string): Promise<{
|
|
11
|
+
success: true;
|
|
12
|
+
data: TelaAgent;
|
|
13
|
+
} | {
|
|
14
|
+
success: false;
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function getTelaAgentSession(event: H3Event, sessionId: string): Promise<{
|
|
18
|
+
success: true;
|
|
19
|
+
data: TelaAgentSession;
|
|
20
|
+
} | {
|
|
21
|
+
success: false;
|
|
22
|
+
error: string;
|
|
23
|
+
}>;
|
|
24
|
+
export declare function endTelaAgentSession(event: H3Event, sessionId: string): Promise<{
|
|
25
|
+
success: true;
|
|
26
|
+
} | {
|
|
27
|
+
success: false;
|
|
28
|
+
error: string;
|
|
29
|
+
}>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
|
+
import {
|
|
3
|
+
telaAgentApiResponseSchema,
|
|
4
|
+
telaAgentEndSessionApiResponseSchema,
|
|
5
|
+
telaAgentRunApiResponseSchema,
|
|
6
|
+
telaAgentSessionApiResponseSchema
|
|
7
|
+
} from "#chat-runtime/types/tela-agent";
|
|
8
|
+
import { getAgentDataToken } from "#chat-runtime/server/utils/agent-api";
|
|
9
|
+
import { logger } from "#chat-runtime/server/utils/logger";
|
|
10
|
+
function getTelaErrorMessage(body, fallback) {
|
|
11
|
+
if (body && typeof body === "object") {
|
|
12
|
+
const record = body;
|
|
13
|
+
if (typeof record.error === "string")
|
|
14
|
+
return record.error;
|
|
15
|
+
if (typeof record.message === "string")
|
|
16
|
+
return record.message;
|
|
17
|
+
}
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
async function readResponseBody(response) {
|
|
21
|
+
const text = await response.text();
|
|
22
|
+
if (!text)
|
|
23
|
+
return null;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(text);
|
|
26
|
+
} catch {
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function telaAgentFetch(event, path, init) {
|
|
31
|
+
const { telaApiUrl } = useRuntimeConfig();
|
|
32
|
+
const token = getAgentDataToken(event);
|
|
33
|
+
const url = `${telaApiUrl}${path}`;
|
|
34
|
+
const response = await fetch(url, {
|
|
35
|
+
...init,
|
|
36
|
+
headers: {
|
|
37
|
+
"Content-Type": "application/json",
|
|
38
|
+
"x-data-token": token,
|
|
39
|
+
"Authorization": `Bearer ${token}`,
|
|
40
|
+
...init.headers
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const body = await readResponseBody(response);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const error = getTelaErrorMessage(body, `Tela API error: ${response.status} - ${response.statusText}`);
|
|
46
|
+
logger.warn({ status: response.status, statusText: response.statusText, path }, "Tela agent API request failed");
|
|
47
|
+
return { success: false, error };
|
|
48
|
+
}
|
|
49
|
+
return { success: true, body };
|
|
50
|
+
}
|
|
51
|
+
export async function runTelaAgent(event, agentId, payload) {
|
|
52
|
+
const encodedAgentId = encodeURIComponent(agentId);
|
|
53
|
+
const result = await telaAgentFetch(event, `/agent/${encodedAgentId}/run`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify(payload)
|
|
56
|
+
});
|
|
57
|
+
if (!result.success)
|
|
58
|
+
return result;
|
|
59
|
+
const parsed = telaAgentRunApiResponseSchema.safeParse(result.body);
|
|
60
|
+
if (!parsed.success) {
|
|
61
|
+
logger.warn({ issues: parsed.error.issues }, "Tela agent run response parse error");
|
|
62
|
+
return { success: false, error: "Invalid response from Tela API" };
|
|
63
|
+
}
|
|
64
|
+
return { success: true, sessionId: parsed.data.data.sessionId };
|
|
65
|
+
}
|
|
66
|
+
export async function getTelaAgent(event, agentId) {
|
|
67
|
+
const encodedAgentId = encodeURIComponent(agentId);
|
|
68
|
+
const result = await telaAgentFetch(event, `/agent/${encodedAgentId}`, {
|
|
69
|
+
method: "GET"
|
|
70
|
+
});
|
|
71
|
+
if (!result.success)
|
|
72
|
+
return result;
|
|
73
|
+
const parsed = telaAgentApiResponseSchema.safeParse(result.body);
|
|
74
|
+
if (!parsed.success) {
|
|
75
|
+
logger.warn({ issues: parsed.error.issues, agentId }, "Tela agent metadata response parse error");
|
|
76
|
+
return { success: false, error: "Invalid agent metadata response from Tela API" };
|
|
77
|
+
}
|
|
78
|
+
return { success: true, data: parsed.data.data };
|
|
79
|
+
}
|
|
80
|
+
export async function getTelaAgentSession(event, sessionId) {
|
|
81
|
+
const encodedSessionId = encodeURIComponent(sessionId);
|
|
82
|
+
const result = await telaAgentFetch(event, `/agent/sessions/${encodedSessionId}`, {
|
|
83
|
+
method: "GET"
|
|
84
|
+
});
|
|
85
|
+
if (!result.success)
|
|
86
|
+
return result;
|
|
87
|
+
const parsed = telaAgentSessionApiResponseSchema.safeParse(result.body);
|
|
88
|
+
if (!parsed.success) {
|
|
89
|
+
logger.warn({ issues: parsed.error.issues, sessionId }, "Tela agent session response parse error");
|
|
90
|
+
return { success: false, error: "Invalid session response from Tela API" };
|
|
91
|
+
}
|
|
92
|
+
return { success: true, data: parsed.data.data };
|
|
93
|
+
}
|
|
94
|
+
export async function endTelaAgentSession(event, sessionId) {
|
|
95
|
+
const encodedSessionId = encodeURIComponent(sessionId);
|
|
96
|
+
const result = await telaAgentFetch(event, `/agent/sessions/${encodedSessionId}`, {
|
|
97
|
+
method: "DELETE"
|
|
98
|
+
});
|
|
99
|
+
if (!result.success)
|
|
100
|
+
return result;
|
|
101
|
+
const parsed = telaAgentEndSessionApiResponseSchema.safeParse(result.body);
|
|
102
|
+
if (!parsed.success || !parsed.data.data.success) {
|
|
103
|
+
logger.warn({ sessionId }, "Tela agent end session response parse error");
|
|
104
|
+
return { success: false, error: "Invalid end-session response from Tela API" };
|
|
105
|
+
}
|
|
106
|
+
return { success: true };
|
|
107
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { MessageStatus, ReasoningItem } from '#chat-runtime/types/agent';
|
|
2
|
+
import type { FileUploadInput, MessageFile } from '#chat-runtime/types/chat';
|
|
3
|
+
import type { TelaAgentExecutionInput, TelaAgentInputVariable, TelaAgentSession, TelaAgentStep } from '#chat-runtime/types/tela-agent';
|
|
4
|
+
export type TelaAgentMessageUpdate = {
|
|
5
|
+
reasoningData?: ReasoningItem[];
|
|
6
|
+
externalUuid?: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
status?: MessageStatus;
|
|
9
|
+
ttft?: string;
|
|
10
|
+
updatedAt?: Date;
|
|
11
|
+
files?: MessageFile[];
|
|
12
|
+
};
|
|
13
|
+
export type TelaAgentUsageValues = {
|
|
14
|
+
promptTokens: number;
|
|
15
|
+
completionTokens: number;
|
|
16
|
+
totalTokens: number;
|
|
17
|
+
promptCost: string;
|
|
18
|
+
completionCost: string;
|
|
19
|
+
totalCost: string;
|
|
20
|
+
durationMs: number | null;
|
|
21
|
+
turns: number | null;
|
|
22
|
+
model: string | null;
|
|
23
|
+
cacheReadInputTokens: number | null;
|
|
24
|
+
cacheCreationInputTokens: number | null;
|
|
25
|
+
modelUsage: null;
|
|
26
|
+
durationApiMs: null;
|
|
27
|
+
webSearchRequests: null;
|
|
28
|
+
webFetchRequests: null;
|
|
29
|
+
};
|
|
30
|
+
type TelaAgentFileInput = FileUploadInput | MessageFile;
|
|
31
|
+
export declare function toTelaAgentInputSchema(files: TelaAgentFileInput[] | undefined, inputVariables: TelaAgentInputVariable[] | null | undefined): TelaAgentExecutionInput[];
|
|
32
|
+
export declare function toTelaAgentAttachments(files: TelaAgentFileInput[] | undefined): {
|
|
33
|
+
vaultRef: string;
|
|
34
|
+
filename: any;
|
|
35
|
+
}[];
|
|
36
|
+
export declare function getTelaAgentStepSyncId(step: TelaAgentStep): string;
|
|
37
|
+
export declare function normalizeTelaAgentReasoningStep(sessionId: string, step: TelaAgentStep): ReasoningItem | null;
|
|
38
|
+
export declare function getNewTelaAgentSteps(session: TelaAgentSession, lastSyncedStepId: string | null | undefined): TelaAgentStep[];
|
|
39
|
+
export declare function extractTelaAgentAssistantContent(session: TelaAgentSession, steps?: TelaAgentStep[]): string;
|
|
40
|
+
export declare function mapTelaAgentSessionToMessageUpdate(session: TelaAgentSession, options?: {
|
|
41
|
+
lastSyncedStepId?: string | null;
|
|
42
|
+
existingReasoningData?: ReasoningItem[] | null;
|
|
43
|
+
pendingMessageTtft?: string | null;
|
|
44
|
+
userMessageCreatedAt?: Date | null;
|
|
45
|
+
now?: Date;
|
|
46
|
+
}): TelaAgentMessageUpdate;
|
|
47
|
+
export declare function mapTelaAgentUsage(session: TelaAgentSession): TelaAgentUsageValues | null;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { createError } from "h3";
|
|
2
|
+
import { jsonMarkdown } from "#chat-runtime/utils/markdown-nodes";
|
|
3
|
+
const INTERRUPTED_GENERATION_TEXT = "_Gera\xE7\xE3o interrompida pelo usu\xE1rio_";
|
|
4
|
+
const POSTGRES_NUL_BYTE = String.fromCharCode(0);
|
|
5
|
+
const REASONING_TAG_PATTERN = /<reasoning>[\s\S]*?(?:<\/reasoning>|$)/g;
|
|
6
|
+
const ANSWER_TAG_PATTERN = /<answer>([\s\S]*?)(?:<\/answer>|$)/;
|
|
7
|
+
const MARKDOWN_HEADING_PATTERN = /(?:^|\r?\n)#{1,6}\s+\S.*$/gm;
|
|
8
|
+
const BINARY_ARTIFACT_MARKERS = [
|
|
9
|
+
"%PDF-",
|
|
10
|
+
`PK${String.fromCharCode(3)}${String.fromCharCode(4)}`
|
|
11
|
+
];
|
|
12
|
+
function getVaultRef(file) {
|
|
13
|
+
return file.vaultReference?.trim() || (file.url.startsWith("vault://") ? file.url : null);
|
|
14
|
+
}
|
|
15
|
+
function requireVaultRef(file) {
|
|
16
|
+
const vaultRef = getVaultRef(file);
|
|
17
|
+
if (!vaultRef) {
|
|
18
|
+
throw createError({
|
|
19
|
+
statusCode: 400,
|
|
20
|
+
statusMessage: "Tela agent attachments require vault-backed files"
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return vaultRef;
|
|
24
|
+
}
|
|
25
|
+
export function toTelaAgentInputSchema(files, inputVariables) {
|
|
26
|
+
const fileInputVariables = (inputVariables ?? []).filter((input) => input.type === "file" && input.name.trim());
|
|
27
|
+
if (fileInputVariables.length !== 1)
|
|
28
|
+
return [];
|
|
29
|
+
const inputName = fileInputVariables[0].name;
|
|
30
|
+
return (files ?? []).map((file) => {
|
|
31
|
+
return {
|
|
32
|
+
type: "file",
|
|
33
|
+
name: inputName,
|
|
34
|
+
vaultRef: requireVaultRef(file),
|
|
35
|
+
filename: file.name.slice(0, 255)
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export function toTelaAgentAttachments(files) {
|
|
40
|
+
return (files ?? []).map((file) => ({
|
|
41
|
+
vaultRef: requireVaultRef(file),
|
|
42
|
+
filename: file.name.slice(0, 255)
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
export function getTelaAgentStepSyncId(step) {
|
|
46
|
+
return step.eventId ?? step.id;
|
|
47
|
+
}
|
|
48
|
+
function isRecord(value) {
|
|
49
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
50
|
+
}
|
|
51
|
+
function sanitizeText(value) {
|
|
52
|
+
return value.replaceAll(POSTGRES_NUL_BYTE, "");
|
|
53
|
+
}
|
|
54
|
+
function sanitizeUnknown(value) {
|
|
55
|
+
if (typeof value === "string")
|
|
56
|
+
return sanitizeText(value);
|
|
57
|
+
if (Array.isArray(value))
|
|
58
|
+
return value.map((item) => sanitizeUnknown(item));
|
|
59
|
+
if (isRecord(value)) {
|
|
60
|
+
return Object.fromEntries(
|
|
61
|
+
Object.entries(value).map(([key, item]) => [key, sanitizeUnknown(item)])
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
function sanitizeRecord(value) {
|
|
67
|
+
return sanitizeUnknown(value);
|
|
68
|
+
}
|
|
69
|
+
function decodeUriSegment(value) {
|
|
70
|
+
try {
|
|
71
|
+
return decodeURIComponent(value);
|
|
72
|
+
} catch {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function extractMissingVaultFileName(error) {
|
|
77
|
+
const messageMatch = /\bFile\s+(.+?)\s+not found in workspace\b/i.exec(error);
|
|
78
|
+
const messageFileName = messageMatch?.[1]?.trim();
|
|
79
|
+
if (messageFileName)
|
|
80
|
+
return sanitizeText(messageFileName);
|
|
81
|
+
const urlMatch = /\/files\/([^:\s?#]+).*?\b404\s+Not Found\b/is.exec(error);
|
|
82
|
+
const urlFileName = urlMatch?.[1]?.trim();
|
|
83
|
+
if (urlFileName)
|
|
84
|
+
return sanitizeText(decodeUriSegment(urlFileName));
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
function formatTelaAgentFailureMessage(error) {
|
|
88
|
+
const sanitizedError = sanitizeText(error ?? "Agent execution failed");
|
|
89
|
+
const missingVaultFileName = extractMissingVaultFileName(sanitizedError);
|
|
90
|
+
if (missingVaultFileName) {
|
|
91
|
+
return `Erro: O arquivo ${JSON.stringify(missingVaultFileName)} n\xE3o foi encontrado neste workspace. Envie o arquivo novamente ou confira se a refer\xEAncia do Vault pertence ao workspace atual.`;
|
|
92
|
+
}
|
|
93
|
+
return `Error: ${sanitizedError}`;
|
|
94
|
+
}
|
|
95
|
+
function extractTaggedAnswer(value) {
|
|
96
|
+
const match = value.match(ANSWER_TAG_PATTERN);
|
|
97
|
+
return match?.[1]?.trim() || null;
|
|
98
|
+
}
|
|
99
|
+
function stripTaggedReasoning(value) {
|
|
100
|
+
return value.replace(REASONING_TAG_PATTERN, "").trim();
|
|
101
|
+
}
|
|
102
|
+
function findBinaryArtifactStart(value) {
|
|
103
|
+
let index = -1;
|
|
104
|
+
for (const marker of BINARY_ARTIFACT_MARKERS) {
|
|
105
|
+
let searchStart = 0;
|
|
106
|
+
while (searchStart < value.length) {
|
|
107
|
+
const markerIndex = value.indexOf(marker, searchStart);
|
|
108
|
+
if (markerIndex === -1)
|
|
109
|
+
break;
|
|
110
|
+
if (markerIndex === 0 || value[markerIndex - 1] === "\n") {
|
|
111
|
+
index = index === -1 ? markerIndex : Math.min(index, markerIndex);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
searchStart = markerIndex + marker.length;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return index;
|
|
118
|
+
}
|
|
119
|
+
function stripBinaryArtifactContent(value) {
|
|
120
|
+
const index = findBinaryArtifactStart(value);
|
|
121
|
+
if (index === -1)
|
|
122
|
+
return value;
|
|
123
|
+
return value.slice(0, index).trimEnd();
|
|
124
|
+
}
|
|
125
|
+
function looksLikeGeneratedArtifactScript(value) {
|
|
126
|
+
const trimmed = value.trim();
|
|
127
|
+
if (!trimmed)
|
|
128
|
+
return false;
|
|
129
|
+
const hasScriptShape = /^#!\/usr\/bin\/env\s+\S+/m.test(trimmed) || /(?:^|\n)# -\*- coding:/.test(trimmed) || /(?:^|\n)from\s+\S+\s+import\s+\S+/.test(trimmed) || /(?:^|\n)import\s+\S+/.test(trimmed) || /(?:^|\n)(?:class|def)\s+\w+/.test(trimmed);
|
|
130
|
+
const hasGeneratedFileSignal = /vault:\/\/|\.output\s*\(|open\s*\([^)]*,\s*['"][wa]/.test(trimmed);
|
|
131
|
+
return hasScriptShape && hasGeneratedFileSignal;
|
|
132
|
+
}
|
|
133
|
+
function findGeneratedArtifactMarkdownStart(value) {
|
|
134
|
+
for (const match of value.matchAll(MARKDOWN_HEADING_PATTERN)) {
|
|
135
|
+
if (match.index === void 0)
|
|
136
|
+
continue;
|
|
137
|
+
const headingOffset = match[0].match(/^[\r\n]+/)?.[0].length ?? 0;
|
|
138
|
+
const markdownStart = match.index + headingOffset;
|
|
139
|
+
const heading = value.slice(markdownStart, match.index + match[0].length).trim();
|
|
140
|
+
if (/^#{1,6}\s+-\*-/.test(heading))
|
|
141
|
+
continue;
|
|
142
|
+
if (looksLikeGeneratedArtifactScript(value.slice(0, markdownStart)))
|
|
143
|
+
return markdownStart;
|
|
144
|
+
}
|
|
145
|
+
return -1;
|
|
146
|
+
}
|
|
147
|
+
function stripGeneratedArtifactScriptPrefix(value) {
|
|
148
|
+
const markdownStart = findGeneratedArtifactMarkdownStart(value);
|
|
149
|
+
if (markdownStart === -1)
|
|
150
|
+
return value;
|
|
151
|
+
return value.slice(markdownStart).trimStart();
|
|
152
|
+
}
|
|
153
|
+
function normalizeTelaAgentResultContent(value) {
|
|
154
|
+
const sanitized = sanitizeText(value);
|
|
155
|
+
const taggedAnswer = extractTaggedAnswer(sanitized);
|
|
156
|
+
let normalized = taggedAnswer ?? sanitized;
|
|
157
|
+
let changed = taggedAnswer !== null;
|
|
158
|
+
const withoutTaggedReasoning = stripTaggedReasoning(normalized);
|
|
159
|
+
changed ||= withoutTaggedReasoning !== normalized;
|
|
160
|
+
normalized = withoutTaggedReasoning;
|
|
161
|
+
const withoutBinaryArtifacts = stripBinaryArtifactContent(normalized);
|
|
162
|
+
changed ||= withoutBinaryArtifacts !== normalized;
|
|
163
|
+
normalized = withoutBinaryArtifacts;
|
|
164
|
+
const withoutGeneratedScript = stripGeneratedArtifactScriptPrefix(normalized);
|
|
165
|
+
changed ||= withoutGeneratedScript !== normalized;
|
|
166
|
+
normalized = withoutGeneratedScript;
|
|
167
|
+
const withoutTrailingBinaryArtifacts = stripBinaryArtifactContent(normalized);
|
|
168
|
+
changed ||= withoutTrailingBinaryArtifacts !== normalized;
|
|
169
|
+
normalized = withoutTrailingBinaryArtifacts;
|
|
170
|
+
return changed ? normalized.trim() : normalized;
|
|
171
|
+
}
|
|
172
|
+
function getStringProperty(record, key) {
|
|
173
|
+
const value = record[key];
|
|
174
|
+
if (typeof value !== "string")
|
|
175
|
+
return null;
|
|
176
|
+
const sanitized = sanitizeText(value);
|
|
177
|
+
return sanitized.trim() ? sanitized : null;
|
|
178
|
+
}
|
|
179
|
+
function getContentPartText(part) {
|
|
180
|
+
return getStringProperty(part, "text") ?? getStringProperty(part, "thinking");
|
|
181
|
+
}
|
|
182
|
+
function normalizeToolResultContent(content) {
|
|
183
|
+
if (typeof content === "string")
|
|
184
|
+
return sanitizeText(content);
|
|
185
|
+
if (Array.isArray(content))
|
|
186
|
+
return sanitizeUnknown(content);
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
function normalizeMessageContentPart(part) {
|
|
190
|
+
if (!isRecord(part))
|
|
191
|
+
return null;
|
|
192
|
+
const type = getStringProperty(part, "type");
|
|
193
|
+
if (!type)
|
|
194
|
+
return null;
|
|
195
|
+
if (type === "text" || type === "thinking" || type === "reasoning") {
|
|
196
|
+
const text2 = getContentPartText(part);
|
|
197
|
+
return text2 ? { type: "text", text: text2 } : null;
|
|
198
|
+
}
|
|
199
|
+
if (type === "tool_use") {
|
|
200
|
+
const id = getStringProperty(part, "id");
|
|
201
|
+
const name = getStringProperty(part, "name");
|
|
202
|
+
if (!id || !name)
|
|
203
|
+
return null;
|
|
204
|
+
return {
|
|
205
|
+
type: "tool_use",
|
|
206
|
+
id,
|
|
207
|
+
name,
|
|
208
|
+
input: isRecord(part.input) ? sanitizeRecord(part.input) : {}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (type === "tool_result") {
|
|
212
|
+
const toolUseId = getStringProperty(part, "tool_use_id");
|
|
213
|
+
if (!toolUseId)
|
|
214
|
+
return null;
|
|
215
|
+
return {
|
|
216
|
+
type: "tool_result",
|
|
217
|
+
tool_use_id: toolUseId,
|
|
218
|
+
content: normalizeToolResultContent(part.content),
|
|
219
|
+
...typeof part.is_error === "boolean" ? { is_error: part.is_error } : {}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
const text = getContentPartText(part);
|
|
223
|
+
return text ? { type: "text", text } : null;
|
|
224
|
+
}
|
|
225
|
+
function normalizeStepMessageContent(step) {
|
|
226
|
+
const record = step;
|
|
227
|
+
const message = record.message;
|
|
228
|
+
if (isRecord(message) && Array.isArray(message.content)) {
|
|
229
|
+
return message.content.map((part) => normalizeMessageContentPart(part)).filter((part) => Boolean(part));
|
|
230
|
+
}
|
|
231
|
+
const text = getStepText(step);
|
|
232
|
+
return text ? [{ type: "text", text }] : [];
|
|
233
|
+
}
|
|
234
|
+
function getStepText(step) {
|
|
235
|
+
const record = step;
|
|
236
|
+
const directText = getStringProperty(record, "content") ?? getStringProperty(record, "text") ?? getStringProperty(record, "output");
|
|
237
|
+
if (directText)
|
|
238
|
+
return directText;
|
|
239
|
+
const message = record.message;
|
|
240
|
+
if (typeof message === "string" && message.trim())
|
|
241
|
+
return message;
|
|
242
|
+
if (isRecord(message) && Array.isArray(message.content)) {
|
|
243
|
+
for (const part of message.content) {
|
|
244
|
+
if (!isRecord(part))
|
|
245
|
+
continue;
|
|
246
|
+
const text = getContentPartText(part);
|
|
247
|
+
if (text)
|
|
248
|
+
return text;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
export function normalizeTelaAgentReasoningStep(sessionId, step) {
|
|
254
|
+
const content = normalizeStepMessageContent(step);
|
|
255
|
+
if (content.length === 0)
|
|
256
|
+
return null;
|
|
257
|
+
const record = step;
|
|
258
|
+
const message = isRecord(record.message) ? record.message : null;
|
|
259
|
+
const sourceType = getStringProperty(record, "type");
|
|
260
|
+
const type = sourceType === "user" ? "user" : "assistant";
|
|
261
|
+
const role = message ? getStringProperty(message, "role") : null;
|
|
262
|
+
const timestamp = typeof record.timestamp === "number" ? record.timestamp : Date.now();
|
|
263
|
+
const timeElapsed = typeof record.timeElapsed === "number" ? record.timeElapsed : 0;
|
|
264
|
+
const parentToolUseId = getStringProperty(record, "parent_tool_use_id");
|
|
265
|
+
return {
|
|
266
|
+
type,
|
|
267
|
+
uuid: getTelaAgentStepSyncId(step),
|
|
268
|
+
message: {
|
|
269
|
+
role: role ?? (type === "user" ? "user" : "assistant"),
|
|
270
|
+
content
|
|
271
|
+
},
|
|
272
|
+
timestamp,
|
|
273
|
+
session_id: sessionId,
|
|
274
|
+
timeElapsed,
|
|
275
|
+
parent_tool_use_id: parentToolUseId
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
export function getNewTelaAgentSteps(session, lastSyncedStepId) {
|
|
279
|
+
if (!lastSyncedStepId)
|
|
280
|
+
return session.steps;
|
|
281
|
+
const lastSyncedIndex = session.steps.findIndex((step) => getTelaAgentStepSyncId(step) === lastSyncedStepId);
|
|
282
|
+
return session.steps.slice(lastSyncedIndex + 1);
|
|
283
|
+
}
|
|
284
|
+
export function extractTelaAgentAssistantContent(session, steps = session.steps) {
|
|
285
|
+
const structuredContent = session.result?.structuredContent;
|
|
286
|
+
if (structuredContent)
|
|
287
|
+
return sanitizeText(jsonMarkdown(structuredContent));
|
|
288
|
+
if (session.result?.content)
|
|
289
|
+
return normalizeTelaAgentResultContent(session.result.content);
|
|
290
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
291
|
+
const text = getStepText(steps[i]);
|
|
292
|
+
if (text)
|
|
293
|
+
return text;
|
|
294
|
+
}
|
|
295
|
+
return "";
|
|
296
|
+
}
|
|
297
|
+
export function mapTelaAgentSessionToMessageUpdate(session, options = {}) {
|
|
298
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
299
|
+
const newSteps = getNewTelaAgentSteps(session, options.lastSyncedStepId);
|
|
300
|
+
const reasoningData = newSteps.map((step) => normalizeTelaAgentReasoningStep(session.sessionId, step)).filter((step) => Boolean(step));
|
|
301
|
+
const accumulatedReasoningData = [
|
|
302
|
+
...options.existingReasoningData ?? [],
|
|
303
|
+
...reasoningData
|
|
304
|
+
];
|
|
305
|
+
const lastStep = newSteps.at(-1);
|
|
306
|
+
const update = {};
|
|
307
|
+
if (reasoningData.length > 0)
|
|
308
|
+
update.reasoningData = accumulatedReasoningData;
|
|
309
|
+
if (reasoningData.length > 0 && lastStep)
|
|
310
|
+
update.externalUuid = getTelaAgentStepSyncId(lastStep);
|
|
311
|
+
if (!options.pendingMessageTtft && options.userMessageCreatedAt && reasoningData.length > 0) {
|
|
312
|
+
update.ttft = String(now.getTime() - options.userMessageCreatedAt.getTime());
|
|
313
|
+
}
|
|
314
|
+
if (session.status === "completed" || session.status === "waiting_messages") {
|
|
315
|
+
update.content = extractTelaAgentAssistantContent(session);
|
|
316
|
+
update.status = "completed";
|
|
317
|
+
update.updatedAt = now;
|
|
318
|
+
} else if (session.status === "failed") {
|
|
319
|
+
update.content = formatTelaAgentFailureMessage(session.error);
|
|
320
|
+
update.status = "failed";
|
|
321
|
+
update.updatedAt = now;
|
|
322
|
+
} else if (session.status === "cancelled") {
|
|
323
|
+
update.content = INTERRUPTED_GENERATION_TEXT;
|
|
324
|
+
update.status = "completed";
|
|
325
|
+
update.updatedAt = now;
|
|
326
|
+
}
|
|
327
|
+
return update;
|
|
328
|
+
}
|
|
329
|
+
export function mapTelaAgentUsage(session) {
|
|
330
|
+
const usage = session.result?.usage;
|
|
331
|
+
if (!usage)
|
|
332
|
+
return null;
|
|
333
|
+
return {
|
|
334
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
335
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
336
|
+
totalTokens: usage.totalTokens ?? 0,
|
|
337
|
+
promptCost: String(usage.promptCost ?? 0),
|
|
338
|
+
completionCost: String(usage.completionCost ?? 0),
|
|
339
|
+
totalCost: String(usage.totalCost ?? 0),
|
|
340
|
+
durationMs: usage.duration ?? null,
|
|
341
|
+
turns: usage.turns ?? null,
|
|
342
|
+
model: null,
|
|
343
|
+
cacheReadInputTokens: null,
|
|
344
|
+
cacheCreationInputTokens: null,
|
|
345
|
+
modelUsage: null,
|
|
346
|
+
durationApiMs: null,
|
|
347
|
+
webSearchRequests: null,
|
|
348
|
+
webFetchRequests: null
|
|
349
|
+
};
|
|
350
|
+
}
|