@adminforth/agent 1.24.13 → 1.25.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/simpleAgent.ts +5 -0
- package/agent/systemPrompt.ts +1 -1
- package/agent/tools/apiTool.ts +1 -0
- package/apiBasedTools.ts +209 -224
- package/build.log +4 -4
- package/custom/skills/{data-analytics → analyze_data}/SKILL.md +1 -1
- package/dist/agent/simpleAgent.js +3 -1
- package/dist/agent/systemPrompt.js +1 -1
- package/dist/agent/tools/apiTool.js +1 -0
- package/dist/apiBasedTools.js +147 -137
- package/dist/custom/skills/{data-analytics → analyze_data}/SKILL.md +1 -1
- package/dist/index.js +16 -7
- package/index.ts +16 -7
- package/package.json +1 -1
package/agent/simpleAgent.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
logger,
|
|
5
5
|
type AdminUser,
|
|
6
6
|
type CompletionAdapter,
|
|
7
|
+
type HttpExtra,
|
|
7
8
|
type IAdminForth,
|
|
8
9
|
} from "adminforth";
|
|
9
10
|
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
@@ -24,6 +25,7 @@ export const contextSchema = z.object({
|
|
|
24
25
|
userTimeZone: z.string(),
|
|
25
26
|
sessionId: z.string(),
|
|
26
27
|
turnId: z.string(),
|
|
28
|
+
httpExtra: z.custom<Partial<HttpExtra>>().optional(),
|
|
27
29
|
emitToolCallEvent: z.custom<ToolCallEventSink>(),
|
|
28
30
|
});
|
|
29
31
|
|
|
@@ -229,6 +231,7 @@ export async function callAgent(params: {
|
|
|
229
231
|
customComponentsDir: string;
|
|
230
232
|
sessionId: string;
|
|
231
233
|
turnId: string;
|
|
234
|
+
httpExtra?: Partial<HttpExtra>;
|
|
232
235
|
userTimeZone: string;
|
|
233
236
|
emitToolCallEvent: ToolCallEventSink;
|
|
234
237
|
sequenceDebugSink: SequenceDebugModelCallSink;
|
|
@@ -246,6 +249,7 @@ export async function callAgent(params: {
|
|
|
246
249
|
customComponentsDir,
|
|
247
250
|
sessionId,
|
|
248
251
|
turnId,
|
|
252
|
+
httpExtra,
|
|
249
253
|
userTimeZone,
|
|
250
254
|
emitToolCallEvent,
|
|
251
255
|
sequenceDebugSink,
|
|
@@ -289,6 +293,7 @@ export async function callAgent(params: {
|
|
|
289
293
|
userTimeZone,
|
|
290
294
|
sessionId,
|
|
291
295
|
turnId,
|
|
296
|
+
httpExtra,
|
|
292
297
|
emitToolCallEvent,
|
|
293
298
|
},
|
|
294
299
|
});
|
package/agent/systemPrompt.ts
CHANGED
|
@@ -83,7 +83,7 @@ export async function buildAgentSystemPrompt(adminforth: IAdminForth) {
|
|
|
83
83
|
"The fetched skill response starts with 'Tools mentioned in this skill'. Read that list first.",
|
|
84
84
|
"You can use get_resource immediately to inspect resource structure and column names.",
|
|
85
85
|
"If the user wants to create, update, delete, or run actions on records, load mutate_data first.",
|
|
86
|
-
"If the user wants to fetch records, load fetch_data first. If the user wants analytics or charts, load
|
|
86
|
+
"If the user wants to fetch records, load fetch_data first. If the user wants analytics or charts, load analyze_data first.",
|
|
87
87
|
"Only call fetch_tool_schema for tool names that are explicitly mentioned in a fetched skill and are not already available as base tools.",
|
|
88
88
|
"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.",
|
|
89
89
|
"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.",
|
package/agent/tools/apiTool.ts
CHANGED
|
@@ -51,6 +51,7 @@ export function createApiTool(toolName: string, apiBasedTool: ApiBasedTool) {
|
|
|
51
51
|
const normalizedInput = (input ?? {}) as Record<string, unknown>;
|
|
52
52
|
return apiBasedTool.call({
|
|
53
53
|
adminUser: runtime.context.adminUser,
|
|
54
|
+
httpExtra: runtime.context.httpExtra,
|
|
54
55
|
inputs: normalizedInput,
|
|
55
56
|
userTimeZone: runtime.context.userTimeZone,
|
|
56
57
|
});
|
package/apiBasedTools.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AdminForthDataTypes,
|
|
3
|
+
logger,
|
|
3
4
|
type AdminUser,
|
|
4
5
|
type HttpExtra,
|
|
5
6
|
type IAdminForth,
|
|
6
|
-
type IAdminForthHttpResponse,
|
|
7
|
-
type IHttpServer,
|
|
8
7
|
type IRegisteredApiSchema,
|
|
9
8
|
} from 'adminforth';
|
|
10
9
|
import dayjs from 'dayjs';
|
|
11
10
|
import timezone from 'dayjs/plugin/timezone.js';
|
|
12
11
|
import utc from 'dayjs/plugin/utc.js';
|
|
13
|
-
import { PassThrough } from 'stream';
|
|
14
12
|
import { inspect } from 'util';
|
|
15
13
|
import YAML from 'yaml';
|
|
16
14
|
|
|
@@ -22,47 +20,6 @@ type CookieItem = {
|
|
|
22
20
|
value: string;
|
|
23
21
|
};
|
|
24
22
|
|
|
25
|
-
type CapturedEndpointHandlerInput = {
|
|
26
|
-
body: Record<string, unknown>;
|
|
27
|
-
adminUser?: AdminUser;
|
|
28
|
-
query: Record<string, string>;
|
|
29
|
-
headers: Record<string, any>;
|
|
30
|
-
cookies: CookieItem[];
|
|
31
|
-
response: IAdminForthHttpResponse;
|
|
32
|
-
requestUrl: string;
|
|
33
|
-
abortSignal: AbortSignal;
|
|
34
|
-
_raw_express_req: any;
|
|
35
|
-
_raw_express_res: any;
|
|
36
|
-
tr: (
|
|
37
|
-
msg: string,
|
|
38
|
-
category: string,
|
|
39
|
-
params: any,
|
|
40
|
-
pluralizationNumber?: number,
|
|
41
|
-
) => Promise<string>;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type EndpointWithSchemas = {
|
|
45
|
-
method: string;
|
|
46
|
-
noAuth?: boolean;
|
|
47
|
-
path: string;
|
|
48
|
-
description?: string;
|
|
49
|
-
request_schema?: unknown;
|
|
50
|
-
response_schema?: unknown;
|
|
51
|
-
responce_schema?: unknown;
|
|
52
|
-
handler: (input: CapturedEndpointHandlerInput) => Promise<any> | any;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
type CapturedEndpoint = EndpointWithSchemas & {
|
|
56
|
-
normalizedResponseSchema?: unknown;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
type ToolHttpResponse = IAdminForthHttpResponse & {
|
|
60
|
-
headers: Array<[string, string]>;
|
|
61
|
-
jsonPayload?: unknown;
|
|
62
|
-
status: number;
|
|
63
|
-
message?: string;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
23
|
type ToolOverrideCallParams = Pick<ApiBasedToolCallParams, 'httpExtra' | 'inputs' | 'userTimeZone'>;
|
|
67
24
|
|
|
68
25
|
type ToolOverrideContext = {
|
|
@@ -388,9 +345,9 @@ function formatDateTimeColumns(
|
|
|
388
345
|
async function applyToolOverride(params: {
|
|
389
346
|
adminforth: IAdminForth;
|
|
390
347
|
adminUser?: AdminUser;
|
|
391
|
-
capturedEndpointsByToolName: Record<string, CapturedEndpoint>;
|
|
392
348
|
httpExtra?: Partial<HttpExtra>;
|
|
393
349
|
inputs?: Record<string, unknown>;
|
|
350
|
+
invokeTool: (toolName: string, params?: ToolOverrideCallParams) => Promise<unknown>;
|
|
394
351
|
output: unknown;
|
|
395
352
|
toolName: string;
|
|
396
353
|
userTimeZone?: string;
|
|
@@ -398,9 +355,9 @@ async function applyToolOverride(params: {
|
|
|
398
355
|
const {
|
|
399
356
|
adminforth,
|
|
400
357
|
adminUser,
|
|
401
|
-
capturedEndpointsByToolName,
|
|
402
358
|
httpExtra,
|
|
403
359
|
inputs,
|
|
360
|
+
invokeTool,
|
|
404
361
|
output,
|
|
405
362
|
toolName,
|
|
406
363
|
userTimeZone,
|
|
@@ -427,19 +384,10 @@ async function applyToolOverride(params: {
|
|
|
427
384
|
inputs,
|
|
428
385
|
userTimeZone,
|
|
429
386
|
invokeTool: async (nestedToolName, nestedParams = {}) => {
|
|
430
|
-
const nestedEndpoint = capturedEndpointsByToolName[nestedToolName];
|
|
431
|
-
|
|
432
|
-
if (!nestedEndpoint) {
|
|
433
|
-
throw new Error(`Tool ${nestedToolName} is not registered`);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
387
|
const nestedInputs = nestedParams.inputs ?? inputs;
|
|
437
388
|
const nestedHttpExtra = nestedParams.httpExtra ?? httpExtra;
|
|
438
389
|
const nestedUserTimeZone = nestedParams.userTimeZone ?? userTimeZone;
|
|
439
|
-
const nestedOutput = await
|
|
440
|
-
adminforth,
|
|
441
|
-
endpoint: nestedEndpoint,
|
|
442
|
-
adminUser,
|
|
390
|
+
const nestedOutput = await invokeTool(nestedToolName, {
|
|
443
391
|
inputs: nestedInputs,
|
|
444
392
|
httpExtra: nestedHttpExtra,
|
|
445
393
|
userTimeZone: nestedUserTimeZone,
|
|
@@ -448,9 +396,9 @@ async function applyToolOverride(params: {
|
|
|
448
396
|
return applyToolOverride({
|
|
449
397
|
adminforth,
|
|
450
398
|
adminUser,
|
|
451
|
-
capturedEndpointsByToolName,
|
|
452
399
|
httpExtra: nestedHttpExtra,
|
|
453
400
|
inputs: nestedInputs,
|
|
401
|
+
invokeTool,
|
|
454
402
|
output: nestedOutput,
|
|
455
403
|
toolName: nestedToolName,
|
|
456
404
|
userTimeZone: nestedUserTimeZone,
|
|
@@ -487,6 +435,10 @@ function openApiSchemaPathToToolName(path: string, adminforth: IAdminForth) {
|
|
|
487
435
|
return endpointPathToToolName(stripAdminApiPrefix(path, adminforth));
|
|
488
436
|
}
|
|
489
437
|
|
|
438
|
+
function formatLogNameList(names: string[]) {
|
|
439
|
+
return names.length ? names.join(', ') : '(none)';
|
|
440
|
+
}
|
|
441
|
+
|
|
490
442
|
export async function formatApiBasedToolCall(params: {
|
|
491
443
|
adminforth: IAdminForth;
|
|
492
444
|
adminUser?: AdminUser;
|
|
@@ -523,84 +475,6 @@ function normalizeCookies(
|
|
|
523
475
|
return Object.entries(cookies).map(([key, value]) => ({ key, value }));
|
|
524
476
|
}
|
|
525
477
|
|
|
526
|
-
function createToolResponse(baseResponse?: IAdminForthHttpResponse): ToolHttpResponse {
|
|
527
|
-
return {
|
|
528
|
-
headers: [],
|
|
529
|
-
status: 200,
|
|
530
|
-
message: undefined,
|
|
531
|
-
setHeader(name, value) {
|
|
532
|
-
this.headers.push([name, value]);
|
|
533
|
-
baseResponse?.setHeader(name, value);
|
|
534
|
-
},
|
|
535
|
-
setStatus(code, message) {
|
|
536
|
-
this.status = code;
|
|
537
|
-
this.message = message;
|
|
538
|
-
baseResponse?.setStatus(code, message);
|
|
539
|
-
},
|
|
540
|
-
blobStream() {
|
|
541
|
-
return baseResponse?.blobStream() ?? new PassThrough();
|
|
542
|
-
},
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
function createRawExpressRequest(params: {
|
|
547
|
-
adminUser?: AdminUser;
|
|
548
|
-
body: Record<string, unknown>;
|
|
549
|
-
cookies: CookieItem[];
|
|
550
|
-
headers: Record<string, any>;
|
|
551
|
-
method: string;
|
|
552
|
-
query: Record<string, string>;
|
|
553
|
-
requestUrl: string;
|
|
554
|
-
}) {
|
|
555
|
-
const cookieHeader = params.cookies
|
|
556
|
-
.map(({ key, value }) => `${key}=${value}`)
|
|
557
|
-
.join('; ');
|
|
558
|
-
|
|
559
|
-
return {
|
|
560
|
-
adminUser: params.adminUser,
|
|
561
|
-
body: params.body,
|
|
562
|
-
destroyed: false,
|
|
563
|
-
headers: {
|
|
564
|
-
...params.headers,
|
|
565
|
-
...(cookieHeader ? { cookie: cookieHeader } : {}),
|
|
566
|
-
},
|
|
567
|
-
method: params.method.toUpperCase(),
|
|
568
|
-
on: () => undefined,
|
|
569
|
-
query: params.query,
|
|
570
|
-
url: params.requestUrl,
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function createRawExpressResponse(response: ToolHttpResponse) {
|
|
575
|
-
const rawResponse = {
|
|
576
|
-
destroyed: false,
|
|
577
|
-
on: () => undefined,
|
|
578
|
-
setHeader(name: string, value: string) {
|
|
579
|
-
response.setHeader(name, value);
|
|
580
|
-
return rawResponse;
|
|
581
|
-
},
|
|
582
|
-
status(code: number) {
|
|
583
|
-
response.status = code;
|
|
584
|
-
return rawResponse;
|
|
585
|
-
},
|
|
586
|
-
send(message: string) {
|
|
587
|
-
response.message = message;
|
|
588
|
-
return rawResponse;
|
|
589
|
-
},
|
|
590
|
-
json(payload: unknown) {
|
|
591
|
-
response.jsonPayload = payload;
|
|
592
|
-
response.message = JSON.stringify(payload);
|
|
593
|
-
return rawResponse;
|
|
594
|
-
},
|
|
595
|
-
write: () => true,
|
|
596
|
-
writeHead: () => rawResponse,
|
|
597
|
-
writableEnded: false,
|
|
598
|
-
end: () => rawResponse,
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
return rawResponse;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
478
|
function normalizeDateTimeInputsToUtc(
|
|
605
479
|
body: Record<string, unknown>,
|
|
606
480
|
adminforth: IAdminForth,
|
|
@@ -679,100 +553,192 @@ function normalizeDateTimeInputsToUtc(
|
|
|
679
553
|
return normalizeValue(body) as Record<string, unknown>;
|
|
680
554
|
}
|
|
681
555
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
556
|
+
const METHODS_WITHOUT_REQUEST_BODY = new Set(['GET', 'HEAD']);
|
|
557
|
+
const HEADERS_NOT_FORWARDED_TO_API_TOOL = new Set([
|
|
558
|
+
'connection',
|
|
559
|
+
'content-length',
|
|
560
|
+
'host',
|
|
561
|
+
'keep-alive',
|
|
562
|
+
'proxy-authenticate',
|
|
563
|
+
'proxy-authorization',
|
|
564
|
+
'te',
|
|
565
|
+
'trailer',
|
|
566
|
+
'transfer-encoding',
|
|
567
|
+
'upgrade',
|
|
568
|
+
]);
|
|
569
|
+
|
|
570
|
+
function getHeaderValue(
|
|
571
|
+
headers: Partial<HttpExtra>['headers'] | undefined,
|
|
572
|
+
headerName: string,
|
|
573
|
+
) {
|
|
574
|
+
const normalizedHeaderName = headerName.toLowerCase();
|
|
575
|
+
const value = Object.entries(headers ?? {}).find(
|
|
576
|
+
([name]) => name.toLowerCase() === normalizedHeaderName,
|
|
577
|
+
)?.[1];
|
|
578
|
+
|
|
579
|
+
if (typeof value !== 'string') {
|
|
580
|
+
return undefined;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return value.split(',')[0].trim();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function isAbsoluteHttpUrl(value: string) {
|
|
587
|
+
try {
|
|
588
|
+
const url = new URL(value);
|
|
589
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
590
|
+
} catch {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function getRequestOrigin(httpExtra?: Partial<HttpExtra>) {
|
|
596
|
+
const requestUrl = httpExtra?.requestUrl;
|
|
597
|
+
|
|
598
|
+
if (requestUrl && isAbsoluteHttpUrl(requestUrl)) {
|
|
599
|
+
return new URL(requestUrl).origin;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const host = getHeaderValue(httpExtra?.headers, 'x-forwarded-host')
|
|
603
|
+
?? getHeaderValue(httpExtra?.headers, 'host');
|
|
604
|
+
|
|
605
|
+
if (!host) {
|
|
606
|
+
return undefined;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const protocol = getHeaderValue(httpExtra?.headers, 'x-forwarded-proto') ?? 'http';
|
|
610
|
+
return `${protocol}://${host}`;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function resolveOpenApiRequestUrl(params: {
|
|
686
614
|
httpExtra?: Partial<HttpExtra>;
|
|
687
|
-
|
|
688
|
-
|
|
615
|
+
path: string;
|
|
616
|
+
toolName: string;
|
|
689
617
|
}) {
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
headers,
|
|
711
|
-
method: endpoint.method,
|
|
712
|
-
query,
|
|
713
|
-
requestUrl,
|
|
714
|
-
});
|
|
715
|
-
const rawResponse = createRawExpressResponse(response);
|
|
716
|
-
const acceptLanguage = headers['accept-language'];
|
|
717
|
-
const tr = (
|
|
718
|
-
msg: string,
|
|
719
|
-
category: string = 'default',
|
|
720
|
-
translationParams: any,
|
|
721
|
-
pluralizationNumber?: number,
|
|
722
|
-
) => adminforth.tr(msg, category, acceptLanguage, translationParams, pluralizationNumber);
|
|
723
|
-
|
|
724
|
-
const output = await endpoint.handler({
|
|
725
|
-
body,
|
|
726
|
-
adminUser,
|
|
727
|
-
query,
|
|
728
|
-
headers,
|
|
729
|
-
cookies,
|
|
730
|
-
response,
|
|
731
|
-
requestUrl,
|
|
732
|
-
abortSignal: abortController.signal,
|
|
733
|
-
_raw_express_req: rawRequest,
|
|
734
|
-
_raw_express_res: rawResponse,
|
|
735
|
-
tr,
|
|
736
|
-
});
|
|
618
|
+
if (isAbsoluteHttpUrl(params.path)) {
|
|
619
|
+
return params.path;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const origin = getRequestOrigin(params.httpExtra);
|
|
623
|
+
|
|
624
|
+
if (!origin) {
|
|
625
|
+
throw new Error(
|
|
626
|
+
`Tool "${params.toolName}" has relative OpenAPI path "${params.path}" but request host header is unavailable.`,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return new URL(params.path, origin).toString();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function createToolRequestHeaders(
|
|
634
|
+
httpExtra: Partial<HttpExtra> | undefined,
|
|
635
|
+
userTimeZone?: string,
|
|
636
|
+
) {
|
|
637
|
+
const headers: Record<string, string> = {};
|
|
737
638
|
|
|
738
|
-
|
|
739
|
-
|
|
639
|
+
for (const [name, value] of Object.entries(httpExtra?.headers ?? {})) {
|
|
640
|
+
const headerName = name.toLowerCase();
|
|
641
|
+
|
|
642
|
+
if (typeof value === 'string' && !HEADERS_NOT_FORWARDED_TO_API_TOOL.has(headerName)) {
|
|
643
|
+
headers[headerName] = value;
|
|
644
|
+
}
|
|
740
645
|
}
|
|
741
646
|
|
|
742
|
-
|
|
743
|
-
|
|
647
|
+
headers.accept = 'application/json';
|
|
648
|
+
headers['content-type'] = 'application/json';
|
|
649
|
+
|
|
650
|
+
if (userTimeZone) {
|
|
651
|
+
headers['x-timezone'] = userTimeZone;
|
|
744
652
|
}
|
|
745
653
|
|
|
746
|
-
|
|
747
|
-
|
|
654
|
+
const cookieHeader = normalizeCookies(httpExtra?.cookies)
|
|
655
|
+
.map(({ key, value }) => `${key}=${value}`)
|
|
656
|
+
.join('; ');
|
|
657
|
+
|
|
658
|
+
if (cookieHeader && !headers.cookie) {
|
|
659
|
+
headers.cookie = cookieHeader;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return headers;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function appendInputsToQueryString(url: string, inputs: Record<string, unknown>) {
|
|
666
|
+
const nextUrl = new URL(url);
|
|
667
|
+
|
|
668
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
669
|
+
if (value === undefined) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (Array.isArray(value)) {
|
|
674
|
+
for (const item of value) {
|
|
675
|
+
nextUrl.searchParams.append(
|
|
676
|
+
key,
|
|
677
|
+
typeof item === 'object' && item !== null ? JSON.stringify(item) : String(item),
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
nextUrl.searchParams.set(
|
|
684
|
+
key,
|
|
685
|
+
typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value),
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
return nextUrl.toString();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function parseOpenApiToolResponse(response: Response) {
|
|
693
|
+
const responseText = await response.text();
|
|
694
|
+
const payload = responseText && response.headers.get('content-type')?.includes('application/json')
|
|
695
|
+
? JSON.parse(responseText)
|
|
696
|
+
: responseText;
|
|
697
|
+
|
|
698
|
+
if (response.ok) {
|
|
699
|
+
return responseText ? payload : { status: response.status };
|
|
748
700
|
}
|
|
749
701
|
|
|
750
702
|
return {
|
|
751
|
-
|
|
703
|
+
error: 'HTTP_ERROR',
|
|
752
704
|
status: response.status,
|
|
705
|
+
statusText: response.statusText,
|
|
706
|
+
response: payload,
|
|
753
707
|
};
|
|
754
708
|
}
|
|
755
709
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
}) as
|
|
768
|
-
|
|
710
|
+
async function callOpenApiSchema(params: {
|
|
711
|
+
adminforth: IAdminForth;
|
|
712
|
+
httpExtra?: Partial<HttpExtra>;
|
|
713
|
+
inputs?: Record<string, unknown>;
|
|
714
|
+
schema: IRegisteredApiSchema;
|
|
715
|
+
toolName: string;
|
|
716
|
+
userTimeZone?: string;
|
|
717
|
+
}) {
|
|
718
|
+
const { adminforth, httpExtra, inputs, schema, toolName, userTimeZone } = params;
|
|
719
|
+
const method = schema.method.toUpperCase();
|
|
720
|
+
const body = normalizeDateTimeInputsToUtc(
|
|
721
|
+
(inputs ?? httpExtra?.body ?? {}) as Record<string, unknown>,
|
|
722
|
+
adminforth,
|
|
723
|
+
userTimeZone,
|
|
724
|
+
);
|
|
725
|
+
const requestUrl = resolveOpenApiRequestUrl({
|
|
726
|
+
httpExtra,
|
|
727
|
+
path: schema.path,
|
|
728
|
+
toolName,
|
|
729
|
+
});
|
|
730
|
+
const hasRequestBody = !METHODS_WITHOUT_REQUEST_BODY.has(method);
|
|
731
|
+
const response = await fetch(hasRequestBody ? requestUrl : appendInputsToQueryString(requestUrl, body), {
|
|
732
|
+
method,
|
|
733
|
+
headers: createToolRequestHeaders(httpExtra, userTimeZone),
|
|
734
|
+
body: hasRequestBody ? JSON.stringify(body) : undefined,
|
|
735
|
+
});
|
|
769
736
|
|
|
770
|
-
|
|
737
|
+
return parseOpenApiToolResponse(response);
|
|
738
|
+
}
|
|
771
739
|
|
|
740
|
+
export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, ApiBasedTool> {
|
|
772
741
|
const apiBasedTools: Record<string, ApiBasedTool> = {};
|
|
773
|
-
const capturedEndpointsByToolName = Object.fromEntries(
|
|
774
|
-
capturedEndpoints.map((endpoint) => [endpointPathToToolName(endpoint.path), endpoint]),
|
|
775
|
-
);
|
|
776
742
|
const openApiSchemas = adminforth.openApi.registeredSchemas.filter(
|
|
777
743
|
(schema) => schema.request_schema || schema.response_schema,
|
|
778
744
|
);
|
|
@@ -783,24 +749,43 @@ export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, Ap
|
|
|
783
749
|
openApiSchemasByToolName.set(toolName, schema);
|
|
784
750
|
}
|
|
785
751
|
|
|
752
|
+
logger.info(
|
|
753
|
+
`AdminForth Agent OpenAPI APIs: ${formatLogNameList(
|
|
754
|
+
adminforth.openApi.registeredSchemas.map((schema) => openApiSchemaPathToToolName(schema.path, adminforth)),
|
|
755
|
+
)}`,
|
|
756
|
+
);
|
|
757
|
+
logger.info(
|
|
758
|
+
`AdminForth Agent OpenAPI tools connected: ${formatLogNameList([...openApiSchemasByToolName.keys()])}`,
|
|
759
|
+
);
|
|
760
|
+
|
|
786
761
|
for (const [toolName, schema] of openApiSchemasByToolName.entries()) {
|
|
787
|
-
const endpoint = capturedEndpointsByToolName[toolName];
|
|
788
762
|
apiBasedTools[toolName] = {
|
|
789
763
|
description: schema.description,
|
|
790
764
|
input_schema: schema.request_schema,
|
|
791
765
|
input_schma: schema.request_schema,
|
|
792
766
|
output_schema: schema.response_schema,
|
|
793
767
|
call: async ({ adminUser, adminuser, inputs, httpExtra, userTimeZone } = {}) => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
768
|
+
const invokeTool = async (
|
|
769
|
+
nextToolName: string,
|
|
770
|
+
nextParams: ToolOverrideCallParams = {},
|
|
771
|
+
) => {
|
|
772
|
+
const nextSchema = openApiSchemasByToolName.get(nextToolName);
|
|
773
|
+
|
|
774
|
+
if (!nextSchema) {
|
|
775
|
+
throw new Error(`Tool ${nextToolName} is not registered in OpenAPI`);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return callOpenApiSchema({
|
|
779
|
+
adminforth,
|
|
780
|
+
schema: nextSchema,
|
|
781
|
+
toolName: nextToolName,
|
|
782
|
+
inputs: nextParams.inputs,
|
|
783
|
+
httpExtra: nextParams.httpExtra,
|
|
784
|
+
userTimeZone: nextParams.userTimeZone,
|
|
785
|
+
});
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
const output = await invokeTool(toolName, {
|
|
804
789
|
inputs,
|
|
805
790
|
httpExtra,
|
|
806
791
|
userTimeZone,
|
|
@@ -809,9 +794,9 @@ export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, Ap
|
|
|
809
794
|
const processedOutput = await applyToolOverride({
|
|
810
795
|
adminforth,
|
|
811
796
|
adminUser: adminUser ?? adminuser,
|
|
812
|
-
capturedEndpointsByToolName,
|
|
813
797
|
httpExtra,
|
|
814
798
|
inputs,
|
|
799
|
+
invokeTool,
|
|
815
800
|
output,
|
|
816
801
|
toolName,
|
|
817
802
|
userTimeZone,
|
package/build.log
CHANGED
|
@@ -31,12 +31,12 @@ custom/incremark_code_renderers/incremarkCodeHighlight.ts
|
|
|
31
31
|
custom/incremark_code_renderers/incremarkRenderer.ts
|
|
32
32
|
custom/incremark_code_renderers/renderIncremarkMarkdown.ts
|
|
33
33
|
custom/skills/
|
|
34
|
-
custom/skills/
|
|
35
|
-
custom/skills/
|
|
34
|
+
custom/skills/analyze_data/
|
|
35
|
+
custom/skills/analyze_data/SKILL.md
|
|
36
36
|
custom/skills/fetch_data/
|
|
37
37
|
custom/skills/fetch_data/SKILL.md
|
|
38
38
|
custom/skills/mutate_data/
|
|
39
39
|
custom/skills/mutate_data/SKILL.md
|
|
40
40
|
|
|
41
|
-
sent 202,
|
|
42
|
-
total size is 199,
|
|
41
|
+
sent 202,067 bytes received 566 bytes 405,266.00 bytes/sec
|
|
42
|
+
total size is 199,749 speedup is 0.99
|
|
@@ -19,6 +19,7 @@ export const contextSchema = z.object({
|
|
|
19
19
|
userTimeZone: z.string(),
|
|
20
20
|
sessionId: z.string(),
|
|
21
21
|
turnId: z.string(),
|
|
22
|
+
httpExtra: z.custom().optional(),
|
|
22
23
|
emitToolCallEvent: z.custom(),
|
|
23
24
|
});
|
|
24
25
|
function isLangChainAgentCompletionAdapter(adapter) {
|
|
@@ -130,7 +131,7 @@ export function createAgentChatModel(params) {
|
|
|
130
131
|
}
|
|
131
132
|
export function callAgent(params) {
|
|
132
133
|
return __awaiter(this, void 0, void 0, function* () {
|
|
133
|
-
const { name, model, summaryModel, modelMiddleware = [], checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, sessionId, turnId, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
|
|
134
|
+
const { name, model, summaryModel, modelMiddleware = [], checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, sessionId, turnId, httpExtra, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
|
|
134
135
|
const tools = yield createAgentTools(customComponentsDir, apiBasedTools);
|
|
135
136
|
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
|
|
136
137
|
const sequenceDebugMiddleware = createSequenceDebugMiddleware(sequenceDebugSink);
|
|
@@ -164,6 +165,7 @@ export function callAgent(params) {
|
|
|
164
165
|
userTimeZone,
|
|
165
166
|
sessionId,
|
|
166
167
|
turnId,
|
|
168
|
+
httpExtra,
|
|
167
169
|
emitToolCallEvent,
|
|
168
170
|
},
|
|
169
171
|
});
|
|
@@ -74,7 +74,7 @@ export function buildAgentSystemPrompt(adminforth) {
|
|
|
74
74
|
"The fetched skill response starts with 'Tools mentioned in this skill'. Read that list first.",
|
|
75
75
|
"You can use get_resource immediately to inspect resource structure and column names.",
|
|
76
76
|
"If the user wants to create, update, delete, or run actions on records, load mutate_data first.",
|
|
77
|
-
"If the user wants to fetch records, load fetch_data first. If the user wants analytics or charts, load
|
|
77
|
+
"If the user wants to fetch records, load fetch_data first. If the user wants analytics or charts, load analyze_data first.",
|
|
78
78
|
"Only call fetch_tool_schema for tool names that are explicitly mentioned in a fetched skill and are not already available as base tools.",
|
|
79
79
|
"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.",
|
|
80
80
|
"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.",
|
|
@@ -49,6 +49,7 @@ export function createApiTool(toolName, apiBasedTool) {
|
|
|
49
49
|
const normalizedInput = (input !== null && input !== void 0 ? input : {});
|
|
50
50
|
return apiBasedTool.call({
|
|
51
51
|
adminUser: runtime.context.adminUser,
|
|
52
|
+
httpExtra: runtime.context.httpExtra,
|
|
52
53
|
inputs: normalizedInput,
|
|
53
54
|
userTimeZone: runtime.context.userTimeZone,
|
|
54
55
|
});
|
package/dist/apiBasedTools.js
CHANGED
|
@@ -7,11 +7,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { AdminForthDataTypes, } from 'adminforth';
|
|
10
|
+
import { AdminForthDataTypes, logger, } from 'adminforth';
|
|
11
11
|
import dayjs from 'dayjs';
|
|
12
12
|
import timezone from 'dayjs/plugin/timezone.js';
|
|
13
13
|
import utc from 'dayjs/plugin/utc.js';
|
|
14
|
-
import { PassThrough } from 'stream';
|
|
15
14
|
import { inspect } from 'util';
|
|
16
15
|
import YAML from 'yaml';
|
|
17
16
|
dayjs.extend(utc);
|
|
@@ -230,7 +229,7 @@ function formatDateTimeColumns(rows, dateTimeColumnNames, userTimeZone) {
|
|
|
230
229
|
function applyToolOverride(params) {
|
|
231
230
|
return __awaiter(this, void 0, void 0, function* () {
|
|
232
231
|
var _a;
|
|
233
|
-
const { adminforth, adminUser,
|
|
232
|
+
const { adminforth, adminUser, httpExtra, inputs, invokeTool, output, toolName, userTimeZone, } = params;
|
|
234
233
|
const sanitizedOutput = sanitizeForYaml(output);
|
|
235
234
|
const override = TOOL_OVERRIDES[toolName];
|
|
236
235
|
if (!override) {
|
|
@@ -250,17 +249,10 @@ function applyToolOverride(params) {
|
|
|
250
249
|
userTimeZone,
|
|
251
250
|
invokeTool: (nestedToolName_1, ...args_1) => __awaiter(this, [nestedToolName_1, ...args_1], void 0, function* (nestedToolName, nestedParams = {}) {
|
|
252
251
|
var _a, _b, _c;
|
|
253
|
-
const nestedEndpoint = capturedEndpointsByToolName[nestedToolName];
|
|
254
|
-
if (!nestedEndpoint) {
|
|
255
|
-
throw new Error(`Tool ${nestedToolName} is not registered`);
|
|
256
|
-
}
|
|
257
252
|
const nestedInputs = (_a = nestedParams.inputs) !== null && _a !== void 0 ? _a : inputs;
|
|
258
253
|
const nestedHttpExtra = (_b = nestedParams.httpExtra) !== null && _b !== void 0 ? _b : httpExtra;
|
|
259
254
|
const nestedUserTimeZone = (_c = nestedParams.userTimeZone) !== null && _c !== void 0 ? _c : userTimeZone;
|
|
260
|
-
const nestedOutput = yield
|
|
261
|
-
adminforth,
|
|
262
|
-
endpoint: nestedEndpoint,
|
|
263
|
-
adminUser,
|
|
255
|
+
const nestedOutput = yield invokeTool(nestedToolName, {
|
|
264
256
|
inputs: nestedInputs,
|
|
265
257
|
httpExtra: nestedHttpExtra,
|
|
266
258
|
userTimeZone: nestedUserTimeZone,
|
|
@@ -268,9 +260,9 @@ function applyToolOverride(params) {
|
|
|
268
260
|
return applyToolOverride({
|
|
269
261
|
adminforth,
|
|
270
262
|
adminUser,
|
|
271
|
-
capturedEndpointsByToolName,
|
|
272
263
|
httpExtra: nestedHttpExtra,
|
|
273
264
|
inputs: nestedInputs,
|
|
265
|
+
invokeTool,
|
|
274
266
|
output: nestedOutput,
|
|
275
267
|
toolName: nestedToolName,
|
|
276
268
|
userTimeZone: nestedUserTimeZone,
|
|
@@ -301,6 +293,9 @@ function stripAdminApiPrefix(path, adminforth) {
|
|
|
301
293
|
function openApiSchemaPathToToolName(path, adminforth) {
|
|
302
294
|
return endpointPathToToolName(stripAdminApiPrefix(path, adminforth));
|
|
303
295
|
}
|
|
296
|
+
function formatLogNameList(names) {
|
|
297
|
+
return names.length ? names.join(', ') : '(none)';
|
|
298
|
+
}
|
|
304
299
|
export function formatApiBasedToolCall(params) {
|
|
305
300
|
return __awaiter(this, void 0, void 0, function* () {
|
|
306
301
|
var _a;
|
|
@@ -326,69 +321,6 @@ function normalizeCookies(cookies) {
|
|
|
326
321
|
}
|
|
327
322
|
return Object.entries(cookies).map(([key, value]) => ({ key, value }));
|
|
328
323
|
}
|
|
329
|
-
function createToolResponse(baseResponse) {
|
|
330
|
-
return {
|
|
331
|
-
headers: [],
|
|
332
|
-
status: 200,
|
|
333
|
-
message: undefined,
|
|
334
|
-
setHeader(name, value) {
|
|
335
|
-
this.headers.push([name, value]);
|
|
336
|
-
baseResponse === null || baseResponse === void 0 ? void 0 : baseResponse.setHeader(name, value);
|
|
337
|
-
},
|
|
338
|
-
setStatus(code, message) {
|
|
339
|
-
this.status = code;
|
|
340
|
-
this.message = message;
|
|
341
|
-
baseResponse === null || baseResponse === void 0 ? void 0 : baseResponse.setStatus(code, message);
|
|
342
|
-
},
|
|
343
|
-
blobStream() {
|
|
344
|
-
var _a;
|
|
345
|
-
return (_a = baseResponse === null || baseResponse === void 0 ? void 0 : baseResponse.blobStream()) !== null && _a !== void 0 ? _a : new PassThrough();
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
function createRawExpressRequest(params) {
|
|
350
|
-
const cookieHeader = params.cookies
|
|
351
|
-
.map(({ key, value }) => `${key}=${value}`)
|
|
352
|
-
.join('; ');
|
|
353
|
-
return {
|
|
354
|
-
adminUser: params.adminUser,
|
|
355
|
-
body: params.body,
|
|
356
|
-
destroyed: false,
|
|
357
|
-
headers: Object.assign(Object.assign({}, params.headers), (cookieHeader ? { cookie: cookieHeader } : {})),
|
|
358
|
-
method: params.method.toUpperCase(),
|
|
359
|
-
on: () => undefined,
|
|
360
|
-
query: params.query,
|
|
361
|
-
url: params.requestUrl,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function createRawExpressResponse(response) {
|
|
365
|
-
const rawResponse = {
|
|
366
|
-
destroyed: false,
|
|
367
|
-
on: () => undefined,
|
|
368
|
-
setHeader(name, value) {
|
|
369
|
-
response.setHeader(name, value);
|
|
370
|
-
return rawResponse;
|
|
371
|
-
},
|
|
372
|
-
status(code) {
|
|
373
|
-
response.status = code;
|
|
374
|
-
return rawResponse;
|
|
375
|
-
},
|
|
376
|
-
send(message) {
|
|
377
|
-
response.message = message;
|
|
378
|
-
return rawResponse;
|
|
379
|
-
},
|
|
380
|
-
json(payload) {
|
|
381
|
-
response.jsonPayload = payload;
|
|
382
|
-
response.message = JSON.stringify(payload);
|
|
383
|
-
return rawResponse;
|
|
384
|
-
},
|
|
385
|
-
write: () => true,
|
|
386
|
-
writeHead: () => rawResponse,
|
|
387
|
-
writableEnded: false,
|
|
388
|
-
end: () => rawResponse,
|
|
389
|
-
};
|
|
390
|
-
return rawResponse;
|
|
391
|
-
}
|
|
392
324
|
function normalizeDateTimeInputsToUtc(body, adminforth, userTimeZone) {
|
|
393
325
|
if (!userTimeZone || typeof body.resourceId !== 'string') {
|
|
394
326
|
return body;
|
|
@@ -437,90 +369,168 @@ function normalizeDateTimeInputsToUtc(body, adminforth, userTimeZone) {
|
|
|
437
369
|
};
|
|
438
370
|
return normalizeValue(body);
|
|
439
371
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
372
|
+
const METHODS_WITHOUT_REQUEST_BODY = new Set(['GET', 'HEAD']);
|
|
373
|
+
const HEADERS_NOT_FORWARDED_TO_API_TOOL = new Set([
|
|
374
|
+
'connection',
|
|
375
|
+
'content-length',
|
|
376
|
+
'host',
|
|
377
|
+
'keep-alive',
|
|
378
|
+
'proxy-authenticate',
|
|
379
|
+
'proxy-authorization',
|
|
380
|
+
'te',
|
|
381
|
+
'trailer',
|
|
382
|
+
'transfer-encoding',
|
|
383
|
+
'upgrade',
|
|
384
|
+
]);
|
|
385
|
+
function getHeaderValue(headers, headerName) {
|
|
386
|
+
var _a;
|
|
387
|
+
const normalizedHeaderName = headerName.toLowerCase();
|
|
388
|
+
const value = (_a = Object.entries(headers !== null && headers !== void 0 ? headers : {}).find(([name]) => name.toLowerCase() === normalizedHeaderName)) === null || _a === void 0 ? void 0 : _a[1];
|
|
389
|
+
if (typeof value !== 'string') {
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
return value.split(',')[0].trim();
|
|
393
|
+
}
|
|
394
|
+
function isAbsoluteHttpUrl(value) {
|
|
395
|
+
try {
|
|
396
|
+
const url = new URL(value);
|
|
397
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
398
|
+
}
|
|
399
|
+
catch (_a) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
function getRequestOrigin(httpExtra) {
|
|
404
|
+
var _a, _b;
|
|
405
|
+
const requestUrl = httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.requestUrl;
|
|
406
|
+
if (requestUrl && isAbsoluteHttpUrl(requestUrl)) {
|
|
407
|
+
return new URL(requestUrl).origin;
|
|
408
|
+
}
|
|
409
|
+
const host = (_a = getHeaderValue(httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.headers, 'x-forwarded-host')) !== null && _a !== void 0 ? _a : getHeaderValue(httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.headers, 'host');
|
|
410
|
+
if (!host) {
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
const protocol = (_b = getHeaderValue(httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.headers, 'x-forwarded-proto')) !== null && _b !== void 0 ? _b : 'http';
|
|
414
|
+
return `${protocol}://${host}`;
|
|
415
|
+
}
|
|
416
|
+
function resolveOpenApiRequestUrl(params) {
|
|
417
|
+
if (isAbsoluteHttpUrl(params.path)) {
|
|
418
|
+
return params.path;
|
|
419
|
+
}
|
|
420
|
+
const origin = getRequestOrigin(params.httpExtra);
|
|
421
|
+
if (!origin) {
|
|
422
|
+
throw new Error(`Tool "${params.toolName}" has relative OpenAPI path "${params.path}" but request host header is unavailable.`);
|
|
423
|
+
}
|
|
424
|
+
return new URL(params.path, origin).toString();
|
|
425
|
+
}
|
|
426
|
+
function createToolRequestHeaders(httpExtra, userTimeZone) {
|
|
427
|
+
var _a;
|
|
428
|
+
const headers = {};
|
|
429
|
+
for (const [name, value] of Object.entries((_a = httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.headers) !== null && _a !== void 0 ? _a : {})) {
|
|
430
|
+
const headerName = name.toLowerCase();
|
|
431
|
+
if (typeof value === 'string' && !HEADERS_NOT_FORWARDED_TO_API_TOOL.has(headerName)) {
|
|
432
|
+
headers[headerName] = value;
|
|
478
433
|
}
|
|
479
|
-
|
|
480
|
-
|
|
434
|
+
}
|
|
435
|
+
headers.accept = 'application/json';
|
|
436
|
+
headers['content-type'] = 'application/json';
|
|
437
|
+
if (userTimeZone) {
|
|
438
|
+
headers['x-timezone'] = userTimeZone;
|
|
439
|
+
}
|
|
440
|
+
const cookieHeader = normalizeCookies(httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.cookies)
|
|
441
|
+
.map(({ key, value }) => `${key}=${value}`)
|
|
442
|
+
.join('; ');
|
|
443
|
+
if (cookieHeader && !headers.cookie) {
|
|
444
|
+
headers.cookie = cookieHeader;
|
|
445
|
+
}
|
|
446
|
+
return headers;
|
|
447
|
+
}
|
|
448
|
+
function appendInputsToQueryString(url, inputs) {
|
|
449
|
+
const nextUrl = new URL(url);
|
|
450
|
+
for (const [key, value] of Object.entries(inputs)) {
|
|
451
|
+
if (value === undefined) {
|
|
452
|
+
continue;
|
|
481
453
|
}
|
|
482
|
-
if (
|
|
483
|
-
|
|
454
|
+
if (Array.isArray(value)) {
|
|
455
|
+
for (const item of value) {
|
|
456
|
+
nextUrl.searchParams.append(key, typeof item === 'object' && item !== null ? JSON.stringify(item) : String(item));
|
|
457
|
+
}
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
nextUrl.searchParams.set(key, typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value));
|
|
461
|
+
}
|
|
462
|
+
return nextUrl.toString();
|
|
463
|
+
}
|
|
464
|
+
function parseOpenApiToolResponse(response) {
|
|
465
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
466
|
+
var _a;
|
|
467
|
+
const responseText = yield response.text();
|
|
468
|
+
const payload = responseText && ((_a = response.headers.get('content-type')) === null || _a === void 0 ? void 0 : _a.includes('application/json'))
|
|
469
|
+
? JSON.parse(responseText)
|
|
470
|
+
: responseText;
|
|
471
|
+
if (response.ok) {
|
|
472
|
+
return responseText ? payload : { status: response.status };
|
|
484
473
|
}
|
|
485
474
|
return {
|
|
486
|
-
|
|
475
|
+
error: 'HTTP_ERROR',
|
|
487
476
|
status: response.status,
|
|
477
|
+
statusText: response.statusText,
|
|
478
|
+
response: payload,
|
|
488
479
|
};
|
|
489
480
|
});
|
|
490
481
|
}
|
|
482
|
+
function callOpenApiSchema(params) {
|
|
483
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
484
|
+
var _a;
|
|
485
|
+
const { adminforth, httpExtra, inputs, schema, toolName, userTimeZone } = params;
|
|
486
|
+
const method = schema.method.toUpperCase();
|
|
487
|
+
const body = normalizeDateTimeInputsToUtc(((_a = inputs !== null && inputs !== void 0 ? inputs : httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.body) !== null && _a !== void 0 ? _a : {}), adminforth, userTimeZone);
|
|
488
|
+
const requestUrl = resolveOpenApiRequestUrl({
|
|
489
|
+
httpExtra,
|
|
490
|
+
path: schema.path,
|
|
491
|
+
toolName,
|
|
492
|
+
});
|
|
493
|
+
const hasRequestBody = !METHODS_WITHOUT_REQUEST_BODY.has(method);
|
|
494
|
+
const response = yield fetch(hasRequestBody ? requestUrl : appendInputsToQueryString(requestUrl, body), {
|
|
495
|
+
method,
|
|
496
|
+
headers: createToolRequestHeaders(httpExtra, userTimeZone),
|
|
497
|
+
body: hasRequestBody ? JSON.stringify(body) : undefined,
|
|
498
|
+
});
|
|
499
|
+
return parseOpenApiToolResponse(response);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
491
502
|
export function prepareApiBasedTools(adminforth) {
|
|
492
|
-
const capturedEndpoints = [];
|
|
493
|
-
const captureServer = {
|
|
494
|
-
setupSpaServer() { },
|
|
495
|
-
endpoint: ((options) => {
|
|
496
|
-
var _a, _b;
|
|
497
|
-
capturedEndpoints.push(Object.assign(Object.assign({}, options), { response_schema: (_a = options.response_schema) !== null && _a !== void 0 ? _a : options.responce_schema, normalizedResponseSchema: (_b = options.response_schema) !== null && _b !== void 0 ? _b : options.responce_schema }));
|
|
498
|
-
}),
|
|
499
|
-
};
|
|
500
|
-
adminforth.setupEndpoints(captureServer);
|
|
501
503
|
const apiBasedTools = {};
|
|
502
|
-
const capturedEndpointsByToolName = Object.fromEntries(capturedEndpoints.map((endpoint) => [endpointPathToToolName(endpoint.path), endpoint]));
|
|
503
504
|
const openApiSchemas = adminforth.openApi.registeredSchemas.filter((schema) => schema.request_schema || schema.response_schema);
|
|
504
505
|
const openApiSchemasByToolName = new Map();
|
|
505
506
|
for (const schema of openApiSchemas) {
|
|
506
507
|
const toolName = openApiSchemaPathToToolName(schema.path, adminforth);
|
|
507
508
|
openApiSchemasByToolName.set(toolName, schema);
|
|
508
509
|
}
|
|
510
|
+
logger.info(`AdminForth Agent OpenAPI APIs: ${formatLogNameList(adminforth.openApi.registeredSchemas.map((schema) => openApiSchemaPathToToolName(schema.path, adminforth)))}`);
|
|
511
|
+
logger.info(`AdminForth Agent OpenAPI tools connected: ${formatLogNameList([...openApiSchemasByToolName.keys()])}`);
|
|
509
512
|
for (const [toolName, schema] of openApiSchemasByToolName.entries()) {
|
|
510
|
-
const endpoint = capturedEndpointsByToolName[toolName];
|
|
511
513
|
apiBasedTools[toolName] = {
|
|
512
514
|
description: schema.description,
|
|
513
515
|
input_schema: schema.request_schema,
|
|
514
516
|
input_schma: schema.request_schema,
|
|
515
517
|
output_schema: schema.response_schema,
|
|
516
518
|
call: (...args_1) => __awaiter(this, [...args_1], void 0, function* ({ adminUser, adminuser, inputs, httpExtra, userTimeZone } = {}) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
519
|
+
const invokeTool = (nextToolName_1, ...args_2) => __awaiter(this, [nextToolName_1, ...args_2], void 0, function* (nextToolName, nextParams = {}) {
|
|
520
|
+
const nextSchema = openApiSchemasByToolName.get(nextToolName);
|
|
521
|
+
if (!nextSchema) {
|
|
522
|
+
throw new Error(`Tool ${nextToolName} is not registered in OpenAPI`);
|
|
523
|
+
}
|
|
524
|
+
return callOpenApiSchema({
|
|
525
|
+
adminforth,
|
|
526
|
+
schema: nextSchema,
|
|
527
|
+
toolName: nextToolName,
|
|
528
|
+
inputs: nextParams.inputs,
|
|
529
|
+
httpExtra: nextParams.httpExtra,
|
|
530
|
+
userTimeZone: nextParams.userTimeZone,
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
const output = yield invokeTool(toolName, {
|
|
524
534
|
inputs,
|
|
525
535
|
httpExtra,
|
|
526
536
|
userTimeZone,
|
|
@@ -528,9 +538,9 @@ export function prepareApiBasedTools(adminforth) {
|
|
|
528
538
|
const processedOutput = yield applyToolOverride({
|
|
529
539
|
adminforth,
|
|
530
540
|
adminUser: adminUser !== null && adminUser !== void 0 ? adminUser : adminuser,
|
|
531
|
-
capturedEndpointsByToolName,
|
|
532
541
|
httpExtra,
|
|
533
542
|
inputs,
|
|
543
|
+
invokeTool,
|
|
534
544
|
output,
|
|
535
545
|
toolName,
|
|
536
546
|
userTimeZone,
|
package/dist/index.js
CHANGED
|
@@ -168,11 +168,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
171
|
-
this.apiBasedTools = buildApiBasedTools(adminforth);
|
|
172
|
-
for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
|
|
173
|
-
assertRequiredApiTool(this.apiBasedTools, toolName);
|
|
174
|
-
}
|
|
175
|
-
assertRequiredApiTool(this.apiBasedTools, "update_record");
|
|
176
171
|
this.agentSystemPromptPromise = buildAgentSystemPrompt(adminforth)
|
|
177
172
|
.then((systemPrompt) => appendCustomSystemPrompt(systemPrompt, this.options.systemPrompt));
|
|
178
173
|
}
|
|
@@ -208,7 +203,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
208
203
|
server.endpoint({
|
|
209
204
|
method: 'POST',
|
|
210
205
|
path: `/agent/response`,
|
|
211
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, _raw_express_res }) {
|
|
206
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, query, headers, cookies, adminUser, response, requestUrl, _raw_express_res }) {
|
|
212
207
|
var _b, e_1, _c, _d;
|
|
213
208
|
var _e, _f, _g;
|
|
214
209
|
const res = _raw_express_res;
|
|
@@ -288,6 +283,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
288
283
|
const selectedMode = (_g = this.options.modes.find((mode) => mode.name === body.mode)) !== null && _g !== void 0 ? _g : this.options.modes[0];
|
|
289
284
|
const { model, summaryModel, modelMiddleware } = yield this.getModeModels(selectedMode, maxTokens);
|
|
290
285
|
const systemPrompt = yield this.agentSystemPromptPromise;
|
|
286
|
+
const apiBasedTools = buildApiBasedTools(this.adminforth);
|
|
287
|
+
for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
|
|
288
|
+
assertRequiredApiTool(apiBasedTools, toolName);
|
|
289
|
+
}
|
|
290
|
+
assertRequiredApiTool(apiBasedTools, "update_record");
|
|
291
|
+
this.apiBasedTools = apiBasedTools;
|
|
291
292
|
const stream = yield callAgent({
|
|
292
293
|
name: `adminforth-agent-${this.pluginInstanceId}`,
|
|
293
294
|
model,
|
|
@@ -300,10 +301,18 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
300
301
|
],
|
|
301
302
|
adminUser,
|
|
302
303
|
adminforth: this.adminforth,
|
|
303
|
-
apiBasedTools
|
|
304
|
+
apiBasedTools,
|
|
304
305
|
customComponentsDir: this.adminforth.config.customization.customComponentsDir,
|
|
305
306
|
sessionId,
|
|
306
307
|
turnId,
|
|
308
|
+
httpExtra: {
|
|
309
|
+
body,
|
|
310
|
+
query,
|
|
311
|
+
headers,
|
|
312
|
+
cookies,
|
|
313
|
+
requestUrl,
|
|
314
|
+
response,
|
|
315
|
+
},
|
|
307
316
|
userTimeZone,
|
|
308
317
|
emitToolCallEvent,
|
|
309
318
|
sequenceDebugSink: sequenceDebugCollector,
|
package/index.ts
CHANGED
|
@@ -204,11 +204,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
207
|
-
this.apiBasedTools = buildApiBasedTools(adminforth);
|
|
208
|
-
for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
|
|
209
|
-
assertRequiredApiTool(this.apiBasedTools, toolName);
|
|
210
|
-
}
|
|
211
|
-
assertRequiredApiTool(this.apiBasedTools, "update_record");
|
|
212
207
|
this.agentSystemPromptPromise = buildAgentSystemPrompt(adminforth)
|
|
213
208
|
.then((systemPrompt) => appendCustomSystemPrompt(systemPrompt, this.options.systemPrompt));
|
|
214
209
|
}
|
|
@@ -248,7 +243,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
248
243
|
server.endpoint({
|
|
249
244
|
method: 'POST',
|
|
250
245
|
path: `/agent/response`,
|
|
251
|
-
handler: async ({ body, adminUser, _raw_express_res }) => {
|
|
246
|
+
handler: async ({ body, query, headers, cookies, adminUser, response, requestUrl, _raw_express_res }) => {
|
|
252
247
|
const res = _raw_express_res;
|
|
253
248
|
const messageId = randomUUID();
|
|
254
249
|
const prompt = body.message;
|
|
@@ -346,6 +341,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
346
341
|
const { model, summaryModel, modelMiddleware } =
|
|
347
342
|
await this.getModeModels(selectedMode, maxTokens);
|
|
348
343
|
const systemPrompt = await this.agentSystemPromptPromise;
|
|
344
|
+
const apiBasedTools = buildApiBasedTools(this.adminforth);
|
|
345
|
+
for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
|
|
346
|
+
assertRequiredApiTool(apiBasedTools, toolName);
|
|
347
|
+
}
|
|
348
|
+
assertRequiredApiTool(apiBasedTools, "update_record");
|
|
349
|
+
this.apiBasedTools = apiBasedTools;
|
|
349
350
|
const stream = await callAgent({
|
|
350
351
|
name: `adminforth-agent-${this.pluginInstanceId}`,
|
|
351
352
|
model,
|
|
@@ -358,10 +359,18 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
358
359
|
],
|
|
359
360
|
adminUser,
|
|
360
361
|
adminforth: this.adminforth,
|
|
361
|
-
apiBasedTools
|
|
362
|
+
apiBasedTools,
|
|
362
363
|
customComponentsDir: this.adminforth.config.customization.customComponentsDir,
|
|
363
364
|
sessionId,
|
|
364
365
|
turnId,
|
|
366
|
+
httpExtra: {
|
|
367
|
+
body,
|
|
368
|
+
query,
|
|
369
|
+
headers,
|
|
370
|
+
cookies,
|
|
371
|
+
requestUrl,
|
|
372
|
+
response,
|
|
373
|
+
},
|
|
365
374
|
userTimeZone,
|
|
366
375
|
emitToolCallEvent,
|
|
367
376
|
sequenceDebugSink: sequenceDebugCollector,
|