@adminforth/agent 1.0.0 → 1.1.1
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/agent/middleware/apiBasedTools.ts +37 -16
- package/agent/toolCallEvents.ts +46 -0
- package/agent/tools/apiTool.ts +4 -39
- package/build.log +2 -2
- package/custom/ConversationArea.vue +4 -7
- package/custom/Message.vue +1 -1
- package/custom/SessionsHistory.vue +3 -3
- package/custom/ToolRenderer.vue +5 -3
- package/custom/ToolsGroup.vue +6 -5
- package/custom/useAgentStore.ts +9 -0
- package/dist/agent/middleware/apiBasedTools.js +30 -12
- package/dist/agent/toolCallEvents.js +39 -1
- package/dist/agent/tools/apiTool.js +4 -37
- package/dist/custom/ConversationArea.vue +4 -7
- package/dist/custom/Message.vue +1 -1
- package/dist/custom/SessionsHistory.vue +3 -3
- package/dist/custom/ToolRenderer.vue +5 -3
- package/dist/custom/ToolsGroup.vue +6 -5
- package/dist/custom/useAgentStore.ts +9 -0
- package/dist/index.js +9 -0
- package/index.ts +8 -0
- package/package.json +1 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { ToolMessage } from "@langchain/core/messages";
|
|
2
2
|
import { createMiddleware } from "langchain";
|
|
3
3
|
import { logger } from "adminforth";
|
|
4
|
-
import type
|
|
4
|
+
import { type ApiBasedTool } from "../../apiBasedTools.js";
|
|
5
|
+
import {
|
|
6
|
+
createToolCallTracker,
|
|
7
|
+
type ToolCallEventSink,
|
|
8
|
+
} from "../toolCallEvents.js";
|
|
5
9
|
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
|
|
6
10
|
import { createApiTool } from "../tools/apiTool.js";
|
|
7
11
|
|
|
@@ -67,30 +71,46 @@ export function createApiBasedToolsMiddleware(
|
|
|
67
71
|
async wrapToolCall(request, handler) {
|
|
68
72
|
const startedAt = Date.now();
|
|
69
73
|
const toolInput = JSON.stringify(request.toolCall.args ?? {});
|
|
74
|
+
const { emitToolCallEvent } = request.runtime.context as {
|
|
75
|
+
emitToolCallEvent: ToolCallEventSink;
|
|
76
|
+
};
|
|
77
|
+
const toolCallTracker = createToolCallTracker({
|
|
78
|
+
emit: emitToolCallEvent,
|
|
79
|
+
toolCallId: request.toolCall.id,
|
|
80
|
+
toolName: request.toolCall.name,
|
|
81
|
+
input: (request.toolCall.args ?? {}) as Record<string, unknown>,
|
|
82
|
+
startedAt,
|
|
83
|
+
});
|
|
84
|
+
toolCallTracker.start();
|
|
70
85
|
logger.info(
|
|
71
86
|
`Invoking tool "${request.toolCall.name}" with input: ${toolInput}`,
|
|
72
87
|
);
|
|
73
88
|
|
|
74
89
|
try {
|
|
75
|
-
|
|
76
|
-
return await handler(request);
|
|
77
|
-
}
|
|
90
|
+
let result;
|
|
78
91
|
|
|
79
|
-
|
|
92
|
+
if (request.tool) {
|
|
93
|
+
result = await handler(request);
|
|
94
|
+
} else {
|
|
95
|
+
const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
|
|
80
96
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
if (enabledApiToolNames.has(request.toolCall.name)) {
|
|
98
|
+
result = await handler({
|
|
99
|
+
...request,
|
|
100
|
+
tool: dynamicTools[request.toolCall.name],
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
result = new ToolMessage({
|
|
104
|
+
content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
|
|
105
|
+
tool_call_id: request.toolCall.id ?? "",
|
|
106
|
+
name: request.toolCall.name,
|
|
107
|
+
status: "error",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
86
110
|
}
|
|
87
111
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
tool_call_id: request.toolCall.id ?? "",
|
|
91
|
-
name: request.toolCall.name,
|
|
92
|
-
status: "error",
|
|
93
|
-
});
|
|
112
|
+
toolCallTracker.finishSuccess(result);
|
|
113
|
+
return result;
|
|
94
114
|
} catch (error) {
|
|
95
115
|
const errorDetails =
|
|
96
116
|
error instanceof Error ? error.stack ?? error.message : String(error);
|
|
@@ -98,6 +118,7 @@ export function createApiBasedToolsMiddleware(
|
|
|
98
118
|
logger.error(
|
|
99
119
|
`Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`,
|
|
100
120
|
);
|
|
121
|
+
toolCallTracker.finishError(error);
|
|
101
122
|
throw error;
|
|
102
123
|
} finally {
|
|
103
124
|
logger.info(
|
package/agent/toolCallEvents.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import YAML from "yaml";
|
|
3
|
+
import { serializeUnknownError } from "../apiBasedTools.js";
|
|
4
|
+
|
|
1
5
|
export type ToolCallEvent =
|
|
2
6
|
| {
|
|
3
7
|
toolCallId: string;
|
|
@@ -15,3 +19,45 @@ export type ToolCallEvent =
|
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
export type ToolCallEventSink = (event: ToolCallEvent) => void;
|
|
22
|
+
|
|
23
|
+
export function createToolCallTracker(params: {
|
|
24
|
+
emit: ToolCallEventSink;
|
|
25
|
+
toolCallId?: string;
|
|
26
|
+
toolName: string;
|
|
27
|
+
input?: Record<string, unknown>;
|
|
28
|
+
startedAt?: number;
|
|
29
|
+
}) {
|
|
30
|
+
const toolCallId = params.toolCallId ?? randomUUID();
|
|
31
|
+
const startedAt = params.startedAt ?? Date.now();
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
start() {
|
|
35
|
+
params.emit({
|
|
36
|
+
toolCallId,
|
|
37
|
+
toolName: params.toolName,
|
|
38
|
+
phase: "start",
|
|
39
|
+
input: YAML.stringify(params.input ?? {}),
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
finishSuccess(output: unknown) {
|
|
43
|
+
params.emit({
|
|
44
|
+
toolCallId,
|
|
45
|
+
toolName: params.toolName,
|
|
46
|
+
phase: "end",
|
|
47
|
+
durationMs: Date.now() - startedAt,
|
|
48
|
+
output: YAML.stringify(output).trimEnd(),
|
|
49
|
+
error: null,
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
finishError(error: unknown) {
|
|
53
|
+
params.emit({
|
|
54
|
+
toolCallId,
|
|
55
|
+
toolName: params.toolName,
|
|
56
|
+
phase: "end",
|
|
57
|
+
durationMs: Date.now() - startedAt,
|
|
58
|
+
output: null,
|
|
59
|
+
error: YAML.stringify(serializeUnknownError(error)).trimEnd(),
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
package/agent/tools/apiTool.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { tool } from "langchain";
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
3
|
-
import YAML from "yaml";
|
|
4
2
|
import type { ApiBasedTool } from "../../apiBasedTools.js";
|
|
5
|
-
import { serializeUnknownError } from "../../apiBasedTools.js";
|
|
6
3
|
|
|
7
4
|
const emptyToolSchema = {
|
|
8
5
|
type: "object",
|
|
@@ -52,43 +49,11 @@ export function createApiTool(toolName: string, apiBasedTool: ApiBasedTool) {
|
|
|
52
49
|
return tool(
|
|
53
50
|
async (input, runtime) => {
|
|
54
51
|
const normalizedInput = (input ?? {}) as Record<string, unknown>;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
toolName,
|
|
60
|
-
phase: "start",
|
|
61
|
-
input: YAML.stringify(normalizedInput),
|
|
52
|
+
return apiBasedTool.call({
|
|
53
|
+
adminUser: runtime.context.adminUser,
|
|
54
|
+
inputs: normalizedInput,
|
|
55
|
+
userTimeZone: runtime.context.userTimeZone,
|
|
62
56
|
});
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const output = await apiBasedTool.call({
|
|
66
|
-
adminUser: runtime.context.adminUser,
|
|
67
|
-
inputs: normalizedInput,
|
|
68
|
-
userTimeZone: runtime.context.userTimeZone,
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
runtime.context.emitToolCallEvent({
|
|
72
|
-
toolCallId,
|
|
73
|
-
toolName,
|
|
74
|
-
phase: "end",
|
|
75
|
-
durationMs: Date.now() - startedAt,
|
|
76
|
-
output,
|
|
77
|
-
error: null,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return output;
|
|
81
|
-
} catch (error) {
|
|
82
|
-
runtime.context.emitToolCallEvent({
|
|
83
|
-
toolCallId,
|
|
84
|
-
toolName,
|
|
85
|
-
phase: "end",
|
|
86
|
-
durationMs: Date.now() - startedAt,
|
|
87
|
-
output: null,
|
|
88
|
-
error: YAML.stringify(serializeUnknownError(error)),
|
|
89
|
-
});
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
57
|
},
|
|
93
58
|
{
|
|
94
59
|
name: toolName,
|
package/build.log
CHANGED
|
@@ -26,5 +26,5 @@ custom/skills/fetch_data/SKILL.md
|
|
|
26
26
|
custom/skills/mutate_data/
|
|
27
27
|
custom/skills/mutate_data/SKILL.md
|
|
28
28
|
|
|
29
|
-
sent 136,
|
|
30
|
-
total size is
|
|
29
|
+
sent 136,840 bytes received 367 bytes 274,414.00 bytes/sec
|
|
30
|
+
total size is 135,334 speedup is 0.99
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
<AutoScrollContainer
|
|
20
|
-
enabled
|
|
20
|
+
:enabled="!showScrollToBottomButton"
|
|
21
21
|
class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
|
|
22
22
|
ref="scrollContainer"
|
|
23
|
+
:threshold="10"
|
|
23
24
|
behavior="smooth"
|
|
24
25
|
>
|
|
25
26
|
|
|
@@ -57,8 +58,8 @@
|
|
|
57
58
|
v-if="props.messages.length === 0"
|
|
58
59
|
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
59
60
|
>
|
|
60
|
-
<p>Start the conversation</p>
|
|
61
|
-
<p class="tracking-normal text-base text">Give any input to begin</p>
|
|
61
|
+
<p>{{ $t('Start the conversation') }}</p>
|
|
62
|
+
<p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
|
|
62
63
|
</div>
|
|
63
64
|
</AutoScrollContainer>
|
|
64
65
|
</template>
|
|
@@ -151,20 +152,16 @@ const groupToolCallParts = (message: IMessage) => {
|
|
|
151
152
|
if(!part?.toolInfo) {
|
|
152
153
|
continue;
|
|
153
154
|
}
|
|
154
|
-
console.log('part', part);
|
|
155
155
|
if (part.toolInfo.toolName === currentToolName) {
|
|
156
|
-
console.log('grouping part with tool name', currentToolName);
|
|
157
156
|
groupedParts[groupedParts.length - 1].groupedTools.push(part);
|
|
158
157
|
continue;
|
|
159
158
|
}
|
|
160
159
|
currentToolName = part.toolInfo.toolName;
|
|
161
|
-
console.log('starting new group with tool name', currentToolName);
|
|
162
160
|
groupedParts.push({
|
|
163
161
|
title: currentToolName,
|
|
164
162
|
groupedTools: [part]
|
|
165
163
|
});
|
|
166
164
|
}
|
|
167
|
-
console.log('groupedParts', groupedParts);
|
|
168
165
|
return groupedParts;
|
|
169
166
|
}
|
|
170
167
|
|
package/custom/Message.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="max-w-[80%] flex px-4
|
|
3
|
+
class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
|
|
4
4
|
@click="handleMarkdownLinkClick"
|
|
5
5
|
:class="props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
|
|
6
6
|
: isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"
|
|
7
7
|
>
|
|
8
8
|
<h3 :class="h3Style">{{ $t('Chat history') }}</h3>
|
|
9
|
-
<Button @click="agentStore.createPreSession()" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
9
|
+
<Button @click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false)" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
10
10
|
<IconPlusOutline class="w-5 h-5" />
|
|
11
11
|
{{ $t('New chat') }}
|
|
12
12
|
</Button>
|
|
13
13
|
<div class="w-full border-b border-gray-200 dark:border-gray-700"/>
|
|
14
14
|
<div class="absolute w-full h-full flex flex-col items-center justify-center bg-gray-100/50 dark:bg-gray-700/50 z-10" v-if="agentStore.isResponseInProgress">
|
|
15
15
|
<Spinner class="w-8 h-8" v-if="agentStore.isResponseInProgress" />
|
|
16
|
-
<p class="mt-2 text-gray-800 dark:text-gray-200">
|
|
16
|
+
<p class="mt-2 text-gray-800 dark:text-gray-200">{{ $t('Generation in progress...') }}</p>
|
|
17
17
|
</div>
|
|
18
18
|
<div v-for="group in groupedSessions" :key="group.dayKey" class="w-full py-2">
|
|
19
19
|
<div class="px-4 pb-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
v-if="!groupedSessions || groupedSessions.length === 0"
|
|
39
39
|
class="w-full h-full flex items-center justify-center text-gray-800 dark:text-gray-200"
|
|
40
40
|
>
|
|
41
|
-
There
|
|
41
|
+
{{ $t('There are no previous chat sessions') }}
|
|
42
42
|
</p>
|
|
43
43
|
</div>
|
|
44
44
|
</template>
|
package/custom/ToolRenderer.vue
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 cursor-pointer text-lightListTableHeadingText dark:text-darkListTableHeadingText hover:opacity-75"
|
|
4
|
+
@click="isInputOutputExpanded = !isInputOutputExpanded"
|
|
5
|
+
>
|
|
3
6
|
<div class="flex items-center gap-3">
|
|
4
7
|
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
|
|
5
8
|
<Spinner v-if="isRunning" class="h-4 w-4" />
|
|
@@ -18,8 +21,7 @@
|
|
|
18
21
|
<IconAngleDownOutline
|
|
19
22
|
v-if="hasToolSections"
|
|
20
23
|
:class="isInputOutputExpanded ? 'rotate-180' : 'rotate-0'"
|
|
21
|
-
class="cursor-pointer transition-transform duration-200 hover:scale-105
|
|
22
|
-
@click="isInputOutputExpanded = !isInputOutputExpanded"
|
|
24
|
+
class="cursor-pointer transition-transform duration-200 hover:scale-105"
|
|
23
25
|
/>
|
|
24
26
|
</div>
|
|
25
27
|
<transition name="expand">
|
package/custom/ToolsGroup.vue
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-for="group in props.toolGroup" :key="group.title">
|
|
3
|
-
<div v-if="group.groupedTools.length > 1" class="
|
|
4
|
-
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5" @click="toggleGroup(group.title)">
|
|
5
|
-
-
|
|
3
|
+
<div v-if="group.groupedTools.length > 1" class="flex flex-col">
|
|
4
|
+
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5 text-lightListTableHeadingText dark:text-darkListTableHeadingText" @click="toggleGroup(group.title)">
|
|
5
|
+
<IconMinusOutline class="w-6 h-6 p-1"/>
|
|
6
|
+
{{ group.title }} {{ 'x' + group.groupedTools.length }}
|
|
6
7
|
<IconAngleDownOutline
|
|
7
8
|
class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
|
|
8
9
|
:class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
</div>
|
|
11
12
|
<transition name="expand">
|
|
12
13
|
<div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
|
|
13
|
-
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" />
|
|
14
|
+
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" class="ml-8"/>
|
|
14
15
|
</div>
|
|
15
16
|
</transition>
|
|
16
17
|
</div>
|
|
@@ -24,7 +25,7 @@ import { Tool } from 'langchain';
|
|
|
24
25
|
import ToolRenderer from './ToolRenderer.vue';
|
|
25
26
|
import type { IPart } from './types';
|
|
26
27
|
import { ref } from 'vue';
|
|
27
|
-
import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
|
+
import { IconAngleDownOutline, IconMinusOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
29
|
|
|
29
30
|
const props = defineProps<{
|
|
30
31
|
toolGroup: {
|
package/custom/useAgentStore.ts
CHANGED
|
@@ -120,6 +120,10 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
120
120
|
});
|
|
121
121
|
const blockCloseOfChat = ref(false);
|
|
122
122
|
|
|
123
|
+
function sortSessionsListByTimestamp(sessionsList: ISessionsListItem[]) {
|
|
124
|
+
return [...sessionsList].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
async function sendMessage() {
|
|
124
128
|
const message = trimmedUserMessage.value;
|
|
125
129
|
if (!message || isResponseInProgress.value) {
|
|
@@ -128,6 +132,11 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
128
132
|
if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
|
|
129
133
|
await createNewSession(message);
|
|
130
134
|
}
|
|
135
|
+
currentSession.value.timestamp = new Date().toISOString();
|
|
136
|
+
sessionList.value = sortSessionsListByTimestamp(sessionList.value.map((s: ISessionsListItem) => s.sessionId === currentSession.value?.sessionId ? {
|
|
137
|
+
...s,
|
|
138
|
+
timestamp: currentSession.value?.timestamp || s.timestamp,
|
|
139
|
+
} : s));
|
|
131
140
|
lastMessage.value = message;
|
|
132
141
|
currentChat.value?.sendMessage({
|
|
133
142
|
text: message,
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { ToolMessage } from "@langchain/core/messages";
|
|
11
11
|
import { createMiddleware } from "langchain";
|
|
12
12
|
import { logger } from "adminforth";
|
|
13
|
+
import { createToolCallTracker, } from "../toolCallEvents.js";
|
|
13
14
|
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
|
|
14
15
|
import { createApiTool } from "../tools/apiTool.js";
|
|
15
16
|
function getEnabledApiToolNames(messages) {
|
|
@@ -58,28 +59,45 @@ export function createApiBasedToolsMiddleware(apiBasedTools) {
|
|
|
58
59
|
},
|
|
59
60
|
wrapToolCall(request, handler) {
|
|
60
61
|
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
-
var _a, _b, _c;
|
|
62
|
+
var _a, _b, _c, _d;
|
|
62
63
|
const startedAt = Date.now();
|
|
63
64
|
const toolInput = JSON.stringify((_a = request.toolCall.args) !== null && _a !== void 0 ? _a : {});
|
|
65
|
+
const { emitToolCallEvent } = request.runtime.context;
|
|
66
|
+
const toolCallTracker = createToolCallTracker({
|
|
67
|
+
emit: emitToolCallEvent,
|
|
68
|
+
toolCallId: request.toolCall.id,
|
|
69
|
+
toolName: request.toolCall.name,
|
|
70
|
+
input: ((_b = request.toolCall.args) !== null && _b !== void 0 ? _b : {}),
|
|
71
|
+
startedAt,
|
|
72
|
+
});
|
|
73
|
+
toolCallTracker.start();
|
|
64
74
|
logger.info(`Invoking tool "${request.toolCall.name}" with input: ${toolInput}`);
|
|
65
75
|
try {
|
|
76
|
+
let result;
|
|
66
77
|
if (request.tool) {
|
|
67
|
-
|
|
78
|
+
result = yield handler(request);
|
|
68
79
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
else {
|
|
81
|
+
const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
|
|
82
|
+
if (enabledApiToolNames.has(request.toolCall.name)) {
|
|
83
|
+
result = yield handler(Object.assign(Object.assign({}, request), { tool: dynamicTools[request.toolCall.name] }));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
result = new ToolMessage({
|
|
87
|
+
content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
|
|
88
|
+
tool_call_id: (_c = request.toolCall.id) !== null && _c !== void 0 ? _c : "",
|
|
89
|
+
name: request.toolCall.name,
|
|
90
|
+
status: "error",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
72
93
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
tool_call_id: (_b = request.toolCall.id) !== null && _b !== void 0 ? _b : "",
|
|
76
|
-
name: request.toolCall.name,
|
|
77
|
-
status: "error",
|
|
78
|
-
});
|
|
94
|
+
toolCallTracker.finishSuccess(result);
|
|
95
|
+
return result;
|
|
79
96
|
}
|
|
80
97
|
catch (error) {
|
|
81
|
-
const errorDetails = error instanceof Error ? (
|
|
98
|
+
const errorDetails = error instanceof Error ? (_d = error.stack) !== null && _d !== void 0 ? _d : error.message : String(error);
|
|
82
99
|
logger.error(`Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`);
|
|
100
|
+
toolCallTracker.finishError(error);
|
|
83
101
|
throw error;
|
|
84
102
|
}
|
|
85
103
|
finally {
|
|
@@ -1 +1,39 @@
|
|
|
1
|
-
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import YAML from "yaml";
|
|
3
|
+
import { serializeUnknownError } from "../apiBasedTools.js";
|
|
4
|
+
export function createToolCallTracker(params) {
|
|
5
|
+
var _a, _b;
|
|
6
|
+
const toolCallId = (_a = params.toolCallId) !== null && _a !== void 0 ? _a : randomUUID();
|
|
7
|
+
const startedAt = (_b = params.startedAt) !== null && _b !== void 0 ? _b : Date.now();
|
|
8
|
+
return {
|
|
9
|
+
start() {
|
|
10
|
+
var _a;
|
|
11
|
+
params.emit({
|
|
12
|
+
toolCallId,
|
|
13
|
+
toolName: params.toolName,
|
|
14
|
+
phase: "start",
|
|
15
|
+
input: YAML.stringify((_a = params.input) !== null && _a !== void 0 ? _a : {}),
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
finishSuccess(output) {
|
|
19
|
+
params.emit({
|
|
20
|
+
toolCallId,
|
|
21
|
+
toolName: params.toolName,
|
|
22
|
+
phase: "end",
|
|
23
|
+
durationMs: Date.now() - startedAt,
|
|
24
|
+
output: YAML.stringify(output).trimEnd(),
|
|
25
|
+
error: null,
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
finishError(error) {
|
|
29
|
+
params.emit({
|
|
30
|
+
toolCallId,
|
|
31
|
+
toolName: params.toolName,
|
|
32
|
+
phase: "end",
|
|
33
|
+
durationMs: Date.now() - startedAt,
|
|
34
|
+
output: null,
|
|
35
|
+
error: YAML.stringify(serializeUnknownError(error)).trimEnd(),
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -8,9 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { tool } from "langchain";
|
|
11
|
-
import { randomUUID } from "crypto";
|
|
12
|
-
import YAML from "yaml";
|
|
13
|
-
import { serializeUnknownError } from "../../apiBasedTools.js";
|
|
14
11
|
const emptyToolSchema = {
|
|
15
12
|
type: "object",
|
|
16
13
|
properties: {},
|
|
@@ -50,41 +47,11 @@ export function createApiTool(toolName, apiBasedTool) {
|
|
|
50
47
|
var _a;
|
|
51
48
|
return tool((input, runtime) => __awaiter(this, void 0, void 0, function* () {
|
|
52
49
|
const normalizedInput = (input !== null && input !== void 0 ? input : {});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
toolName,
|
|
58
|
-
phase: "start",
|
|
59
|
-
input: YAML.stringify(normalizedInput),
|
|
50
|
+
return apiBasedTool.call({
|
|
51
|
+
adminUser: runtime.context.adminUser,
|
|
52
|
+
inputs: normalizedInput,
|
|
53
|
+
userTimeZone: runtime.context.userTimeZone,
|
|
60
54
|
});
|
|
61
|
-
try {
|
|
62
|
-
const output = yield apiBasedTool.call({
|
|
63
|
-
adminUser: runtime.context.adminUser,
|
|
64
|
-
inputs: normalizedInput,
|
|
65
|
-
userTimeZone: runtime.context.userTimeZone,
|
|
66
|
-
});
|
|
67
|
-
runtime.context.emitToolCallEvent({
|
|
68
|
-
toolCallId,
|
|
69
|
-
toolName,
|
|
70
|
-
phase: "end",
|
|
71
|
-
durationMs: Date.now() - startedAt,
|
|
72
|
-
output,
|
|
73
|
-
error: null,
|
|
74
|
-
});
|
|
75
|
-
return output;
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
runtime.context.emitToolCallEvent({
|
|
79
|
-
toolCallId,
|
|
80
|
-
toolName,
|
|
81
|
-
phase: "end",
|
|
82
|
-
durationMs: Date.now() - startedAt,
|
|
83
|
-
output: null,
|
|
84
|
-
error: YAML.stringify(serializeUnknownError(error)),
|
|
85
|
-
});
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
88
55
|
}), {
|
|
89
56
|
name: toolName,
|
|
90
57
|
description: (_a = apiBasedTool.description) !== null && _a !== void 0 ? _a : `${toolName} tool`,
|
|
@@ -17,9 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
<AutoScrollContainer
|
|
20
|
-
enabled
|
|
20
|
+
:enabled="!showScrollToBottomButton"
|
|
21
21
|
class="flex flex-col overflow-y-auto border-t border-gray-200 dark:border-gray-700"
|
|
22
22
|
ref="scrollContainer"
|
|
23
|
+
:threshold="10"
|
|
23
24
|
behavior="smooth"
|
|
24
25
|
>
|
|
25
26
|
|
|
@@ -57,8 +58,8 @@
|
|
|
57
58
|
v-if="props.messages.length === 0"
|
|
58
59
|
class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium"
|
|
59
60
|
>
|
|
60
|
-
<p>Start the conversation</p>
|
|
61
|
-
<p class="tracking-normal text-base text">Give any input to begin</p>
|
|
61
|
+
<p>{{ $t('Start the conversation') }}</p>
|
|
62
|
+
<p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
|
|
62
63
|
</div>
|
|
63
64
|
</AutoScrollContainer>
|
|
64
65
|
</template>
|
|
@@ -151,20 +152,16 @@ const groupToolCallParts = (message: IMessage) => {
|
|
|
151
152
|
if(!part?.toolInfo) {
|
|
152
153
|
continue;
|
|
153
154
|
}
|
|
154
|
-
console.log('part', part);
|
|
155
155
|
if (part.toolInfo.toolName === currentToolName) {
|
|
156
|
-
console.log('grouping part with tool name', currentToolName);
|
|
157
156
|
groupedParts[groupedParts.length - 1].groupedTools.push(part);
|
|
158
157
|
continue;
|
|
159
158
|
}
|
|
160
159
|
currentToolName = part.toolInfo.toolName;
|
|
161
|
-
console.log('starting new group with tool name', currentToolName);
|
|
162
160
|
groupedParts.push({
|
|
163
161
|
title: currentToolName,
|
|
164
162
|
groupedTools: [part]
|
|
165
163
|
});
|
|
166
164
|
}
|
|
167
|
-
console.log('groupedParts', groupedParts);
|
|
168
165
|
return groupedParts;
|
|
169
166
|
}
|
|
170
167
|
|
package/dist/custom/Message.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="max-w-[80%] flex px-4
|
|
3
|
+
class="max-w-[80%] flex px-4 m-2 rounded-xl border border-gray-200 dark:border-gray-700"
|
|
4
4
|
@click="handleMarkdownLinkClick"
|
|
5
5
|
:class="props.role === 'user' ? 'bg-lightListTableHeading dark:bg-darkListTableHeading self-end'
|
|
6
6
|
: isTypeReasoning || isTypeToolCall ? 'bg-transparent border-none self-start'
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"
|
|
7
7
|
>
|
|
8
8
|
<h3 :class="h3Style">{{ $t('Chat history') }}</h3>
|
|
9
|
-
<Button @click="agentStore.createPreSession()" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
9
|
+
<Button @click="agentStore.createPreSession(); agentStore.setSessionHistoryOpen(false)" :disabled="agentStore.isResponseInProgress" class="w-[360px] mx-4 my-2 mb-4 rounded-3xl text-gray-800 dark:text-gray-200">
|
|
10
10
|
<IconPlusOutline class="w-5 h-5" />
|
|
11
11
|
{{ $t('New chat') }}
|
|
12
12
|
</Button>
|
|
13
13
|
<div class="w-full border-b border-gray-200 dark:border-gray-700"/>
|
|
14
14
|
<div class="absolute w-full h-full flex flex-col items-center justify-center bg-gray-100/50 dark:bg-gray-700/50 z-10" v-if="agentStore.isResponseInProgress">
|
|
15
15
|
<Spinner class="w-8 h-8" v-if="agentStore.isResponseInProgress" />
|
|
16
|
-
<p class="mt-2 text-gray-800 dark:text-gray-200">
|
|
16
|
+
<p class="mt-2 text-gray-800 dark:text-gray-200">{{ $t('Generation in progress...') }}</p>
|
|
17
17
|
</div>
|
|
18
18
|
<div v-for="group in groupedSessions" :key="group.dayKey" class="w-full py-2">
|
|
19
19
|
<div class="px-4 pb-2 text-xs font-semibold uppercase tracking-[0.2em] text-gray-500 dark:text-gray-400">
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
v-if="!groupedSessions || groupedSessions.length === 0"
|
|
39
39
|
class="w-full h-full flex items-center justify-center text-gray-800 dark:text-gray-200"
|
|
40
40
|
>
|
|
41
|
-
There
|
|
41
|
+
{{ $t('There are no previous chat sessions') }}
|
|
42
42
|
</p>
|
|
43
43
|
</div>
|
|
44
44
|
</template>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="inline-flex m-2 max-w-[80%] flex-col gap-3 rounded-xl p-2 cursor-pointer text-lightListTableHeadingText dark:text-darkListTableHeadingText hover:opacity-75"
|
|
4
|
+
@click="isInputOutputExpanded = !isInputOutputExpanded"
|
|
5
|
+
>
|
|
3
6
|
<div class="flex items-center gap-3">
|
|
4
7
|
<div class="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-white/70 dark:bg-blue-700/20">
|
|
5
8
|
<Spinner v-if="isRunning" class="h-4 w-4" />
|
|
@@ -18,8 +21,7 @@
|
|
|
18
21
|
<IconAngleDownOutline
|
|
19
22
|
v-if="hasToolSections"
|
|
20
23
|
:class="isInputOutputExpanded ? 'rotate-180' : 'rotate-0'"
|
|
21
|
-
class="cursor-pointer transition-transform duration-200 hover:scale-105
|
|
22
|
-
@click="isInputOutputExpanded = !isInputOutputExpanded"
|
|
24
|
+
class="cursor-pointer transition-transform duration-200 hover:scale-105"
|
|
23
25
|
/>
|
|
24
26
|
</div>
|
|
25
27
|
<transition name="expand">
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<template v-for="group in props.toolGroup" :key="group.title">
|
|
3
|
-
<div v-if="group.groupedTools.length > 1" class="
|
|
4
|
-
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5" @click="toggleGroup(group.title)">
|
|
5
|
-
-
|
|
3
|
+
<div v-if="group.groupedTools.length > 1" class="flex flex-col">
|
|
4
|
+
<div class="flex items-center gap-2 p-2 m-2 cursor-pointer hover:opacity-75 break-all font-mono text-sm leading-5 text-lightListTableHeadingText dark:text-darkListTableHeadingText" @click="toggleGroup(group.title)">
|
|
5
|
+
<IconMinusOutline class="w-6 h-6 p-1"/>
|
|
6
|
+
{{ group.title }} {{ 'x' + group.groupedTools.length }}
|
|
6
7
|
<IconAngleDownOutline
|
|
7
8
|
class="transition-transform duration-200 hover:scale-105 hover:opacity-75"
|
|
8
9
|
:class="expandedGroups.includes(group.title) ? 'rotate-180' : 'rotate-0'"
|
|
@@ -10,7 +11,7 @@
|
|
|
10
11
|
</div>
|
|
11
12
|
<transition name="expand">
|
|
12
13
|
<div v-show="expandedGroups.includes(group.title)" class="flex flex-col">
|
|
13
|
-
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" />
|
|
14
|
+
<ToolRenderer v-for="part in group.groupedTools" :key="part.text + part.type" :data="part" class="ml-8"/>
|
|
14
15
|
</div>
|
|
15
16
|
</transition>
|
|
16
17
|
</div>
|
|
@@ -24,7 +25,7 @@ import { Tool } from 'langchain';
|
|
|
24
25
|
import ToolRenderer from './ToolRenderer.vue';
|
|
25
26
|
import type { IPart } from './types';
|
|
26
27
|
import { ref } from 'vue';
|
|
27
|
-
import { IconAngleDownOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
|
+
import { IconAngleDownOutline, IconMinusOutline } from '@iconify-prerendered/vue-flowbite';
|
|
28
29
|
|
|
29
30
|
const props = defineProps<{
|
|
30
31
|
toolGroup: {
|
|
@@ -120,6 +120,10 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
120
120
|
});
|
|
121
121
|
const blockCloseOfChat = ref(false);
|
|
122
122
|
|
|
123
|
+
function sortSessionsListByTimestamp(sessionsList: ISessionsListItem[]) {
|
|
124
|
+
return [...sessionsList].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
async function sendMessage() {
|
|
124
128
|
const message = trimmedUserMessage.value;
|
|
125
129
|
if (!message || isResponseInProgress.value) {
|
|
@@ -128,6 +132,11 @@ export const useAgentStore = defineStore('agent', () => {
|
|
|
128
132
|
if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
|
|
129
133
|
await createNewSession(message);
|
|
130
134
|
}
|
|
135
|
+
currentSession.value.timestamp = new Date().toISOString();
|
|
136
|
+
sessionList.value = sortSessionsListByTimestamp(sessionList.value.map((s: ISessionsListItem) => s.sessionId === currentSession.value?.sessionId ? {
|
|
137
|
+
...s,
|
|
138
|
+
timestamp: currentSession.value?.timestamp || s.timestamp,
|
|
139
|
+
} : s));
|
|
131
140
|
lastMessage.value = message;
|
|
132
141
|
currentChat.value?.sendMessage({
|
|
133
142
|
text: message,
|
package/dist/index.js
CHANGED
|
@@ -71,6 +71,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
71
71
|
return { ok: true };
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
updateSessionDate(sessionId) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
yield this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
|
|
77
|
+
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
return { ok: true };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
74
82
|
getSessionTurns(sessionId) {
|
|
75
83
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
84
|
const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
|
|
@@ -134,6 +142,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
134
142
|
const userTimeZone = (_e = body.timeZone) !== null && _e !== void 0 ? _e : 'UTC';
|
|
135
143
|
const sessionId = body.sessionId || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.pk) || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.username) || 'default';
|
|
136
144
|
const turnId = yield this.createNewTurn(sessionId, prompt);
|
|
145
|
+
yield this.updateSessionDate(sessionId);
|
|
137
146
|
let fullResponse = "";
|
|
138
147
|
let isStreamClosed = false;
|
|
139
148
|
const sequenceDebugCollector = createSequenceDebugCollector();
|
package/index.ts
CHANGED
|
@@ -87,6 +87,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
87
87
|
return {ok: true};
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
private async updateSessionDate(sessionId: string) {
|
|
91
|
+
await this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
|
|
92
|
+
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
93
|
+
});
|
|
94
|
+
return {ok: true};
|
|
95
|
+
}
|
|
96
|
+
|
|
90
97
|
private async getSessionTurns(sessionId: string) {
|
|
91
98
|
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
92
99
|
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
@@ -150,6 +157,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
150
157
|
const userTimeZone = (body.timeZone as string | undefined) ?? 'UTC';
|
|
151
158
|
const sessionId = body.sessionId || adminUser?.pk || adminUser?.username || 'default';
|
|
152
159
|
const turnId = await this.createNewTurn(sessionId, prompt);
|
|
160
|
+
await this.updateSessionDate(sessionId);
|
|
153
161
|
let fullResponse = "";
|
|
154
162
|
let isStreamClosed = false;
|
|
155
163
|
const sequenceDebugCollector = createSequenceDebugCollector();
|