@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.
@@ -17,6 +17,32 @@ import YAML from 'yaml';
17
17
  dayjs.extend(utc);
18
18
  dayjs.extend(timezone);
19
19
  const DEFAULT_USER_TIME_ZONE = 'UTC';
20
+ function getInputString(inputs, key) {
21
+ const value = inputs === null || inputs === void 0 ? void 0 : inputs[key];
22
+ return typeof value === 'string' && value ? value : undefined;
23
+ }
24
+ function getInputArrayLength(inputs, key) {
25
+ const value = inputs === null || inputs === void 0 ? void 0 : inputs[key];
26
+ return Array.isArray(value) ? value.length : undefined;
27
+ }
28
+ function resourceLabel(adminforth, inputs) {
29
+ var _a, _b;
30
+ const resourceId = getInputString(inputs, 'resourceId');
31
+ const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
32
+ return (_b = (_a = resource === null || resource === void 0 ? void 0 : resource.label) !== null && _a !== void 0 ? _a : resourceId) !== null && _b !== void 0 ? _b : 'resource';
33
+ }
34
+ function getDataPrefix(inputs) {
35
+ const offset = typeof (inputs === null || inputs === void 0 ? void 0 : inputs.offset) === 'number' ? inputs.offset : undefined;
36
+ const limit = typeof (inputs === null || inputs === void 0 ? void 0 : inputs.limit) === 'number' ? inputs.limit : undefined;
37
+ if (offset !== undefined && limit !== undefined) {
38
+ return `${offset}-${offset + limit} `;
39
+ }
40
+ return limit === undefined ? '' : `${limit} `;
41
+ }
42
+ function actionText(inputs) {
43
+ const actionId = getInputString(inputs, 'actionId');
44
+ return actionId ? ` action ${actionId}` : ' action';
45
+ }
20
46
  const TOOL_OVERRIDES = {
21
47
  get_resource: {
22
48
  wipe_frontend_specific_data: [
@@ -25,11 +51,10 @@ const TOOL_OVERRIDES = {
25
51
  'resource.options.actions[].customComponent',
26
52
  'resource.options.pageInjections',
27
53
  ],
28
- format_tool: (_a) => __awaiter(void 0, [_a], void 0, function* ({}) {
29
- return "get resource Apartments";
30
- })
54
+ format_tool: ({ resourceLabel }) => `Get ${resourceLabel} resource`,
31
55
  },
32
56
  get_resource_data: {
57
+ format_tool: ({ inputs, resourceLabel }) => (`Get ${getDataPrefix(inputs)}${resourceLabel}`),
33
58
  post_process_response: (_a) => __awaiter(void 0, [_a], void 0, function* ({ output, inputs, invokeTool, userTimeZone }) {
34
59
  if (hasToolError(output)) {
35
60
  return output;
@@ -47,9 +72,35 @@ const TOOL_OVERRIDES = {
47
72
  formatDateTimeColumns(response.data, dateTimeColumnNames, localizedTimeZone);
48
73
  return response;
49
74
  }),
50
- format_tool: (_a) => __awaiter(void 0, [_a], void 0, function* ({}) {
51
- return "get 1-20 Apartment filtered listed=yes";
52
- })
75
+ },
76
+ aggregate: {
77
+ format_tool: ({ resourceLabel }) => `Aggregate ${resourceLabel}`,
78
+ },
79
+ start_custom_action: {
80
+ format_tool: ({ inputs, resourceLabel }) => `Run ${resourceLabel}${actionText(inputs)}`,
81
+ },
82
+ start_custom_bulk_action: {
83
+ format_tool: ({ inputs, resourceLabel }) => {
84
+ const recordCount = getInputArrayLength(inputs, 'recordIds');
85
+ const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
86
+ return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
87
+ },
88
+ },
89
+ start_bulk_action: {
90
+ format_tool: ({ inputs, resourceLabel }) => {
91
+ const recordCount = getInputArrayLength(inputs, 'recordIds');
92
+ const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
93
+ return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
94
+ },
95
+ },
96
+ create_record: {
97
+ format_tool: ({ resourceLabel }) => `Create ${resourceLabel}`,
98
+ },
99
+ update_record: {
100
+ format_tool: ({ resourceLabel }) => `Update ${resourceLabel}`,
101
+ },
102
+ delete_record: {
103
+ format_tool: ({ resourceLabel }) => `Delete ${resourceLabel}`,
53
104
  },
54
105
  };
55
106
  function sanitizeForYaml(value) {
@@ -212,6 +263,7 @@ function applyToolOverride(params) {
212
263
  adminUser,
213
264
  inputs: nestedInputs,
214
265
  httpExtra: nestedHttpExtra,
266
+ userTimeZone: nestedUserTimeZone,
215
267
  });
216
268
  return applyToolOverride({
217
269
  adminforth,
@@ -234,6 +286,22 @@ function endpointPathToToolName(path) {
234
286
  .replace(/[^a-zA-Z0-9_]+/g, '_')
235
287
  .replace(/^_+|_+$/g, '');
236
288
  }
289
+ export function formatApiBasedToolCall(params) {
290
+ return __awaiter(this, void 0, void 0, function* () {
291
+ var _a;
292
+ const formatTool = (_a = TOOL_OVERRIDES[params.toolName]) === null || _a === void 0 ? void 0 : _a.format_tool;
293
+ return yield (formatTool === null || formatTool === void 0 ? void 0 : formatTool({
294
+ adminUser: params.adminUser,
295
+ httpExtra: params.httpExtra,
296
+ inputs: params.inputs,
297
+ resourceLabel: resourceLabel(params.adminforth, params.inputs),
298
+ userTimeZone: params.userTimeZone,
299
+ invokeTool: () => __awaiter(this, void 0, void 0, function* () {
300
+ throw new Error('Tool info formatting cannot invoke tools');
301
+ }),
302
+ }));
303
+ });
304
+ }
237
305
  function normalizeCookies(cookies) {
238
306
  if (!cookies) {
239
307
  return [];
@@ -306,13 +374,61 @@ function createRawExpressResponse(response) {
306
374
  };
307
375
  return rawResponse;
308
376
  }
377
+ function normalizeDateTimeInputsToUtc(body, adminforth, userTimeZone) {
378
+ if (!userTimeZone || typeof body.resourceId !== 'string') {
379
+ return body;
380
+ }
381
+ const resource = adminforth.config.resources.find((res) => res.resourceId === body.resourceId);
382
+ if (!resource) {
383
+ return body;
384
+ }
385
+ const columnsByName = new Map(resource.dataSourceColumns.map((column) => [column.name, column]));
386
+ const normalizeColumnValue = (value, columnType) => {
387
+ if (Array.isArray(value)) {
388
+ return value.map((item) => normalizeColumnValue(item, columnType));
389
+ }
390
+ if (typeof value !== 'string' || value === '') {
391
+ return value;
392
+ }
393
+ if (columnType === AdminForthDataTypes.DATETIME) {
394
+ return dayjs.tz(value, userTimeZone).utc().toISOString();
395
+ }
396
+ if (columnType === AdminForthDataTypes.TIME) {
397
+ const userDate = dayjs().tz(userTimeZone).format('YYYY-MM-DD');
398
+ return dayjs.tz(`${userDate}T${value}`, userTimeZone).utc().format('HH:mm:ss');
399
+ }
400
+ };
401
+ const normalizeValue = (value, key) => {
402
+ const column = key ? columnsByName.get(key) : undefined;
403
+ if ((column === null || column === void 0 ? void 0 : column.type) === AdminForthDataTypes.DATETIME || (column === null || column === void 0 ? void 0 : column.type) === AdminForthDataTypes.TIME) {
404
+ return normalizeColumnValue(value, column.type);
405
+ }
406
+ if (Array.isArray(value)) {
407
+ return value.map((item) => normalizeValue(item));
408
+ }
409
+ if (!value || typeof value !== 'object') {
410
+ return value;
411
+ }
412
+ const record = value;
413
+ const filterColumn = typeof record.field === 'string' ? columnsByName.get(record.field) : undefined;
414
+ if ('value' in record &&
415
+ ((filterColumn === null || filterColumn === void 0 ? void 0 : filterColumn.type) === AdminForthDataTypes.DATETIME || (filterColumn === null || filterColumn === void 0 ? void 0 : filterColumn.type) === AdminForthDataTypes.TIME)) {
416
+ return Object.assign(Object.assign({}, record), { value: normalizeColumnValue(record.value, filterColumn.type) });
417
+ }
418
+ return Object.fromEntries(Object.entries(record).map(([nestedKey, nestedValue]) => [
419
+ nestedKey,
420
+ normalizeValue(nestedValue, nestedKey),
421
+ ]));
422
+ };
423
+ return normalizeValue(body);
424
+ }
309
425
  function callCapturedEndpoint(params) {
310
426
  return __awaiter(this, void 0, void 0, function* () {
311
427
  var _a, _b, _c, _d;
312
- const { adminforth, adminUser, endpoint, httpExtra, inputs } = params;
428
+ const { adminforth, adminUser, endpoint, httpExtra, inputs, userTimeZone } = params;
313
429
  const response = createToolResponse(httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.response);
314
- const headers = Object.assign({ 'content-type': 'application/json' }, ((_a = httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.headers) !== null && _a !== void 0 ? _a : {}));
315
- const body = ((_b = inputs !== null && inputs !== void 0 ? inputs : httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.body) !== null && _b !== void 0 ? _b : {});
430
+ const headers = Object.assign({ 'content-type': 'application/json', 'X-TimeZone': userTimeZone }, ((_a = httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.headers) !== null && _a !== void 0 ? _a : {}));
431
+ const body = normalizeDateTimeInputsToUtc(((_b = inputs !== null && inputs !== void 0 ? inputs : httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.body) !== null && _b !== void 0 ? _b : {}), adminforth, headers['X-TimeZone']);
316
432
  const query = (_c = httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.query) !== null && _c !== void 0 ? _c : {};
317
433
  const cookies = normalizeCookies(httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.cookies);
318
434
  const requestUrl = (_d = httpExtra === null || httpExtra === void 0 ? void 0 : httpExtra.requestUrl) !== null && _d !== void 0 ? _d : `${adminforth.config.baseUrl}/adminapi/v1${endpoint.path}`;
@@ -387,6 +503,7 @@ export function prepareApiBasedTools(adminforth) {
387
503
  adminUser: adminUser !== null && adminUser !== void 0 ? adminUser : adminuser,
388
504
  inputs,
389
505
  httpExtra,
506
+ userTimeZone,
390
507
  });
391
508
  const processedOutput = yield applyToolOverride({
392
509
  adminforth,
@@ -7,7 +7,7 @@
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
9
9
  "keywords": [],
10
- "author": "",
10
+ "author": "DevForth (https://devforth.io)",
11
11
  "license": "ISC",
12
12
  "packageManager": "pnpm@10.33.0",
13
13
  "dependencies": {
@@ -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
 
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
18
18
  import { randomUUID } from 'crypto';
19
19
  import { HumanMessage, SystemMessage } from "langchain";
20
20
  import { MemorySaver } from "@langchain/langgraph";
21
- import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
21
+ import { createAgentChatModel, callAgent, } from "./agent/simpleAgent.js";
22
22
  import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
23
23
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
24
24
  import { prepareApiBasedTools as buildApiBasedTools, } from './apiBasedTools.js';
@@ -91,22 +91,34 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
91
91
  });
92
92
  }
93
93
  getModeModels(mode, maxTokens) {
94
- const cachedModels = this.modelsByModeName.get(mode.name);
95
- if (cachedModels) {
96
- return cachedModels;
97
- }
98
- const models = {
99
- model: createAgentChatModel({
100
- adapter: mode.completionAdapter,
101
- maxTokens,
102
- }),
103
- summaryModel: createAgentChatModel({
104
- adapter: mode.completionAdapter,
105
- maxTokens,
106
- }),
107
- };
108
- this.modelsByModeName.set(mode.name, models);
109
- return models;
94
+ return __awaiter(this, void 0, void 0, function* () {
95
+ const cachedModels = this.modelsByModeName.get(mode.name);
96
+ if (cachedModels) {
97
+ return yield cachedModels;
98
+ }
99
+ const modelsPromise = Promise.all([
100
+ createAgentChatModel({
101
+ adapter: mode.completionAdapter,
102
+ maxTokens,
103
+ }),
104
+ createAgentChatModel({
105
+ adapter: mode.completionAdapter,
106
+ maxTokens,
107
+ }),
108
+ ]).then(([primaryModel, summaryModel]) => ({
109
+ model: primaryModel.model,
110
+ summaryModel: summaryModel.model,
111
+ modelProvider: primaryModel.provider,
112
+ }));
113
+ this.modelsByModeName.set(mode.name, modelsPromise);
114
+ try {
115
+ return yield modelsPromise;
116
+ }
117
+ catch (error) {
118
+ this.modelsByModeName.delete(mode.name);
119
+ throw error;
120
+ }
121
+ });
110
122
  }
111
123
  getCheckpointer() {
112
124
  if (this.checkpointer) {
@@ -272,18 +284,20 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
272
284
  });
273
285
  const maxTokens = (_f = this.options.maxTokens) !== null && _f !== void 0 ? _f : 10000;
274
286
  const selectedMode = (_g = this.options.modes.find((mode) => mode.name === body.mode)) !== null && _g !== void 0 ? _g : this.options.modes[0];
275
- const { model, summaryModel } = this.getModeModels(selectedMode, maxTokens);
287
+ const { model, summaryModel, modelProvider } = yield this.getModeModels(selectedMode, maxTokens);
276
288
  const systemPrompt = yield this.agentSystemPromptPromise;
277
289
  const stream = yield callAgent({
278
290
  name: `adminforth-agent-${this.pluginInstanceId}`,
279
291
  model,
280
292
  summaryModel,
293
+ modelProvider,
281
294
  checkpointer: this.getCheckpointer(),
282
295
  messages: [
283
296
  new SystemMessage(systemPrompt),
284
297
  new HumanMessage(prompt),
285
298
  ],
286
299
  adminUser,
300
+ adminforth: this.adminforth,
287
301
  apiBasedTools: this.apiBasedTools,
288
302
  customComponentsDir: this.adminforth.config.customization.customComponentsDir,
289
303
  sessionId,
package/index.ts CHANGED
@@ -10,7 +10,12 @@ import type { PluginOptions } from './types.js';
10
10
  import { randomUUID } from 'crypto';
11
11
  import { HumanMessage, SystemMessage } from "langchain";
12
12
  import { MemorySaver, type BaseCheckpointSaver } from "@langchain/langgraph";
13
- import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
13
+ import {
14
+ createAgentChatModel,
15
+ callAgent,
16
+ type AgentChatModel,
17
+ type AgentModelProvider,
18
+ } from "./agent/simpleAgent.js";
14
19
  import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
15
20
  import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
16
21
  import {
@@ -24,7 +29,6 @@ import {
24
29
  } from "./agent/systemPrompt.js";
25
30
  import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "./agent/tools/index.js";
26
31
  import type { ToolCallEvent } from "./agent/toolCallEvents.js";
27
- import type { ChatOpenAI } from "@langchain/openai";
28
32
 
29
33
  function isAggregateErrorLike(
30
34
  error: unknown,
@@ -75,10 +79,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
75
79
  private checkpointer: BaseCheckpointSaver | null = null;
76
80
  private readonly modelsByModeName = new Map<
77
81
  string,
78
- {
79
- model: ChatOpenAI;
80
- summaryModel: ChatOpenAI;
81
- }
82
+ Promise<{
83
+ model: AgentChatModel;
84
+ summaryModel: AgentChatModel;
85
+ modelProvider: AgentModelProvider;
86
+ }>
82
87
  >();
83
88
 
84
89
  private async createNewTurn(sessionId: string, prompt: string, response?: string) {
@@ -118,29 +123,39 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
118
123
  }));
119
124
  }
120
125
 
121
- private getModeModels(
126
+ private async getModeModels(
122
127
  mode: PluginOptions["modes"][number],
123
128
  maxTokens: number,
124
129
  ) {
125
130
  const cachedModels = this.modelsByModeName.get(mode.name);
126
131
 
127
132
  if (cachedModels) {
128
- return cachedModels;
133
+ return await cachedModels;
129
134
  }
130
135
 
131
- const models = {
132
- model: createAgentChatModel({
136
+ const modelsPromise = Promise.all([
137
+ createAgentChatModel({
133
138
  adapter: mode.completionAdapter,
134
139
  maxTokens,
135
140
  }),
136
- summaryModel: createAgentChatModel({
141
+ createAgentChatModel({
137
142
  adapter: mode.completionAdapter,
138
143
  maxTokens,
139
144
  }),
140
- };
145
+ ]).then(([primaryModel, summaryModel]) => ({
146
+ model: primaryModel.model,
147
+ summaryModel: summaryModel.model,
148
+ modelProvider: primaryModel.provider,
149
+ }));
150
+
151
+ this.modelsByModeName.set(mode.name, modelsPromise);
141
152
 
142
- this.modelsByModeName.set(mode.name, models);
143
- return models;
153
+ try {
154
+ return await modelsPromise;
155
+ } catch (error) {
156
+ this.modelsByModeName.delete(mode.name);
157
+ throw error;
158
+ }
144
159
  }
145
160
 
146
161
  private getCheckpointer() {
@@ -326,18 +341,21 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
326
341
 
327
342
  const maxTokens = this.options.maxTokens ?? 10000;
328
343
  const selectedMode = this.options.modes.find((mode) => mode.name === body.mode) ?? this.options.modes[0];
329
- const { model, summaryModel } = this.getModeModels(selectedMode, maxTokens);
344
+ const { model, summaryModel, modelProvider } =
345
+ await this.getModeModels(selectedMode, maxTokens);
330
346
  const systemPrompt = await this.agentSystemPromptPromise;
331
347
  const stream = await callAgent({
332
348
  name: `adminforth-agent-${this.pluginInstanceId}`,
333
349
  model,
334
350
  summaryModel,
351
+ modelProvider,
335
352
  checkpointer: this.getCheckpointer(),
336
353
  messages: [
337
354
  new SystemMessage(systemPrompt),
338
355
  new HumanMessage(prompt),
339
356
  ],
340
357
  adminUser,
358
+ adminforth: this.adminforth,
341
359
  apiBasedTools: this.apiBasedTools,
342
360
  customComponentsDir: this.adminforth.config.customization.customComponentsDir,
343
361
  sessionId,
package/package.json CHANGED
@@ -1,19 +1,28 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.22.2",
3
+ "version": "1.24.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
+ "homepage": "https://adminforth.dev/docs/tutorial/Plugins/agent/",
7
8
  "publishConfig": {
8
9
  "access": "public"
9
10
  },
10
11
  "scripts": {
11
12
  "build": "tsc && rsync -av --exclude 'node_modules' custom dist/"
12
13
  },
13
- "keywords": [],
14
- "author": "",
14
+ "keywords": [
15
+ "adminforth",
16
+ "ai-agent",
17
+ "tool-calling",
18
+ "chat-ui",
19
+ "langgraph",
20
+ "llm",
21
+ "session-memory"
22
+ ],
23
+ "author": "DevForth (https://devforth.io)",
15
24
  "license": "ISC",
16
- "description": "",
25
+ "description": "AI agent plugin for AdminForth with tool-based workflows and persistent chat sessions",
17
26
  "devDependencies": {
18
27
  "@types/node": "latest",
19
28
  "semantic-release": "^24.2.1",
@@ -21,11 +30,13 @@
21
30
  "typescript": "^5.7.3"
22
31
  },
23
32
  "dependencies": {
33
+ "@langchain/anthropic": "1.3.26",
24
34
  "@langchain/core": "^1.1.40",
35
+ "@langchain/google-genai": "2.1.27",
25
36
  "@langchain/langgraph": "^1.2.8",
26
37
  "@langchain/langgraph-checkpoint": "^1.0.1",
27
38
  "@langchain/openai": "^1.4.4",
28
- "adminforth": "2.27.0-next.47",
39
+ "adminforth": "2.42.0",
29
40
  "dayjs": "^1.11.20",
30
41
  "langchain": "^1.3.3",
31
42
  "yaml": "^2.8.3",