Skip to content

Commit a782346

Browse files
committed
feat: implement navigation functionality and enhance agent event handling
1 parent 272e6d7 commit a782346

15 files changed

Lines changed: 403 additions & 10 deletions

agent/middleware/sequenceDebug.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export type SequenceDebug = {
2626
reasoningTokens: number;
2727
text: string;
2828
textTokens: number;
29+
uncachedInputTokens: number;
30+
cachedInputTokens: number;
31+
outputTokens: number;
2932
cachedTokens: number;
3033
responseId: string | null;
3134
toolCalls: SequenceDebugToolCall[];
@@ -45,13 +48,17 @@ type SequenceDebugModelCall = {
4548
reasoningTokens: number;
4649
text: string;
4750
textTokens: number;
51+
uncachedInputTokens: number;
52+
cachedInputTokens: number;
53+
outputTokens: number;
4854
cachedTokens: number;
4955
responseId: string | null;
5056
resultType: SequenceDebugResultType;
5157
};
5258

5359
type OpenAiUsageMetadata = {
5460
input_tokens?: number;
61+
output_tokens?: number;
5562
input_token_details?: {
5663
cache_read?: number;
5764
};
@@ -82,6 +89,9 @@ function createPendingSequenceDebug(sequenceId: number): PendingSequenceDebug {
8289
reasoningTokens: 0,
8390
text: "",
8491
textTokens: 0,
92+
uncachedInputTokens: 0,
93+
cachedInputTokens: 0,
94+
outputTokens: 0,
8595
cachedTokens: 0,
8696
responseId: null,
8797
toolCalls: [],
@@ -112,6 +122,9 @@ function finalizeSequenceDebug(sequence: PendingSequenceDebug): SequenceDebug {
112122
reasoningTokens: sequence.reasoningTokens,
113123
text: sequence.text,
114124
textTokens: sequence.textTokens,
125+
uncachedInputTokens: sequence.uncachedInputTokens,
126+
cachedInputTokens: sequence.cachedInputTokens,
127+
outputTokens: sequence.outputTokens,
115128
cachedTokens: sequence.cachedTokens,
116129
responseId: sequence.responseId,
117130
toolCalls: sequence.toolCalls.map(({ completed: _completed, ...toolCall }) => toolCall),
@@ -133,6 +146,21 @@ function getDebugModelName(model: SequenceDebugPromptModel) {
133146
return typeof model.getName === "function" ? model.getName() : undefined;
134147
}
135148

149+
function getDebugToolName(tool: unknown) {
150+
if (!tool || typeof tool !== "object") {
151+
return null;
152+
}
153+
154+
const name = (tool as { name?: unknown }).name;
155+
return typeof name === "string" ? name : null;
156+
}
157+
158+
function formatToolsForDebug(tools: unknown[]) {
159+
return tools.map((tool) => ({
160+
name: getDebugToolName(tool),
161+
}));
162+
}
163+
136164
function stringifyPromptForDebug(params: {
137165
model: SequenceDebugPromptModel;
138166
systemMessage: { text: string };
@@ -160,7 +188,7 @@ function stringifyPromptForDebug(params: {
160188
},
161189
systemMessage,
162190
messages,
163-
...(tools.length > 0 ? { tools } : {}),
191+
...(tools.length > 0 ? { tools: formatToolsForDebug(tools) } : {}),
164192
...(toolChoice !== undefined ? { toolChoice } : {}),
165193
...(modelSettings ? { modelSettings } : {}),
166194
...(invocationParams ? { invocationParams } : {}),
@@ -219,6 +247,9 @@ async function countTokens(model: unknown, content: string) {
219247
}
220248

221249
function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCall {
250+
const usageMetadata = message.usage_metadata as OpenAiUsageMetadata | undefined;
251+
const promptTokens = usageMetadata?.input_tokens ?? 0;
252+
const cachedInputTokens = usageMetadata?.input_token_details?.cache_read ?? 0;
222253
const blocks = getMessageBlocks(message);
223254
const reasoning = blocks
224255
.filter((block: any) => block?.type === "reasoning")
@@ -230,16 +261,15 @@ function extractSequenceResponseDebug(message: AIMessage): SequenceDebugModelCal
230261
.join("");
231262

232263
return {
233-
promptTokens:
234-
(message.usage_metadata as OpenAiUsageMetadata | undefined)?.input_tokens ??
235-
0,
264+
promptTokens,
236265
reasoning,
237266
reasoningTokens: 0,
238267
text: textFromBlocks || (typeof message.content === "string" ? message.content : ""),
239268
textTokens: 0,
240-
cachedTokens:
241-
(message.usage_metadata as OpenAiUsageMetadata | undefined)
242-
?.input_token_details?.cache_read ?? 0,
269+
uncachedInputTokens: Math.max(promptTokens - cachedInputTokens, 0),
270+
cachedInputTokens,
271+
outputTokens: usageMetadata?.output_tokens ?? 0,
272+
cachedTokens: cachedInputTokens,
243273
responseId:
244274
(message.response_metadata as OpenAiResponseMetadata | undefined)?.id ??
245275
null,
@@ -290,6 +320,9 @@ export function createSequenceDebugCollector(): SequenceDebugCollector {
290320
sequenceDebug.reasoningTokens = params.reasoningTokens;
291321
sequenceDebug.text = params.text;
292322
sequenceDebug.textTokens = params.textTokens;
323+
sequenceDebug.uncachedInputTokens = params.uncachedInputTokens;
324+
sequenceDebug.cachedInputTokens = params.cachedInputTokens;
325+
sequenceDebug.outputTokens = params.outputTokens;
293326
sequenceDebug.cachedTokens = params.cachedTokens;
294327
sequenceDebug.responseId = params.responseId;
295328
sequenceDebug.resultType = params.resultType;
@@ -387,6 +420,8 @@ export function createSequenceDebugMiddleware(
387420
promptTokens,
388421
reasoningTokens,
389422
textTokens,
423+
uncachedInputTokens: debug.promptTokens ? debug.uncachedInputTokens : promptTokens,
424+
outputTokens: debug.outputTokens || reasoningTokens + textTokens,
390425
});
391426
return response;
392427
},

agent/simpleAgent.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import type { ApiBasedTool } from "../apiBasedTools.js";
2020
import type { ToolCallEventSink } from "./toolCallEvents.js";
2121
import type { CurrentPageContext } from "./tools/getUserLocation.js";
22+
import type { AgentEventEmitter } from "../agentEvents.js";
2223

2324
export const contextSchema = z.object({
2425
adminUser: z.custom<AdminUser>(),
@@ -27,7 +28,11 @@ export const contextSchema = z.object({
2728
turnId: z.string(),
2829
abortSignal: z.custom<AbortSignal>().optional(),
2930
currentPage: z.custom<CurrentPageContext>().optional(),
31+
chatSurface: z.string().optional(),
32+
adminBaseUrl: z.string().optional(),
33+
adminPublicOrigin: z.string().optional(),
3034
emitToolCallEvent: z.custom<ToolCallEventSink>(),
35+
emitAgentEvent: z.custom<AgentEventEmitter>().optional(),
3136
});
3237

3338
export type AgentChatModel = BaseChatModel<any, any>;
@@ -234,9 +239,12 @@ export async function callAgent(params: {
234239
sessionId: string;
235240
turnId: string;
236241
currentPage?: CurrentPageContext;
242+
chatSurface?: string;
243+
adminPublicOrigin?: string;
237244
userTimeZone: string;
238245
abortSignal?: AbortSignal;
239246
emitToolCallEvent: ToolCallEventSink;
247+
emitAgentEvent?: AgentEventEmitter;
240248
sequenceDebugSink: SequenceDebugModelCallSink;
241249
}) {
242250
const {
@@ -254,9 +262,12 @@ export async function callAgent(params: {
254262
sessionId,
255263
turnId,
256264
currentPage,
265+
chatSurface,
266+
adminPublicOrigin,
257267
userTimeZone,
258268
abortSignal,
259269
emitToolCallEvent,
270+
emitAgentEvent,
260271
sequenceDebugSink,
261272
} = params;
262273

@@ -305,7 +316,11 @@ export async function callAgent(params: {
305316
turnId,
306317
abortSignal,
307318
currentPage,
319+
chatSurface,
320+
adminBaseUrl: adminforth.config.baseUrlSlashed,
321+
adminPublicOrigin,
308322
emitToolCallEvent,
323+
emitAgentEvent,
309324
},
310325
});
311326
}

agent/systemPrompt.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,16 @@ export function buildAgentTurnSystemPrompt(input: {
7070
adminUser: AdminUser;
7171
usernameField: string;
7272
userLanguage: DetectedLanguage | null;
73+
chatSurface?: string;
7374
}) {
7475
return [
7576
input.agentSystemPrompt,
7677
formatAdminUserPrompt(input.adminUser, input.usernameField),
78+
input.chatSurface
79+
? `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.`
80+
: "",
7781
formatLanguagePrompt(input.userLanguage),
78-
].join("\n\n");
82+
].filter(Boolean).join("\n\n");
7983
}
8084

8185
function formatResources(resources: AdminForthResource[]) {
@@ -124,6 +128,7 @@ export async function buildAgentSystemPrompt(
124128
"When fetch_tool_schema succeeds, that tool becomes available on the next step.",
125129
"All admin links must be root-relative and start with '/'.",
126130
"Build record links as '/resource/{resourceId}/show/{primary key}'. Never use bare 'resource/{resourceId}/show/{primary key}' without the leading slash.",
131+
"When the user asks to open or show a page in the AdminForth UI, call navigate_user instead of only sending a link.",
127132
"Try to call as many tools as possible in parallel in one step.",
128133
];
129134

agent/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createFetchToolSchemaTool } from "./fetchToolSchema.js";
44
import type { ApiBasedTool } from "../../apiBasedTools.js";
55
import { createApiTool } from "./apiTool.js";
66
import { createGetUserLocationTool } from "./getUserLocation.js";
7+
import { createNavigateUserTool } from "./navigateUser.js";
78

89
export const ALWAYS_AVAILABLE_API_TOOL_NAMES = ["get_resource"] as const;
910

@@ -23,6 +24,7 @@ export async function createAgentTools(
2324
return createApiTool(toolName, apiBasedTool);
2425
}),
2526
createGetUserLocationTool(),
27+
createNavigateUserTool(),
2628
await createFetchSkillTool(customComponentsDir, pluginCustomFolderPaths),
2729
await createFetchToolSchemaTool(apiBasedTools),
2830
];

0 commit comments

Comments
 (0)