@adminforth/agent 1.0.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/.woodpecker/buildRelease.sh +13 -0
- package/.woodpecker/buildSlackNotify.sh +46 -0
- package/.woodpecker/release.yml +57 -0
- package/agent/middleware/apiBasedTools.ts +109 -0
- package/agent/middleware/sequenceDebug.ts +302 -0
- package/agent/simpleAgent.ts +291 -0
- package/agent/skills/registry.ts +135 -0
- package/agent/systemPrompt.ts +69 -0
- package/agent/toolCallEvents.ts +17 -0
- package/agent/tools/apiTool.ts +99 -0
- package/agent/tools/fetchSkill.ts +58 -0
- package/agent/tools/fetchToolSchema.ts +50 -0
- package/agent/tools/index.ts +26 -0
- package/apiBasedTools.ts +625 -0
- package/build.log +30 -0
- package/custom/ChatSurface.vue +184 -0
- package/custom/ConversationArea.vue +175 -0
- package/custom/Message.vue +206 -0
- package/custom/SessionsHistory.vue +93 -0
- package/custom/ToolRenderer.vue +131 -0
- package/custom/ToolsGroup.vue +67 -0
- package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/custom/package.json +26 -0
- package/custom/pnpm-lock.yaml +1467 -0
- package/custom/skills/fetch_data/SKILL.md +15 -0
- package/custom/skills/mutate_data/SKILL.md +108 -0
- package/custom/tsconfig.json +16 -0
- package/custom/types.ts +34 -0
- package/custom/useAgentStore.ts +349 -0
- package/dist/agent/middleware/apiBasedTools.js +91 -0
- package/dist/agent/middleware/sequenceDebug.js +210 -0
- package/dist/agent/simpleAgent.js +173 -0
- package/dist/agent/skills/registry.js +108 -0
- package/dist/agent/systemPrompt.js +64 -0
- package/dist/agent/toolCallEvents.js +1 -0
- package/dist/agent/tools/apiTool.js +93 -0
- package/dist/agent/tools/fetchSkill.js +51 -0
- package/dist/agent/tools/fetchToolSchema.js +36 -0
- package/dist/agent/tools/index.js +28 -0
- package/dist/apiBasedTools.js +412 -0
- package/dist/custom/ChatSurface.vue +184 -0
- package/dist/custom/ConversationArea.vue +175 -0
- package/dist/custom/Message.vue +206 -0
- package/dist/custom/SessionsHistory.vue +93 -0
- package/dist/custom/ToolRenderer.vue +131 -0
- package/dist/custom/ToolsGroup.vue +67 -0
- package/dist/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +301 -0
- package/dist/custom/incremark_code_renderers/incremarkCodeHighlight.ts +285 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +653 -0
- package/dist/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +118 -0
- package/dist/custom/package.json +26 -0
- package/dist/custom/pnpm-lock.yaml +1467 -0
- package/dist/custom/skills/fetch_data/SKILL.md +15 -0
- package/dist/custom/skills/mutate_data/SKILL.md +108 -0
- package/dist/custom/tsconfig.json +16 -0
- package/dist/custom/types.ts +34 -0
- package/dist/custom/useAgentStore.ts +349 -0
- package/dist/index.js +415 -0
- package/dist/types.js +1 -0
- package/index.ts +457 -0
- package/package.json +58 -0
- package/tsconfig.json +13 -0
- package/types.ts +45 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: fetch_data
|
|
2
|
+
description: Fetch one or more records. Use to find records entities. To use this skill you first need to call get_resource tool to know resource column names. This tool returns only atomic record(s) not capable with any aggregations.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Involved tools
|
|
6
|
+
|
|
7
|
+
You can use tool `get_resource_data` it returns one or more records and is capable of using filters
|
|
8
|
+
|
|
9
|
+
# Instructions
|
|
10
|
+
|
|
11
|
+
To find specific data record you should use filters. ILIKE filters are preferred when we are unsure the input is clear.You can combine filters with OR if you want to search multiple fields.If user queries one record you should try to fetch up to 5 records and if more then one returned return output them all to user and ask to select one. When you communicate about record with user, show its several most important fields.
|
|
12
|
+
|
|
13
|
+
For long texts show only several first words and add "..." at the end (only if user did not request this field specifically).
|
|
14
|
+
|
|
15
|
+
Also when you communicate with user about record, add related link to this record. For example /{BASE_URL}/resource/{resourceId}/show/{primary key}. Use _label from `get_resource_data` as anchor text for link (use markdown link).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
name: mutate_data
|
|
2
|
+
description: Create/update/delete some record of resource or call actions on one or multiple records
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# General rules
|
|
6
|
+
|
|
7
|
+
- if there is a dedicated action for some routine (result of `get_resource` tool call, field actions), prefer this to manual updating of records, for example, if you want to approve some comment, prefer calling `approve` action instead of updating `approved` field of comment record (because in action there might be some additional logic like sending notification to user, updating some counters and so on)
|
|
8
|
+
|
|
9
|
+
## Confirmation
|
|
10
|
+
|
|
11
|
+
Before performing any state mutation including action calls edit/delete please fetch record which is going to be edited/deleted and show user record in format field → value (show several most important fields which can help user to understand what exactly record he is going to edit or delete).
|
|
12
|
+
|
|
13
|
+
For field values with long texts show only several first words and add "..." at the end.
|
|
14
|
+
Also please add related link to record with will be changed. For example /{BASE_URL}/resource/{resourceId}/show/{primary key}. Use _label from `get_resource_data` as anchor text for link (use markdown link).
|
|
15
|
+
And in the same message ask user for final confirmation.
|
|
16
|
+
|
|
17
|
+
When creating new record, show user all data which you gona create and in same message ask for confirmation.
|
|
18
|
+
|
|
19
|
+
Accept any positive confirmation from user like "yes", "sure", "+", anything non-negative call to action, can be considered as confirmation.
|
|
20
|
+
|
|
21
|
+
# Calling actions
|
|
22
|
+
|
|
23
|
+
To call action on some record you can use `start_custom_action` tool, or `start_custom_bulk_action` if you need to perform action on several records at once.
|
|
24
|
+
|
|
25
|
+
Before calling any of this action you should understand whether this action is allowed. User result of `get_resource` tool call and check `action.allowed` - if this attribute is true or is not exists, assume action is allowed. If this attribute is false, action is not allowed, you should warn user that this action is not allowed for him.
|
|
26
|
+
|
|
27
|
+
### Example
|
|
28
|
+
|
|
29
|
+
If you want to block some user you can confirm that this action by saying:
|
|
30
|
+
|
|
31
|
+
```I am going to block user:
|
|
32
|
+
* Username: john_doe
|
|
33
|
+
* Email: john_doe@example.com
|
|
34
|
+
* IP Country: USA
|
|
35
|
+
* Currently blocked: No // show this field only if it exists in user record
|
|
36
|
+
|
|
37
|
+
View [John Doe](/resource/users/show/123)
|
|
38
|
+
Are you sure?
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Updating
|
|
42
|
+
|
|
43
|
+
You can use tool `update_record` tool it updates fields of record. To update `allowedActions.edit` should be set to true and
|
|
44
|
+
`updated` column `showIn.edit` should be true at the same time. If one of this condition is not met, explain to user that is
|
|
45
|
+
not allowed to edit
|
|
46
|
+
|
|
47
|
+
In addition to instructions above show user the table of edits (old value/new value)
|
|
48
|
+
|
|
49
|
+
### Examples
|
|
50
|
+
|
|
51
|
+
For example if you gonna modify user record, in confirmation please share full user info (not only username but also email, ip country - anything which help adminto check that that is correct user). Message could look like this:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
I am going to update user:
|
|
55
|
+
* Username: john_doe
|
|
56
|
+
* Email: john_doe@example.com
|
|
57
|
+
* IP Country: USA
|
|
58
|
+
I am going to change email from john_doe@example.com to new_email@example.com
|
|
59
|
+
|
|
60
|
+
View [John Doe](/admin/resource/users/show/123)
|
|
61
|
+
|
|
62
|
+
Are you sure?
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Deleting
|
|
67
|
+
|
|
68
|
+
To delete some record you can use `delete_record` tool. To delete record `allowedActions.delete` should be set to true.
|
|
69
|
+
|
|
70
|
+
### Example
|
|
71
|
+
|
|
72
|
+
If you gonna delete user record, in confirmation please share full user info (not only username but also email, ip country - anything which help adminto check that that is correct user). Message could look like this:
|
|
73
|
+
|
|
74
|
+
```I am going to delete user:
|
|
75
|
+
* Username: john_doe
|
|
76
|
+
* Email: john_doe@example.com
|
|
77
|
+
* Signed up: 2024 Jan 1
|
|
78
|
+
* IP Country: USA
|
|
79
|
+
|
|
80
|
+
View [John Doe](/admin/resource/users/show/123)
|
|
81
|
+
|
|
82
|
+
Are you sure?
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Creating
|
|
86
|
+
|
|
87
|
+
To create new record you can use tool `create_record`. To create record `allowedActions.create` should be set to true.
|
|
88
|
+
|
|
89
|
+
When calling `create_record` tool pass only columns which have `showIn.create` set to true and `backendOnly` is not set to true.
|
|
90
|
+
|
|
91
|
+
For decimal fields please use string values with dot as decimal separator.
|
|
92
|
+
|
|
93
|
+
After creation of new record also show user a link to this record. If several records record were created, show links to all of them in list.
|
|
94
|
+
|
|
95
|
+
Omit any pictures or file paths, you are not capable of doing it. If they are not required all is good, if they are required, explain to user that you are not able to create record because of this reason.
|
|
96
|
+
|
|
97
|
+
### Example
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
I am going to create user:
|
|
102
|
+
* Username: john_doe
|
|
103
|
+
* Email: john_doe@example.com
|
|
104
|
+
|
|
105
|
+
View [John Doe](/admin/resource/users/show/421) # 421 is id of new created record
|
|
106
|
+
|
|
107
|
+
Are you sure?
|
|
108
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"baseUrl": ".", // This should point to your project root
|
|
4
|
+
"paths": {
|
|
5
|
+
"@/*": [
|
|
6
|
+
"../node_modules/adminforth/dist/spa/src/*"
|
|
7
|
+
],
|
|
8
|
+
"*": [
|
|
9
|
+
"../node_modules/adminforth/dist/spa/node_modules/*"
|
|
10
|
+
],
|
|
11
|
+
"@@/*": [
|
|
12
|
+
"."
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
package/custom/types.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface IPartData {
|
|
2
|
+
toolCallId: string;
|
|
3
|
+
toolName: string;
|
|
4
|
+
phase: 'start' | 'end';
|
|
5
|
+
input?: any;
|
|
6
|
+
output?: any;
|
|
7
|
+
durationMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface IPart {
|
|
10
|
+
type: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
state?: 'started' | 'thinking' | 'processing' | 'streaming' | 'done';
|
|
13
|
+
data?: IPartData;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IMessage {
|
|
17
|
+
id: string;
|
|
18
|
+
role: 'user' | 'assistant';
|
|
19
|
+
metadata?: any,
|
|
20
|
+
parts: IPart[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface IAgentSession {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
title: string;
|
|
26
|
+
timestamp: string;
|
|
27
|
+
messages: IMessage[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ISessionsListItem {
|
|
31
|
+
sessionId: string;
|
|
32
|
+
title: string;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { defineStore } from 'pinia';
|
|
2
|
+
import { IAgentSession, ISessionsListItem, IMessage } from './types';
|
|
3
|
+
import { ref, nextTick, computed, watch, onMounted, shallowRef } from 'vue';
|
|
4
|
+
import { callAdminForthApi } from '@/utils';
|
|
5
|
+
import { useAdminforth } from '@/adminforth';
|
|
6
|
+
import { Chat } from "@ai-sdk/vue";
|
|
7
|
+
import { DefaultChatTransport } from 'ai';
|
|
8
|
+
import { useCoreStore } from '@/stores/core';
|
|
9
|
+
|
|
10
|
+
export const useAgentStore = defineStore('agent', () => {
|
|
11
|
+
const activeSessionId = ref<string | null>(null);
|
|
12
|
+
const currentSession = ref<IAgentSession | null>(null);
|
|
13
|
+
const sessionList = ref<ISessionsListItem[]>([]);
|
|
14
|
+
const sessions = ref<Record<string, IAgentSession>>({});
|
|
15
|
+
const adminforth = useAdminforth();
|
|
16
|
+
const isChatOpen = ref(false);
|
|
17
|
+
const isSessionHistoryOpen = ref(false);
|
|
18
|
+
const textInput = ref<HTMLInputElement | null>(null);
|
|
19
|
+
const userMessageInput = ref();
|
|
20
|
+
const trimmedUserMessage = computed(() => userMessageInput.value ? userMessageInput.value.trim() : '');
|
|
21
|
+
const lastMessage = ref('');
|
|
22
|
+
const isTeleportedToBody = ref(false);
|
|
23
|
+
const setIsTeleportedToBody = (isTeleported: boolean) => {
|
|
24
|
+
isTeleportedToBody.value = isTeleported;
|
|
25
|
+
}
|
|
26
|
+
const coreStore = useCoreStore();
|
|
27
|
+
const appRoot = ref<HTMLElement | null>(null);
|
|
28
|
+
const header = ref<HTMLElement | null>(null);
|
|
29
|
+
const chatWidth = ref(600);
|
|
30
|
+
function setLocalStorageItem(key: string, value: string) {
|
|
31
|
+
window.localStorage.setItem(`${coreStore.config.brandName || 'adminforth'}-${key}`, value);
|
|
32
|
+
}
|
|
33
|
+
function getLocalStorageItem(key: string) {
|
|
34
|
+
return window.localStorage.getItem(`${coreStore.config.brandName || 'adminforth'}-${key}`);
|
|
35
|
+
}
|
|
36
|
+
watch(isTeleportedToBody, (newVal: boolean) => {
|
|
37
|
+
setLocalStorageItem('isTeleportedToBody', newVal ? 'true' : 'false');
|
|
38
|
+
})
|
|
39
|
+
watch(isChatOpen, (newVal: boolean) => {
|
|
40
|
+
setLocalStorageItem('isChatOpen', newVal ? 'true' : 'false');
|
|
41
|
+
})
|
|
42
|
+
watch(chatWidth, (newVal: number) => {
|
|
43
|
+
setLocalStorageItem('chatWidth', newVal.toString());
|
|
44
|
+
})
|
|
45
|
+
onMounted(() => {
|
|
46
|
+
chatWidth.value = parseInt(getLocalStorageItem('chatWidth') || '600', 10);
|
|
47
|
+
isTeleportedToBody.value = getLocalStorageItem('isTeleportedToBody') === 'true';
|
|
48
|
+
if (isTeleportedToBody.value) {
|
|
49
|
+
isChatOpen.value = getLocalStorageItem('isChatOpen') === 'true';
|
|
50
|
+
}
|
|
51
|
+
if (coreStore.isMobile) {
|
|
52
|
+
chatWidth.value = window.innerWidth;
|
|
53
|
+
}
|
|
54
|
+
appRoot.value = document.getElementById('app');
|
|
55
|
+
header.value = document.getElementById('af-header-nav');
|
|
56
|
+
if (appRoot.value && header.value) {
|
|
57
|
+
nextTick(() => {
|
|
58
|
+
appRoot.value.style.transition = 'padding-right 200ms ease-in-out';
|
|
59
|
+
header.value.style.transition = 'padding-right 200ms ease-in-out';
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
function setChatWidth(width: number) {
|
|
64
|
+
if (appRoot.value && header.value) {
|
|
65
|
+
appRoot.value.style.transition = '';
|
|
66
|
+
header.value.style.transition = '';
|
|
67
|
+
}
|
|
68
|
+
chatWidth.value = width;
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
watch([isTeleportedToBody, isChatOpen, chatWidth], ([newIsTeleportedToBody, newIsChatOpen, newChatWidth]: [boolean, boolean, number]) => {
|
|
72
|
+
if (appRoot.value && header.value) {
|
|
73
|
+
if (newIsTeleportedToBody && newIsChatOpen) {
|
|
74
|
+
appRoot.value.style.paddingRight = `${chatWidth.value}px`;
|
|
75
|
+
header.value.style.paddingRight = `${chatWidth.value}px`;
|
|
76
|
+
} else {
|
|
77
|
+
appRoot.value.style.paddingRight = '';
|
|
78
|
+
header.value.style.paddingRight = '';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
const chats = new Map<string, Chat>();
|
|
83
|
+
const currentChat = shallowRef<Chat>(null);
|
|
84
|
+
function setCurrentChat(sessionId: string) {
|
|
85
|
+
if (chats.has(sessionId)) {
|
|
86
|
+
currentChat.value = chats.get(sessionId) || null;
|
|
87
|
+
} else {
|
|
88
|
+
const newChat = new Chat({
|
|
89
|
+
transport: new DefaultChatTransport({
|
|
90
|
+
api: `${import.meta.env.VITE_ADMINFORTH_PUBLIC_PATH || ''}/adminapi/v1/agent/response`,
|
|
91
|
+
credentials: 'include',
|
|
92
|
+
prepareSendMessagesRequest({ messages }: any) {
|
|
93
|
+
const message = lastMessage.value;
|
|
94
|
+
const body = {
|
|
95
|
+
message,
|
|
96
|
+
sessionId,
|
|
97
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
headers: {
|
|
102
|
+
Accept: 'text/event-stream',
|
|
103
|
+
'x-vercel-ai-ui-message-stream': 'v1',
|
|
104
|
+
},
|
|
105
|
+
body
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}),
|
|
109
|
+
onError(error: unknown) {
|
|
110
|
+
console.error("Chat error:", error);
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
chats.set(sessionId, newChat);
|
|
114
|
+
currentChat.value = newChat;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
const isResponseInProgress = computed( () => {
|
|
119
|
+
return currentChat.value?.status === 'streaming';
|
|
120
|
+
});
|
|
121
|
+
const blockCloseOfChat = ref(false);
|
|
122
|
+
|
|
123
|
+
async function sendMessage() {
|
|
124
|
+
const message = trimmedUserMessage.value;
|
|
125
|
+
if (!message || isResponseInProgress.value) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!currentSession.value || currentSession.value.sessionId === 'pre-session') {
|
|
129
|
+
await createNewSession(message);
|
|
130
|
+
}
|
|
131
|
+
lastMessage.value = message;
|
|
132
|
+
currentChat.value?.sendMessage({
|
|
133
|
+
text: message,
|
|
134
|
+
});
|
|
135
|
+
userMessageInput.value = '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function closeChat() {
|
|
139
|
+
if (blockCloseOfChat.value) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
isChatOpen.value = false;
|
|
143
|
+
isSessionHistoryOpen.value = false;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function openChat() {
|
|
147
|
+
isChatOpen.value = true;
|
|
148
|
+
nextTick(() => {
|
|
149
|
+
textInput.value?.focus();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function setIsChatOpen(isOpen: boolean) {
|
|
154
|
+
isOpen ? openChat() : closeChat();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function setSessionHistoryOpen(isOpen: boolean) {
|
|
158
|
+
isSessionHistoryOpen.value = isOpen;
|
|
159
|
+
}
|
|
160
|
+
function regisrerTextInput(el: HTMLInputElement | null) {
|
|
161
|
+
textInput.value = el;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
//create a pre-session, until user will type something, so we can save session
|
|
166
|
+
async function createPreSession() {
|
|
167
|
+
saveCurrentSessionInCache();
|
|
168
|
+
if (sessionList.value.some((s: ISessionsListItem) => s.sessionId === 'pre-session')) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
sessionList.value.unshift({
|
|
172
|
+
sessionId: 'pre-session',
|
|
173
|
+
title: 'New Session',
|
|
174
|
+
timestamp: new Date().toISOString(),
|
|
175
|
+
})
|
|
176
|
+
activeSessionId.value = 'pre-session';
|
|
177
|
+
currentSession.value = {
|
|
178
|
+
sessionId: 'pre-session',
|
|
179
|
+
title: 'New Session',
|
|
180
|
+
timestamp: new Date().toISOString(),
|
|
181
|
+
messages: [],
|
|
182
|
+
};
|
|
183
|
+
sessions.value['pre-session'] = currentSession.value;
|
|
184
|
+
setCurrentChat('pre-session');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function deletePreSession() {
|
|
188
|
+
sessionList.value = sessionList.value.filter((s: ISessionsListItem) => s.sessionId !== 'pre-session');
|
|
189
|
+
if (activeSessionId.value === 'pre-session') {
|
|
190
|
+
activeSessionId.value = null;
|
|
191
|
+
currentSession.value = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function createNewSession(triggerMessage?: string) {
|
|
196
|
+
try {
|
|
197
|
+
const res = await callAdminForthApi({
|
|
198
|
+
method: 'POST',
|
|
199
|
+
path: '/agent/create-session',
|
|
200
|
+
body: {
|
|
201
|
+
triggerMessage
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
if (res.error) {
|
|
205
|
+
console.error('Error creating new session:', res.error);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
deletePreSession();
|
|
209
|
+
sessions.value[res.sessionId] = res;
|
|
210
|
+
sessionList.value.unshift({
|
|
211
|
+
sessionId: res.sessionId,
|
|
212
|
+
title: res.title,
|
|
213
|
+
timestamp: new Date().toISOString(),
|
|
214
|
+
});
|
|
215
|
+
setActiveSession(res.sessionId);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('Error creating new session', error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function deleteSession(sessionId: string) {
|
|
222
|
+
if (sessionId === 'pre-session') {
|
|
223
|
+
deletePreSession();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
blockCloseOfChat.value = true;
|
|
227
|
+
const isConfirmed = await adminforth.confirm({message: 'Are you sure, that you want to delete this session?', yes: 'Yes', no: 'No'})
|
|
228
|
+
blockCloseOfChat.value = false;
|
|
229
|
+
if (!isConfirmed) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const res = await callAdminForthApi({
|
|
234
|
+
method: 'POST',
|
|
235
|
+
path: '/agent/delete-session',
|
|
236
|
+
body: { sessionId },
|
|
237
|
+
});
|
|
238
|
+
if (res.error) {
|
|
239
|
+
console.error('Error deleting session:', res.error);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
delete sessions.value[sessionId];
|
|
243
|
+
sessionList.value = sessionList.value.filter((s: ISessionsListItem) => s.sessionId !== sessionId);
|
|
244
|
+
if (activeSessionId.value === sessionId) {
|
|
245
|
+
activeSessionId.value = null;
|
|
246
|
+
currentSession.value = null;
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Error deleting session', error);
|
|
250
|
+
}
|
|
251
|
+
if(sessionId === activeSessionId.value) {
|
|
252
|
+
activeSessionId.value = sessionList.value.length > 0 ? sessionList.value[0].sessionId : null;
|
|
253
|
+
if (activeSessionId.value) {
|
|
254
|
+
currentSession.value = sessions.value[activeSessionId.value] || null;
|
|
255
|
+
} else {
|
|
256
|
+
currentSession.value = null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function fetchSession(sessionId: string) {
|
|
262
|
+
try {
|
|
263
|
+
const res = await callAdminForthApi({
|
|
264
|
+
method: 'POST',
|
|
265
|
+
path: '/agent/get-session-info',
|
|
266
|
+
body: { sessionId },
|
|
267
|
+
});
|
|
268
|
+
if (res.error) {
|
|
269
|
+
console.error('Error fetching session:', res.error);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
sessions.value[sessionId] = res.session;
|
|
273
|
+
setCurrentChat(sessionId);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error('Error fetching session', error);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function saveCurrentSessionInCache() {
|
|
280
|
+
if (currentSession.value) {
|
|
281
|
+
currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
|
|
282
|
+
role: m.role,
|
|
283
|
+
text: m.parts.map((p: any) => p.text).join(' '),
|
|
284
|
+
})) || [];
|
|
285
|
+
sessions.value[currentSession.value.sessionId] = currentSession.value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function setActiveSession(sessionId: string) {
|
|
290
|
+
activeSessionId.value = sessionId;
|
|
291
|
+
saveCurrentSessionInCache();
|
|
292
|
+
if (!sessions.value[sessionId]) {
|
|
293
|
+
await fetchSession(sessionId);
|
|
294
|
+
}
|
|
295
|
+
currentSession.value = sessions.value[sessionId];
|
|
296
|
+
setCurrentChat(sessionId);
|
|
297
|
+
currentChat.value.messages = currentSession.value?.messages.map((m: any) => ({
|
|
298
|
+
role: m.role,
|
|
299
|
+
parts:[{
|
|
300
|
+
type: 'text',
|
|
301
|
+
text: m.text,
|
|
302
|
+
state: 'done',
|
|
303
|
+
}]
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function fetchSessionsList() {
|
|
308
|
+
try {
|
|
309
|
+
const res = await callAdminForthApi({
|
|
310
|
+
method: 'POST',
|
|
311
|
+
path: '/agent/get-sessions',
|
|
312
|
+
});
|
|
313
|
+
if (res.error) {
|
|
314
|
+
console.error('Error fetching sessions list:', res.error);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
sessionList.value = res.sessions;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('Error fetching sessions list', error);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
//_________-Sessions management-_____________
|
|
325
|
+
activeSessionId,
|
|
326
|
+
currentSession,
|
|
327
|
+
sessions,
|
|
328
|
+
sessionList,
|
|
329
|
+
setActiveSession,
|
|
330
|
+
fetchSessionsList,
|
|
331
|
+
deleteSession,
|
|
332
|
+
createPreSession,
|
|
333
|
+
//____________________________________________
|
|
334
|
+
regisrerTextInput,
|
|
335
|
+
isChatOpen,
|
|
336
|
+
setIsChatOpen,
|
|
337
|
+
isSessionHistoryOpen,
|
|
338
|
+
setSessionHistoryOpen,
|
|
339
|
+
sendMessage,
|
|
340
|
+
userMessageInput,
|
|
341
|
+
chatMessages: computed(() => currentChat.value?.messages || []),
|
|
342
|
+
trimmedUserMessage,
|
|
343
|
+
isResponseInProgress,
|
|
344
|
+
isTeleportedToBody,
|
|
345
|
+
setIsTeleportedToBody,
|
|
346
|
+
chatWidth,
|
|
347
|
+
setChatWidth,
|
|
348
|
+
}
|
|
349
|
+
})
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { ToolMessage } from "@langchain/core/messages";
|
|
11
|
+
import { createMiddleware } from "langchain";
|
|
12
|
+
import { logger } from "adminforth";
|
|
13
|
+
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
|
|
14
|
+
import { createApiTool } from "../tools/apiTool.js";
|
|
15
|
+
function getEnabledApiToolNames(messages) {
|
|
16
|
+
const enabledToolNames = new Set();
|
|
17
|
+
for (const message of messages) {
|
|
18
|
+
if (!ToolMessage.isInstance(message) || message.name !== "fetch_tool_schema") {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const content = typeof message.content === "string"
|
|
22
|
+
? message.content
|
|
23
|
+
: Array.isArray(message.content)
|
|
24
|
+
? message.content
|
|
25
|
+
.map((block) => typeof block === "string"
|
|
26
|
+
? block
|
|
27
|
+
: "text" in block
|
|
28
|
+
? block.text
|
|
29
|
+
: "")
|
|
30
|
+
.join("")
|
|
31
|
+
: "";
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(content);
|
|
34
|
+
if (parsed.status === 200 && parsed.name) {
|
|
35
|
+
enabledToolNames.add(parsed.name);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (_a) { }
|
|
39
|
+
}
|
|
40
|
+
return enabledToolNames;
|
|
41
|
+
}
|
|
42
|
+
export function createApiBasedToolsMiddleware(apiBasedTools) {
|
|
43
|
+
const alwaysAvailableApiToolNames = new Set(ALWAYS_AVAILABLE_API_TOOL_NAMES);
|
|
44
|
+
const dynamicTools = Object.fromEntries(Object.entries(apiBasedTools).map(([toolName, apiBasedTool]) => [
|
|
45
|
+
toolName,
|
|
46
|
+
createApiTool(toolName, apiBasedTool),
|
|
47
|
+
]));
|
|
48
|
+
return createMiddleware({
|
|
49
|
+
name: "ApiBasedToolsMiddleware",
|
|
50
|
+
wrapModelCall(request, handler) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
|
|
53
|
+
const tools = [...enabledApiToolNames]
|
|
54
|
+
.filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
|
|
55
|
+
.map((toolName) => dynamicTools[toolName]);
|
|
56
|
+
return handler(Object.assign(Object.assign({}, request), { tools: [...request.tools, ...tools] }));
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
wrapToolCall(request, handler) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
var _a, _b, _c;
|
|
62
|
+
const startedAt = Date.now();
|
|
63
|
+
const toolInput = JSON.stringify((_a = request.toolCall.args) !== null && _a !== void 0 ? _a : {});
|
|
64
|
+
logger.info(`Invoking tool "${request.toolCall.name}" with input: ${toolInput}`);
|
|
65
|
+
try {
|
|
66
|
+
if (request.tool) {
|
|
67
|
+
return yield handler(request);
|
|
68
|
+
}
|
|
69
|
+
const enabledApiToolNames = getEnabledApiToolNames(request.state.messages);
|
|
70
|
+
if (enabledApiToolNames.has(request.toolCall.name)) {
|
|
71
|
+
return yield handler(Object.assign(Object.assign({}, request), { tool: dynamicTools[request.toolCall.name] }));
|
|
72
|
+
}
|
|
73
|
+
return new ToolMessage({
|
|
74
|
+
content: `Tool "${request.toolCall.name}" is not loaded. Call fetch_tool_schema first.`,
|
|
75
|
+
tool_call_id: (_b = request.toolCall.id) !== null && _b !== void 0 ? _b : "",
|
|
76
|
+
name: request.toolCall.name,
|
|
77
|
+
status: "error",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const errorDetails = error instanceof Error ? (_c = error.stack) !== null && _c !== void 0 ? _c : error.message : String(error);
|
|
82
|
+
logger.error(`Tool "${request.toolCall.name}" failed after ${Date.now() - startedAt}ms with input: ${toolInput}\n${errorDetails}`);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
logger.info(`Tool "${request.toolCall.name}" finished in ${Date.now() - startedAt}ms`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|