@adminforth/agent 1.49.3 → 1.50.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/agent/middleware/sequenceDebug.ts +42 -7
- package/agent/simpleAgent.ts +15 -0
- package/agent/systemPrompt.ts +6 -1
- package/agent/tools/index.ts +2 -0
- package/agent/tools/navigateUser.ts +210 -0
- package/agentEvents.ts +4 -0
- package/agentTurnService.ts +14 -0
- package/build.log +2 -2
- package/chatSurfaceService.ts +7 -0
- package/custom/composables/agentStore/useAgentChat.ts +8 -1
- package/custom/composables/useAgentAudio.ts +5 -0
- package/custom/composables/useAgentStore.ts +43 -0
- package/custom/tsconfig.json +0 -1
- package/custom/types.ts +6 -0
- package/dist/agent/middleware/sequenceDebug.d.ts +6 -0
- package/dist/agent/middleware/sequenceDebug.js +33 -6
- package/dist/agent/simpleAgent.d.ts +8 -0
- package/dist/agent/simpleAgent.js +9 -1
- package/dist/agent/systemPrompt.d.ts +1 -0
- package/dist/agent/systemPrompt.js +5 -1
- package/dist/agent/tools/index.js +2 -0
- package/dist/agent/tools/navigateUser.d.ts +55 -0
- package/dist/agent/tools/navigateUser.js +163 -0
- package/dist/agentEvents.d.ts +3 -0
- package/dist/agentTurnService.d.ts +2 -0
- package/dist/agentTurnService.js +10 -0
- package/dist/chatSurfaceService.js +10 -3
- package/dist/custom/composables/agentStore/useAgentChat.ts +8 -1
- package/dist/custom/composables/useAgentAudio.ts +5 -0
- package/dist/custom/composables/useAgentStore.ts +43 -0
- package/dist/custom/tsconfig.json +0 -1
- package/dist/custom/types.ts +6 -0
- package/dist/endpoints/chatSurfaces.js +20 -0
- package/dist/surfaces/web-sse/createSseEventEmitter.js +11 -0
- package/endpoints/chatSurfaces.ts +29 -0
- package/package.json +1 -1
- package/surfaces/web-sse/createSseEventEmitter.ts +12 -0
package/custom/types.ts
CHANGED
|
@@ -16,6 +16,9 @@ export type SequenceDebug = {
|
|
|
16
16
|
reasoningTokens: number;
|
|
17
17
|
text: string;
|
|
18
18
|
textTokens: number;
|
|
19
|
+
uncachedInputTokens: number;
|
|
20
|
+
cachedInputTokens: number;
|
|
21
|
+
outputTokens: number;
|
|
19
22
|
cachedTokens: number;
|
|
20
23
|
responseId: string | null;
|
|
21
24
|
toolCalls: SequenceDebugToolCall[];
|
|
@@ -28,6 +31,9 @@ type SequenceDebugModelCall = {
|
|
|
28
31
|
reasoningTokens: number;
|
|
29
32
|
text: string;
|
|
30
33
|
textTokens: number;
|
|
34
|
+
uncachedInputTokens: number;
|
|
35
|
+
cachedInputTokens: number;
|
|
36
|
+
outputTokens: number;
|
|
31
37
|
cachedTokens: number;
|
|
32
38
|
responseId: string | null;
|
|
33
39
|
resultType: SequenceDebugResultType;
|
|
@@ -30,6 +30,9 @@ function createPendingSequenceDebug(sequenceId) {
|
|
|
30
30
|
reasoningTokens: 0,
|
|
31
31
|
text: "",
|
|
32
32
|
textTokens: 0,
|
|
33
|
+
uncachedInputTokens: 0,
|
|
34
|
+
cachedInputTokens: 0,
|
|
35
|
+
outputTokens: 0,
|
|
33
36
|
cachedTokens: 0,
|
|
34
37
|
responseId: null,
|
|
35
38
|
toolCalls: [],
|
|
@@ -55,6 +58,9 @@ function finalizeSequenceDebug(sequence) {
|
|
|
55
58
|
reasoningTokens: sequence.reasoningTokens,
|
|
56
59
|
text: sequence.text,
|
|
57
60
|
textTokens: sequence.textTokens,
|
|
61
|
+
uncachedInputTokens: sequence.uncachedInputTokens,
|
|
62
|
+
cachedInputTokens: sequence.cachedInputTokens,
|
|
63
|
+
outputTokens: sequence.outputTokens,
|
|
58
64
|
cachedTokens: sequence.cachedTokens,
|
|
59
65
|
responseId: sequence.responseId,
|
|
60
66
|
toolCalls: sequence.toolCalls.map((_a) => {
|
|
@@ -68,6 +74,18 @@ function finalizeSequenceDebug(sequence) {
|
|
|
68
74
|
function getDebugModelName(model) {
|
|
69
75
|
return typeof model.getName === "function" ? model.getName() : undefined;
|
|
70
76
|
}
|
|
77
|
+
function getDebugToolName(tool) {
|
|
78
|
+
if (!tool || typeof tool !== "object") {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const name = tool.name;
|
|
82
|
+
return typeof name === "string" ? name : null;
|
|
83
|
+
}
|
|
84
|
+
function formatToolsForDebug(tools) {
|
|
85
|
+
return tools.map((tool) => ({
|
|
86
|
+
name: getDebugToolName(tool),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
71
89
|
function stringifyPromptForDebug(params) {
|
|
72
90
|
var _a, _b, _c;
|
|
73
91
|
const { model, systemMessage, messages, tools, toolChoice, modelSettings } = params;
|
|
@@ -79,7 +97,7 @@ function stringifyPromptForDebug(params) {
|
|
|
79
97
|
provider: (_c = (_b = model._defaultConfig) === null || _b === void 0 ? void 0 : _b.modelProvider) !== null && _c !== void 0 ? _c : null,
|
|
80
98
|
configuredModel: typeof model.model === "string" ? model.model : null,
|
|
81
99
|
}, systemMessage,
|
|
82
|
-
messages }, (tools.length > 0 ? { tools } : {})), (toolChoice !== undefined ? { toolChoice } : {})), (modelSettings ? { modelSettings } : {})), (invocationParams ? { invocationParams } : {})));
|
|
100
|
+
messages }, (tools.length > 0 ? { tools: formatToolsForDebug(tools) } : {})), (toolChoice !== undefined ? { toolChoice } : {})), (modelSettings ? { modelSettings } : {})), (invocationParams ? { invocationParams } : {})));
|
|
83
101
|
}
|
|
84
102
|
function getMessageBlocks(message) {
|
|
85
103
|
if (Array.isArray(message.contentBlocks)) {
|
|
@@ -115,7 +133,10 @@ function countTokens(model, content) {
|
|
|
115
133
|
});
|
|
116
134
|
}
|
|
117
135
|
function extractSequenceResponseDebug(message) {
|
|
118
|
-
var _a, _b, _c, _d, _e, _f
|
|
136
|
+
var _a, _b, _c, _d, _e, _f;
|
|
137
|
+
const usageMetadata = message.usage_metadata;
|
|
138
|
+
const promptTokens = (_a = usageMetadata === null || usageMetadata === void 0 ? void 0 : usageMetadata.input_tokens) !== null && _a !== void 0 ? _a : 0;
|
|
139
|
+
const cachedInputTokens = (_c = (_b = usageMetadata === null || usageMetadata === void 0 ? void 0 : usageMetadata.input_token_details) === null || _b === void 0 ? void 0 : _b.cache_read) !== null && _c !== void 0 ? _c : 0;
|
|
119
140
|
const blocks = getMessageBlocks(message);
|
|
120
141
|
const reasoning = blocks
|
|
121
142
|
.filter((block) => (block === null || block === void 0 ? void 0 : block.type) === "reasoning")
|
|
@@ -126,13 +147,16 @@ function extractSequenceResponseDebug(message) {
|
|
|
126
147
|
.map((block) => { var _a; return String((_a = block.text) !== null && _a !== void 0 ? _a : ""); })
|
|
127
148
|
.join("");
|
|
128
149
|
return {
|
|
129
|
-
promptTokens
|
|
150
|
+
promptTokens,
|
|
130
151
|
reasoning,
|
|
131
152
|
reasoningTokens: 0,
|
|
132
153
|
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
|
|
133
154
|
textTokens: 0,
|
|
134
|
-
|
|
135
|
-
|
|
155
|
+
uncachedInputTokens: Math.max(promptTokens - cachedInputTokens, 0),
|
|
156
|
+
cachedInputTokens,
|
|
157
|
+
outputTokens: (_d = usageMetadata === null || usageMetadata === void 0 ? void 0 : usageMetadata.output_tokens) !== null && _d !== void 0 ? _d : 0,
|
|
158
|
+
cachedTokens: cachedInputTokens,
|
|
159
|
+
responseId: (_f = (_e = message.response_metadata) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : null,
|
|
136
160
|
resultType: hasToolCallSignal(message) ? "tool_calls" : "final_text",
|
|
137
161
|
};
|
|
138
162
|
}
|
|
@@ -171,6 +195,9 @@ export function createSequenceDebugCollector() {
|
|
|
171
195
|
sequenceDebug.reasoningTokens = params.reasoningTokens;
|
|
172
196
|
sequenceDebug.text = params.text;
|
|
173
197
|
sequenceDebug.textTokens = params.textTokens;
|
|
198
|
+
sequenceDebug.uncachedInputTokens = params.uncachedInputTokens;
|
|
199
|
+
sequenceDebug.cachedInputTokens = params.cachedInputTokens;
|
|
200
|
+
sequenceDebug.outputTokens = params.outputTokens;
|
|
174
201
|
sequenceDebug.cachedTokens = params.cachedTokens;
|
|
175
202
|
sequenceDebug.responseId = params.responseId;
|
|
176
203
|
sequenceDebug.resultType = params.resultType;
|
|
@@ -244,7 +271,7 @@ export function createSequenceDebugMiddleware(sink) {
|
|
|
244
271
|
]);
|
|
245
272
|
sink.handleModelCallComplete(Object.assign(Object.assign({}, debug), { promptTokens,
|
|
246
273
|
reasoningTokens,
|
|
247
|
-
textTokens }));
|
|
274
|
+
textTokens, uncachedInputTokens: debug.promptTokens ? debug.uncachedInputTokens : promptTokens, outputTokens: debug.outputTokens || reasoningTokens + textTokens }));
|
|
248
275
|
return response;
|
|
249
276
|
});
|
|
250
277
|
},
|
|
@@ -6,6 +6,7 @@ import { createSequenceDebugMiddleware, type SequenceDebugModelCallSink } from "
|
|
|
6
6
|
import type { ApiBasedTool } from "../apiBasedTools.js";
|
|
7
7
|
import type { ToolCallEventSink } from "./toolCallEvents.js";
|
|
8
8
|
import type { CurrentPageContext } from "./tools/getUserLocation.js";
|
|
9
|
+
import type { AgentEventEmitter } from "../agentEvents.js";
|
|
9
10
|
export declare const contextSchema: z.ZodObject<{
|
|
10
11
|
adminUser: z.ZodCustom<AdminUser, AdminUser>;
|
|
11
12
|
userTimeZone: z.ZodString;
|
|
@@ -13,7 +14,11 @@ export declare const contextSchema: z.ZodObject<{
|
|
|
13
14
|
turnId: z.ZodString;
|
|
14
15
|
abortSignal: z.ZodOptional<z.ZodCustom<AbortSignal, AbortSignal>>;
|
|
15
16
|
currentPage: z.ZodOptional<z.ZodCustom<CurrentPageContext, CurrentPageContext>>;
|
|
17
|
+
chatSurface: z.ZodOptional<z.ZodString>;
|
|
18
|
+
adminBaseUrl: z.ZodOptional<z.ZodString>;
|
|
19
|
+
adminPublicOrigin: z.ZodOptional<z.ZodString>;
|
|
16
20
|
emitToolCallEvent: z.ZodCustom<ToolCallEventSink, ToolCallEventSink>;
|
|
21
|
+
emitAgentEvent: z.ZodOptional<z.ZodCustom<AgentEventEmitter, AgentEventEmitter>>;
|
|
17
22
|
}, z.core.$strip>;
|
|
18
23
|
export type AgentChatModel = BaseChatModel<any, any>;
|
|
19
24
|
export type AgentModelPurpose = "primary" | "summary";
|
|
@@ -54,9 +59,12 @@ export declare function callAgent(params: {
|
|
|
54
59
|
sessionId: string;
|
|
55
60
|
turnId: string;
|
|
56
61
|
currentPage?: CurrentPageContext;
|
|
62
|
+
chatSurface?: string;
|
|
63
|
+
adminPublicOrigin?: string;
|
|
57
64
|
userTimeZone: string;
|
|
58
65
|
abortSignal?: AbortSignal;
|
|
59
66
|
emitToolCallEvent: ToolCallEventSink;
|
|
67
|
+
emitAgentEvent?: AgentEventEmitter;
|
|
60
68
|
sequenceDebugSink: SequenceDebugModelCallSink;
|
|
61
69
|
}): Promise<import("@langchain/core/utils/stream").IterableReadableStream<[import("langchain").BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>, Record<string, any>]>>;
|
|
62
70
|
export {};
|
|
@@ -21,7 +21,11 @@ export const contextSchema = z.object({
|
|
|
21
21
|
turnId: z.string(),
|
|
22
22
|
abortSignal: z.custom().optional(),
|
|
23
23
|
currentPage: z.custom().optional(),
|
|
24
|
+
chatSurface: z.string().optional(),
|
|
25
|
+
adminBaseUrl: z.string().optional(),
|
|
26
|
+
adminPublicOrigin: z.string().optional(),
|
|
24
27
|
emitToolCallEvent: z.custom(),
|
|
28
|
+
emitAgentEvent: z.custom().optional(),
|
|
25
29
|
});
|
|
26
30
|
function isLangChainAgentCompletionAdapter(adapter) {
|
|
27
31
|
return typeof adapter
|
|
@@ -132,7 +136,7 @@ export function createAgentChatModel(params) {
|
|
|
132
136
|
}
|
|
133
137
|
export function callAgent(params) {
|
|
134
138
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
-
const { name, model, summaryModel, modelMiddleware = [], checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, pluginCustomFolderPaths, sessionId, turnId, currentPage, userTimeZone, abortSignal, emitToolCallEvent, sequenceDebugSink, } = params;
|
|
139
|
+
const { name, model, summaryModel, modelMiddleware = [], checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, pluginCustomFolderPaths, sessionId, turnId, currentPage, chatSurface, adminPublicOrigin, userTimeZone, abortSignal, emitToolCallEvent, emitAgentEvent, sequenceDebugSink, } = params;
|
|
136
140
|
const tools = yield createAgentTools(customComponentsDir, apiBasedTools, pluginCustomFolderPaths);
|
|
137
141
|
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
|
|
138
142
|
const sequenceDebugMiddleware = createSequenceDebugMiddleware(sequenceDebugSink);
|
|
@@ -169,7 +173,11 @@ export function callAgent(params) {
|
|
|
169
173
|
turnId,
|
|
170
174
|
abortSignal,
|
|
171
175
|
currentPage,
|
|
176
|
+
chatSurface,
|
|
177
|
+
adminBaseUrl: adminforth.config.baseUrlSlashed,
|
|
178
|
+
adminPublicOrigin,
|
|
172
179
|
emitToolCallEvent,
|
|
180
|
+
emitAgentEvent,
|
|
173
181
|
},
|
|
174
182
|
});
|
|
175
183
|
});
|
|
@@ -7,5 +7,6 @@ export declare function buildAgentTurnSystemPrompt(input: {
|
|
|
7
7
|
adminUser: AdminUser;
|
|
8
8
|
usernameField: string;
|
|
9
9
|
userLanguage: DetectedLanguage | null;
|
|
10
|
+
chatSurface?: string;
|
|
10
11
|
}): string;
|
|
11
12
|
export declare function buildAgentSystemPrompt(adminforth: IAdminForth, hiddenResourceIds?: Iterable<string>): Promise<string>;
|
|
@@ -58,8 +58,11 @@ export function buildAgentTurnSystemPrompt(input) {
|
|
|
58
58
|
return [
|
|
59
59
|
input.agentSystemPrompt,
|
|
60
60
|
formatAdminUserPrompt(input.adminUser, input.usernameField),
|
|
61
|
+
input.chatSurface
|
|
62
|
+
? `Current chat surface: ${input.chatSurface}. The user is not in the AdminForth web UI, so tools cannot move their browser. When navigate_user returns a link, send that link to the user.`
|
|
63
|
+
: "",
|
|
61
64
|
formatLanguagePrompt(input.userLanguage),
|
|
62
|
-
].join("\n\n");
|
|
65
|
+
].filter(Boolean).join("\n\n");
|
|
63
66
|
}
|
|
64
67
|
function formatResources(resources) {
|
|
65
68
|
return resources
|
|
@@ -99,6 +102,7 @@ export function buildAgentSystemPrompt(adminforth_1) {
|
|
|
99
102
|
"When fetch_tool_schema succeeds, that tool becomes available on the next step.",
|
|
100
103
|
"All admin links must be root-relative and start with '/'.",
|
|
101
104
|
"Build record links as '/resource/{resourceId}/show/{primary key}'. Never use bare 'resource/{resourceId}/show/{primary key}' without the leading slash.",
|
|
105
|
+
"When the user asks to open or show a page in the AdminForth UI, call navigate_user instead of only sending a link.",
|
|
102
106
|
"Try to call as many tools as possible in parallel in one step.",
|
|
103
107
|
];
|
|
104
108
|
return sections.filter(Boolean).join("\n\n");
|
|
@@ -11,6 +11,7 @@ import { createFetchSkillTool } from "./fetchSkill.js";
|
|
|
11
11
|
import { createFetchToolSchemaTool } from "./fetchToolSchema.js";
|
|
12
12
|
import { createApiTool } from "./apiTool.js";
|
|
13
13
|
import { createGetUserLocationTool } from "./getUserLocation.js";
|
|
14
|
+
import { createNavigateUserTool } from "./navigateUser.js";
|
|
14
15
|
export const ALWAYS_AVAILABLE_API_TOOL_NAMES = ["get_resource"];
|
|
15
16
|
export function createAgentTools(customComponentsDir_1, apiBasedTools_1) {
|
|
16
17
|
return __awaiter(this, arguments, void 0, function* (customComponentsDir, apiBasedTools, pluginCustomFolderPaths = []) {
|
|
@@ -23,6 +24,7 @@ export function createAgentTools(customComponentsDir_1, apiBasedTools_1) {
|
|
|
23
24
|
return createApiTool(toolName, apiBasedTool);
|
|
24
25
|
}),
|
|
25
26
|
createGetUserLocationTool(),
|
|
27
|
+
createNavigateUserTool(),
|
|
26
28
|
yield createFetchSkillTool(customComponentsDir, pluginCustomFolderPaths),
|
|
27
29
|
yield createFetchToolSchemaTool(apiBasedTools),
|
|
28
30
|
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare function createNavigateUserTool(): import("langchain").DynamicStructuredTool<z.ZodObject<{
|
|
3
|
+
targetPath: z.ZodOptional<z.ZodString>;
|
|
4
|
+
resourceId: z.ZodOptional<z.ZodString>;
|
|
5
|
+
mode: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
|
|
6
|
+
list: "list";
|
|
7
|
+
show: "show";
|
|
8
|
+
edit: "edit";
|
|
9
|
+
create: "create";
|
|
10
|
+
}>>>;
|
|
11
|
+
recordId: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
12
|
+
filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
13
|
+
column: z.ZodString;
|
|
14
|
+
operator: z.ZodString;
|
|
15
|
+
value: z.ZodUnknown;
|
|
16
|
+
}, z.core.$strip>>>;
|
|
17
|
+
sort: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
column: z.ZodString;
|
|
19
|
+
direction: z.ZodEnum<{
|
|
20
|
+
asc: "asc";
|
|
21
|
+
desc: "desc";
|
|
22
|
+
}>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
query: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>>;
|
|
25
|
+
}, z.core.$strip>, {
|
|
26
|
+
mode: "list" | "show" | "edit" | "create";
|
|
27
|
+
targetPath?: string | undefined;
|
|
28
|
+
resourceId?: string | undefined;
|
|
29
|
+
recordId?: string | number | undefined;
|
|
30
|
+
filters?: {
|
|
31
|
+
column: string;
|
|
32
|
+
operator: string;
|
|
33
|
+
value: unknown;
|
|
34
|
+
}[] | undefined;
|
|
35
|
+
sort?: {
|
|
36
|
+
column: string;
|
|
37
|
+
direction: "asc" | "desc";
|
|
38
|
+
} | undefined;
|
|
39
|
+
query?: Record<string, string | number | boolean> | undefined;
|
|
40
|
+
}, {
|
|
41
|
+
targetPath?: string | undefined;
|
|
42
|
+
resourceId?: string | undefined;
|
|
43
|
+
mode?: "list" | "show" | "edit" | "create" | undefined;
|
|
44
|
+
recordId?: string | number | undefined;
|
|
45
|
+
filters?: {
|
|
46
|
+
column: string;
|
|
47
|
+
operator: string;
|
|
48
|
+
value: unknown;
|
|
49
|
+
}[] | undefined;
|
|
50
|
+
sort?: {
|
|
51
|
+
column: string;
|
|
52
|
+
direction: "asc" | "desc";
|
|
53
|
+
} | undefined;
|
|
54
|
+
query?: Record<string, string | number | boolean> | undefined;
|
|
55
|
+
}, string, unknown, "navigate_user">;
|
|
@@ -0,0 +1,163 @@
|
|
|
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 { tool } from "langchain";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
const filterSchema = z.object({
|
|
13
|
+
column: z.string().min(1).describe("Resource column name."),
|
|
14
|
+
operator: z
|
|
15
|
+
.string()
|
|
16
|
+
.min(1)
|
|
17
|
+
.describe("Filter operator suffix, for example eq, gte, lte, like, in."),
|
|
18
|
+
value: z.unknown().describe("Filter value. Dates should be ISO strings."),
|
|
19
|
+
});
|
|
20
|
+
const navigateUserSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
targetPath: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Root-relative AdminForth path to open, with optional query string and hash, for example /resource/adminuser?sort=created_at__desc."),
|
|
26
|
+
resourceId: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("Resource id to build an AdminForth resource route for."),
|
|
30
|
+
mode: z
|
|
31
|
+
.enum(["list", "show", "edit", "create"])
|
|
32
|
+
.optional()
|
|
33
|
+
.default("list")
|
|
34
|
+
.describe("Resource page mode. Defaults to list."),
|
|
35
|
+
recordId: z
|
|
36
|
+
.union([z.string(), z.number()])
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Record primary key for show or edit resource pages."),
|
|
39
|
+
filters: z
|
|
40
|
+
.array(filterSchema)
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("List page filters. Each item becomes filter__{column}__{operator}=JSON.stringify(value)."),
|
|
43
|
+
sort: z
|
|
44
|
+
.object({
|
|
45
|
+
column: z.string().min(1),
|
|
46
|
+
direction: z.enum(["asc", "desc"]),
|
|
47
|
+
})
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("List page sort. Becomes sort={column}__{direction}."),
|
|
50
|
+
query: z
|
|
51
|
+
.record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Additional query parameters to append to the target URL."),
|
|
54
|
+
})
|
|
55
|
+
.refine((input) => input.targetPath || input.resourceId, {
|
|
56
|
+
message: "Either targetPath or resourceId is required.",
|
|
57
|
+
});
|
|
58
|
+
function normalizeTargetPath(targetPath, currentPage) {
|
|
59
|
+
const trimmed = targetPath.trim();
|
|
60
|
+
if (!trimmed) {
|
|
61
|
+
throw new Error("targetPath cannot be empty.");
|
|
62
|
+
}
|
|
63
|
+
const currentOrigin = (currentPage === null || currentPage === void 0 ? void 0 : currentPage.url) ? new URL(currentPage.url).origin : undefined;
|
|
64
|
+
if (currentOrigin) {
|
|
65
|
+
const targetUrl = new URL(trimmed, currentOrigin);
|
|
66
|
+
if (targetUrl.origin !== currentOrigin) {
|
|
67
|
+
throw new Error("Only same-origin navigation targets are allowed.");
|
|
68
|
+
}
|
|
69
|
+
return `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`;
|
|
70
|
+
}
|
|
71
|
+
const fallbackOrigin = "http://adminforth.local";
|
|
72
|
+
const targetUrl = new URL(trimmed, fallbackOrigin);
|
|
73
|
+
if (targetUrl.origin !== fallbackOrigin) {
|
|
74
|
+
throw new Error("Only relative AdminForth paths are allowed when current origin is unavailable.");
|
|
75
|
+
}
|
|
76
|
+
return `${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`;
|
|
77
|
+
}
|
|
78
|
+
function appendQueryParams(path, params) {
|
|
79
|
+
const queryString = params.toString();
|
|
80
|
+
if (!queryString) {
|
|
81
|
+
return path;
|
|
82
|
+
}
|
|
83
|
+
const hashIndex = path.indexOf("#");
|
|
84
|
+
const pathWithoutHash = hashIndex === -1 ? path : path.slice(0, hashIndex);
|
|
85
|
+
const hash = hashIndex === -1 ? "" : path.slice(hashIndex);
|
|
86
|
+
const separator = pathWithoutHash.includes("?") ? "&" : "?";
|
|
87
|
+
return `${pathWithoutHash}${separator}${queryString}${hash}`;
|
|
88
|
+
}
|
|
89
|
+
function buildResourcePath(input) {
|
|
90
|
+
var _a;
|
|
91
|
+
if (!input.resourceId) {
|
|
92
|
+
throw new Error("resourceId is required to build a resource route.");
|
|
93
|
+
}
|
|
94
|
+
const resourceId = encodeURIComponent(input.resourceId);
|
|
95
|
+
const mode = (_a = input.mode) !== null && _a !== void 0 ? _a : "list";
|
|
96
|
+
if (mode === "show" || mode === "edit") {
|
|
97
|
+
if (input.recordId === undefined || input.recordId === null) {
|
|
98
|
+
throw new Error(`recordId is required for ${mode} resource pages.`);
|
|
99
|
+
}
|
|
100
|
+
return `/resource/${resourceId}/${mode}/${encodeURIComponent(String(input.recordId))}`;
|
|
101
|
+
}
|
|
102
|
+
if (mode === "create") {
|
|
103
|
+
return `/resource/${resourceId}/create`;
|
|
104
|
+
}
|
|
105
|
+
return `/resource/${resourceId}`;
|
|
106
|
+
}
|
|
107
|
+
function buildQueryParams(input) {
|
|
108
|
+
var _a, _b;
|
|
109
|
+
const params = new URLSearchParams();
|
|
110
|
+
for (const filter of (_a = input.filters) !== null && _a !== void 0 ? _a : []) {
|
|
111
|
+
params.set(`filter__${filter.column}__${filter.operator}`, JSON.stringify(filter.value));
|
|
112
|
+
}
|
|
113
|
+
if (input.sort) {
|
|
114
|
+
params.set("sort", `${input.sort.column}__${input.sort.direction}`);
|
|
115
|
+
}
|
|
116
|
+
for (const [key, value] of Object.entries((_b = input.query) !== null && _b !== void 0 ? _b : {})) {
|
|
117
|
+
params.set(key, String(value));
|
|
118
|
+
}
|
|
119
|
+
return params;
|
|
120
|
+
}
|
|
121
|
+
function buildSurfaceUrl(targetPath, adminBaseUrl, adminPublicOrigin) {
|
|
122
|
+
var _a;
|
|
123
|
+
const normalizedBasePath = (_a = adminBaseUrl === null || adminBaseUrl === void 0 ? void 0 : adminBaseUrl.replace(/\/+$/, "")) !== null && _a !== void 0 ? _a : "";
|
|
124
|
+
const normalizedTargetPath = targetPath.replace(/^\/+/, "");
|
|
125
|
+
const path = `${normalizedBasePath}/${normalizedTargetPath}`;
|
|
126
|
+
return adminPublicOrigin ? new URL(path, adminPublicOrigin).toString() : path;
|
|
127
|
+
}
|
|
128
|
+
export function createNavigateUserTool() {
|
|
129
|
+
return tool((input, runtime) => __awaiter(this, void 0, void 0, function* () {
|
|
130
|
+
var _a;
|
|
131
|
+
const context = runtime.context;
|
|
132
|
+
const currentPage = context.currentPage;
|
|
133
|
+
const basePath = input.targetPath
|
|
134
|
+
? normalizeTargetPath(input.targetPath, currentPage)
|
|
135
|
+
: buildResourcePath(input);
|
|
136
|
+
const targetPath = appendQueryParams(basePath, buildQueryParams(input));
|
|
137
|
+
if (context.chatSurface) {
|
|
138
|
+
const url = buildSurfaceUrl(targetPath, context.adminBaseUrl, context.adminPublicOrigin);
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
status: 200,
|
|
141
|
+
action: "link",
|
|
142
|
+
surface: context.chatSurface,
|
|
143
|
+
targetPath,
|
|
144
|
+
url,
|
|
145
|
+
message: `Send this link to the user: ${url}`,
|
|
146
|
+
}, null, 2);
|
|
147
|
+
}
|
|
148
|
+
yield ((_a = context.emitAgentEvent) === null || _a === void 0 ? void 0 : _a.call(context, {
|
|
149
|
+
type: "open-page",
|
|
150
|
+
targetPath,
|
|
151
|
+
}));
|
|
152
|
+
return JSON.stringify({
|
|
153
|
+
status: 200,
|
|
154
|
+
action: "navigate",
|
|
155
|
+
targetPath,
|
|
156
|
+
message: `Navigation requested to ${targetPath}.`,
|
|
157
|
+
}, null, 2);
|
|
158
|
+
}), {
|
|
159
|
+
name: "navigate_user",
|
|
160
|
+
description: "Navigate the user to another AdminForth page. Use this only when the user asks to open, show, go to, or switch to a resource list/detail page, including filtered or sorted resource lists. Or if the user is asked to open something on the left. Do not use this tool in any other case.",
|
|
161
|
+
schema: navigateUserSchema,
|
|
162
|
+
});
|
|
163
|
+
}
|
package/dist/agentEvents.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type RunAndPersistAgentResponseInput = {
|
|
|
10
10
|
modeName?: string | null;
|
|
11
11
|
userTimeZone: string;
|
|
12
12
|
currentPage?: CurrentPageContext;
|
|
13
|
+
chatSurface?: string;
|
|
14
|
+
adminPublicOrigin?: string;
|
|
13
15
|
abortSignal?: AbortSignal;
|
|
14
16
|
adminUser: AdminUser;
|
|
15
17
|
emit?: AgentEventEmitter;
|
package/dist/agentTurnService.js
CHANGED
|
@@ -70,6 +70,7 @@ export class AgentTurnService {
|
|
|
70
70
|
adminUser: input.adminUser,
|
|
71
71
|
usernameField: adminforth.config.auth.usernameField,
|
|
72
72
|
userLanguage,
|
|
73
|
+
chatSurface: input.chatSurface,
|
|
73
74
|
});
|
|
74
75
|
const apiBasedTools = buildApiBasedTools(adminforth, this.serviceOptions.getInternalAgentResourceIds());
|
|
75
76
|
const stream = yield callAgent({
|
|
@@ -90,6 +91,8 @@ export class AgentTurnService {
|
|
|
90
91
|
sessionId: input.sessionId,
|
|
91
92
|
turnId: input.turnId,
|
|
92
93
|
currentPage: input.currentPage,
|
|
94
|
+
chatSurface: input.chatSurface,
|
|
95
|
+
adminPublicOrigin: input.adminPublicOrigin,
|
|
93
96
|
userTimeZone: input.userTimeZone,
|
|
94
97
|
abortSignal: input.abortSignal,
|
|
95
98
|
emitToolCallEvent: (event) => {
|
|
@@ -100,6 +103,7 @@ export class AgentTurnService {
|
|
|
100
103
|
data: event,
|
|
101
104
|
}));
|
|
102
105
|
},
|
|
106
|
+
emitAgentEvent: input.emit,
|
|
103
107
|
sequenceDebugSink: input.sequenceDebugCollector,
|
|
104
108
|
});
|
|
105
109
|
try {
|
|
@@ -221,6 +225,8 @@ export class AgentTurnService {
|
|
|
221
225
|
modeName: input.modeName,
|
|
222
226
|
userTimeZone: input.userTimeZone,
|
|
223
227
|
currentPage: input.currentPage,
|
|
228
|
+
chatSurface: input.chatSurface,
|
|
229
|
+
adminPublicOrigin: input.adminPublicOrigin,
|
|
224
230
|
abortSignal: input.abortSignal,
|
|
225
231
|
adminUser: input.adminUser,
|
|
226
232
|
sequenceDebugCollector,
|
|
@@ -268,6 +274,8 @@ export class AgentTurnService {
|
|
|
268
274
|
modeName: input.modeName,
|
|
269
275
|
userTimeZone: input.userTimeZone,
|
|
270
276
|
currentPage: input.currentPage,
|
|
277
|
+
chatSurface: input.chatSurface,
|
|
278
|
+
adminPublicOrigin: input.adminPublicOrigin,
|
|
271
279
|
abortSignal: input.abortSignal,
|
|
272
280
|
adminUser: input.adminUser,
|
|
273
281
|
emit: input.emit,
|
|
@@ -345,6 +353,8 @@ export class AgentTurnService {
|
|
|
345
353
|
modeName: input.modeName,
|
|
346
354
|
userTimeZone: input.userTimeZone,
|
|
347
355
|
currentPage: input.currentPage,
|
|
356
|
+
chatSurface: input.chatSurface,
|
|
357
|
+
adminPublicOrigin: input.adminPublicOrigin,
|
|
348
358
|
abortSignal: input.abortSignal,
|
|
349
359
|
adminUser: input.adminUser,
|
|
350
360
|
emit: (event) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -129,15 +129,20 @@ export class ChatSurfaceService {
|
|
|
129
129
|
}
|
|
130
130
|
handleAgentSurfaceResponse(incoming, sink, adminUser, prompt, options) {
|
|
131
131
|
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
-
var _a, _b, _c;
|
|
132
|
+
var _a, _b, _c, _d;
|
|
133
133
|
const emitDone = (_a = options === null || options === void 0 ? void 0 : options.emitDone) !== null && _a !== void 0 ? _a : true;
|
|
134
|
+
const adminPublicOrigin = typeof ((_b = incoming.metadata) === null || _b === void 0 ? void 0 : _b.adminPublicOrigin) === "string"
|
|
135
|
+
? incoming.metadata.adminPublicOrigin
|
|
136
|
+
: undefined;
|
|
134
137
|
const sessionId = yield this.sessionStore.getOrCreateChatSurfaceSession(Object.assign(Object.assign({}, incoming), { prompt }), adminUser);
|
|
135
138
|
if (emitDone) {
|
|
136
139
|
yield this.handleTurn({
|
|
137
140
|
prompt,
|
|
138
141
|
sessionId,
|
|
139
142
|
modeName: incoming.modeName,
|
|
140
|
-
userTimeZone: (
|
|
143
|
+
userTimeZone: (_c = incoming.userTimeZone) !== null && _c !== void 0 ? _c : "UTC",
|
|
144
|
+
chatSurface: incoming.surface,
|
|
145
|
+
adminPublicOrigin,
|
|
141
146
|
adminUser,
|
|
142
147
|
emit: this.createEventEmitter(sink),
|
|
143
148
|
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
@@ -149,7 +154,9 @@ export class ChatSurfaceService {
|
|
|
149
154
|
prompt,
|
|
150
155
|
sessionId,
|
|
151
156
|
modeName: incoming.modeName,
|
|
152
|
-
userTimeZone: (
|
|
157
|
+
userTimeZone: (_d = incoming.userTimeZone) !== null && _d !== void 0 ? _d : "UTC",
|
|
158
|
+
chatSurface: incoming.surface,
|
|
159
|
+
adminPublicOrigin,
|
|
153
160
|
adminUser,
|
|
154
161
|
emit: this.createEventEmitter(sink),
|
|
155
162
|
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
@@ -13,11 +13,13 @@ type AgentImportMeta = ImportMeta & {
|
|
|
13
13
|
type CreateAgentChatManagerOptions = {
|
|
14
14
|
lastMessage: Ref<string>;
|
|
15
15
|
activeModeName: Ref<string | null>;
|
|
16
|
+
onOpenPage: (targetPath: string) => void;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export function createAgentChatManager({
|
|
19
20
|
lastMessage,
|
|
20
21
|
activeModeName,
|
|
22
|
+
onOpenPage,
|
|
21
23
|
}: CreateAgentChatManagerOptions) {
|
|
22
24
|
const chats = new Map<string, Chat<any>>();
|
|
23
25
|
const currentChat = shallowRef<Chat<any> | null>();
|
|
@@ -52,6 +54,11 @@ export function createAgentChatManager({
|
|
|
52
54
|
onError(error: unknown) {
|
|
53
55
|
console.error('Chat error:', error);
|
|
54
56
|
},
|
|
57
|
+
onData(dataPart: any) {
|
|
58
|
+
if (dataPart?.type === 'data-open-page' && typeof dataPart.data?.targetPath === 'string') {
|
|
59
|
+
onOpenPage(dataPart.data.targetPath);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
55
62
|
});
|
|
56
63
|
chats.set(sessionId, newChat);
|
|
57
64
|
currentChat.value = newChat;
|
|
@@ -67,4 +74,4 @@ export function createAgentChatManager({
|
|
|
67
74
|
setCurrentChat,
|
|
68
75
|
abortCurrentChatRequest,
|
|
69
76
|
};
|
|
70
|
-
}
|
|
77
|
+
}
|
|
@@ -187,6 +187,11 @@ export const useAgentAudio = defineStore('agentAudio', () => {
|
|
|
187
187
|
playStandByAudio();
|
|
188
188
|
}
|
|
189
189
|
agentStore.addDataToolCallMessage(event.data);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (event.type === 'open-page') {
|
|
194
|
+
agentStore.openAgentPage(event.data.targetPath);
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
197
|
|