Skip to content

Commit 6d72f75

Browse files
committed
feat: enhance agent functionality with plugin custom folder paths support
1 parent c4b163f commit 6d72f75

9 files changed

Lines changed: 91 additions & 58 deletions

File tree

agent/middleware/apiBasedTools.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,15 @@ export function createApiBasedToolsMiddleware(
6666
const tools = [...enabledApiToolNames]
6767
.filter((toolName) => !alwaysAvailableApiToolNames.has(toolName))
6868
.map((toolName) => dynamicTools[toolName]);
69+
const availableTools = [...request.tools, ...tools];
70+
71+
logger.info(
72+
`AdminForth Agent callable tools: ${availableTools.map((tool) => tool.name).join(", ")}`,
73+
);
6974

7075
return handler({
7176
...request,
72-
tools: [...request.tools, ...tools],
77+
tools: availableTools,
7378
});
7479
},
7580
async wrapToolCall(request, handler) {

agent/simpleAgent.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export async function callAgent(params: {
230230
adminforth: IAdminForth;
231231
apiBasedTools: Record<string, ApiBasedTool>;
232232
customComponentsDir: string;
233+
pluginCustomFolderPaths: string[];
233234
sessionId: string;
234235
turnId: string;
235236
currentPage?: CurrentPageContext;
@@ -249,6 +250,7 @@ export async function callAgent(params: {
249250
adminforth,
250251
apiBasedTools,
251252
customComponentsDir,
253+
pluginCustomFolderPaths,
252254
sessionId,
253255
turnId,
254256
currentPage,
@@ -258,7 +260,11 @@ export async function callAgent(params: {
258260
sequenceDebugSink,
259261
} = params;
260262

261-
const tools = await createAgentTools(customComponentsDir, apiBasedTools);
263+
const tools = await createAgentTools(
264+
customComponentsDir,
265+
apiBasedTools,
266+
pluginCustomFolderPaths,
267+
);
262268
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
263269
const sequenceDebugMiddleware = createSequenceDebugMiddleware(
264270
sequenceDebugSink,

agent/skills/registry.ts

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
import { readdir, readFile } from "fs/promises";
22
import path from "path";
3-
import { fileURLToPath } from "url";
43
import { parse as parseYaml } from "yaml";
54

6-
const PLUGIN_SKILLS_DIRECTORY_PATH = fileURLToPath(
7-
new URL("../../custom/skills/", import.meta.url),
8-
);
9-
const USER_PLUGIN_SKILLS_DIRECTORY_PATH_SEGMENTS = [
10-
"plugins",
11-
"adminforth-agent",
12-
"skills",
13-
];
145
const SKILL_MARKDOWN_FILENAME = "SKILL.md";
156
const SKILL_FRONTMATTER_SEPARATOR = "\n---\n";
167

@@ -21,6 +12,13 @@ export interface AgentSkillManifest {
2112
instructions: string;
2213
}
2314

15+
function normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths: string[] = []) {
16+
return Array.from(new Set(
17+
pluginCustomFolderPaths
18+
.map((pluginCustomFolderPath) => path.resolve(pluginCustomFolderPath, "skills")),
19+
));
20+
}
21+
2422
function parseSkillManifest(directoryName: string, markdown: string): AgentSkillManifest {
2523
const [frontmatterBlock, instructions = ""] = markdown.split("\r\n").join("\n").split(
2624
SKILL_FRONTMATTER_SEPARATOR,
@@ -83,43 +81,42 @@ export function getProjectSkillsDirectoryPath(customComponentsDir: string) {
8381
return path.resolve(customComponentsDir, "skills");
8482
}
8583

86-
export function getProjectPluginSkillsDirectoryPath(customComponentsDir: string) {
87-
return path.resolve(
88-
customComponentsDir,
89-
...USER_PLUGIN_SKILLS_DIRECTORY_PATH_SEGMENTS,
90-
);
91-
}
92-
93-
function getProjectSkillDirectoryPaths(customComponentsDir: string) {
94-
return [
84+
function getProjectSkillDirectoryPaths(
85+
customComponentsDir: string,
86+
pluginCustomFolderPaths: string[] = [],
87+
) {
88+
return Array.from(new Set([
9589
getProjectSkillsDirectoryPath(customComponentsDir),
96-
getProjectPluginSkillsDirectoryPath(customComponentsDir),
97-
];
98-
}
99-
100-
export async function listBundledSkillManifests() {
101-
return await listDirectorySkillManifests(PLUGIN_SKILLS_DIRECTORY_PATH);
90+
...normalizePluginSkillDirectoryPaths(pluginCustomFolderPaths),
91+
]));
10292
}
10393

104-
export async function listProjectSkillManifests(customComponentsDir: string) {
94+
export async function listProjectSkillManifests(
95+
customComponentsDir: string,
96+
pluginCustomFolderPaths: string[] = [],
97+
) {
10598
return mergeSkillManifests(
10699
await Promise.all(
107-
getProjectSkillDirectoryPaths(customComponentsDir).map(
100+
getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths).map(
108101
listDirectorySkillManifests,
109102
),
110103
),
111104
);
112105
}
113106

114-
export async function listSkillManifests(customComponentsDir: string) {
115-
return mergeSkillManifests([
116-
await listProjectSkillManifests(customComponentsDir),
117-
await listBundledSkillManifests(),
118-
]);
107+
export async function listSkillManifests(
108+
customComponentsDir: string,
109+
pluginCustomFolderPaths: string[] = [],
110+
) {
111+
return await listProjectSkillManifests(customComponentsDir, pluginCustomFolderPaths);
119112
}
120113

121-
export async function loadSkillManifest(skillName: string, customComponentsDir: string) {
122-
const manifests = await listSkillManifests(customComponentsDir);
114+
export async function loadSkillManifest(
115+
skillName: string,
116+
customComponentsDir: string,
117+
pluginCustomFolderPaths: string[] = [],
118+
) {
119+
const manifests = await listSkillManifests(customComponentsDir, pluginCustomFolderPaths);
123120

124121
return (
125122
manifests.find(
@@ -129,16 +126,23 @@ export async function loadSkillManifest(skillName: string, customComponentsDir:
129126
);
130127
}
131128

132-
export async function loadSkillMarkdown(skillName: string, customComponentsDir: string) {
133-
const manifest = await loadSkillManifest(skillName, customComponentsDir);
129+
export async function loadSkillMarkdown(
130+
skillName: string,
131+
customComponentsDir: string,
132+
pluginCustomFolderPaths: string[] = [],
133+
) {
134+
const manifest = await loadSkillManifest(
135+
skillName,
136+
customComponentsDir,
137+
pluginCustomFolderPaths,
138+
);
134139

135140
if (!manifest) {
136141
return null;
137142
}
138143

139144
const directories = [
140-
...getProjectSkillDirectoryPaths(customComponentsDir),
141-
PLUGIN_SKILLS_DIRECTORY_PATH,
145+
...getProjectSkillDirectoryPaths(customComponentsDir, pluginCustomFolderPaths),
142146
];
143147

144148
for (const skillsDirectoryPath of directories) {

agent/systemPrompt.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { AdminForthResource, AdminUser, IAdminForth } from "adminforth";
22
import type { DetectedLanguage } from "./languageDetect.js";
33
import {
4-
listBundledSkillManifests,
54
listProjectSkillManifests,
65
type AgentSkillManifest,
76
} from "./skills/registry.js";
@@ -96,10 +95,12 @@ export async function buildAgentSystemPrompt(
9695
hiddenResourceIds: Iterable<string> = [],
9796
) {
9897
const customComponentsDir = adminforth.config.customization.customComponentsDir ?? "custom";
99-
const [primarySkills, defaultSkills] = await Promise.all([
100-
listProjectSkillManifests(customComponentsDir),
101-
listBundledSkillManifests(),
102-
]);
98+
const pluginCustomFolderPaths = adminforth.activatedPlugins
99+
.map((plugin) => plugin.customFolderPath);
100+
const skills = await listProjectSkillManifests(
101+
customComponentsDir,
102+
pluginCustomFolderPaths,
103+
);
103104
const adminBasePath = adminforth.config.baseUrlSlashed;
104105
const hiddenResourceIdSet = new Set(hiddenResourceIds);
105106
const visibleResources = adminforth.config.resources.filter(
@@ -109,11 +110,9 @@ export async function buildAgentSystemPrompt(
109110
DEFAULT_AGENT_SYSTEM_PROMPT,
110111
`ADMIN_BASE_PATH: ${adminBasePath}`,
111112
`List of resources:\n${formatResources(visibleResources)}`,
112-
primarySkills.length > 0
113-
? `You have primary skills set:\n${formatSkills(primarySkills, "skill_name")}`
113+
skills.length > 0
114+
? `You have skills set:\n${formatSkills(skills, "skill_name")}`
114115
: "",
115-
"You have next default skills which you can fallback to if primary skill set does not provide a good skill:\n" +
116-
formatSkills(defaultSkills, "skill_name"),
117116
"Before using any skill, call fetch_skill to load its full instructions.",
118117
"The fetched skill response starts with 'Tools mentioned in this skill'. Read that list first.",
119118
"You can use get_resource immediately to inspect resource structure and column names.",
@@ -123,8 +122,8 @@ export async function buildAgentSystemPrompt(
123122
"If a fetched skill lists a non-base tool you need, call fetch_tool_schema for it immediately instead of telling the user the tool is unavailable.",
124123
"For example: for record creation load mutate_data, read its tool list, call fetch_tool_schema for create_record, and then use create_record after confirmation.",
125124
"When fetch_tool_schema succeeds, that tool becomes available on the next step.",
126-
"All admin links must be relative paths and must start with ADMIN_BASE_PATH.",
127-
"Build record links as ADMIN_BASE_PATH + resource/{resourceId}/show/{primary key}. Do not prepend any extra slash before resource.",
125+
"All admin links must be root-relative and start with '/'.",
126+
"Build record links as '/resource/{resourceId}/show/{primary key}'. Never use bare 'resource/{resourceId}/show/{primary key}' without the leading slash.",
128127
"Try to call as many tools as possible in parallel in one step.",
129128
];
130129

agent/tools/fetchSkill.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,24 @@ function serializeSkillManifests(skillManifests: AgentSkillManifest[]) {
2121
}));
2222
}
2323

24-
export async function createFetchSkillTool(customComponentsDir: string) {
25-
const availableSkills = await listSkillManifests(customComponentsDir);
24+
export async function createFetchSkillTool(
25+
customComponentsDir: string,
26+
pluginCustomFolderPaths: string[] = [],
27+
) {
28+
const availableSkills = await listSkillManifests(
29+
customComponentsDir,
30+
pluginCustomFolderPaths,
31+
);
2632
const availableSkillNames = availableSkills.map((skill) => skill.name);
2733

2834
return tool(
2935
async ({ skillName }) => {
3036
try {
31-
const skillMarkdown = await loadSkillMarkdown(skillName, customComponentsDir);
37+
const skillMarkdown = await loadSkillMarkdown(
38+
skillName,
39+
customComponentsDir,
40+
pluginCustomFolderPaths,
41+
);
3242

3343
if (!skillMarkdown) {
3444
return [

agent/tools/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const ALWAYS_AVAILABLE_API_TOOL_NAMES = ["get_resource"] as const;
1010
export async function createAgentTools(
1111
customComponentsDir: string,
1212
apiBasedTools: Record<string, ApiBasedTool>,
13+
pluginCustomFolderPaths: string[] = [],
1314
): Promise<ClientTool[]> {
1415
return [
1516
...ALWAYS_AVAILABLE_API_TOOL_NAMES.map((toolName) => {
@@ -22,7 +23,7 @@ export async function createAgentTools(
2223
return createApiTool(toolName, apiBasedTool);
2324
}),
2425
createGetUserLocationTool(),
25-
await createFetchSkillTool(customComponentsDir),
26+
await createFetchSkillTool(customComponentsDir, pluginCustomFolderPaths),
2627
await createFetchToolSchemaTool(apiBasedTools),
2728
];
2829
}

agentTurnService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export class AgentTurnService {
138138
adminforth,
139139
apiBasedTools,
140140
customComponentsDir: adminforth.config.customization.customComponentsDir ?? "custom",
141+
pluginCustomFolderPaths: adminforth.activatedPlugins.map((plugin) => plugin.customFolderPath),
141142
sessionId: input.sessionId,
142143
turnId: input.turnId,
143144
currentPage: input.currentPage,

custom/composables/agentStore/useAgentSessions.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { callAdminForthApi } from '@/utils';
33
import type { Chat } from '../../chat';
44
import type { IAgentSession, ISessionsListItem, IPart } from '../../types';
55
import { PRE_SESSION_ID } from './constants';
6-
import { useI18n } from 'vue-i18n';
6+
import { i18nInstance } from '@/i18n';
77

88
type AdminforthLike = {
99
confirm(options: { message: string; yes: string; no: string }): Promise<boolean>;
@@ -42,7 +42,10 @@ export function createAgentSessionManager({
4242
return [...sessionsListToSort].sort((a: ISessionsListItem, b: ISessionsListItem) => b.timestamp.localeCompare(a.timestamp));
4343
}
4444

45-
const { t } = useI18n();
45+
function t(key: string) {
46+
return i18nInstance?.global.t(key) ?? key;
47+
}
48+
4649
function saveCurrentSessionInCache() {
4750
if (currentSession.value) {
4851
currentSession.value.messages = currentChat.value?.messages.map((m: any) => ({
@@ -342,4 +345,4 @@ export function createAgentSessionManager({
342345
setCurrentChatStatus,
343346
updateLastAgentMessage
344347
};
345-
}
348+
}

custom/conversation_area/TextRenderer.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,11 @@
115115
return `${window.location.pathname}${window.location.search}${href}`;
116116
}
117117
118-
const resolvedUrl = new URL(href, window.location.href);
118+
const isAbsoluteWithScheme = /^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href);
119+
const baseUrl = isAbsoluteWithScheme
120+
? undefined
121+
: `${window.location.origin}/`;
122+
const resolvedUrl = new URL(href, baseUrl ?? window.location.href);
119123
if (resolvedUrl.origin !== window.location.origin) {
120124
return null;
121125
}

0 commit comments

Comments
 (0)