@adminforth/agent 2.0.9 → 2.1.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.
Files changed (187) hide show
  1. package/README.md +333 -10
  2. package/dist/application/ports.d.ts +66 -0
  3. package/dist/application/runTurnUseCase.d.ts +68 -0
  4. package/dist/application/runTurnUseCase.js +358 -0
  5. package/dist/{agentEvents.d.ts → domain/agentEvents.d.ts} +1 -1
  6. package/dist/{agent → domain}/systemPrompt.js +1 -1
  7. package/dist/{agent/turn → domain}/turnTypes.d.ts +41 -37
  8. package/dist/{agent/turn/VegaLiteStreamBuffer.d.ts → domain/vegaLiteStreamBuffer.d.ts} +1 -1
  9. package/dist/index.d.ts +3 -2
  10. package/dist/index.js +40 -35
  11. package/dist/{agent/runtime/AgentContext.d.ts → llm/agentContext.d.ts} +3 -3
  12. package/dist/{agent/simpleAgent.d.ts → llm/agentModels.d.ts} +18 -16
  13. package/dist/llm/agentModels.js +65 -0
  14. package/dist/{agent/runtime/AgentRuntime.d.ts → llm/agentRuntime.d.ts} +11 -2
  15. package/dist/{agent/runtime/AgentRuntime.js → llm/agentRuntime.js} +38 -6
  16. package/dist/llm/langGraphLlm.d.ts +25 -0
  17. package/dist/llm/langGraphLlm.js +54 -0
  18. package/dist/{agent/middleware/apiBasedTools.d.ts → llm/middleware/apiToolsMiddleware.d.ts} +1 -1
  19. package/dist/{agent/middleware/apiBasedTools.js → llm/middleware/apiToolsMiddleware.js} +8 -14
  20. package/dist/{agent → llm}/middleware/sequenceDebug.d.ts +1 -1
  21. package/dist/{agent/models/AgentModelFactory.d.ts → llm/modelFactory.d.ts} +1 -1
  22. package/dist/{agent/models/AgentModelFactory.js → llm/modelFactory.js} +1 -1
  23. package/dist/llm/streamAdapter.d.ts +18 -0
  24. package/dist/llm/streamAdapter.js +117 -0
  25. package/dist/{sessionStore.d.ts → persistence/sessionStore.d.ts} +12 -2
  26. package/dist/{sessionStore.js → persistence/sessionStore.js} +33 -0
  27. package/dist/{agent/tools/AgentToolProvider.d.ts → tools/agentToolProvider.d.ts} +1 -1
  28. package/dist/{agent/tools/AgentToolProvider.js → tools/agentToolProvider.js} +1 -1
  29. package/dist/{apiBasedTools.js → tools/apiBasedTools.js} +10 -6
  30. package/dist/{agent/tools → tools}/apiTool.d.ts +1 -1
  31. package/dist/{agent/tools → tools}/fetchSkill.js +1 -1
  32. package/dist/{agent/tools → tools}/fetchToolSchema.d.ts +1 -1
  33. package/dist/{agent/tools → tools}/index.d.ts +1 -1
  34. package/dist/{agent/tools → tools}/navigateUser.d.ts +3 -3
  35. package/dist/{endpoints → transport/http}/context.d.ts +2 -2
  36. package/dist/transport/http/context.js +1 -0
  37. package/dist/{endpoints/core.js → transport/http/coreEndpoints.js} +3 -3
  38. package/dist/{endpoints/sessions.js → transport/http/sessionEndpoints.js} +7 -1
  39. package/dist/transport/sse/sseWriter.d.ts +29 -0
  40. package/dist/{surfaces/web-sse/createSseEventEmitter.js → transport/sse/sseWriter.js} +15 -0
  41. package/dist/{chatSurfaceService.d.ts → transport/surfaces/chatSurfaceService.d.ts} +3 -3
  42. package/dist/{chatSurfaceService.js → transport/surfaces/chatSurfaceService.js} +7 -5
  43. package/dist/{agent/speech/SpeechTurnService.d.ts → transport/surfaces/speechTurnService.d.ts} +1 -1
  44. package/dist/{agent/speech/SpeechTurnService.js → transport/surfaces/speechTurnService.js} +2 -2
  45. package/dist/types.d.ts +10 -4
  46. package/package.json +11 -2
  47. package/.woodpecker/buildRelease.sh +0 -13
  48. package/.woodpecker/buildSlackNotify.sh +0 -46
  49. package/.woodpecker/release.yml +0 -57
  50. package/agent/checkpointer.ts +0 -329
  51. package/agent/languageDetect.ts +0 -92
  52. package/agent/middleware/apiBasedTools.ts +0 -174
  53. package/agent/middleware/openAiResponsesContinuation.ts +0 -92
  54. package/agent/middleware/sequenceDebug.ts +0 -429
  55. package/agent/models/AgentModeResolver.ts +0 -9
  56. package/agent/models/AgentModelFactory.ts +0 -28
  57. package/agent/runtime/AgentContext.ts +0 -30
  58. package/agent/runtime/AgentRuntime.ts +0 -89
  59. package/agent/simpleAgent.ts +0 -199
  60. package/agent/skills/registry.ts +0 -162
  61. package/agent/speech/SpeechTurnService.ts +0 -179
  62. package/agent/systemPrompt.ts +0 -133
  63. package/agent/toolCallEvents.ts +0 -135
  64. package/agent/tools/AgentToolProvider.ts +0 -28
  65. package/agent/tools/apiTool.ts +0 -66
  66. package/agent/tools/fetchSkill.ts +0 -68
  67. package/agent/tools/fetchToolSchema.ts +0 -47
  68. package/agent/tools/getUserLocation.ts +0 -45
  69. package/agent/tools/index.ts +0 -31
  70. package/agent/tools/navigateUser.ts +0 -210
  71. package/agent/turn/TurnContextBuilder.ts +0 -36
  72. package/agent/turn/TurnLifecycleService.ts +0 -47
  73. package/agent/turn/TurnPersistenceService.ts +0 -33
  74. package/agent/turn/TurnPromptBuilder.ts +0 -51
  75. package/agent/turn/TurnStreamConsumer.ts +0 -69
  76. package/agent/turn/VegaLiteStreamBuffer.ts +0 -90
  77. package/agent/turn/turnTypes.ts +0 -100
  78. package/agentEvents.ts +0 -75
  79. package/agentTurnService.ts +0 -314
  80. package/apiBasedTools.ts +0 -702
  81. package/build.log +0 -67
  82. package/chatSurfaceService.ts +0 -319
  83. package/custom/ChatFooter.vue +0 -173
  84. package/custom/ChatHeader.vue +0 -78
  85. package/custom/ChatSurface.vue +0 -160
  86. package/custom/CustomAutoScrollContainer.vue +0 -164
  87. package/custom/SessionsHistory.vue +0 -88
  88. package/custom/chat.ts +0 -82
  89. package/custom/composables/agentAudio/utils.ts +0 -220
  90. package/custom/composables/agentStore/constants.ts +0 -19
  91. package/custom/composables/agentStore/pageContext.ts +0 -8
  92. package/custom/composables/agentStore/useAgentChat.ts +0 -241
  93. package/custom/composables/agentStore/useAgentPlaceholder.ts +0 -142
  94. package/custom/composables/agentStore/useAgentSessions.ts +0 -350
  95. package/custom/composables/useAgentAudio.ts +0 -272
  96. package/custom/composables/useAgentStore.ts +0 -510
  97. package/custom/composables/useAgentTransitions.ts +0 -58
  98. package/custom/conversation_area/ConversationArea.vue +0 -301
  99. package/custom/conversation_area/MessageRenderer.vue +0 -49
  100. package/custom/conversation_area/ProcessingTimeline.vue +0 -286
  101. package/custom/conversation_area/ReasoningRenderer.vue +0 -79
  102. package/custom/conversation_area/SystemMessageRenderer.vue +0 -28
  103. package/custom/conversation_area/TextRenderer.vue +0 -182
  104. package/custom/conversation_area/ThreeDotsAnimation.vue +0 -35
  105. package/custom/conversation_area/ToolApprovalRenderer.vue +0 -98
  106. package/custom/conversation_area/ToolRenderer.vue +0 -178
  107. package/custom/conversation_area/ToolsGroup.vue +0 -59
  108. package/custom/env.d.ts +0 -7
  109. package/custom/incremark_code_renderers/IncremarkShikiCodeBlock.vue +0 -393
  110. package/custom/incremark_code_renderers/incremarkCodeHighlight.ts +0 -285
  111. package/custom/incremark_code_renderers/incremarkRenderer.ts +0 -653
  112. package/custom/incremark_code_renderers/renderIncremarkMarkdown.ts +0 -118
  113. package/custom/package.json +0 -30
  114. package/custom/pnpm-lock.yaml +0 -2394
  115. package/custom/public/agentAudio/agent-processing.mp3 +0 -0
  116. package/custom/skills/analyze_data/SKILL.md +0 -25
  117. package/custom/skills/charts/SKILL.md +0 -193
  118. package/custom/skills/fetch_data/SKILL.md +0 -31
  119. package/custom/skills/mutate_data/SKILL.md +0 -121
  120. package/custom/speech_recognition_frontend/AudioLines.vue +0 -43
  121. package/custom/speech_recognition_frontend/MicrophoneButon.vue +0 -185
  122. package/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +0 -22
  123. package/custom/speech_recognition_frontend/voiceActivityDetection.ts +0 -62
  124. package/custom/tsconfig.json +0 -15
  125. package/custom/types.ts +0 -108
  126. package/custom/utils.ts +0 -38
  127. package/dist/agent/middleware/openAiResponsesContinuation.d.ts +0 -1
  128. package/dist/agent/middleware/openAiResponsesContinuation.js +0 -66
  129. package/dist/agent/models/AgentModeResolver.d.ts +0 -9
  130. package/dist/agent/models/AgentModeResolver.js +0 -9
  131. package/dist/agent/simpleAgent.js +0 -118
  132. package/dist/agent/turn/TurnContextBuilder.d.ts +0 -14
  133. package/dist/agent/turn/TurnContextBuilder.js +0 -31
  134. package/dist/agent/turn/TurnLifecycleService.d.ts +0 -24
  135. package/dist/agent/turn/TurnLifecycleService.js +0 -47
  136. package/dist/agent/turn/TurnPersistenceService.d.ts +0 -13
  137. package/dist/agent/turn/TurnPersistenceService.js +0 -35
  138. package/dist/agent/turn/TurnPromptBuilder.d.ts +0 -19
  139. package/dist/agent/turn/TurnPromptBuilder.js +0 -43
  140. package/dist/agent/turn/TurnStreamConsumer.d.ts +0 -11
  141. package/dist/agent/turn/TurnStreamConsumer.js +0 -84
  142. package/dist/agentTurnService.d.ts +0 -24
  143. package/dist/agentTurnService.js +0 -269
  144. package/dist/surfaces/web-sse/createSseEventEmitter.d.ts +0 -14
  145. package/endpoints/chatSurfaces.ts +0 -66
  146. package/endpoints/context.ts +0 -52
  147. package/endpoints/core.ts +0 -181
  148. package/endpoints/sessions.ts +0 -183
  149. package/errors.ts +0 -10
  150. package/index.ts +0 -192
  151. package/pnpm-workspace.yaml +0 -4
  152. package/sanitizeSpeechText.ts +0 -32
  153. package/sessionStore.ts +0 -105
  154. package/surfaces/web-sse/createSseEventEmitter.ts +0 -311
  155. package/tsconfig.json +0 -14
  156. package/types.ts +0 -127
  157. /package/dist/{agent/turn/turnTypes.js → application/ports.js} +0 -0
  158. /package/dist/{agentEvents.js → domain/agentEvents.js} +0 -0
  159. /package/dist/{agent → domain}/languageDetect.d.ts +0 -0
  160. /package/dist/{agent → domain}/languageDetect.js +0 -0
  161. /package/dist/{agent → domain}/systemPrompt.d.ts +0 -0
  162. /package/dist/{agent → domain}/toolCallEvents.d.ts +0 -0
  163. /package/dist/{agent → domain}/toolCallEvents.js +0 -0
  164. /package/dist/{endpoints/context.js → domain/turnTypes.js} +0 -0
  165. /package/dist/{agent/turn/VegaLiteStreamBuffer.js → domain/vegaLiteStreamBuffer.js} +0 -0
  166. /package/dist/{agent/runtime/AgentContext.js → llm/agentContext.js} +0 -0
  167. /package/dist/{agent → llm}/middleware/sequenceDebug.js +0 -0
  168. /package/dist/{agent/checkpointer.d.ts → persistence/checkpointStore.d.ts} +0 -0
  169. /package/dist/{agent/checkpointer.js → persistence/checkpointStore.js} +0 -0
  170. /package/dist/{errors.d.ts → shared/errors.d.ts} +0 -0
  171. /package/dist/{errors.js → shared/errors.js} +0 -0
  172. /package/dist/{sanitizeSpeechText.d.ts → shared/sanitizeSpeechText.d.ts} +0 -0
  173. /package/dist/{sanitizeSpeechText.js → shared/sanitizeSpeechText.js} +0 -0
  174. /package/dist/{apiBasedTools.d.ts → tools/apiBasedTools.d.ts} +0 -0
  175. /package/dist/{agent/tools → tools}/apiTool.js +0 -0
  176. /package/dist/{agent/tools → tools}/fetchSkill.d.ts +0 -0
  177. /package/dist/{agent/tools → tools}/fetchToolSchema.js +0 -0
  178. /package/dist/{agent/tools → tools}/getUserLocation.d.ts +0 -0
  179. /package/dist/{agent/tools → tools}/getUserLocation.js +0 -0
  180. /package/dist/{agent/tools → tools}/index.js +0 -0
  181. /package/dist/{agent/tools → tools}/navigateUser.js +0 -0
  182. /package/dist/{agent → tools}/skills/registry.d.ts +0 -0
  183. /package/dist/{agent → tools}/skills/registry.js +0 -0
  184. /package/dist/{endpoints/chatSurfaces.d.ts → transport/http/chatSurfaceEndpoints.d.ts} +0 -0
  185. /package/dist/{endpoints/chatSurfaces.js → transport/http/chatSurfaceEndpoints.js} +0 -0
  186. /package/dist/{endpoints/core.d.ts → transport/http/coreEndpoints.d.ts} +0 -0
  187. /package/dist/{endpoints/sessions.d.ts → transport/http/sessionEndpoints.d.ts} +0 -0
