adminizer 5.0.0-build.2 → 5.0.0-build.21
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/assets/abnfDiagram-VCTEODGH-CkmsvdDi.js +1 -0
- package/assets/add-CESPH_aI.js +1 -0
- package/assets/add-group-CQ4Xm8Ru.js +1 -0
- package/assets/add-user-B-QNjR4B.js +1 -0
- package/assets/ai-assistant/agent.es.js +28362 -0
- package/assets/app-C5Ju5Zra.css +1 -0
- package/assets/app-Ca5yUSBv.js +138 -0
- package/assets/arc-B-Dt8YNl.js +1 -0
- package/assets/architectureDiagram-5GKGNRK7-Ca58kq-l.js +36 -0
- package/assets/asmcrypto-DZeQH0LC.js +1 -0
- package/assets/blockDiagram-NRAW4CY4-CuqE52IG.js +129 -0
- package/assets/c4Diagram-UCG6FXSJ-Cx8z-0Bw.js +38 -0
- package/assets/catalog-CkSbxvx0.js +6 -0
- package/assets/channel-C9z6Pi_j.js +1 -0
- package/assets/chunk-2Q5K7J3B-CXISVKTC.js +1 -0
- package/assets/chunk-5VM5RSS4-DYGeeVYe.js +15 -0
- package/assets/chunk-F27PBJKO-BeY-dbKj.js +1 -0
- package/assets/chunk-G27WJ6UU-DeX_eH7C.js +231 -0
- package/assets/chunk-JWPE2WC7-DaxI9q0u.js +1 -0
- package/assets/chunk-LCL6LL3I-B7PkaEz_.js +206 -0
- package/assets/chunk-POPQ4Y6H-lwu6ZpsN.js +1 -0
- package/assets/chunk-SVP7TREG-CVFllgl1.js +88 -0
- package/assets/chunk-XXDRQBXY-DDLN0rcg.js +1 -0
- package/assets/classDiagram-DTDB5LWJ-BKtCIC3B.js +1 -0
- package/assets/classDiagram-v2-JRS7N3AN-BKtCIC3B.js +1 -0
- package/assets/controls/ckeditor.es.js +9370 -9306
- package/assets/controls/handsontable.es.js +11971 -11938
- package/assets/controls/jsoneditor.es.js +13104 -13008
- package/assets/controls/leaflet.es.js +1023 -1004
- package/assets/controls/toast-ui.es.js +1843 -1834
- package/assets/cose-bilkent-JH36ORCC-CWRyUkhQ.js +1 -0
- package/assets/cynefin-OW5HDTMX-B1iLDXCC.js +166 -0
- package/assets/cynefinDiagram-5FMLGOSQ-DaXQHr3l.js +62 -0
- package/assets/cytoscape.esm-yzknjiTM.js +321 -0
- package/assets/dagre-3AP2YEHR-DFORa4Hz.js +4 -0
- package/assets/dashboard-Ssa1_Of3.js +3 -0
- package/assets/debounce-B3mXI_sx.js +1 -0
- package/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/assets/diagram-S7CK7UJ4-CgP6KBo0.js +30 -0
- package/assets/diagram-UQ7AKVKN-oYfQe6w0.js +41 -0
- package/assets/diagram-VSXAHHWV-DfCU5wgt.js +3 -0
- package/assets/diagram-VX7I27RA-BITJVrNY.js +24 -0
- package/assets/diagram-Z3DM3KII-D8J9Ssjy.js +24 -0
- package/assets/docs-DHMt_Rq1.js +1 -0
- package/assets/ebnfDiagram-PWID7BFC-qpNmoYgx.js +1 -0
- package/assets/erDiagram-SSCWMZ5O-DDF-Y0zs.js +99 -0
- package/assets/flowDiagram-A5DVABFB-CjKVkR9T.js +168 -0
- package/assets/ganttDiagram-EL5Y4UJY-BXGQ53w6.js +292 -0
- package/assets/gitGraphDiagram-WWUBYQGX-BLMevdLh.js +106 -0
- package/assets/history-tBOo2gd5.js +1 -0
- package/assets/infoDiagram-RXCK75RN-BAnPdnyw.js +2 -0
- package/assets/init-Gi6I4Gst.js +1 -0
- package/assets/init-user-DrNUHa6r.js +1 -0
- package/assets/isObject-DL-BuaBl.js +1 -0
- package/assets/ishikawaDiagram-5VMMS53U-PnL0Z4Li.js +70 -0
- package/assets/journeyDiagram-EYS64GPL-CLcsCSwF.js +139 -0
- package/assets/kanban-definition-3QL26DDD-C2eU3uBE.js +89 -0
- package/assets/katex-HP8lGamR.js +257 -0
- package/assets/layout-BXoYdHdx.js +1 -0
- package/assets/linear-BSzrP0oP.js +1 -0
- package/assets/list-CLg6xeIK.js +4 -0
- package/assets/login-Ce7o3-o_.js +1 -0
- package/assets/manifest.json +834 -58
- package/assets/{media-manager-BDbpSSjH.js → media-manager-D7G1P3fz.js} +3 -3
- package/assets/mermaid.core-aKMJHeEk.js +310 -0
- package/assets/mindmap-definition-FBJOCRG2-Dzbz5ghA.js +96 -0
- package/assets/module-BsXph_pA.js +1 -0
- package/assets/notification-D-Zz1gF_.js +20 -0
- package/assets/ordinal-Cboi1Yqb.js +1 -0
- package/assets/pegDiagram-XKGWAZYB-BTdWkU0Y.js +1 -0
- package/assets/pieDiagram-E7YTZNPT-Dnw8-TB-.js +39 -0
- package/assets/quadrantDiagram-AXDQQJYC-CALzLlg9.js +7 -0
- package/assets/railroadDiagram-O6MQD6OU-CTA1vzcv.js +1 -0
- package/assets/register-CNnBkzej.js +1 -0
- package/assets/requirementDiagram-EFPCY7ZU-UX2CF5HA.js +84 -0
- package/assets/ru-CoNgAwFK.js +1 -0
- package/assets/sankeyDiagram-P5KCCOFB-4aPp3ATV.js +40 -0
- package/assets/sequenceDiagram-WJ2MYXX4--kltkwJw.js +162 -0
- package/assets/sizeCapture-X5ZJPWSS-Ca9-g5QY.js +1 -0
- package/assets/stateDiagram-HBIQ2CUA-Diaf5Q7I.js +1 -0
- package/assets/stateDiagram-v2-4QOOHH4V-C6FlRp7u.js +1 -0
- package/assets/swimlanes-XN3QIQJK-DhqX_myE.js +1 -0
- package/assets/swimlanesDiagram-VK2B7HYN-i_3LTEBk.js +8 -0
- package/assets/timeline-definition-24CTP7MA-CwkJQbhq.js +120 -0
- package/assets/toNumber-DZb-r9sy.js +1 -0
- package/assets/use-appearance-DC-DESKB.js +1 -0
- package/assets/user-filters-list-W47DTBp2.js +1 -0
- package/assets/vennDiagram-4TSXK5OY-BNM3_cVS.js +34 -0
- package/assets/wardleyDiagram-VM6X3IG4-BODb1NP8.js +78 -0
- package/assets/welcome-BeBm6NIi.js +1 -0
- package/assets/with-app-layout-B90DtzYC.js +31 -0
- package/assets/xychartDiagram-S5SC5T6Z-DthqEh3g.js +7 -0
- package/controllers/access-rights/permissionOptions.d.ts +2 -0
- package/controllers/access-rights/permissionOptions.js +23 -0
- package/controllers/add.js +1 -1
- package/controllers/addGroup.js +15 -6
- package/controllers/ai/AiAgentController.d.ts +66 -0
- package/controllers/ai/AiAgentController.js +435 -0
- package/controllers/ai/AiAssistantController.d.ts +0 -4
- package/controllers/ai/AiAssistantController.js +4 -78
- package/controllers/catalog/FrontendCatalogAdapter.d.ts +1 -1
- package/controllers/catalog/FrontentCatalogAdapter.d.ts +1 -1
- package/controllers/docs/index.d.ts +22 -0
- package/controllers/docs/index.js +126 -0
- package/controllers/edit.js +1 -1
- package/controllers/editGroup.js +15 -6
- package/controllers/feedback/feedbackController.d.ts +1 -1
- package/controllers/filter-fields/savedFilters.js +1 -1
- package/controllers/history-actions/HistoryController.d.ts +1 -1
- package/controllers/history-actions/HistoryController.js +6 -6
- package/controllers/list.js +1 -1
- package/controllers/login.js +1 -1
- package/controllers/media-manager/mediaManagerAdapter.d.ts +1 -1
- package/controllers/notifications/NotificationController.js +14 -8
- package/controllers/view.js +2 -2
- package/controllers/widgets/filterInfo.js +1 -1
- package/helpers/accessRightsHelper.d.ts +11 -4
- package/helpers/accessRightsHelper.js +82 -12
- package/helpers/assetVersionHelper.d.ts +14 -0
- package/helpers/assetVersionHelper.js +42 -0
- package/helpers/configHelper.d.ts +3 -3
- package/helpers/configHelper.js +20 -18
- package/helpers/controllerHelper.d.ts +3 -2
- package/helpers/controllerHelper.js +25 -15
- package/helpers/fieldsHelper.d.ts +2 -2
- package/helpers/fieldsHelper.js +3 -3
- package/helpers/filterDatetimeHelper.d.ts +1 -1
- package/helpers/inertiaActionsHelper.d.ts +3 -3
- package/helpers/inertiaActionsHelper.js +4 -5
- package/helpers/inertiaAddHelper.d.ts +5 -5
- package/helpers/inertiaAddHelper.js +7 -6
- package/helpers/inertiaGroupHelper.d.ts +3 -3
- package/helpers/inertiaGroupHelper.js +10 -2
- package/helpers/inertiaListHelper.d.ts +4 -4
- package/helpers/inertiaListHelper.js +7 -8
- package/helpers/inertiaMenuHelper.d.ts +2 -3
- package/helpers/inertiaMenuHelper.js +3 -26
- package/helpers/inertiaUserHelper.d.ts +3 -3
- package/helpers/menuHelper.d.ts +2 -2
- package/helpers/modelResourceHelper.d.ts +9 -0
- package/helpers/modelResourceHelper.js +43 -0
- package/helpers/navigationAccessHelper.d.ts +13 -0
- package/helpers/navigationAccessHelper.js +40 -0
- package/helpers/translationHelper.d.ts +2 -2
- package/index.d.ts +69 -58
- package/index.js +11 -0
- package/interfaces/adminpanelConfig.d.ts +18 -5
- package/interfaces/global.d.ts +6 -6
- package/interfaces/internalModelAccess.d.ts +1 -1
- package/interfaces/listQueryBuilder.d.ts +1 -1
- package/interfaces/types.d.ts +171 -2
- package/lib/Adminizer.d.ts +46 -26
- package/lib/Adminizer.js +37 -1
- package/lib/DataAccessor.d.ts +7 -6
- package/lib/DataAccessor.js +50 -48
- package/lib/MiddlewareManager.d.ts +1 -1
- package/lib/access-rights/AccessRightsHandler.d.ts +2 -2
- package/lib/admin-links/AdminLinkHandler.d.ts +72 -0
- package/lib/admin-links/AdminLinkHandler.js +175 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.d.ts +39 -0
- package/lib/ai-assistant/AbstractAiConversationHistoryService.js +98 -0
- package/lib/ai-assistant/AbstractAiModelService.d.ts +140 -3
- package/lib/ai-assistant/AbstractAiModelService.js +154 -1
- package/lib/ai-assistant/AiAgentRunManager.d.ts +35 -0
- package/lib/ai-assistant/AiAgentRunManager.js +109 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.d.ts +58 -0
- package/lib/ai-assistant/AiAssistantAgentSkillHandler.js +87 -0
- package/lib/ai-assistant/AiAssistantHandler.d.ts +16 -5
- package/lib/ai-assistant/AiAssistantHandler.js +35 -1
- package/lib/ai-assistant/AiAssistantUiMethodHandler.d.ts +60 -0
- package/lib/ai-assistant/AiAssistantUiMethodHandler.js +138 -0
- package/lib/ai-assistant/builtinAgentSkills.d.ts +9 -0
- package/lib/ai-assistant/builtinAgentSkills.js +256 -0
- package/lib/ai-assistant/jsonSafe.d.ts +13 -0
- package/lib/ai-assistant/jsonSafe.js +81 -0
- package/lib/app-manager/AdminizerApp.d.ts +40 -11
- package/lib/app-manager/AppManager.d.ts +2 -2
- package/lib/app-manager/AppManager.js +54 -50
- package/lib/app-manager/AssetHandler.d.ts +2 -2
- package/lib/app-manager/ConfigLayerHandler.d.ts +2 -2
- package/lib/app-manager/ControllerHandler.d.ts +2 -2
- package/lib/catalog/AbstractCatalog.d.ts +1 -1
- package/lib/catalog/CatalogHandler.d.ts +1 -1
- package/lib/controls/ControlsHandler.d.ts +1 -1
- package/lib/controls/codeEditor/MonacoEditor.d.ts +1 -1
- package/lib/controls/geojsoneditor/GeoEditor.d.ts +1 -1
- package/lib/controls/jsoneditor/JsonEditor.d.ts +1 -1
- package/lib/controls/markdown/ToastUiEditor.d.ts +1 -1
- package/lib/controls/table/Handsontable.d.ts +1 -1
- package/lib/controls/wysiwyg/CKeditor.d.ts +1 -1
- package/lib/docs/AbstractDocumentation.d.ts +100 -0
- package/lib/docs/AbstractDocumentation.js +108 -0
- package/lib/docs/DocumentationHandler.d.ts +100 -0
- package/lib/docs/DocumentationHandler.js +198 -0
- package/lib/docs/FileDocumentation.d.ts +47 -0
- package/lib/docs/FileDocumentation.js +189 -0
- package/lib/docs/docsAgentSkills.d.ts +10 -0
- package/lib/docs/docsAgentSkills.js +93 -0
- package/lib/feedback/FeedbackHandler.d.ts +2 -2
- package/lib/filters/ConditionValidator.d.ts +1 -1
- package/lib/filters/FilterCustomFieldHandler.d.ts +2 -2
- package/lib/filters/FilterService.d.ts +4 -4
- package/lib/filters/FilterService.js +4 -3
- package/lib/filters/index.d.ts +6 -6
- package/lib/history-actions/AbstractHistoryAdapter.d.ts +6 -6
- package/lib/history-actions/AbstractHistoryAdapter.js +11 -9
- package/lib/history-actions/DefaultHistoryAdapter.d.ts +4 -4
- package/lib/history-actions/HistoryHandler.d.ts +1 -1
- package/lib/list-query-builder/ListQueryBuilder.d.ts +7 -7
- package/lib/media-manager/AbstractMediaManager.d.ts +2 -2
- package/lib/media-manager/MediaManagerHandler.d.ts +1 -1
- package/lib/media-manager/Thumb.d.ts +1 -1
- package/lib/media-manager/helpers/MediaManagerHelper.d.ts +5 -5
- package/lib/model/AbstractModel.d.ts +11 -4
- package/lib/model/InternalModelAccessFactory.d.ts +2 -2
- package/lib/model/ModelHandler.d.ts +50 -5
- package/lib/model/ModelHandler.js +107 -8
- package/lib/model/adapter/sequelize.d.ts +7 -5
- package/lib/model/adapter/sequelize.js +79 -46
- package/lib/model/adapter/typeorm.d.ts +2 -2
- package/lib/model/adapter/typeorm.js +1 -1
- package/lib/model/internalModelRepository.d.ts +2 -2
- package/lib/notifications/AbstractNotificationService.d.ts +4 -4
- package/lib/notifications/GeneralNotificationService.d.ts +2 -2
- package/lib/notifications/GeneralNotificationService.js +6 -5
- package/lib/notifications/NotificationHandler.d.ts +2 -2
- package/lib/notifications/SystemNotificationService.d.ts +2 -2
- package/lib/notifications/SystemNotificationService.js +1 -1
- package/lib/requestProcessor.d.ts +1 -1
- package/lib/ui-i18n/uiTranslationKeys.d.ts +2 -1
- package/lib/ui-i18n/uiTranslationKeys.js +42 -1
- package/lib/widgets/abstractAction.d.ts +1 -1
- package/lib/widgets/abstractCustom.d.ts +2 -2
- package/lib/widgets/abstractInfo.d.ts +1 -1
- package/lib/widgets/abstractLink.d.ts +2 -2
- package/lib/widgets/abstractSwitch.d.ts +1 -1
- package/lib/widgets/abstractWidgetBase.d.ts +1 -1
- package/lib/widgets/widgetHandler.d.ts +9 -9
- package/lib/widgets/widgetHandler.js +5 -1
- package/models/Filter.d.ts +1 -1
- package/models/FilterColumn.d.ts +1 -1
- package/models/Group.d.ts +3 -2
- package/models/HistoryActions.d.ts +1 -1
- package/models/MediaManagerAP.d.ts +2 -2
- package/models/MediaManagerAssociationsAP.d.ts +1 -1
- package/models/MediaManagerMetaAP.d.ts +1 -1
- package/models/User.d.ts +2 -2
- package/models/UserNotification.d.ts +1 -1
- package/package.json +29 -20
- package/policies/authPolicies.js +6 -6
- package/policies/permissionResolvers.d.ts +1 -0
- package/policies/permissionResolvers.js +3 -0
- package/services/FeedService.d.ts +3 -3
- package/system/Router.d.ts +1 -1
- package/system/Router.js +27 -1
- package/system/bindAccessRights.d.ts +1 -1
- package/system/bindAccessRights.js +2 -3
- package/system/bindAuthorization.d.ts +1 -1
- package/system/bindControls.d.ts +1 -1
- package/system/bindCors.d.ts +1 -1
- package/system/bindCustomFilterHandlers.d.ts +1 -1
- package/system/bindDashboardWidgets.d.ts +1 -1
- package/system/bindDev.d.ts +1 -1
- package/system/bindDocs.d.ts +13 -0
- package/system/bindDocs.js +77 -0
- package/system/bindHistory.d.ts +1 -1
- package/system/bindInertia.d.ts +1 -1
- package/system/bindInertia.js +23 -1
- package/system/bindMediaManager.d.ts +1 -1
- package/system/bindNotifications.d.ts +1 -1
- package/system/bindReqFunctions.d.ts +1 -1
- package/system/bindTranslations.d.ts +1 -1
- package/system/buildInternalModelAccess.d.ts +3 -3
- package/system/buildInternalModelAccess.js +5 -2
- package/system/defaults.d.ts +1 -1
- package/system/routePolicies.d.ts +1 -1
- package/system/systemModelContracts.d.ts +1 -1
- package/system/systemModelContracts.js +11 -7
- package/system/validateSystemModels.d.ts +1 -1
- package/system/validateSystemModels.js +29 -9
- package/assets/add-group-BdHFzKng.js +0 -1
- package/assets/add-p6yGCoZ7.js +0 -1
- package/assets/add-user-CDspCLu4.js +0 -1
- package/assets/app-DNXiApCk.css +0 -1
- package/assets/app-layout-DCr_-JY3.js +0 -1
- package/assets/app.js +0 -138
- package/assets/catalog-XN-_p9TB.js +0 -6
- package/assets/dashboard-CHZO2QFY.js +0 -3
- package/assets/debounce-BMmXVQ06.js +0 -1
- package/assets/history-CQd0FojQ.js +0 -1
- package/assets/init-user-8aUohVDq.js +0 -1
- package/assets/list-PHWgTHsg.js +0 -4
- package/assets/login-CO4laf6m.js +0 -1
- package/assets/module-DVyQtIxm.js +0 -1
- package/assets/notification-B87XG8nQ.js +0 -20
- package/assets/register-DBYIMXGQ.js +0 -1
- package/assets/ru-rYKdQbWF.js +0 -1
- package/assets/use-appearance-_EbbjHTh.js +0 -1
- package/assets/user-filters-list-BRzDbPkx.js +0 -1
- package/assets/welcome-DiXmrTgc.js +0 -1
|
@@ -1,14 +1,151 @@
|
|
|
1
|
-
import { AiAssistantMessage, AiAssistantModelInfo } from '../../interfaces/types';
|
|
2
|
-
import { User } from '../../models/User';
|
|
1
|
+
import { AiAgentConnectionStatus, AiAgentLimits, AiAgentModelOption, AiAgentPublish, AiAgentSavedFile, AiAgentSessionMeta, AiAgentUiHints, AiAgentUiSchema, AiAgentUploadedFile, AiAssistantMessage, AiAssistantModelInfo } from '../../interfaces/types.js';
|
|
2
|
+
import { User } from '../../models/User.js';
|
|
3
|
+
import type { Adminizer } from '../Adminizer.js';
|
|
4
|
+
import { AbstractAiConversationHistoryService } from './AbstractAiConversationHistoryService.js';
|
|
5
|
+
import type { AiAssistantAdminLink, AiAssistantNavigationTarget } from './AiAssistantUiMethodHandler.js';
|
|
6
|
+
import type { AiAssistantAgentSkillDescriptor, AiAssistantSkillUser } from './AiAssistantAgentSkillHandler.js';
|
|
7
|
+
export interface AiAgentChatCommand {
|
|
8
|
+
/** Command name without the leading slash, e.g. `summarize`. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Short explanation rendered in the command picker. */
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface AiAgentChatCommandContext {
|
|
14
|
+
command: AiAgentChatCommand;
|
|
15
|
+
arguments: string;
|
|
16
|
+
input: string | Array<Record<string, unknown>>;
|
|
17
|
+
user: User;
|
|
18
|
+
publish: AiAgentPublish;
|
|
19
|
+
}
|
|
20
|
+
export interface AiAgentChatCommandResult {
|
|
21
|
+
/** Text rendered as the command response and stored in the dialog. */
|
|
22
|
+
text?: string;
|
|
23
|
+
/** Set false for ephemeral commands that must not enter agent history. */
|
|
24
|
+
persist?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export type AiAgentChatCommandHandler = (context: AiAgentChatCommandContext) => Promise<AiAgentChatCommandResult | void> | AiAgentChatCommandResult | void;
|
|
3
27
|
/**
|
|
4
28
|
* Base class for AI assistant models. App modules register access rights
|
|
5
29
|
* and expose model metadata through this service.
|
|
30
|
+
*
|
|
31
|
+
* Two levels of capability:
|
|
32
|
+
*
|
|
33
|
+
* 1. `generateReply` — required. A plain request/response chat model; the
|
|
34
|
+
* assistant panel runs it through the same streaming transport and shows
|
|
35
|
+
* the answer as a single text part.
|
|
36
|
+
* 2. The optional agent members below. A service that implements `streamReply`
|
|
37
|
+
* is driven as a real agent: tool calls, reasoning, attachments and a
|
|
38
|
+
* server-side per-user session (history, compaction, model switching) are
|
|
39
|
+
* surfaced by the panel. Whatever the service does not implement is simply
|
|
40
|
+
* not rendered, so partial implementations are fine.
|
|
6
41
|
*/
|
|
7
42
|
export declare abstract class AbstractAiModelService {
|
|
8
43
|
readonly id: string;
|
|
9
44
|
readonly name: string;
|
|
10
45
|
readonly description?: string;
|
|
11
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Set when the model is registered. Agent implementations need it to read
|
|
48
|
+
* the panel config, the model registry and the user's permissions.
|
|
49
|
+
*/
|
|
50
|
+
protected adminizer: Adminizer;
|
|
51
|
+
readonly conversationHistory: AbstractAiConversationHistoryService;
|
|
52
|
+
private readonly chatCommands;
|
|
53
|
+
protected constructor(metadata: AiAssistantModelInfo, conversationHistory?: AbstractAiConversationHistoryService);
|
|
54
|
+
/** Called by AiAssistantHandler on registration; not part of the model API. */
|
|
55
|
+
attachAdminizer(adminizer: Adminizer): void;
|
|
12
56
|
getMetadata(): AiAssistantModelInfo;
|
|
57
|
+
/**
|
|
58
|
+
* Registers a slash command owned by this agent. The panel only displays
|
|
59
|
+
* it and sends its raw `/command arguments` text back to this service;
|
|
60
|
+
* command handling never lives in the browser.
|
|
61
|
+
*/
|
|
62
|
+
registerChatCommand(command: AiAgentChatCommand, handler?: AiAgentChatCommandHandler): void;
|
|
63
|
+
/** Commands available for autocomplete in the panel. */
|
|
64
|
+
getChatCommands(): AiAgentChatCommand[];
|
|
65
|
+
/** One declarative contract for the shared agent UI. */
|
|
66
|
+
getUiSchema(locale?: string): AiAgentUiSchema;
|
|
67
|
+
/**
|
|
68
|
+
* Browser capabilities available to a particular user. Tool-capable
|
|
69
|
+
* services can turn these descriptors into their provider's tool format.
|
|
70
|
+
*/
|
|
71
|
+
protected getUiMethods(user: User): import("./AiAssistantUiMethodHandler").AiAssistantUiMethod[];
|
|
72
|
+
/**
|
|
73
|
+
* Server-side implementation of the built-in `search-admin-links` skill.
|
|
74
|
+
* An OpenHarness/AI SDK adapter can return this value directly from its
|
|
75
|
+
* tool's `execute` callback.
|
|
76
|
+
*/
|
|
77
|
+
protected searchAdminLinks(user: User, query?: string): AiAssistantAdminLink[];
|
|
78
|
+
/**
|
|
79
|
+
* Built-in and app-contributed server skills this user may call. Data
|
|
80
|
+
* skills are already scoped to that user's model permissions, so an agent
|
|
81
|
+
* exposing them can be offered to non-administrator accounts.
|
|
82
|
+
*/
|
|
83
|
+
protected getAgentSkills(user: User): AiAssistantAgentSkillDescriptor[];
|
|
84
|
+
/**
|
|
85
|
+
* Executes an app-owned server skill after its permission check. The result
|
|
86
|
+
* is normalized to plain JSON: it becomes a tool result inside the agent's
|
|
87
|
+
* message history, which every later turn re-validates.
|
|
88
|
+
*/
|
|
89
|
+
protected executeAgentSkill(id: string, input: Record<string, unknown>, user: User): Promise<unknown>;
|
|
90
|
+
/** Identity of the user an agent turn is running for. */
|
|
91
|
+
protected describeUser(user: User): AiAssistantSkillUser;
|
|
92
|
+
/**
|
|
93
|
+
* Opens an admin page for the user: either a concrete `href` or a link
|
|
94
|
+
* template plus `params` (a record page, a catalog item…). The target is
|
|
95
|
+
* validated against that user's permissions and the resolved URL is
|
|
96
|
+
* returned, so it can be reported back as the tool result.
|
|
97
|
+
*/
|
|
98
|
+
protected openAdminLink(target: AiAssistantNavigationTarget, user: User, publish: AiAgentPublish): string;
|
|
99
|
+
/**
|
|
100
|
+
* Ask the shared panel to execute a registered browser method. The
|
|
101
|
+
* registry and permission check stay server-side, so an LLM cannot invoke
|
|
102
|
+
* an arbitrary client event.
|
|
103
|
+
*/
|
|
104
|
+
protected publishUiMethod(id: string, input: Record<string, unknown>, user: User, publish: AiAgentPublish): void;
|
|
105
|
+
/**
|
|
106
|
+
* Invoked by the agent transport for a registered `/command`. Returning
|
|
107
|
+
* null intentionally lets an unhandled command flow into `streamReply`
|
|
108
|
+
* or `generateReply` as regular agent input. A handler should return text
|
|
109
|
+
* instead of publishing a plain text frame itself, so its response can be
|
|
110
|
+
* persisted in the active conversation.
|
|
111
|
+
*/
|
|
112
|
+
executeChatCommand(prompt: string, input: string | Array<Record<string, unknown>>, user: User, publish: AiAgentPublish): Promise<AiAgentChatCommandResult | null>;
|
|
13
113
|
abstract generateReply(prompt: string, history: AiAssistantMessage[], user: User): Promise<string>;
|
|
114
|
+
/** True when the service streams runs itself instead of answering in one shot. */
|
|
115
|
+
isStreamingAgent(): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Run one turn and report progress through `publish`. `input` is either the
|
|
118
|
+
* plain prompt or an ai-sdk style ModelMessage[] when files are attached.
|
|
119
|
+
* The closing `done` frame is emitted by the run manager, not by the service.
|
|
120
|
+
*/
|
|
121
|
+
streamReply?(input: string | Array<Record<string, unknown>>, user: User, publish: AiAgentPublish): Promise<void>;
|
|
122
|
+
/** Current model, context usage and turn count of this user's session. */
|
|
123
|
+
getSessionMeta?(user: User): AiAgentSessionMeta;
|
|
124
|
+
/** Session history as ai-sdk ModelMessage[]; the panel rebuilds the thread from it. */
|
|
125
|
+
getSessionHistory?(user: User): Array<Record<string, unknown>>;
|
|
126
|
+
/** Restores a stored dialog when the user switches conversations. */
|
|
127
|
+
restoreSessionHistory?(user: User, messages: Array<Record<string, unknown>>): Promise<void> | void;
|
|
128
|
+
/** Drop the session. Returns whether there was anything to drop. */
|
|
129
|
+
resetSession?(user: User): boolean;
|
|
130
|
+
/** Summarize the session to free context. Returns e.g. tokensBefore/tokensAfter. */
|
|
131
|
+
compactSession?(user: User): Promise<Record<string, unknown>>;
|
|
132
|
+
/** LLMs the user may switch between inside this service. */
|
|
133
|
+
getModelChoices?(): Promise<AiAgentModelOption[]>;
|
|
134
|
+
/** Switch the session model. Returns whether the model changed. */
|
|
135
|
+
setCurrentModel?(user: User, model: string): Promise<boolean>;
|
|
136
|
+
/** Persist uploads before the turn, so tools can refer to them by id. */
|
|
137
|
+
saveUploadedFiles?(user: User, files: AiAgentUploadedFile[]): Promise<AiAgentSavedFile[]>;
|
|
138
|
+
/**
|
|
139
|
+
* Provisioning state. Omit when the service is always usable; when present
|
|
140
|
+
* and not `ready`, the panel shows a loader instead of the composer.
|
|
141
|
+
*/
|
|
142
|
+
getConnectionStatus?(): Promise<AiAgentConnectionStatus> | AiAgentConnectionStatus;
|
|
143
|
+
/** Budget / rate limits of the provider key, when the provider exposes them. */
|
|
144
|
+
getLimits?(forceRefresh: boolean): Promise<AiAgentLimits | null>;
|
|
145
|
+
/**
|
|
146
|
+
* Panel copy for this service: title, welcome hint, composer placeholder,
|
|
147
|
+
* starter prompts and connection screens. The locale is resolved from the
|
|
148
|
+
* current admin user before this method is called.
|
|
149
|
+
*/
|
|
150
|
+
getUiHints?(locale?: string): AiAgentUiHints;
|
|
14
151
|
}
|
|
@@ -1,15 +1,41 @@
|
|
|
1
|
+
import { InMemoryAiConversationHistoryService } from './AbstractAiConversationHistoryService.js';
|
|
2
|
+
import { toJsonSafe } from './jsonSafe.js';
|
|
1
3
|
/**
|
|
2
4
|
* Base class for AI assistant models. App modules register access rights
|
|
3
5
|
* and expose model metadata through this service.
|
|
6
|
+
*
|
|
7
|
+
* Two levels of capability:
|
|
8
|
+
*
|
|
9
|
+
* 1. `generateReply` — required. A plain request/response chat model; the
|
|
10
|
+
* assistant panel runs it through the same streaming transport and shows
|
|
11
|
+
* the answer as a single text part.
|
|
12
|
+
* 2. The optional agent members below. A service that implements `streamReply`
|
|
13
|
+
* is driven as a real agent: tool calls, reasoning, attachments and a
|
|
14
|
+
* server-side per-user session (history, compaction, model switching) are
|
|
15
|
+
* surfaced by the panel. Whatever the service does not implement is simply
|
|
16
|
+
* not rendered, so partial implementations are fine.
|
|
4
17
|
*/
|
|
5
18
|
export class AbstractAiModelService {
|
|
6
19
|
id;
|
|
7
20
|
name;
|
|
8
21
|
description;
|
|
9
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Set when the model is registered. Agent implementations need it to read
|
|
24
|
+
* the panel config, the model registry and the user's permissions.
|
|
25
|
+
*/
|
|
26
|
+
adminizer;
|
|
27
|
+
conversationHistory;
|
|
28
|
+
chatCommands = new Map();
|
|
29
|
+
constructor(metadata, conversationHistory = new InMemoryAiConversationHistoryService()) {
|
|
10
30
|
this.id = metadata.id;
|
|
11
31
|
this.name = metadata.name;
|
|
12
32
|
this.description = metadata.description;
|
|
33
|
+
this.conversationHistory = conversationHistory;
|
|
34
|
+
}
|
|
35
|
+
/** Called by AiAssistantHandler on registration; not part of the model API. */
|
|
36
|
+
attachAdminizer(adminizer) {
|
|
37
|
+
this.adminizer = adminizer;
|
|
38
|
+
this.conversationHistory.initialize(this, adminizer);
|
|
13
39
|
}
|
|
14
40
|
getMetadata() {
|
|
15
41
|
return {
|
|
@@ -18,4 +44,131 @@ export class AbstractAiModelService {
|
|
|
18
44
|
description: this.description,
|
|
19
45
|
};
|
|
20
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Registers a slash command owned by this agent. The panel only displays
|
|
49
|
+
* it and sends its raw `/command arguments` text back to this service;
|
|
50
|
+
* command handling never lives in the browser.
|
|
51
|
+
*/
|
|
52
|
+
registerChatCommand(command, handler) {
|
|
53
|
+
const id = command.id.trim().replace(/^\/+/, '').toLowerCase();
|
|
54
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(id)) {
|
|
55
|
+
throw new Error(`Invalid AI agent command id: ${command.id}`);
|
|
56
|
+
}
|
|
57
|
+
this.chatCommands.set(id, {
|
|
58
|
+
command: { id, description: command.description?.trim() || undefined },
|
|
59
|
+
handler,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/** Commands available for autocomplete in the panel. */
|
|
63
|
+
getChatCommands() {
|
|
64
|
+
const commands = [...this.chatCommands.values()].map(({ command }) => ({ ...command }));
|
|
65
|
+
if (typeof this.compactSession === 'function' && !this.chatCommands.has('compact')) {
|
|
66
|
+
commands.unshift({
|
|
67
|
+
id: 'compact',
|
|
68
|
+
description: 'Prune or summarize older messages to free context.',
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return commands;
|
|
72
|
+
}
|
|
73
|
+
/** One declarative contract for the shared agent UI. */
|
|
74
|
+
getUiSchema(locale) {
|
|
75
|
+
return {
|
|
76
|
+
...(this.getUiHints?.(locale) ?? {}),
|
|
77
|
+
commands: this.getChatCommands(),
|
|
78
|
+
panels: {
|
|
79
|
+
history: true,
|
|
80
|
+
models: typeof this.getModelChoices === 'function' && typeof this.setCurrentModel === 'function',
|
|
81
|
+
limits: typeof this.getLimits === 'function',
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Browser capabilities available to a particular user. Tool-capable
|
|
87
|
+
* services can turn these descriptors into their provider's tool format.
|
|
88
|
+
*/
|
|
89
|
+
getUiMethods(user) {
|
|
90
|
+
return this.adminizer.aiAssistantUiMethodHandler.getAvailable(user);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Server-side implementation of the built-in `search-admin-links` skill.
|
|
94
|
+
* An OpenHarness/AI SDK adapter can return this value directly from its
|
|
95
|
+
* tool's `execute` callback.
|
|
96
|
+
*/
|
|
97
|
+
searchAdminLinks(user, query) {
|
|
98
|
+
return this.adminizer.aiAssistantUiMethodHandler.searchAdminLinks(user, query);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Built-in and app-contributed server skills this user may call. Data
|
|
102
|
+
* skills are already scoped to that user's model permissions, so an agent
|
|
103
|
+
* exposing them can be offered to non-administrator accounts.
|
|
104
|
+
*/
|
|
105
|
+
getAgentSkills(user) {
|
|
106
|
+
return this.adminizer.aiAssistantAgentSkillHandler.getAvailable(user);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Executes an app-owned server skill after its permission check. The result
|
|
110
|
+
* is normalized to plain JSON: it becomes a tool result inside the agent's
|
|
111
|
+
* message history, which every later turn re-validates.
|
|
112
|
+
*/
|
|
113
|
+
async executeAgentSkill(id, input, user) {
|
|
114
|
+
const result = await this.adminizer.aiAssistantAgentSkillHandler.execute(id, input, user);
|
|
115
|
+
return toJsonSafe(result) ?? null;
|
|
116
|
+
}
|
|
117
|
+
/** Identity of the user an agent turn is running for. */
|
|
118
|
+
describeUser(user) {
|
|
119
|
+
return this.adminizer.aiAssistantAgentSkillHandler.describeUser(user);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Opens an admin page for the user: either a concrete `href` or a link
|
|
123
|
+
* template plus `params` (a record page, a catalog item…). The target is
|
|
124
|
+
* validated against that user's permissions and the resolved URL is
|
|
125
|
+
* returned, so it can be reported back as the tool result.
|
|
126
|
+
*/
|
|
127
|
+
openAdminLink(target, user, publish) {
|
|
128
|
+
const href = this.adminizer.aiAssistantUiMethodHandler.resolveNavigation(user, target);
|
|
129
|
+
this.publishUiMethod('navigate', { href }, user, publish);
|
|
130
|
+
return href;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Ask the shared panel to execute a registered browser method. The
|
|
134
|
+
* registry and permission check stay server-side, so an LLM cannot invoke
|
|
135
|
+
* an arbitrary client event.
|
|
136
|
+
*/
|
|
137
|
+
publishUiMethod(id, input, user, publish) {
|
|
138
|
+
const method = this.getUiMethods(user).find((candidate) => candidate.id === id);
|
|
139
|
+
if (!method)
|
|
140
|
+
throw new Error(`AI assistant UI method "${id}" is not available`);
|
|
141
|
+
publish({ type: 'ui.method', method: method.id, action: method.action, input });
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Invoked by the agent transport for a registered `/command`. Returning
|
|
145
|
+
* null intentionally lets an unhandled command flow into `streamReply`
|
|
146
|
+
* or `generateReply` as regular agent input. A handler should return text
|
|
147
|
+
* instead of publishing a plain text frame itself, so its response can be
|
|
148
|
+
* persisted in the active conversation.
|
|
149
|
+
*/
|
|
150
|
+
async executeChatCommand(prompt, input, user, publish) {
|
|
151
|
+
const match = /^\/([a-z][a-z0-9_-]*)(?:\s+([\s\S]*))?$/i.exec(prompt.trim());
|
|
152
|
+
if (!match)
|
|
153
|
+
return null;
|
|
154
|
+
const entry = this.chatCommands.get(match[1].toLowerCase());
|
|
155
|
+
if (entry?.handler) {
|
|
156
|
+
const result = await entry.handler({ command: entry.command, arguments: match[2] ?? '', input, user, publish });
|
|
157
|
+
return { persist: true, ...result };
|
|
158
|
+
}
|
|
159
|
+
if (match[1].toLowerCase() !== 'compact' || typeof this.compactSession !== 'function')
|
|
160
|
+
return null;
|
|
161
|
+
const result = await this.compactSession(user);
|
|
162
|
+
const compacted = result.compacted === true;
|
|
163
|
+
const before = Number(result.tokensBefore ?? 0);
|
|
164
|
+
const after = Number(result.tokensAfter ?? 0);
|
|
165
|
+
const text = compacted
|
|
166
|
+
? `Context compacted${before > 0 ? `: ${before} → ${after} tokens.` : '.'}`
|
|
167
|
+
: 'Nothing to compact yet.';
|
|
168
|
+
return { text, persist: true };
|
|
169
|
+
}
|
|
170
|
+
/** True when the service streams runs itself instead of answering in one shot. */
|
|
171
|
+
isStreamingAgent() {
|
|
172
|
+
return typeof this.streamReply === 'function';
|
|
173
|
+
}
|
|
21
174
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AiAgentStreamEvent } from '../../interfaces/types.js';
|
|
2
|
+
interface AgentRun {
|
|
3
|
+
id: string;
|
|
4
|
+
userId: number;
|
|
5
|
+
modelId: string;
|
|
6
|
+
createdAt: number;
|
|
7
|
+
events: AiAgentStreamEvent[];
|
|
8
|
+
clients: Set<ResType>;
|
|
9
|
+
done: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Buffered SSE transport for agent runs.
|
|
13
|
+
*
|
|
14
|
+
* A run is started by a POST that returns immediately (so the browser can open
|
|
15
|
+
* the stream) and then writes its events here. Every event is stored with its
|
|
16
|
+
* index and used as the SSE event id, which lets EventSource resume through
|
|
17
|
+
* `Last-Event-ID` after a reconnect without replaying what the client already
|
|
18
|
+
* processed.
|
|
19
|
+
*/
|
|
20
|
+
export declare class AiAgentRunManager {
|
|
21
|
+
private readonly runs;
|
|
22
|
+
create(userId: number, modelId: string): AgentRun;
|
|
23
|
+
/** Only the owner may read a run. */
|
|
24
|
+
get(runId: string, userId: number): AgentRun | undefined;
|
|
25
|
+
publish(run: AgentRun, event: AiAgentStreamEvent): void;
|
|
26
|
+
/** Marks the run finished and flushes the closing frame to every client. */
|
|
27
|
+
finish(run: AgentRun, event: AiAgentStreamEvent): void;
|
|
28
|
+
/** Streams a run to one client: replays what it missed, then follows live. */
|
|
29
|
+
subscribe(run: AgentRun, req: ReqType, res: ResType): void;
|
|
30
|
+
private write;
|
|
31
|
+
/** SSE payloads must be JSON-safe and bounded. */
|
|
32
|
+
private sanitize;
|
|
33
|
+
private cleanup;
|
|
34
|
+
}
|
|
35
|
+
export type { AgentRun };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** Runs are kept only long enough for a reconnecting EventSource to catch up. */
|
|
2
|
+
const MAX_RUN_AGE_MS = 10 * 60 * 1000;
|
|
3
|
+
/** Hard cap per SSE frame, so a chatty tool result cannot blow up memory. */
|
|
4
|
+
const MAX_EVENT_JSON_CHARS = 64 * 1024;
|
|
5
|
+
/**
|
|
6
|
+
* Buffered SSE transport for agent runs.
|
|
7
|
+
*
|
|
8
|
+
* A run is started by a POST that returns immediately (so the browser can open
|
|
9
|
+
* the stream) and then writes its events here. Every event is stored with its
|
|
10
|
+
* index and used as the SSE event id, which lets EventSource resume through
|
|
11
|
+
* `Last-Event-ID` after a reconnect without replaying what the client already
|
|
12
|
+
* processed.
|
|
13
|
+
*/
|
|
14
|
+
export class AiAgentRunManager {
|
|
15
|
+
runs = new Map();
|
|
16
|
+
create(userId, modelId) {
|
|
17
|
+
this.cleanup();
|
|
18
|
+
const id = `run-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
19
|
+
const run = {
|
|
20
|
+
id,
|
|
21
|
+
userId,
|
|
22
|
+
modelId,
|
|
23
|
+
createdAt: Date.now(),
|
|
24
|
+
events: [],
|
|
25
|
+
clients: new Set(),
|
|
26
|
+
done: false,
|
|
27
|
+
};
|
|
28
|
+
this.runs.set(id, run);
|
|
29
|
+
return run;
|
|
30
|
+
}
|
|
31
|
+
/** Only the owner may read a run. */
|
|
32
|
+
get(runId, userId) {
|
|
33
|
+
const run = this.runs.get(runId);
|
|
34
|
+
return run && run.userId === userId ? run : undefined;
|
|
35
|
+
}
|
|
36
|
+
publish(run, event) {
|
|
37
|
+
const safe = this.sanitize(event);
|
|
38
|
+
const index = run.events.push(safe) - 1;
|
|
39
|
+
for (const client of run.clients)
|
|
40
|
+
this.write(client, 'agent', safe, index);
|
|
41
|
+
}
|
|
42
|
+
/** Marks the run finished and flushes the closing frame to every client. */
|
|
43
|
+
finish(run, event) {
|
|
44
|
+
run.done = true;
|
|
45
|
+
this.publish(run, event);
|
|
46
|
+
for (const client of run.clients)
|
|
47
|
+
client.end();
|
|
48
|
+
run.clients.clear();
|
|
49
|
+
}
|
|
50
|
+
/** Streams a run to one client: replays what it missed, then follows live. */
|
|
51
|
+
subscribe(run, req, res) {
|
|
52
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
53
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
54
|
+
res.setHeader('Connection', 'keep-alive');
|
|
55
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
56
|
+
res.flushHeaders?.();
|
|
57
|
+
res.write('retry: 3000\n\n');
|
|
58
|
+
this.write(res, 'connected', { runId: run.id });
|
|
59
|
+
const lastEventId = Number(req.headers['last-event-id']);
|
|
60
|
+
const start = Number.isFinite(lastEventId) ? lastEventId + 1 : 0;
|
|
61
|
+
for (let i = start; i < run.events.length; i++) {
|
|
62
|
+
this.write(res, 'agent', run.events[i], i);
|
|
63
|
+
}
|
|
64
|
+
if (run.done) {
|
|
65
|
+
res.end();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
run.clients.add(res);
|
|
69
|
+
req.on('close', () => run.clients.delete(res));
|
|
70
|
+
}
|
|
71
|
+
write(res, event, data, id) {
|
|
72
|
+
if (id !== undefined)
|
|
73
|
+
res.write(`id: ${id}\n`);
|
|
74
|
+
res.write(`event: ${event}\n`);
|
|
75
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
76
|
+
}
|
|
77
|
+
/** SSE payloads must be JSON-safe and bounded. */
|
|
78
|
+
sanitize(event) {
|
|
79
|
+
if (!event || typeof event !== 'object')
|
|
80
|
+
return event;
|
|
81
|
+
let safe = event;
|
|
82
|
+
// A finished agent turn may carry the whole history — the panel never
|
|
83
|
+
// uses it and it would dwarf every other frame.
|
|
84
|
+
if (safe.type === 'done' && 'messages' in safe) {
|
|
85
|
+
const { messages: _messages, ...rest } = safe;
|
|
86
|
+
safe = rest;
|
|
87
|
+
}
|
|
88
|
+
if (safe.error instanceof Error) {
|
|
89
|
+
safe = { ...safe, error: safe.error.message };
|
|
90
|
+
}
|
|
91
|
+
const json = JSON.stringify(safe);
|
|
92
|
+
if (json.length <= MAX_EVENT_JSON_CHARS)
|
|
93
|
+
return safe;
|
|
94
|
+
return {
|
|
95
|
+
type: safe.type || 'event',
|
|
96
|
+
truncated: true,
|
|
97
|
+
originalChars: json.length,
|
|
98
|
+
message: `${safe.type || 'event'} payload was truncated to keep SSE memory bounded.`,
|
|
99
|
+
preview: `${json.slice(0, MAX_EVENT_JSON_CHARS - 1)}…`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
cleanup() {
|
|
103
|
+
const cutoff = Date.now() - MAX_RUN_AGE_MS;
|
|
104
|
+
for (const [id, run] of this.runs) {
|
|
105
|
+
if (run.createdAt < cutoff)
|
|
106
|
+
this.runs.delete(id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { User } from '../../models/User.js';
|
|
2
|
+
import type { Adminizer } from '../Adminizer.js';
|
|
3
|
+
import type { AppRuntime } from '../app-manager/AdminizerApp.js';
|
|
4
|
+
/** Identity of the admin user on whose behalf a skill runs. */
|
|
5
|
+
export interface AiAssistantSkillUser {
|
|
6
|
+
id?: number;
|
|
7
|
+
login: string;
|
|
8
|
+
fullName?: string;
|
|
9
|
+
email?: string;
|
|
10
|
+
locale?: string;
|
|
11
|
+
isAdministrator: boolean;
|
|
12
|
+
groups: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface AiAssistantAgentSkillContext {
|
|
15
|
+
user: User;
|
|
16
|
+
/** The same user as a plain descriptor, safe to return in a tool result. */
|
|
17
|
+
userIdentity: AiAssistantSkillUser;
|
|
18
|
+
runtime: AppRuntime;
|
|
19
|
+
}
|
|
20
|
+
/** A server-side tool made available to every compatible registered agent. */
|
|
21
|
+
export interface AiAssistantAgentSkill {
|
|
22
|
+
id: string;
|
|
23
|
+
description: string;
|
|
24
|
+
inputSchema: Record<string, unknown>;
|
|
25
|
+
accessRightsToken?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Advertise the calling user inside the input schema. The agent then sees
|
|
28
|
+
* who it acts for and echoes it back; the value is always overwritten with
|
|
29
|
+
* the authenticated identity, so it cannot be used to impersonate.
|
|
30
|
+
*/
|
|
31
|
+
requiresUser?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Per-user refinement of what the agent is told about this skill, e.g. an
|
|
34
|
+
* enum of the models this particular user may read.
|
|
35
|
+
*/
|
|
36
|
+
describe?(user: User, adminizer: Adminizer): {
|
|
37
|
+
description?: string;
|
|
38
|
+
inputSchema?: Record<string, unknown>;
|
|
39
|
+
} | undefined;
|
|
40
|
+
execute(input: Record<string, unknown>, context: AiAssistantAgentSkillContext): unknown | Promise<unknown>;
|
|
41
|
+
}
|
|
42
|
+
export type AiAssistantAgentSkillDescriptor = Omit<AiAssistantAgentSkill, 'execute' | 'describe'>;
|
|
43
|
+
/** Input property that carries the calling user for `requiresUser` skills. */
|
|
44
|
+
export declare const CURRENT_USER_PARAM = "currentUser";
|
|
45
|
+
export declare class AiAssistantAgentSkillHandler {
|
|
46
|
+
private readonly adminizer;
|
|
47
|
+
private readonly skills;
|
|
48
|
+
constructor(adminizer: Adminizer);
|
|
49
|
+
add(skill: AiAssistantAgentSkill, owner?: string): void;
|
|
50
|
+
remove(id: string, owner?: string): boolean;
|
|
51
|
+
/** Skills this user may call, described with their permissions applied. */
|
|
52
|
+
getAvailable(user: User): AiAssistantAgentSkillDescriptor[];
|
|
53
|
+
execute(id: string, input: Record<string, unknown>, user: User): Promise<unknown>;
|
|
54
|
+
/** Plain, JSON-safe identity of an admin user. */
|
|
55
|
+
describeUser(user: User): AiAssistantSkillUser;
|
|
56
|
+
private isPermitted;
|
|
57
|
+
private describe;
|
|
58
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { buildBuiltinAgentSkills } from './builtinAgentSkills.js';
|
|
2
|
+
/** Input property that carries the calling user for `requiresUser` skills. */
|
|
3
|
+
export const CURRENT_USER_PARAM = 'currentUser';
|
|
4
|
+
export class AiAssistantAgentSkillHandler {
|
|
5
|
+
adminizer;
|
|
6
|
+
skills = new Map();
|
|
7
|
+
constructor(adminizer) {
|
|
8
|
+
this.adminizer = adminizer;
|
|
9
|
+
// Built-in skills only read `adminizer` when they run, so registering
|
|
10
|
+
// them from the constructor is safe while Adminizer is still wiring up.
|
|
11
|
+
for (const skill of buildBuiltinAgentSkills(adminizer))
|
|
12
|
+
this.add(skill);
|
|
13
|
+
}
|
|
14
|
+
add(skill, owner = 'host') {
|
|
15
|
+
const id = skill.id.trim().toLowerCase();
|
|
16
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(id))
|
|
17
|
+
throw new Error(`Invalid AI assistant skill id: ${skill.id}`);
|
|
18
|
+
if (!skill.description.trim())
|
|
19
|
+
throw new Error(`AI assistant skill "${id}" requires a description`);
|
|
20
|
+
if (this.skills.has(id))
|
|
21
|
+
throw new Error(`AI assistant skill "${id}" is already registered`);
|
|
22
|
+
this.skills.set(id, { ...skill, id, owner });
|
|
23
|
+
}
|
|
24
|
+
remove(id, owner) {
|
|
25
|
+
const skill = this.skills.get(id);
|
|
26
|
+
if (!skill || (owner && skill.owner !== owner))
|
|
27
|
+
return false;
|
|
28
|
+
return this.skills.delete(id);
|
|
29
|
+
}
|
|
30
|
+
/** Skills this user may call, described with their permissions applied. */
|
|
31
|
+
getAvailable(user) {
|
|
32
|
+
return [...this.skills.values()]
|
|
33
|
+
.filter((skill) => this.isPermitted(skill, user))
|
|
34
|
+
.map((skill) => this.describe(skill, user));
|
|
35
|
+
}
|
|
36
|
+
async execute(id, input, user) {
|
|
37
|
+
const skill = this.skills.get(id.trim().toLowerCase());
|
|
38
|
+
if (!skill || !this.isPermitted(skill, user)) {
|
|
39
|
+
throw new Error(`AI assistant skill "${id}" is not available`);
|
|
40
|
+
}
|
|
41
|
+
const userIdentity = this.describeUser(user);
|
|
42
|
+
// Whatever the model passed as the user is discarded: a skill acts for
|
|
43
|
+
// the authenticated admin user only.
|
|
44
|
+
const payload = skill.requiresUser
|
|
45
|
+
? { ...input, [CURRENT_USER_PARAM]: userIdentity.login }
|
|
46
|
+
: input;
|
|
47
|
+
return skill.execute(payload, {
|
|
48
|
+
user,
|
|
49
|
+
userIdentity,
|
|
50
|
+
runtime: this.adminizer.appManager.createRuntime(skill.owner),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Plain, JSON-safe identity of an admin user. */
|
|
54
|
+
describeUser(user) {
|
|
55
|
+
return {
|
|
56
|
+
id: user.id,
|
|
57
|
+
login: user.login,
|
|
58
|
+
fullName: user.fullName || undefined,
|
|
59
|
+
email: user.email || undefined,
|
|
60
|
+
locale: user.locale || undefined,
|
|
61
|
+
isAdministrator: Boolean(user.isAdministrator),
|
|
62
|
+
groups: (user.groups ?? []).map((group) => group?.name).filter(Boolean),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
isPermitted(skill, user) {
|
|
66
|
+
return !skill.accessRightsToken || this.adminizer.accessRightsHelper.hasPermission(skill.accessRightsToken, user);
|
|
67
|
+
}
|
|
68
|
+
describe(skill, user) {
|
|
69
|
+
const refinement = skill.describe?.(user, this.adminizer) ?? {};
|
|
70
|
+
const { execute: _execute, describe: _describe, owner: _owner, ...rest } = skill;
|
|
71
|
+
const inputSchema = { ...(refinement.inputSchema ?? skill.inputSchema) };
|
|
72
|
+
if (skill.requiresUser) {
|
|
73
|
+
const identity = this.describeUser(user);
|
|
74
|
+
const properties = { ...(inputSchema.properties ?? {}) };
|
|
75
|
+
properties[CURRENT_USER_PARAM] = {
|
|
76
|
+
type: 'string',
|
|
77
|
+
enum: [identity.login],
|
|
78
|
+
description: `Login of the admin user this conversation belongs to (id ${identity.id}`
|
|
79
|
+
+ `${identity.fullName ? `, ${identity.fullName}` : ''}). Always pass "${identity.login}".`,
|
|
80
|
+
};
|
|
81
|
+
const required = new Set([...(inputSchema.required ?? []), CURRENT_USER_PARAM]);
|
|
82
|
+
inputSchema.properties = properties;
|
|
83
|
+
inputSchema.required = [...required];
|
|
84
|
+
}
|
|
85
|
+
return { ...rest, description: refinement.description ?? skill.description, inputSchema };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
import { Adminizer } from '../Adminizer';
|
|
2
|
-
import { AbstractAiModelService } from './AbstractAiModelService';
|
|
3
|
-
import { AiAssistantMessage, AiAssistantModelInfo } from '../../interfaces/types';
|
|
4
|
-
import { User } from '../../models/User';
|
|
1
|
+
import { Adminizer } from '../Adminizer.js';
|
|
2
|
+
import { AbstractAiModelService } from './AbstractAiModelService.js';
|
|
3
|
+
import { AiAssistantMessage, AiAssistantModelInfo } from '../../interfaces/types.js';
|
|
4
|
+
import { User } from '../../models/User.js';
|
|
5
5
|
export declare class AiAssistantHandler {
|
|
6
6
|
private readonly adminizer;
|
|
7
7
|
private readonly models;
|
|
8
|
+
private readonly owners;
|
|
8
9
|
private readonly history;
|
|
9
10
|
constructor(adminizer: Adminizer);
|
|
10
|
-
|
|
11
|
+
/**
|
|
12
|
+
* @param owner App name when the model belongs to an app. Models without an
|
|
13
|
+
* owner belong to the host application and are never removed automatically.
|
|
14
|
+
*/
|
|
15
|
+
registerModel(service: AbstractAiModelService, owner?: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* @param owner When passed, the model is kept if it is owned by someone
|
|
18
|
+
* else, so a disabled app never removes another owner's model.
|
|
19
|
+
*/
|
|
20
|
+
unregisterModel(id: string, owner?: string): void;
|
|
11
21
|
getModel(id: string): AbstractAiModelService | undefined;
|
|
12
22
|
getModels(): AiAssistantModelInfo[];
|
|
13
23
|
getHistory(userId: number, modelId: string): AiAssistantMessage[];
|
|
14
24
|
resetHistory(userId: number, modelId: string): void;
|
|
25
|
+
replaceHistory(userId: number, modelId: string, history: AiAssistantMessage[]): void;
|
|
15
26
|
sendMessage(user: User, modelId: string, prompt: string): Promise<{
|
|
16
27
|
history: AiAssistantMessage[];
|
|
17
28
|
}>;
|