@adminforth/agent 1.22.2 → 1.24.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/README.md +31 -0
- package/agent/middleware/apiBasedTools.ts +27 -4
- package/agent/middleware/sequenceDebug.ts +39 -2
- package/agent/simpleAgent.ts +283 -52
- package/agent/toolCallEvents.ts +3 -0
- package/apiBasedTools.ts +189 -11
- package/build.log +2 -2
- package/custom/package.json +1 -1
- package/custom/skills/mutate_data/SKILL.md +5 -2
- package/dist/agent/middleware/apiBasedTools.js +22 -3
- package/dist/agent/middleware/sequenceDebug.js +18 -2
- package/dist/agent/simpleAgent.js +167 -27
- package/dist/agent/toolCallEvents.js +1 -0
- package/dist/apiBasedTools.js +126 -9
- package/dist/custom/package.json +1 -1
- package/dist/custom/skills/mutate_data/SKILL.md +5 -2
- package/dist/index.js +32 -18
- package/index.ts +33 -15
- package/package.json +16 -5
package/apiBasedTools.ts
CHANGED
|
@@ -65,10 +65,11 @@ type ToolHttpResponse = IAdminForthHttpResponse & {
|
|
|
65
65
|
type ToolOverrideCallParams = Pick<ApiBasedToolCallParams, 'httpExtra' | 'inputs' | 'userTimeZone'>;
|
|
66
66
|
|
|
67
67
|
type ToolOverrideContext = {
|
|
68
|
-
output
|
|
68
|
+
output?: unknown;
|
|
69
69
|
adminUser?: AdminUser;
|
|
70
70
|
httpExtra?: Partial<HttpExtra>;
|
|
71
71
|
inputs?: Record<string, unknown>;
|
|
72
|
+
resourceLabel?: string;
|
|
72
73
|
userTimeZone?: string;
|
|
73
74
|
invokeTool: (toolName: string, params?: ToolOverrideCallParams) => Promise<unknown>;
|
|
74
75
|
};
|
|
@@ -94,8 +95,46 @@ type GetResourceDataToolResponse = {
|
|
|
94
95
|
options?: Record<string, unknown>;
|
|
95
96
|
};
|
|
96
97
|
|
|
98
|
+
type DateTimeColumnType = AdminForthDataTypes.DATETIME | AdminForthDataTypes.TIME;
|
|
99
|
+
|
|
97
100
|
const DEFAULT_USER_TIME_ZONE = 'UTC';
|
|
98
101
|
|
|
102
|
+
function getInputString(inputs: Record<string, unknown> | undefined, key: string) {
|
|
103
|
+
const value = inputs?.[key];
|
|
104
|
+
|
|
105
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function getInputArrayLength(inputs: Record<string, unknown> | undefined, key: string) {
|
|
109
|
+
const value = inputs?.[key];
|
|
110
|
+
|
|
111
|
+
return Array.isArray(value) ? value.length : undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resourceLabel(adminforth: IAdminForth, inputs: Record<string, unknown> | undefined) {
|
|
115
|
+
const resourceId = getInputString(inputs, 'resourceId');
|
|
116
|
+
const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
|
|
117
|
+
|
|
118
|
+
return resource?.label ?? resourceId ?? 'resource';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function getDataPrefix(inputs: Record<string, unknown> | undefined) {
|
|
122
|
+
const offset = typeof inputs?.offset === 'number' ? inputs.offset : undefined;
|
|
123
|
+
const limit = typeof inputs?.limit === 'number' ? inputs.limit : undefined;
|
|
124
|
+
|
|
125
|
+
if (offset !== undefined && limit !== undefined) {
|
|
126
|
+
return `${offset}-${offset + limit} `;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return limit === undefined ? '' : `${limit} `;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function actionText(inputs: Record<string, unknown> | undefined) {
|
|
133
|
+
const actionId = getInputString(inputs, 'actionId');
|
|
134
|
+
|
|
135
|
+
return actionId ? ` action ${actionId}` : ' action';
|
|
136
|
+
}
|
|
137
|
+
|
|
99
138
|
const TOOL_OVERRIDES: Record<string, ToolOverride> = {
|
|
100
139
|
get_resource: {
|
|
101
140
|
wipe_frontend_specific_data: [
|
|
@@ -104,11 +143,12 @@ const TOOL_OVERRIDES: Record<string, ToolOverride> = {
|
|
|
104
143
|
'resource.options.actions[].customComponent',
|
|
105
144
|
'resource.options.pageInjections',
|
|
106
145
|
],
|
|
107
|
-
format_tool:
|
|
108
|
-
return "get resource Apartments"
|
|
109
|
-
}
|
|
146
|
+
format_tool: ({ resourceLabel }) => `Get ${resourceLabel} resource`,
|
|
110
147
|
},
|
|
111
148
|
get_resource_data: {
|
|
149
|
+
format_tool: ({ inputs, resourceLabel }) => (
|
|
150
|
+
`Get ${getDataPrefix(inputs)}${resourceLabel}`
|
|
151
|
+
),
|
|
112
152
|
post_process_response: async ({ output, inputs, invokeTool, userTimeZone }) => {
|
|
113
153
|
if (hasToolError(output)) {
|
|
114
154
|
return output;
|
|
@@ -130,9 +170,37 @@ const TOOL_OVERRIDES: Record<string, ToolOverride> = {
|
|
|
130
170
|
|
|
131
171
|
return response;
|
|
132
172
|
},
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
173
|
+
},
|
|
174
|
+
aggregate: {
|
|
175
|
+
format_tool: ({ resourceLabel }) => `Aggregate ${resourceLabel}`,
|
|
176
|
+
},
|
|
177
|
+
start_custom_action: {
|
|
178
|
+
format_tool: ({ inputs, resourceLabel }) => `Run ${resourceLabel}${actionText(inputs)}`,
|
|
179
|
+
},
|
|
180
|
+
start_custom_bulk_action: {
|
|
181
|
+
format_tool: ({ inputs, resourceLabel }) => {
|
|
182
|
+
const recordCount = getInputArrayLength(inputs, 'recordIds');
|
|
183
|
+
const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
|
|
184
|
+
|
|
185
|
+
return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
start_bulk_action: {
|
|
189
|
+
format_tool: ({ inputs, resourceLabel }) => {
|
|
190
|
+
const recordCount = getInputArrayLength(inputs, 'recordIds');
|
|
191
|
+
const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
|
|
192
|
+
|
|
193
|
+
return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
create_record: {
|
|
197
|
+
format_tool: ({ resourceLabel }) => `Create ${resourceLabel}`,
|
|
198
|
+
},
|
|
199
|
+
update_record: {
|
|
200
|
+
format_tool: ({ resourceLabel }) => `Update ${resourceLabel}`,
|
|
201
|
+
},
|
|
202
|
+
delete_record: {
|
|
203
|
+
format_tool: ({ resourceLabel }) => `Delete ${resourceLabel}`,
|
|
136
204
|
},
|
|
137
205
|
};
|
|
138
206
|
|
|
@@ -373,6 +441,7 @@ async function applyToolOverride(params: {
|
|
|
373
441
|
adminUser,
|
|
374
442
|
inputs: nestedInputs,
|
|
375
443
|
httpExtra: nestedHttpExtra,
|
|
444
|
+
userTimeZone: nestedUserTimeZone,
|
|
376
445
|
});
|
|
377
446
|
|
|
378
447
|
return applyToolOverride({
|
|
@@ -398,7 +467,31 @@ function endpointPathToToolName(path: string) {
|
|
|
398
467
|
.replace(/^_+|_+$/g, '');
|
|
399
468
|
}
|
|
400
469
|
|
|
401
|
-
function
|
|
470
|
+
export async function formatApiBasedToolCall(params: {
|
|
471
|
+
adminforth: IAdminForth;
|
|
472
|
+
adminUser?: AdminUser;
|
|
473
|
+
httpExtra?: Partial<HttpExtra>;
|
|
474
|
+
inputs?: Record<string, unknown>;
|
|
475
|
+
toolName: string;
|
|
476
|
+
userTimeZone?: string;
|
|
477
|
+
}) {
|
|
478
|
+
const formatTool = TOOL_OVERRIDES[params.toolName]?.format_tool;
|
|
479
|
+
|
|
480
|
+
return await formatTool?.({
|
|
481
|
+
adminUser: params.adminUser,
|
|
482
|
+
httpExtra: params.httpExtra,
|
|
483
|
+
inputs: params.inputs,
|
|
484
|
+
resourceLabel: resourceLabel(params.adminforth, params.inputs),
|
|
485
|
+
userTimeZone: params.userTimeZone,
|
|
486
|
+
invokeTool: async () => {
|
|
487
|
+
throw new Error('Tool info formatting cannot invoke tools');
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function normalizeCookies(
|
|
493
|
+
cookies?: Partial<HttpExtra>['cookies'] | Record<string, string>,
|
|
494
|
+
): CookieItem[] {
|
|
402
495
|
if (!cookies) {
|
|
403
496
|
return [];
|
|
404
497
|
}
|
|
@@ -488,20 +581,104 @@ function createRawExpressResponse(response: ToolHttpResponse) {
|
|
|
488
581
|
return rawResponse;
|
|
489
582
|
}
|
|
490
583
|
|
|
584
|
+
function normalizeDateTimeInputsToUtc(
|
|
585
|
+
body: Record<string, unknown>,
|
|
586
|
+
adminforth: IAdminForth,
|
|
587
|
+
userTimeZone?: string,
|
|
588
|
+
): Record<string, unknown> {
|
|
589
|
+
if (!userTimeZone || typeof body.resourceId !== 'string') {
|
|
590
|
+
return body;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const resource = adminforth.config.resources.find((res) => res.resourceId === body.resourceId);
|
|
594
|
+
|
|
595
|
+
if (!resource) {
|
|
596
|
+
return body;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const columnsByName = new Map(resource.dataSourceColumns.map((column) => [column.name, column]));
|
|
600
|
+
|
|
601
|
+
const normalizeColumnValue = (
|
|
602
|
+
value: unknown,
|
|
603
|
+
columnType: DateTimeColumnType,
|
|
604
|
+
): unknown => {
|
|
605
|
+
if (Array.isArray(value)) {
|
|
606
|
+
return value.map((item) => normalizeColumnValue(item, columnType));
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (typeof value !== 'string' || value === '') {
|
|
610
|
+
return value;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (columnType === AdminForthDataTypes.DATETIME) {
|
|
614
|
+
return dayjs.tz(value, userTimeZone).utc().toISOString();
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (columnType === AdminForthDataTypes.TIME) {
|
|
618
|
+
const userDate = dayjs().tz(userTimeZone).format('YYYY-MM-DD');
|
|
619
|
+
return dayjs.tz(`${userDate}T${value}`, userTimeZone).utc().format('HH:mm:ss');
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
const normalizeValue = (value: unknown, key?: string): unknown => {
|
|
624
|
+
const column = key ? columnsByName.get(key) : undefined;
|
|
625
|
+
|
|
626
|
+
if (column?.type === AdminForthDataTypes.DATETIME || column?.type === AdminForthDataTypes.TIME) {
|
|
627
|
+
return normalizeColumnValue(value, column.type);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (Array.isArray(value)) {
|
|
631
|
+
return value.map((item) => normalizeValue(item));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (!value || typeof value !== 'object') {
|
|
635
|
+
return value;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const record = value as Record<string, unknown>;
|
|
639
|
+
const filterColumn = typeof record.field === 'string' ? columnsByName.get(record.field) : undefined;
|
|
640
|
+
|
|
641
|
+
if (
|
|
642
|
+
'value' in record &&
|
|
643
|
+
(filterColumn?.type === AdminForthDataTypes.DATETIME || filterColumn?.type === AdminForthDataTypes.TIME)
|
|
644
|
+
) {
|
|
645
|
+
return {
|
|
646
|
+
...record,
|
|
647
|
+
value: normalizeColumnValue(record.value, filterColumn.type),
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return Object.fromEntries(
|
|
652
|
+
Object.entries(record).map(([nestedKey, nestedValue]) => [
|
|
653
|
+
nestedKey,
|
|
654
|
+
normalizeValue(nestedValue, nestedKey),
|
|
655
|
+
]),
|
|
656
|
+
);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
return normalizeValue(body) as Record<string, unknown>;
|
|
660
|
+
}
|
|
661
|
+
|
|
491
662
|
async function callCapturedEndpoint(params: {
|
|
492
663
|
adminforth: IAdminForth;
|
|
493
664
|
adminUser?: AdminUser;
|
|
494
665
|
endpoint: CapturedEndpoint;
|
|
495
666
|
httpExtra?: Partial<HttpExtra>;
|
|
496
667
|
inputs?: Record<string, unknown>;
|
|
668
|
+
userTimeZone?: string;
|
|
497
669
|
}) {
|
|
498
|
-
const { adminforth, adminUser, endpoint, httpExtra, inputs } = params;
|
|
670
|
+
const { adminforth, adminUser, endpoint, httpExtra, inputs, userTimeZone } = params;
|
|
499
671
|
const response = createToolResponse(httpExtra?.response);
|
|
500
672
|
const headers = {
|
|
501
673
|
'content-type': 'application/json',
|
|
674
|
+
'X-TimeZone': userTimeZone,
|
|
502
675
|
...(httpExtra?.headers ?? {}),
|
|
503
676
|
};
|
|
504
|
-
const body = (
|
|
677
|
+
const body = normalizeDateTimeInputsToUtc(
|
|
678
|
+
(inputs ?? httpExtra?.body ?? {}) as Record<string, unknown>,
|
|
679
|
+
adminforth,
|
|
680
|
+
headers['X-TimeZone'],
|
|
681
|
+
);
|
|
505
682
|
const query = httpExtra?.query ?? {};
|
|
506
683
|
const cookies = normalizeCookies(httpExtra?.cookies);
|
|
507
684
|
const requestUrl = httpExtra?.requestUrl ?? `${adminforth.config.baseUrl}/adminapi/v1${endpoint.path}`;
|
|
@@ -596,6 +773,7 @@ export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, Ap
|
|
|
596
773
|
adminUser: adminUser ?? adminuser,
|
|
597
774
|
inputs,
|
|
598
775
|
httpExtra,
|
|
776
|
+
userTimeZone,
|
|
599
777
|
});
|
|
600
778
|
|
|
601
779
|
const processedOutput = await applyToolOverride({
|
|
@@ -629,4 +807,4 @@ export function serializeApiBasedTool(tool: ApiBasedTool | undefined) {
|
|
|
629
807
|
output_schema: tool.output_schema,
|
|
630
808
|
call: '[Function]',
|
|
631
809
|
};
|
|
632
|
-
}
|
|
810
|
+
}
|
package/build.log
CHANGED
|
@@ -38,5 +38,5 @@ custom/skills/fetch_data/SKILL.md
|
|
|
38
38
|
custom/skills/mutate_data/
|
|
39
39
|
custom/skills/mutate_data/SKILL.md
|
|
40
40
|
|
|
41
|
-
sent
|
|
42
|
-
total size is 197,
|
|
41
|
+
sent 200,003 bytes received 562 bytes 401,130.00 bytes/sec
|
|
42
|
+
total size is 197,699 speedup is 0.99
|
package/custom/package.json
CHANGED
|
@@ -73,8 +73,7 @@ Are you sure?
|
|
|
73
73
|
## Updating
|
|
74
74
|
|
|
75
75
|
You can use tool `update_record` tool it updates fields of record. To update `allowedActions.edit` should be set to true and
|
|
76
|
-
`updated` column `showIn.edit` should be true at the same time. If one of this condition is not met, explain to user that is
|
|
77
|
-
not allowed to edit
|
|
76
|
+
`updated` column `showIn.edit` should be true at the same time. If one of this condition is not met, explain to user that is not allowed to edit
|
|
78
77
|
|
|
79
78
|
In addition to instructions above show user the table of edits (old value/new value)
|
|
80
79
|
|
|
@@ -126,6 +125,10 @@ After creation of new record also show user a link to this record. If several re
|
|
|
126
125
|
|
|
127
126
|
Omit any pictures or file paths, you are not capable of doing it. If they are not required all is good, if they are required, explain to user that you are not able to create record because of this reason.
|
|
128
127
|
|
|
128
|
+
### Working with dates
|
|
129
|
+
|
|
130
|
+
When you create or update date or datetime fields, please use ISO format for this. For example, "2024-01-01" for date and "2024-01-01T12:00:00Z" for datetime. If user provides date in different format, try to parse it and convert to ISO format.
|
|
131
|
+
|
|
129
132
|
### Example
|
|
130
133
|
|
|
131
134
|
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { ToolMessage } from "@langchain/core/messages";
|
|
11
11
|
import { createMiddleware } from "langchain";
|
|
12
12
|
import { logger } from "adminforth";
|
|
13
|
+
import { formatApiBasedToolCall, } from "../../apiBasedTools.js";
|
|
13
14
|
import { createToolCallTracker, } from "../toolCallEvents.js";
|
|
14
15
|
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "../tools/index.js";
|
|
15
16
|
import { createApiTool } from "../tools/apiTool.js";
|
|
@@ -40,7 +41,7 @@ function getEnabledApiToolNames(messages) {
|
|
|
40
41
|
}
|
|
41
42
|
return enabledToolNames;
|
|
42
43
|
}
|
|
43
|
-
export function createApiBasedToolsMiddleware(apiBasedTools) {
|
|
44
|
+
export function createApiBasedToolsMiddleware(apiBasedTools, adminforth) {
|
|
44
45
|
const alwaysAvailableApiToolNames = new Set(ALWAYS_AVAILABLE_API_TOOL_NAMES);
|
|
45
46
|
const dynamicTools = Object.fromEntries(Object.entries(apiBasedTools).map(([toolName, apiBasedTool]) => [
|
|
46
47
|
toolName,
|
|
@@ -62,12 +63,30 @@ export function createApiBasedToolsMiddleware(apiBasedTools) {
|
|
|
62
63
|
var _a, _b, _c, _d;
|
|
63
64
|
const startedAt = Date.now();
|
|
64
65
|
const toolInput = JSON.stringify((_a = request.toolCall.args) !== null && _a !== void 0 ? _a : {});
|
|
65
|
-
const { emitToolCallEvent } = request.runtime.context;
|
|
66
|
+
const { adminUser, emitToolCallEvent, userTimeZone } = request.runtime.context;
|
|
67
|
+
const toolArgs = ((_b = request.toolCall.args) !== null && _b !== void 0 ? _b : {});
|
|
68
|
+
let toolInfo;
|
|
69
|
+
if (request.toolCall.name === "fetch_skill") {
|
|
70
|
+
toolInfo = `Load ${toolArgs.skillName.split("_").join(" ")} skill`;
|
|
71
|
+
}
|
|
72
|
+
else if (request.toolCall.name === "fetch_tool_schema") {
|
|
73
|
+
toolInfo = `Load ${toolArgs.toolName.split("_").join(" ")} tool `;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
toolInfo = yield formatApiBasedToolCall({
|
|
77
|
+
adminforth,
|
|
78
|
+
adminUser,
|
|
79
|
+
inputs: toolArgs,
|
|
80
|
+
toolName: request.toolCall.name,
|
|
81
|
+
userTimeZone,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
66
84
|
const toolCallTracker = createToolCallTracker({
|
|
67
85
|
emit: emitToolCallEvent,
|
|
68
86
|
toolCallId: request.toolCall.id,
|
|
69
87
|
toolName: request.toolCall.name,
|
|
70
|
-
|
|
88
|
+
toolInfo,
|
|
89
|
+
input: toolArgs,
|
|
71
90
|
startedAt,
|
|
72
91
|
});
|
|
73
92
|
toolCallTracker.start();
|
|
@@ -66,15 +66,31 @@ function finalizeSequenceDebug(sequence) {
|
|
|
66
66
|
resultType: (_a = sequence.resultType) !== null && _a !== void 0 ? _a : "final_text",
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
function getDebugModelName(model) {
|
|
70
|
+
return typeof model.getName === "function" ? model.getName() : undefined;
|
|
71
|
+
}
|
|
72
|
+
function supportsOpenAiResponseDebug(model) {
|
|
73
|
+
return (getDebugModelName(model) === "ChatOpenAI" &&
|
|
74
|
+
typeof model.model === "string" &&
|
|
75
|
+
typeof model.invocationParams === "function");
|
|
76
|
+
}
|
|
69
77
|
function stringifyPromptForDebug(params) {
|
|
70
|
-
var _a;
|
|
78
|
+
var _a, _b, _c, _d;
|
|
71
79
|
const { model, systemMessage, messages, tools, toolChoice, modelSettings } = params;
|
|
80
|
+
if (!supportsOpenAiResponseDebug(model)) {
|
|
81
|
+
return YAML.stringify(Object.assign(Object.assign(Object.assign({ model: {
|
|
82
|
+
name: (_a = getDebugModelName(model)) !== null && _a !== void 0 ? _a : null,
|
|
83
|
+
provider: (_c = (_b = model._defaultConfig) === null || _b === void 0 ? void 0 : _b.modelProvider) !== null && _c !== void 0 ? _c : null,
|
|
84
|
+
configuredModel: typeof model.model === "string" ? model.model : null,
|
|
85
|
+
}, systemMessage,
|
|
86
|
+
messages }, (tools.length > 0 ? { tools } : {})), (toolChoice !== undefined ? { toolChoice } : {})), (modelSettings ? { modelSettings } : {})));
|
|
87
|
+
}
|
|
72
88
|
return YAML.stringify(Object.assign({ input: convertMessagesToResponsesInput({
|
|
73
89
|
messages: [
|
|
74
90
|
...(systemMessage.text === "" ? [] : [systemMessage]),
|
|
75
91
|
...messages,
|
|
76
92
|
],
|
|
77
|
-
zdrEnabled: (
|
|
93
|
+
zdrEnabled: (_d = model.zdrEnabled) !== null && _d !== void 0 ? _d : false,
|
|
78
94
|
model: model.model,
|
|
79
95
|
}) }, model.invocationParams(Object.assign(Object.assign(Object.assign({}, (modelSettings !== null && modelSettings !== void 0 ? modelSettings : {})), (tools.length > 0 ? { tools } : {})), (toolChoice !== undefined ? { tool_choice: toolChoice } : {})))));
|
|
80
96
|
}
|
|
@@ -8,10 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { createAgent, summarizationMiddleware } from "langchain";
|
|
11
|
-
import {
|
|
11
|
+
import { MODEL_PROVIDER_CONFIG, getChatModelByClassName, } from "langchain/chat_models/universal";
|
|
12
|
+
import { logger, } from "adminforth";
|
|
12
13
|
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
13
14
|
import { z } from "zod";
|
|
14
|
-
import { ChatOpenAI } from "@langchain/openai";
|
|
15
15
|
import { createAgentTools } from "./tools/index.js";
|
|
16
16
|
import { createApiBasedToolsMiddleware } from "./middleware/apiBasedTools.js";
|
|
17
17
|
import { createSequenceDebugMiddleware, } from "./middleware/sequenceDebug.js";
|
|
@@ -23,6 +23,149 @@ export const contextSchema = z.object({
|
|
|
23
23
|
turnId: z.string(),
|
|
24
24
|
emitToolCallEvent: z.custom(),
|
|
25
25
|
});
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return typeof value === "object" && value !== null;
|
|
28
|
+
}
|
|
29
|
+
function normalizeProvider(value) {
|
|
30
|
+
if (typeof value !== "string") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const normalized = value.toLowerCase().replace(/[_\s]+/g, "-");
|
|
34
|
+
if (["openai", "open-ai"].includes(normalized)) {
|
|
35
|
+
return "openai";
|
|
36
|
+
}
|
|
37
|
+
if (["anthropic", "claude"].includes(normalized)) {
|
|
38
|
+
return "anthropic";
|
|
39
|
+
}
|
|
40
|
+
if ([
|
|
41
|
+
"google",
|
|
42
|
+
"gemini",
|
|
43
|
+
"google-genai",
|
|
44
|
+
"google-gemini",
|
|
45
|
+
"google-generative-ai",
|
|
46
|
+
"google-generativeai",
|
|
47
|
+
].includes(normalized)) {
|
|
48
|
+
return "google-genai";
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function detectProviderFromConstructorName(constructorName) {
|
|
53
|
+
const normalized = constructorName === null || constructorName === void 0 ? void 0 : constructorName.toLowerCase();
|
|
54
|
+
if (!normalized) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
if (normalized.includes("openai")) {
|
|
58
|
+
return "openai";
|
|
59
|
+
}
|
|
60
|
+
if (normalized.includes("anthropic") || normalized.includes("claude")) {
|
|
61
|
+
return "anthropic";
|
|
62
|
+
}
|
|
63
|
+
if (normalized.includes("gemini") || normalized.includes("google")) {
|
|
64
|
+
return "google-genai";
|
|
65
|
+
}
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
function detectProviderFromModelName(model) {
|
|
69
|
+
const normalized = model === null || model === void 0 ? void 0 : model.toLowerCase();
|
|
70
|
+
if (!normalized) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
if (normalized.startsWith("claude")) {
|
|
74
|
+
return "anthropic";
|
|
75
|
+
}
|
|
76
|
+
if (normalized.startsWith("gemini")) {
|
|
77
|
+
return "google-genai";
|
|
78
|
+
}
|
|
79
|
+
if (/^(gpt|o[1-9]|chatgpt)/.test(normalized)) {
|
|
80
|
+
return "openai";
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
function detectAgentModelProvider(adapter) {
|
|
85
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
86
|
+
const options = (_a = adapter.options) !== null && _a !== void 0 ? _a : {};
|
|
87
|
+
return ((_j = (_h = (_g = (_f = (_e = (_c = (_b = normalizeProvider(options.modelProvider)) !== null && _b !== void 0 ? _b : normalizeProvider(options.provider)) !== null && _c !== void 0 ? _c : detectProviderFromConstructorName((_d = adapter.constructor) === null || _d === void 0 ? void 0 : _d.name)) !== null && _e !== void 0 ? _e : (options.openAiApiKey || options.openAIApiKey
|
|
88
|
+
? "openai"
|
|
89
|
+
: undefined)) !== null && _f !== void 0 ? _f : (options.anthropicApiKey ? "anthropic" : undefined)) !== null && _g !== void 0 ? _g : (options.geminiApiKey ||
|
|
90
|
+
options.googleApiKey ||
|
|
91
|
+
options.googleGenAiApiKey ||
|
|
92
|
+
options.googleGenerativeAiApiKey
|
|
93
|
+
? "google-genai"
|
|
94
|
+
: undefined)) !== null && _h !== void 0 ? _h : detectProviderFromModelName(options.model)) !== null && _j !== void 0 ? _j : (() => {
|
|
95
|
+
throw new Error("Could not infer completion adapter provider. Set options.modelProvider to openai, anthropic, or google-genai.");
|
|
96
|
+
})());
|
|
97
|
+
}
|
|
98
|
+
function getProviderApiKey(provider, options) {
|
|
99
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
100
|
+
switch (provider) {
|
|
101
|
+
case "openai":
|
|
102
|
+
return (_b = (_a = options === null || options === void 0 ? void 0 : options.openAiApiKey) !== null && _a !== void 0 ? _a : options === null || options === void 0 ? void 0 : options.openAIApiKey) !== null && _b !== void 0 ? _b : options === null || options === void 0 ? void 0 : options.apiKey;
|
|
103
|
+
case "anthropic":
|
|
104
|
+
return (_c = options === null || options === void 0 ? void 0 : options.anthropicApiKey) !== null && _c !== void 0 ? _c : options === null || options === void 0 ? void 0 : options.apiKey;
|
|
105
|
+
case "google-genai":
|
|
106
|
+
return ((_g = (_f = (_e = (_d = options === null || options === void 0 ? void 0 : options.geminiApiKey) !== null && _d !== void 0 ? _d : options === null || options === void 0 ? void 0 : options.googleApiKey) !== null && _e !== void 0 ? _e : options === null || options === void 0 ? void 0 : options.googleGenAiApiKey) !== null && _f !== void 0 ? _f : options === null || options === void 0 ? void 0 : options.googleGenerativeAiApiKey) !== null && _g !== void 0 ? _g : options === null || options === void 0 ? void 0 : options.apiKey);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function getProviderModel(provider, options) {
|
|
110
|
+
if (options === null || options === void 0 ? void 0 : options.model) {
|
|
111
|
+
return options.model;
|
|
112
|
+
}
|
|
113
|
+
if (provider === "openai") {
|
|
114
|
+
return "gpt-5-nano";
|
|
115
|
+
}
|
|
116
|
+
if (provider === "google-genai") {
|
|
117
|
+
return "gemini-3-flash-preview";
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`CompletionAdapter for provider ${provider} must expose options.model`);
|
|
120
|
+
}
|
|
121
|
+
function buildChatModelConfig(params) {
|
|
122
|
+
var _a, _b;
|
|
123
|
+
const { provider, options, maxTokens } = params;
|
|
124
|
+
const apiKey = getProviderApiKey(provider, options);
|
|
125
|
+
if (!apiKey) {
|
|
126
|
+
const optionName = provider === "openai"
|
|
127
|
+
? "options.openAiApiKey"
|
|
128
|
+
: provider === "anthropic"
|
|
129
|
+
? "options.anthropicApiKey"
|
|
130
|
+
: "options.geminiApiKey";
|
|
131
|
+
throw new Error(`CompletionAdapter must expose ${optionName} for ${provider} agent mode`);
|
|
132
|
+
}
|
|
133
|
+
const model = getProviderModel(provider, options);
|
|
134
|
+
const baseURL = (_a = options === null || options === void 0 ? void 0 : options.baseURL) !== null && _a !== void 0 ? _a : options === null || options === void 0 ? void 0 : options.baseUrl;
|
|
135
|
+
const extraRequestBodyParameters = Object.assign({}, ((_b = options === null || options === void 0 ? void 0 : options.extraRequestBodyParameters) !== null && _b !== void 0 ? _b : {}));
|
|
136
|
+
if (provider === "openai" && isRecord(extraRequestBodyParameters.reasoning)) {
|
|
137
|
+
extraRequestBodyParameters.reasoning = Object.assign(Object.assign({}, extraRequestBodyParameters.reasoning), { summary: "auto" });
|
|
138
|
+
}
|
|
139
|
+
const config = Object.assign({ model,
|
|
140
|
+
apiKey,
|
|
141
|
+
maxTokens, streaming: true }, extraRequestBodyParameters);
|
|
142
|
+
if (typeof (options === null || options === void 0 ? void 0 : options.timeoutMs) === "number") {
|
|
143
|
+
config.timeout = options.timeoutMs;
|
|
144
|
+
}
|
|
145
|
+
if (baseURL) {
|
|
146
|
+
config.baseURL = baseURL;
|
|
147
|
+
config.baseUrl = baseURL;
|
|
148
|
+
config.configuration = {
|
|
149
|
+
baseURL,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (provider === "openai") {
|
|
153
|
+
config.openAIApiKey = apiKey;
|
|
154
|
+
config.useResponsesApi = true;
|
|
155
|
+
config.outputVersion = "v1";
|
|
156
|
+
config.promptCacheKey = `adminforth-agent:${model}:system-v1:tools-v1`;
|
|
157
|
+
config.promptCacheRetention = "in_memory";
|
|
158
|
+
}
|
|
159
|
+
if (provider === "anthropic") {
|
|
160
|
+
config.anthropicApiKey = apiKey;
|
|
161
|
+
}
|
|
162
|
+
if (provider === "google-genai") {
|
|
163
|
+
config.geminiApiKey = apiKey;
|
|
164
|
+
config.googleApiKey = apiKey;
|
|
165
|
+
config.maxOutputTokens = maxTokens;
|
|
166
|
+
}
|
|
167
|
+
return { model, config };
|
|
168
|
+
}
|
|
26
169
|
function getFiniteNumber(value) {
|
|
27
170
|
return typeof value === "number" && Number.isFinite(value)
|
|
28
171
|
? value
|
|
@@ -102,38 +245,35 @@ function createAgentLlmMetricsLogger() {
|
|
|
102
245
|
return new AgentLlmMetricsLogger();
|
|
103
246
|
}
|
|
104
247
|
export function createAgentChatModel(params) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
baseURL,
|
|
123
|
-
},
|
|
124
|
-
}
|
|
125
|
-
: {})));
|
|
248
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
249
|
+
var _a;
|
|
250
|
+
const adapter = params.adapter;
|
|
251
|
+
const options = (_a = adapter.options) !== null && _a !== void 0 ? _a : {};
|
|
252
|
+
const provider = detectAgentModelProvider(adapter);
|
|
253
|
+
const { config } = buildChatModelConfig({
|
|
254
|
+
provider,
|
|
255
|
+
options,
|
|
256
|
+
maxTokens: params.maxTokens,
|
|
257
|
+
});
|
|
258
|
+
const className = MODEL_PROVIDER_CONFIG[provider].className;
|
|
259
|
+
const ChatModelClass = yield getChatModelByClassName(className, provider);
|
|
260
|
+
return {
|
|
261
|
+
model: new ChatModelClass(config),
|
|
262
|
+
provider,
|
|
263
|
+
};
|
|
264
|
+
});
|
|
126
265
|
}
|
|
127
266
|
export function callAgent(params) {
|
|
128
267
|
return __awaiter(this, void 0, void 0, function* () {
|
|
129
|
-
const { name, model, summaryModel, checkpointer, messages, adminUser, apiBasedTools, customComponentsDir, sessionId, turnId, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
|
|
268
|
+
const { name, model, summaryModel, modelProvider, checkpointer, messages, adminUser, adminforth, apiBasedTools, customComponentsDir, sessionId, turnId, userTimeZone, emitToolCallEvent, sequenceDebugSink, } = params;
|
|
130
269
|
const tools = yield createAgentTools(customComponentsDir, apiBasedTools);
|
|
131
|
-
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools);
|
|
132
|
-
const openAiResponsesContinuationMiddleware = createOpenAiResponsesContinuationMiddleware();
|
|
270
|
+
const apiBasedToolsMiddleware = createApiBasedToolsMiddleware(apiBasedTools, adminforth);
|
|
133
271
|
const sequenceDebugMiddleware = createSequenceDebugMiddleware(sequenceDebugSink);
|
|
134
272
|
const middleware = [
|
|
135
273
|
apiBasedToolsMiddleware,
|
|
136
|
-
|
|
274
|
+
...(modelProvider === "openai"
|
|
275
|
+
? [createOpenAiResponsesContinuationMiddleware()]
|
|
276
|
+
: []),
|
|
137
277
|
sequenceDebugMiddleware,
|
|
138
278
|
summarizationMiddleware({
|
|
139
279
|
model: summaryModel,
|