package/apiBasedTools.ts DELETED
@@ -1,702 +0,0 @@
1
- import {
2
- AdminForthDataTypes,
3
- logger,
4
- type AdminUser,
5
- type IAdminForth,
6
- type IAdminForthHttpResponse,
7
- type IRegisteredApiSchema,
8
- } from 'adminforth';
9
- import dayjs from 'dayjs';
10
- import timezone from 'dayjs/plugin/timezone.js';
11
- import utc from 'dayjs/plugin/utc.js';
12
- import YAML from 'yaml';
13
-
14
- dayjs.extend(utc);
15
- dayjs.extend(timezone);
16
-
17
-
18
- type ToolOverrideContext = {
19
- adminforth: IAdminForth;
20
- output?: unknown;
21
- adminUser?: AdminUser;
22
- inputs?: Record<string, unknown>;
23
- resourceLabel?: string;
24
- userTimeZone?: string;
25
- };
26
-
27
- type ToolOverride = {
28
- wipe_frontend_specific_data?: readonly string[];
29
- format_tool?: (params: ToolOverrideContext) => Promise<string> | string;
30
- post_process_response?: (params: ToolOverrideContext) => Promise<unknown> | unknown;
31
- };
32
-
33
- type GetResourceDataToolResponse = {
34
- data: Array<Record<string, unknown>>;
35
- total?: number;
36
- options?: Record<string, unknown>;
37
- };
38
-
39
- type DateTimeColumnType = AdminForthDataTypes.DATETIME | AdminForthDataTypes.TIME;
40
- type RegisteredApiToolSchema = IRegisteredApiSchema & {
41
- handler: (input: unknown) => void | Promise<unknown>;
42
- agent?: AgentToolMeta;
43
- };
44
-
45
- const DEFAULT_USER_TIME_ZONE = 'UTC';
46
-
47
- type AgentToolMeta = {
48
- isDangerous?: boolean;
49
- };
50
-
51
- function hasRegisteredApiToolHandler(schema: IRegisteredApiSchema): schema is RegisteredApiToolSchema {
52
- return typeof (schema as { handler?: unknown }).handler === 'function';
53
- }
54
-
55
- function getInputString(inputs: Record<string, unknown> | undefined, key: string) {
56
- const value = inputs?.[key];
57
-
58
- return typeof value === 'string' && value ? value : undefined;
59
- }
60
-
61
- function isHiddenResourceCall(
62
- hiddenResourceIds: ReadonlySet<string>,
63
- inputs: Record<string, unknown> | undefined,
64
- ) {
65
- const resourceId = getInputString(inputs, 'resourceId');
66
-
67
- return resourceId ? hiddenResourceIds.has(resourceId) : false;
68
- }
69
-
70
- function getInputArrayLength(inputs: Record<string, unknown> | undefined, key: string) {
71
- const value = inputs?.[key];
72
-
73
- return Array.isArray(value) ? value.length : undefined;
74
- }
75
-
76
- function resourceLabel(adminforth: IAdminForth, inputs: Record<string, unknown> | undefined) {
77
- const resourceId = getInputString(inputs, 'resourceId');
78
- const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
79
-
80
- return resource?.label ?? resourceId ?? 'resource';
81
- }
82
-
83
- function getDataPrefix(inputs: Record<string, unknown> | undefined) {
84
- const offset = typeof inputs?.offset === 'number' ? inputs.offset : undefined;
85
- const limit = typeof inputs?.limit === 'number' ? inputs.limit : undefined;
86
-
87
- if (offset !== undefined && limit !== undefined) {
88
- return `${offset}-${offset + limit} `;
89
- }
90
-
91
- return limit === undefined ? '' : `${limit} `;
92
- }
93
-
94
- function actionText(inputs: Record<string, unknown> | undefined) {
95
- const actionId = getInputString(inputs, 'actionId');
96
-
97
- return actionId ? ` action ${actionId}` : ' action';
98
- }
99
-
100
- const TOOL_OVERRIDES: Record<string, ToolOverride> = {
101
- get_resource: {
102
- wipe_frontend_specific_data: [
103
- 'resource.columns[].filterOptions',
104
- 'resource.columns[].components',
105
- 'resource.options.actions[].customComponent',
106
- 'resource.options.pageInjections',
107
- ],
108
- format_tool: ({ resourceLabel }) => `Get ${resourceLabel} resource`,
109
- },
110
- get_resource_data: {
111
- format_tool: ({ inputs, resourceLabel }) => (
112
- `Get ${getDataPrefix(inputs)}${resourceLabel}`
113
- ),
114
- post_process_response: async ({ adminforth, output, inputs, userTimeZone }) => {
115
- if (hasToolError(output)) {
116
- return output;
117
- }
118
-
119
- const dateTimeColumnNames = getDateTimeColumnNames(adminforth, inputs);
120
-
121
- if (dateTimeColumnNames.length === 0) {
122
- return output;
123
- }
124
-
125
- if (!hasGetResourceDataRows(output)) {
126
- logger.warn(
127
- `Skipping datetime formatting for get_resource_data because response.data is not an array for resource ${getInputString(inputs, 'resourceId') ?? 'unknown'}`,
128
- );
129
- return output;
130
- }
131
-
132
- const localizedTimeZone = userTimeZone ?? DEFAULT_USER_TIME_ZONE;
133
- formatDateTimeColumns(output.data, dateTimeColumnNames, localizedTimeZone);
134
-
135
- return output;
136
- },
137
- },
138
- aggregate: {
139
- format_tool: ({ resourceLabel }) => `Aggregate ${resourceLabel}`,
140
- },
141
- start_custom_action: {
142
- format_tool: ({ inputs, resourceLabel }) => `Run ${resourceLabel}${actionText(inputs)}`,
143
- },
144
- start_custom_bulk_action: {
145
- format_tool: ({ inputs, resourceLabel }) => {
146
- const recordCount = getInputArrayLength(inputs, 'recordIds');
147
- const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
148
-
149
- return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
150
- },
151
- },
152
- start_bulk_action: {
153
- format_tool: ({ inputs, resourceLabel }) => {
154
- const recordCount = getInputArrayLength(inputs, 'recordIds');
155
- const recordsText = recordCount === undefined ? '' : ` for ${recordCount} records`;
156
-
157
- return `Run ${resourceLabel}${actionText(inputs)}${recordsText}`;
158
- },
159
- },
160
- create_record: {
161
- format_tool: ({ resourceLabel }) => `Create ${resourceLabel}`,
162
- },
163
- update_record: {
164
- format_tool: ({ resourceLabel }) => `Update ${resourceLabel}`,
165
- },
166
- delete_record: {
167
- format_tool: ({ resourceLabel }) => `Delete ${resourceLabel}`,
168
- },
169
- };
170
-
171
- export type ApiBasedToolCallParams = {
172
- adminUser?: AdminUser;
173
- adminuser?: AdminUser;
174
- abortSignal?: AbortSignal;
175
- inputs?: Record<string, unknown>;
176
- userTimeZone?: string;
177
- acceptLanguage?: string;
178
- };
179
-
180
- export type ApiBasedTool = {
181
- description?: string;
182
- input_schema?: unknown;
183
- agent?: AgentToolMeta;
184
- call: (params?: ApiBasedToolCallParams) => Promise<string>;
185
- };
186
-
187
- function sanitizeForYaml(
188
- value: unknown,
189
- ): unknown {
190
- const traversalStack: object[] = [];
191
- const serialized = JSON.stringify(value, function (this: unknown, _key: string, nestedValue: unknown) {
192
- if (typeof nestedValue === 'function' || typeof nestedValue === 'symbol' || nestedValue === undefined) {
193
- return undefined;
194
- }
195
-
196
- if (typeof nestedValue === 'bigint') {
197
- return nestedValue.toString();
198
- }
199
-
200
- if (typeof nestedValue !== 'object' || nestedValue === null) {
201
- return nestedValue;
202
- }
203
-
204
- if (nestedValue instanceof Map) {
205
- return Object.fromEntries(nestedValue);
206
- }
207
-
208
- if (nestedValue instanceof Set) {
209
- return Array.from(nestedValue.values());
210
- }
211
-
212
- while (traversalStack.length > 0 && traversalStack[traversalStack.length - 1] !== this) {
213
- traversalStack.pop();
214
- }
215
-
216
- if (traversalStack.includes(nestedValue)) {
217
- return undefined;
218
- }
219
-
220
- traversalStack.push(nestedValue);
221
- return nestedValue;
222
- });
223
-
224
- if (serialized === undefined) {
225
- return null;
226
- }
227
-
228
- return JSON.parse(serialized);
229
- }
230
-
231
- function wipePath(target: unknown, pathParts: string[]): void {
232
- if (!target || typeof target !== 'object' || pathParts.length === 0) {
233
- return;
234
- }
235
-
236
- const [currentPart, ...rest] = pathParts;
237
- const isArrayTraversal = currentPart.endsWith('[]');
238
- const key = isArrayTraversal ? currentPart.slice(0, -2) : currentPart;
239
- const targetRecord = target as Record<string, unknown>;
240
-
241
- if (!(key in targetRecord)) {
242
- return;
243
- }
244
-
245
- if (rest.length === 0) {
246
- delete targetRecord[key];
247
- return;
248
- }
249
-
250
- const nextValue = targetRecord[key];
251
-
252
- if (isArrayTraversal) {
253
- if (!Array.isArray(nextValue)) {
254
- return;
255
- }
256
-
257
- for (const item of nextValue) {
258
- wipePath(item, rest);
259
- }
260
-
261
- return;
262
- }
263
-
264
- wipePath(nextValue, rest);
265
- }
266
-
267
- function hasToolError(output: unknown): output is { error: unknown } {
268
- return typeof output === 'object' && output !== null && 'error' in output;
269
- }
270
-
271
- function hasGetResourceDataRows(output: unknown): output is GetResourceDataToolResponse {
272
- if (typeof output !== 'object' || output === null || !('data' in output)) {
273
- return false;
274
- }
275
-
276
- return Array.isArray((output as { data?: unknown }).data);
277
- }
278
-
279
- function getDateTimeColumnNames(
280
- adminforth: IAdminForth,
281
- inputs: Record<string, unknown> | undefined,
282
- ): string[] {
283
- const resourceId = getInputString(inputs, 'resourceId');
284
- const resource = adminforth.config.resources.find((res) => res.resourceId === resourceId);
285
-
286
- if (!resource) {
287
- return [];
288
- }
289
-
290
- return resource.dataSourceColumns
291
- .filter((column) => column.type === AdminForthDataTypes.DATETIME)
292
- .map((column) => column.name);
293
- }
294
-
295
- function formatGmtOffset(offsetMinutes: number): string {
296
- const sign = offsetMinutes >= 0 ? '+' : '-';
297
- const absoluteOffsetMinutes = Math.abs(offsetMinutes);
298
- const hours = Math.floor(absoluteOffsetMinutes / 60);
299
- const minutes = absoluteOffsetMinutes % 60;
300
-
301
- if (minutes === 0) {
302
- return `GMT${sign}${hours}`;
303
- }
304
-
305
- return `GMT${sign}${hours}:${String(minutes).padStart(2, '0')}`;
306
- }
307
-
308
- function formatDateTimeValue(value: string, userTimeZone: string): string {
309
- const localizedValue = dayjs.utc(value).tz(userTimeZone);
310
- return `${localizedValue.format('DD MMM YYYY, HH:mm:ss.SSS')} (${formatGmtOffset(localizedValue.utcOffset())})`;
311
- }
312
-
313
- function formatDateTimeColumns(
314
- rows: Array<Record<string, unknown>>,
315
- dateTimeColumnNames: string[],
316
- userTimeZone: string,
317
- ): void {
318
- for (const row of rows) {
319
- for (const columnName of dateTimeColumnNames) {
320
- const value = row[columnName];
321
-
322
- if (typeof value === 'string' && value) {
323
- row[columnName] = formatDateTimeValue(value, userTimeZone);
324
- }
325
- }
326
- }
327
- }
328
-
329
- async function applyToolOverride(params: {
330
- adminforth: IAdminForth;
331
- adminUser?: AdminUser;
332
- inputs?: Record<string, unknown>;
333
- output: unknown;
334
- toolName: string;
335
- userTimeZone?: string;
336
- }): Promise<unknown> {
337
- const { adminforth, adminUser, inputs, output, toolName, userTimeZone } = params;
338
- const sanitizedOutput = sanitizeForYaml(output);
339
- const override = TOOL_OVERRIDES[toolName];
340
-
341
- if (!override) {
342
- return sanitizedOutput;
343
- }
344
-
345
- for (const path of override.wipe_frontend_specific_data ?? []) {
346
- wipePath(sanitizedOutput, path.split('.'));
347
- }
348
-
349
- return override.post_process_response
350
- ? sanitizeForYaml(await override.post_process_response({
351
- adminforth,
352
- adminUser,
353
- output: sanitizedOutput,
354
- inputs,
355
- userTimeZone,
356
- }))
357
- : sanitizedOutput;
358
- }
359
-
360
- function endpointPathToToolName(path: string) {
361
- return path
362
- .replace(/^\/+/, '')
363
- .replace(/[^a-zA-Z0-9_]+/g, '_')
364
- .replace(/^_+|_+$/g, '');
365
- }
366
-
367
- function stripAdminApiPrefix(path: string, adminforth: IAdminForth) {
368
- const configuredBaseUrl = adminforth.config.baseUrl || '';
369
- const normalizedBaseUrl = configuredBaseUrl.endsWith('/')
370
- ? configuredBaseUrl.slice(0, -1)
371
- : configuredBaseUrl;
372
- const apiPrefix = `${normalizedBaseUrl}/adminapi/v1`;
373
-
374
- if (path.startsWith(apiPrefix)) {
375
- const strippedPath = path.slice(apiPrefix.length);
376
- return strippedPath.startsWith('/') ? strippedPath : `/${strippedPath}`;
377
- }
378
-
379
- return path;
380
- }
381
-
382
- function openApiSchemaPathToToolName(path: string, adminforth: IAdminForth) {
383
- return endpointPathToToolName(stripAdminApiPrefix(path, adminforth));
384
- }
385
-
386
- function formatLogNameList(names: string[]) {
387
- return names.length ? names.join(', ') : '(none)';
388
- }
389
-
390
- export async function formatApiBasedToolCall(params: {
391
- adminforth: IAdminForth;
392
- adminUser?: AdminUser;
393
- inputs?: Record<string, unknown>;
394
- toolName: string;
395
- userTimeZone?: string;
396
- }) {
397
- return await TOOL_OVERRIDES[params.toolName]?.format_tool?.({
398
- adminforth: params.adminforth,
399
- adminUser: params.adminUser,
400
- inputs: params.inputs,
401
- resourceLabel: resourceLabel(params.adminforth, params.inputs),
402
- userTimeZone: params.userTimeZone,
403
- });
404
- }
405
-
406
- function normalizeDateTimeInputsToUtc(
407
- body: Record<string, unknown>,
408
- adminforth: IAdminForth,
409
- userTimeZone?: string,
410
- ): Record<string, unknown> {
411
- if (!userTimeZone || typeof body.resourceId !== 'string') {
412
- return body;
413
- }
414
-
415
- const resource = adminforth.config.resources.find((res) => res.resourceId === body.resourceId);
416
-
417
- if (!resource) {
418
- return body;
419
- }
420
-
421
- const columnsByName = new Map(resource.dataSourceColumns.map((column) => [column.name, column]));
422
-
423
- const normalizeColumnValue = (
424
- value: unknown,
425
- columnType: DateTimeColumnType,
426
- ): unknown => {
427
- if (Array.isArray(value)) {
428
- return value.map((item) => normalizeColumnValue(item, columnType));
429
- }
430
-
431
- if (typeof value !== 'string' || value === '') {
432
- return value;
433
- }
434
-
435
- if (columnType === AdminForthDataTypes.DATETIME) {
436
- return dayjs.tz(value, userTimeZone).utc().toISOString();
437
- }
438
-
439
- const userDate = dayjs().tz(userTimeZone).format('YYYY-MM-DD');
440
- return dayjs.tz(`${userDate}T${value}`, userTimeZone).utc().format('HH:mm:ss');
441
- };
442
-
443
- const normalizeValue = (value: unknown, key?: string): unknown => {
444
- const column = key ? columnsByName.get(key) : undefined;
445
-
446
- if (column?.type === AdminForthDataTypes.DATETIME || column?.type === AdminForthDataTypes.TIME) {
447
- return normalizeColumnValue(value, column.type);
448
- }
449
-
450
- if (Array.isArray(value)) {
451
- return value.map((item) => normalizeValue(item));
452
- }
453
-
454
- if (!value || typeof value !== 'object') {
455
- return value;
456
- }
457
-
458
- const record = value as Record<string, unknown>;
459
- const filterColumn = typeof record.field === 'string' ? columnsByName.get(record.field) : undefined;
460
-
461
- if (
462
- 'value' in record &&
463
- (filterColumn?.type === AdminForthDataTypes.DATETIME || filterColumn?.type === AdminForthDataTypes.TIME)
464
- ) {
465
- return {
466
- ...record,
467
- value: normalizeColumnValue(record.value, filterColumn.type),
468
- };
469
- }
470
-
471
- return Object.fromEntries(
472
- Object.entries(record).map(([nestedKey, nestedValue]) => [
473
- nestedKey,
474
- normalizeValue(nestedValue, nestedKey),
475
- ]),
476
- );
477
- };
478
-
479
- return normalizeValue(body) as Record<string, unknown>;
480
- }
481
-
482
- const METHODS_WITHOUT_REQUEST_BODY = new Set<string>(['GET', 'HEAD']);
483
- const TOOL_HANDLER_TIMEOUT_MS = 15_000;
484
- const EMPTY_HANDLER_RESPONSE_ERROR = {
485
- error: 'EMPTY_HANDLER_RESPONSE',
486
- message: 'Tool handler completed without returning a response.',
487
- };
488
- const TOOL_HANDLER_TIMEOUT_ERROR = {
489
- error: 'TOOL_HANDLER_TIMEOUT',
490
- message: `Tool handler timed out after ${TOOL_HANDLER_TIMEOUT_MS / 1000} seconds.`,
491
- };
492
-
493
- function createDirectToolResponse(): IAdminForthHttpResponse & {
494
- headers: Array<[string, string]>;
495
- status: number;
496
- message?: string;
497
- } {
498
- const headers: Array<[string, string]> = [];
499
-
500
- return {
501
- headers,
502
- status: 200,
503
- setHeader(name, value) {
504
- headers.push([name, value]);
505
- },
506
- setStatus(code, message) {
507
- this.status = code;
508
- this.message = message;
509
- },
510
- blobStream() {
511
- throw new Error('blobStream is not available for API-based agent tools');
512
- },
513
- };
514
- }
515
-
516
- function validationErrorResponse(
517
- error: 'REQUEST_VALIDATION_FAILED' | 'RESPONSE_VALIDATION_FAILED',
518
- details: unknown,
519
- ) {
520
- return {
521
- error,
522
- details,
523
- };
524
- }
525
-
526
- async function withToolHandlerTimeout<T>(callback: (abortSignal: AbortSignal) => T | Promise<T>, abortSignal?: AbortSignal) {
527
- const controller = new AbortController();
528
- const abortHandler = () => controller.abort();
529
- let timeout: ReturnType<typeof setTimeout>;
530
- const timeoutPromise = new Promise<typeof TOOL_HANDLER_TIMEOUT_ERROR>((resolve) => {
531
- timeout = setTimeout(() => {
532
- controller.abort();
533
- resolve(TOOL_HANDLER_TIMEOUT_ERROR);
534
- }, TOOL_HANDLER_TIMEOUT_MS);
535
- });
536
-
537
- abortSignal?.addEventListener('abort', abortHandler, { once: true });
538
-
539
- try {
540
- return await Promise.race([
541
- callback(controller.signal),
542
- timeoutPromise,
543
- ]);
544
- } finally {
545
- clearTimeout(timeout!);
546
- abortSignal?.removeEventListener('abort', abortHandler);
547
- }
548
- }
549
-
550
- async function callOpenApiSchema(params: {
551
- adminforth: IAdminForth;
552
- adminUser?: AdminUser;
553
- abortSignal?: AbortSignal;
554
- inputs?: Record<string, unknown>;
555
- schema: RegisteredApiToolSchema;
556
- toolName: string;
557
- userTimeZone?: string;
558
- acceptLanguage?: string;
559
- }) {
560
- const { adminforth, adminUser, abortSignal, inputs, schema, toolName, userTimeZone, acceptLanguage } = params;
561
- const method = schema.method.toUpperCase();
562
- const normalizedInputs = normalizeDateTimeInputsToUtc(
563
- (inputs ?? {}) as Record<string, unknown>,
564
- adminforth,
565
- userTimeZone,
566
- );
567
- const hasRequestBody = !METHODS_WITHOUT_REQUEST_BODY.has(method);
568
- const body = hasRequestBody ? normalizedInputs : {};
569
- const query = hasRequestBody ? {} : normalizedInputs;
570
- const requestValidation = adminforth.openApi.validateRequestSchema(schema, body);
571
-
572
- if (!requestValidation.valid) {
573
- return validationErrorResponse('REQUEST_VALIDATION_FAILED', requestValidation.errors);
574
- }
575
-
576
- const response = createDirectToolResponse();
577
- logger.info(`Calling OpenAPI tool "${toolName}" with direct handler`);
578
- const lang = acceptLanguage ?? "en";
579
- const tr = (
580
- msg: string,
581
- category: string,
582
- trParams: unknown,
583
- pluralizationNumber?: number,
584
- ) => adminforth.tr(msg, category, lang, trParams, pluralizationNumber);
585
- const output = await withToolHandlerTimeout((handlerAbortSignal) => schema.handler({
586
- body,
587
- query,
588
- headers: {},
589
- cookies: [],
590
- adminUser,
591
- response,
592
- requestUrl: schema.path,
593
- abortSignal: handlerAbortSignal,
594
- _raw_express_req: undefined as never,
595
- _raw_express_res: undefined as never,
596
- tr,
597
- }),
598
- abortSignal,
599
- );
600
-
601
- if (response.message) {
602
- return response.status >= 400
603
- ? {
604
- error: 'HANDLER_ERROR',
605
- status: response.status,
606
- response: response.message,
607
- }
608
- : response.message;
609
- }
610
-
611
- if (output === undefined) {
612
- return EMPTY_HANDLER_RESPONSE_ERROR;
613
- }
614
-
615
- if (output === null) {
616
- return { status: response.status };
617
- }
618
-
619
- const responseValidation = adminforth.openApi.validateResponseSchema(schema, output);
620
-
621
- if (!responseValidation.valid) {
622
- return validationErrorResponse('RESPONSE_VALIDATION_FAILED', responseValidation.errors);
623
- }
624
-
625
- return response.status >= 400
626
- ? {
627
- error: 'HANDLER_ERROR',
628
- status: response.status,
629
- response: output,
630
- }
631
- : output;
632
- }
633
-
634
- function stringifyToolOutput(output: unknown): string {
635
- return YAML.stringify(output === undefined ? EMPTY_HANDLER_RESPONSE_ERROR : output);
636
- }
637
-
638
- export function prepareApiBasedTools(
639
- adminforth: IAdminForth,
640
- hiddenResourceIds: Iterable<string> = [],
641
- ): Record<string, ApiBasedTool> {
642
- const apiBasedTools: Record<string, ApiBasedTool> = {};
643
- const openApiSchemas = adminforth.openApi.registeredSchemas;
644
- const openApiSchemasByToolName = new Map<string, RegisteredApiToolSchema>();
645
- const hiddenResourceIdSet = new Set(hiddenResourceIds);
646
-
647
- for (const schema of openApiSchemas) {
648
- const toolName = openApiSchemaPathToToolName(schema.path, adminforth);
649
- if (hasRegisteredApiToolHandler(schema)) {
650
- openApiSchemasByToolName.set(toolName, schema);
651
- }
652
- }
653
-
654
- logger.info(
655
- `AdminForth Agent OpenAPI APIs: ${formatLogNameList(
656
- adminforth.openApi.registeredSchemas.map((schema) => openApiSchemaPathToToolName(schema.path, adminforth)),
657
- )}`,
658
- );
659
- logger.info(
660
- `AdminForth Agent OpenAPI tools connected: ${formatLogNameList([...openApiSchemasByToolName.keys()])}`,
661
- );
662
-
663
- for (const [toolName, schema] of openApiSchemasByToolName.entries()) {
664
- apiBasedTools[toolName] = {
665
- description: schema.description,
666
- input_schema: schema.request_schema,
667
- agent: schema.agent,
668
- call: async ({ adminUser, adminuser, abortSignal, inputs, userTimeZone, acceptLanguage } = {}) => {
669
- if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
670
- return stringifyToolOutput({
671
- error: 'RESOURCE_NOT_AVAILABLE',
672
- message: 'This resource is not available to the agent.',
673
- });
674
- }
675
-
676
- const output = await callOpenApiSchema({
677
- adminforth,
678
- adminUser: adminUser ?? adminuser,
679
- abortSignal,
680
- schema,
681
- toolName,
682
- inputs,
683
- userTimeZone,
684
- acceptLanguage,
685
- });
686
-
687
- const processedOutput = await applyToolOverride({
688
- adminforth,
689
- adminUser: adminUser ?? adminuser,
690
- inputs,
691
- output,
692
- toolName,
693
- userTimeZone,
694
- });
695
-
696
- return stringifyToolOutput(processedOutput);
697
- },
698
- };
699
- }
700
-
701
- return apiBasedTools;
702
- }