@acorex/modules 21.0.0-next.54 → 21.0.0-next.56
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/ai-management/README.md +3 -2
- package/fesm2022/{acorex-modules-ai-management-agent.entity-D6-0_Ms3.mjs → acorex-modules-ai-management-agent.entity-X51GLTdi.mjs} +69 -6
- package/fesm2022/acorex-modules-ai-management-agent.entity-X51GLTdi.mjs.map +1 -0
- package/fesm2022/{acorex-modules-ai-management-assist.entity-CnyoIO-Z.mjs → acorex-modules-ai-management-assist.entity-2h5KE9UH.mjs} +58 -4
- package/fesm2022/acorex-modules-ai-management-assist.entity-2h5KE9UH.mjs.map +1 -0
- package/fesm2022/acorex-modules-ai-management-rule.entity-CQNx4QEB.mjs +121 -0
- package/fesm2022/acorex-modules-ai-management-rule.entity-CQNx4QEB.mjs.map +1 -0
- package/fesm2022/acorex-modules-ai-management.mjs +864 -945
- package/fesm2022/acorex-modules-ai-management.mjs.map +1 -1
- package/fesm2022/{acorex-modules-conversation-acorex-modules-conversation-Bnjyq-wp.mjs → acorex-modules-conversation-acorex-modules-conversation-UNhA-qi5.mjs} +71 -41
- package/fesm2022/acorex-modules-conversation-acorex-modules-conversation-UNhA-qi5.mjs.map +1 -0
- package/fesm2022/{acorex-modules-conversation-assist-delegated-agent-detail-popup.component-Df5LmYZI.mjs → acorex-modules-conversation-assist-delegated-agent-detail-popup.component-Be58gcns.mjs} +6 -6
- package/fesm2022/acorex-modules-conversation-assist-delegated-agent-detail-popup.component-Be58gcns.mjs.map +1 -0
- package/fesm2022/{acorex-modules-conversation-comments-page.component-BXI4smIr.mjs → acorex-modules-conversation-comments-page.component-3XBLeMW8.mjs} +2 -2
- package/fesm2022/{acorex-modules-conversation-comments-page.component-BXI4smIr.mjs.map → acorex-modules-conversation-comments-page.component-3XBLeMW8.mjs.map} +1 -1
- package/fesm2022/{acorex-modules-conversation-send-assist-chat-message.command-B5qJnpCK.mjs → acorex-modules-conversation-send-assist-chat-message.command-CSu-lJuu.mjs} +2 -2
- package/fesm2022/{acorex-modules-conversation-send-assist-chat-message.command-B5qJnpCK.mjs.map → acorex-modules-conversation-send-assist-chat-message.command-CSu-lJuu.mjs.map} +1 -1
- package/fesm2022/{acorex-modules-conversation-start-assist-chat.command-DI3LAaDF.mjs → acorex-modules-conversation-start-assist-chat.command-CyoncQB1.mjs} +2 -2
- package/fesm2022/{acorex-modules-conversation-start-assist-chat.command-DI3LAaDF.mjs.map → acorex-modules-conversation-start-assist-chat.command-CyoncQB1.mjs.map} +1 -1
- package/fesm2022/acorex-modules-conversation.mjs +1 -1
- package/package.json +2 -2
- package/types/acorex-modules-ai-management.d.ts +725 -582
- package/types/acorex-modules-conversation.d.ts +2 -0
- package/fesm2022/acorex-modules-ai-management-agent.entity-D6-0_Ms3.mjs.map +0 -1
- package/fesm2022/acorex-modules-ai-management-assist.entity-CnyoIO-Z.mjs.map +0 -1
- package/fesm2022/acorex-modules-conversation-acorex-modules-conversation-Bnjyq-wp.mjs.map +0 -1
- package/fesm2022/acorex-modules-conversation-assist-delegated-agent-detail-popup.component-Df5LmYZI.mjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex/platform/auth';
|
|
2
|
-
import { AXPSettingsService, AXPRegionalSetting, AXPFileStorageStatus, AXPFileStorageService, AXP_MENU_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER } from '@acorex/platform/common';
|
|
2
|
+
import { AXPOutputContractTranscriptSegmentService, AXPSettingsService, AXPRegionalSetting, AXPFileStorageStatus, AXPFileStorageService, AXP_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES, AXP_MENU_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER, AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER } from '@acorex/platform/common';
|
|
3
3
|
import * as i1$1 from '@acorex/platform/core';
|
|
4
4
|
import { objectKeyValueTransforms, AXPTagProvider, AXPPlatformScope, AXPComponentSlotModule, AXP_MODULE_MANIFEST_PROVIDER, AXP_DATASOURCE_DEFINITION_PROVIDER, AXP_TAG_PROVIDER } from '@acorex/platform/core';
|
|
5
|
-
import { AXPEntityService, AXP_ENTITY_DEFINITION_LOADER
|
|
5
|
+
import { AXPEntityService, AXP_ENTITY_DEFINITION_LOADER } from '@acorex/platform/layout/entity';
|
|
6
6
|
import { AXPWidgetsCatalog, AXPWidgetGroupEnum, AXP_WIDGETS_EDITOR_CATEGORY, AXP_WIDGET_DEFINITION_PROVIDER } from '@acorex/platform/layout/widget-core';
|
|
7
7
|
import { AXP_NAME_PROPERTY, AXP_DATA_PATH_PROPERTY, AXPWidgetsModule } from '@acorex/platform/layout/widgets';
|
|
8
8
|
import * as i0 from '@angular/core';
|
|
@@ -75,6 +75,13 @@ const RootConfig = {
|
|
|
75
75
|
titlePlural: '@ai-management:open-ai-endpoints.entities.endpoint.plural',
|
|
76
76
|
icon: 'fa-light fa-plug',
|
|
77
77
|
},
|
|
78
|
+
rule: {
|
|
79
|
+
name: 'AiRule',
|
|
80
|
+
fullName: `${config.module}.AiRule`,
|
|
81
|
+
title: '@ai-management:rules.entities.rule.title',
|
|
82
|
+
titlePlural: '@ai-management:rules.entities.rule.plural',
|
|
83
|
+
icon: 'fa-light fa-list-check',
|
|
84
|
+
},
|
|
78
85
|
},
|
|
79
86
|
};
|
|
80
87
|
//#endregion
|
|
@@ -113,11 +120,13 @@ class AXMAiManagementEntityProvider {
|
|
|
113
120
|
case RootConfig.entities.model.name:
|
|
114
121
|
return (await import('./acorex-modules-ai-management-model.entity-DiDaXVa3.mjs')).factory();
|
|
115
122
|
case RootConfig.entities.assist.name:
|
|
116
|
-
return (await import('./acorex-modules-ai-management-assist.entity-
|
|
123
|
+
return (await import('./acorex-modules-ai-management-assist.entity-2h5KE9UH.mjs')).assistFactory();
|
|
117
124
|
case RootConfig.entities.agent.name:
|
|
118
|
-
return (await import('./acorex-modules-ai-management-agent.entity-
|
|
125
|
+
return (await import('./acorex-modules-ai-management-agent.entity-X51GLTdi.mjs')).agentFactory();
|
|
119
126
|
case RootConfig.entities.openAiEndpoint.name:
|
|
120
127
|
return (await import('./acorex-modules-ai-management-open-ai-endpoint.entity-CZLNKtl0.mjs')).openAiEndpointFactory();
|
|
128
|
+
case RootConfig.entities.rule.name:
|
|
129
|
+
return (await import('./acorex-modules-ai-management-rule.entity-CQNx4QEB.mjs')).ruleFactory();
|
|
121
130
|
default:
|
|
122
131
|
return null;
|
|
123
132
|
}
|
|
@@ -147,6 +156,8 @@ const AXPAiManagementMenuKeys = {
|
|
|
147
156
|
Assists: 'AiManagement:Menu:Assists',
|
|
148
157
|
/** Delegated specialist agents (supervisor routing). */
|
|
149
158
|
Agents: 'AiManagement:Menu:Agents',
|
|
159
|
+
/** Reusable system-prompt rules (Markdown fragments). */
|
|
160
|
+
Rules: 'AiManagement:Menu:Rules',
|
|
150
161
|
};
|
|
151
162
|
//#endregion
|
|
152
163
|
|
|
@@ -228,6 +239,14 @@ class AXMAiManagementMenuProvider {
|
|
|
228
239
|
priority: 1,
|
|
229
240
|
policy: aiMenuPolicy,
|
|
230
241
|
},
|
|
242
|
+
{
|
|
243
|
+
name: AXPAiManagementMenuKeys.Rules,
|
|
244
|
+
text: '@ai-management:module.menus.rules.title',
|
|
245
|
+
path: this.entityService.createPath(module.name, RootConfig.entities.rule.name),
|
|
246
|
+
icon: RootConfig.entities.rule.icon,
|
|
247
|
+
priority: 3,
|
|
248
|
+
policy: aiMenuPolicy,
|
|
249
|
+
},
|
|
231
250
|
]);
|
|
232
251
|
//#endregion
|
|
233
252
|
}
|
|
@@ -644,30 +663,26 @@ const AI_MODEL_SPEECH_CATALOG_DATASOURCE_NAME = 'ai-management-model-catalog-spe
|
|
|
644
663
|
* Registry name: {@link AI_MODEL_TTS_CATALOG_DATASOURCE_NAME}.
|
|
645
664
|
*/
|
|
646
665
|
const AI_MODEL_TTS_CATALOG_DATASOURCE_NAME = 'ai-management-model-catalog-tts';
|
|
647
|
-
/**
|
|
648
|
-
function
|
|
666
|
+
/** Reads `modelPurposes` from a catalog row (API/mock must provide the array). */
|
|
667
|
+
function readModelPurposes(row) {
|
|
649
668
|
if (!row) {
|
|
650
|
-
return [
|
|
669
|
+
return [];
|
|
651
670
|
}
|
|
652
671
|
const multi = row['modelPurposes'];
|
|
653
|
-
if (Array.isArray(multi)
|
|
654
|
-
|
|
655
|
-
.map((x) => String(x).trim())
|
|
656
|
-
.filter((x) => x.length > 0);
|
|
657
|
-
return [...new Set(out)];
|
|
658
|
-
}
|
|
659
|
-
const legacy = row['modelPurpose'];
|
|
660
|
-
if (legacy == null || legacy === '') {
|
|
661
|
-
return ['chat'];
|
|
672
|
+
if (!Array.isArray(multi)) {
|
|
673
|
+
return [];
|
|
662
674
|
}
|
|
663
|
-
|
|
675
|
+
const out = multi
|
|
676
|
+
.map((x) => String(x).trim())
|
|
677
|
+
.filter((x) => x.length > 0);
|
|
678
|
+
return [...new Set(out)];
|
|
664
679
|
}
|
|
665
680
|
function modelRowHasPurpose(row, purpose) {
|
|
666
|
-
return
|
|
681
|
+
return readModelPurposes(row).includes(purpose);
|
|
667
682
|
}
|
|
668
683
|
/** Rows usable in chat / completion pickers: include `chat` or `vision`; not image/speech/tts-only. */
|
|
669
684
|
function isChatPurposeModelRow(row) {
|
|
670
|
-
const p =
|
|
685
|
+
const p = readModelPurposes(row);
|
|
671
686
|
return p.includes('chat') || p.includes('vision');
|
|
672
687
|
}
|
|
673
688
|
function isImagePurposeModelRow(row) {
|
|
@@ -822,6 +837,65 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
822
837
|
type: Injectable
|
|
823
838
|
}] });
|
|
824
839
|
|
|
840
|
+
//#region ---- Imports ----
|
|
841
|
+
//#endregion
|
|
842
|
+
/** Registry name for transcript-segment presets ({@link AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER}). */
|
|
843
|
+
const AI_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_DATASOURCE_NAME = 'ai-management-output-contract-transcript-segment';
|
|
844
|
+
//#endregion
|
|
845
|
+
//#region ---- Provider ----
|
|
846
|
+
class AXMOutputContractTranscriptSegmentDataSourceDefinition {
|
|
847
|
+
constructor() {
|
|
848
|
+
this.segmentService = inject(AXPOutputContractTranscriptSegmentService);
|
|
849
|
+
}
|
|
850
|
+
items() {
|
|
851
|
+
const columns = [
|
|
852
|
+
{ name: 'id', title: 'ID', datatype: 'string', type: AXPWidgetsList.Editors.TextBox },
|
|
853
|
+
{ name: 'title', title: 'Title', datatype: 'string', type: AXPWidgetsList.Editors.TextBox },
|
|
854
|
+
];
|
|
855
|
+
return Promise.resolve([
|
|
856
|
+
{
|
|
857
|
+
name: AI_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_DATASOURCE_NAME,
|
|
858
|
+
title: 'AI output contract (transcript segment)',
|
|
859
|
+
source: () => new AXDataSource({
|
|
860
|
+
key: 'id',
|
|
861
|
+
load: async () => {
|
|
862
|
+
const definitions = await this.segmentService.getAll();
|
|
863
|
+
const items = definitions.map((d) => ({
|
|
864
|
+
id: d.name,
|
|
865
|
+
name: d.name,
|
|
866
|
+
title: d.title,
|
|
867
|
+
...(d.description ? { description: d.description } : {}),
|
|
868
|
+
}));
|
|
869
|
+
return { items, total: items.length };
|
|
870
|
+
},
|
|
871
|
+
byKey: async (key) => {
|
|
872
|
+
const definition = await this.segmentService.get(key);
|
|
873
|
+
if (!definition) {
|
|
874
|
+
return null;
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
id: definition.name,
|
|
878
|
+
name: definition.name,
|
|
879
|
+
title: definition.title,
|
|
880
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
881
|
+
};
|
|
882
|
+
},
|
|
883
|
+
pageSize: 100,
|
|
884
|
+
}),
|
|
885
|
+
columns,
|
|
886
|
+
filters: [],
|
|
887
|
+
textField: { name: 'title', title: 'Title' },
|
|
888
|
+
valueField: { name: 'id', title: 'ID' },
|
|
889
|
+
},
|
|
890
|
+
]);
|
|
891
|
+
}
|
|
892
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMOutputContractTranscriptSegmentDataSourceDefinition, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
893
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMOutputContractTranscriptSegmentDataSourceDefinition }); }
|
|
894
|
+
}
|
|
895
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMOutputContractTranscriptSegmentDataSourceDefinition, decorators: [{
|
|
896
|
+
type: Injectable
|
|
897
|
+
}] });
|
|
898
|
+
|
|
825
899
|
//#region ---- Imports ----
|
|
826
900
|
//#endregion
|
|
827
901
|
/**
|
|
@@ -896,6 +970,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
896
970
|
type: Injectable
|
|
897
971
|
}] });
|
|
898
972
|
|
|
973
|
+
//#region ---- Imports ----
|
|
974
|
+
//#endregion
|
|
899
975
|
//#region ---- Structured output ----
|
|
900
976
|
/** Default max completion tokens when the AiModel row has no `maxOutputTokens` and the caller omits `maxTokens`. */
|
|
901
977
|
const AXPAiAssistDefaultStructuredMaxTokens = 128_000;
|
|
@@ -935,815 +1011,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
935
1011
|
|
|
936
1012
|
//#region ---- Imports ----
|
|
937
1013
|
//#endregion
|
|
938
|
-
//#region ----
|
|
1014
|
+
//#region ---- Supervisor tool naming (display / routing) ----
|
|
939
1015
|
/** Prefix for supervisor-delegated specialist tools (`${AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX}${agentId}`). */
|
|
940
1016
|
const AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX = 'agent:';
|
|
941
|
-
/** Catalog `name` of the conversation follow-up delegated agent (engine auto-invoke). */
|
|
942
|
-
const AXPAI_CONVERSATION_FOLLOW_UP_AGENT_NAME = 'follow-up-prompt-generator';
|
|
943
|
-
/** Default cap for main assist / direct-agent chat tool rounds (one provider call per step). */
|
|
944
|
-
const AXPAI_ENGINE_DEFAULT_MAX_STEPS = 16;
|
|
945
|
-
/** Default cap for delegated specialist sub-runs when the agent row has no positive `maxSteps`. */
|
|
946
|
-
const AXPAI_DELEGATED_AGENT_DEFAULT_MAX_STEPS = 8;
|
|
947
|
-
/** System prompt for vision OCR / document text extraction. */
|
|
948
|
-
const AXPAI_DOCUMENT_VISION_EXTRACTION_SYSTEM = 'You are a precise document-reading assistant. Extract readable plain text from the attached file. Preserve headings and lists where obvious. If the content is unreadable, return a short explanation. Output plain text only, no markdown code fences.';
|
|
949
|
-
/** Default user line when the caller passes an empty instruction for vision extraction. */
|
|
950
|
-
const AXPAI_DOCUMENT_VISION_DEFAULT_USER_INSTRUCTION = 'Extract all readable text from this document. Return plain text only.';
|
|
951
|
-
//#endregion
|
|
952
|
-
//#region ---- Transcript helpers ----
|
|
953
|
-
/**
|
|
954
|
-
* Reads a stored-file artifact from a normalized tool handler body (`{ success, data }`) or a flat `data` object.
|
|
955
|
-
*/
|
|
956
|
-
function axpAiExtractStoredFileArtifactFromUnknown(content) {
|
|
957
|
-
if (content == null || typeof content !== 'object' || Array.isArray(content)) {
|
|
958
|
-
return null;
|
|
959
|
-
}
|
|
960
|
-
const o = content;
|
|
961
|
-
const dataRaw = o['data'];
|
|
962
|
-
if (o['success'] === true && dataRaw != null && typeof dataRaw === 'object' && !Array.isArray(dataRaw)) {
|
|
963
|
-
return axpAiExtractStoredFileArtifactFromDataRecord(dataRaw);
|
|
964
|
-
}
|
|
965
|
-
return axpAiExtractStoredFileArtifactFromDataRecord(o);
|
|
966
|
-
}
|
|
967
|
-
function axpAiExtractStoredFileArtifactFromDataRecord(d) {
|
|
968
|
-
const fileId = typeof d['fileId'] === 'string' ? d['fileId'].trim() : '';
|
|
969
|
-
const mimeType = typeof d['mimeType'] === 'string' ? d['mimeType'].trim() : '';
|
|
970
|
-
if (!fileId || !mimeType) {
|
|
971
|
-
return null;
|
|
972
|
-
}
|
|
973
|
-
const nameRaw = d['name'];
|
|
974
|
-
const name = typeof nameRaw === 'string' && nameRaw.trim() ? nameRaw.trim() : undefined;
|
|
975
|
-
return { fileId, mimeType, ...(name ? { name } : {}) };
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Returns the last stored-file artifact announced in any `tool` → `tool_result` segment (delegated sub-runs, main chat).
|
|
979
|
-
*/
|
|
980
|
-
function axpAiExtractLastStoredFileArtifactFromChatMessages(messages) {
|
|
981
|
-
let last = null;
|
|
982
|
-
for (const m of messages) {
|
|
983
|
-
if (m.role !== 'tool') {
|
|
984
|
-
continue;
|
|
985
|
-
}
|
|
986
|
-
for (const r of m.responses) {
|
|
987
|
-
if (r.type !== 'tool_result') {
|
|
988
|
-
continue;
|
|
989
|
-
}
|
|
990
|
-
const hit = axpAiExtractStoredFileArtifactFromUnknown(r.content);
|
|
991
|
-
if (hit) {
|
|
992
|
-
last = hit;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
return last;
|
|
997
|
-
}
|
|
998
|
-
function axpAiCloneChatMessages(messages) {
|
|
999
|
-
try {
|
|
1000
|
-
return structuredClone(messages);
|
|
1001
|
-
}
|
|
1002
|
-
catch {
|
|
1003
|
-
return JSON.parse(JSON.stringify(messages));
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
/**
|
|
1007
|
-
* Ensures the delegated specialist transcript replays stored files in assistant `responses`
|
|
1008
|
-
* (not only prose that points at tool JSON).
|
|
1009
|
-
*/
|
|
1010
|
-
function axpAiDelegatedSubTranscriptAppendFileArtifactFromToolResults(messages) {
|
|
1011
|
-
const artifact = axpAiExtractLastStoredFileArtifactFromChatMessages(messages);
|
|
1012
|
-
if (!artifact) {
|
|
1013
|
-
return messages;
|
|
1014
|
-
}
|
|
1015
|
-
const out = axpAiCloneChatMessages(messages);
|
|
1016
|
-
let lastAssistant = -1;
|
|
1017
|
-
for (let i = out.length - 1; i >= 0; i--) {
|
|
1018
|
-
if (out[i].role === 'assistant') {
|
|
1019
|
-
lastAssistant = i;
|
|
1020
|
-
break;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
if (lastAssistant < 0) {
|
|
1024
|
-
return out;
|
|
1025
|
-
}
|
|
1026
|
-
const msg = out[lastAssistant];
|
|
1027
|
-
const duplicate = msg.responses.some((r) => r.type === 'file' && r.content.fileId === artifact.fileId && r.content.mimeType === artifact.mimeType);
|
|
1028
|
-
if (duplicate) {
|
|
1029
|
-
return out;
|
|
1030
|
-
}
|
|
1031
|
-
out[lastAssistant] = {
|
|
1032
|
-
...msg,
|
|
1033
|
-
responses: [...msg.responses, { type: 'file', content: artifact }],
|
|
1034
|
-
};
|
|
1035
|
-
return out;
|
|
1036
|
-
}
|
|
1037
|
-
function axpAiIsDelegatedAgentResultSegment(value) {
|
|
1038
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1039
|
-
return false;
|
|
1040
|
-
}
|
|
1041
|
-
const o = value;
|
|
1042
|
-
const t = o['type'];
|
|
1043
|
-
if (t === 'text' && typeof o['content'] === 'string') {
|
|
1044
|
-
return true;
|
|
1045
|
-
}
|
|
1046
|
-
if (t === 'think' && typeof o['content'] === 'string') {
|
|
1047
|
-
return true;
|
|
1048
|
-
}
|
|
1049
|
-
if (t === 'node') {
|
|
1050
|
-
return 'content' in o;
|
|
1051
|
-
}
|
|
1052
|
-
if (t === 'file' && o['content'] != null && typeof o['content'] === 'object' && !Array.isArray(o['content'])) {
|
|
1053
|
-
const c = o['content'];
|
|
1054
|
-
return typeof c['fileId'] === 'string' && typeof c['mimeType'] === 'string';
|
|
1055
|
-
}
|
|
1056
|
-
if (t === 'followUp' && Array.isArray(o['content'])) {
|
|
1057
|
-
return axpAiNormalizeFollowUpContent(o['content']).length > 0;
|
|
1058
|
-
}
|
|
1059
|
-
return false;
|
|
1060
|
-
}
|
|
1061
|
-
/**
|
|
1062
|
-
* Parses a JSON-like array of specialist segments (e.g. from persisted tool output). Invalid entries are skipped.
|
|
1063
|
-
*/
|
|
1064
|
-
function axpAiParseDelegatedAgentResultSegmentsFromUnknown(raw) {
|
|
1065
|
-
if (!Array.isArray(raw)) {
|
|
1066
|
-
return [];
|
|
1067
|
-
}
|
|
1068
|
-
const out = [];
|
|
1069
|
-
for (const item of raw) {
|
|
1070
|
-
if (axpAiIsDelegatedAgentResultSegment(item)) {
|
|
1071
|
-
out.push(item);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
return out;
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Collects ordered {@link AXPAiDelegatedAgentResultSegment} from a delegated sub-run transcript
|
|
1078
|
-
* (excludes {@link AXPAiChatMessage.delegatedReflection} lines).
|
|
1079
|
-
*/
|
|
1080
|
-
function axpAiCollectDelegatedAgentOutcomeResponses(messages) {
|
|
1081
|
-
const out = [];
|
|
1082
|
-
for (const m of messages) {
|
|
1083
|
-
if (m.role !== 'assistant' || axpAiChatMessageIsDelegatedReflectionExcluded(m)) {
|
|
1084
|
-
continue;
|
|
1085
|
-
}
|
|
1086
|
-
for (const r of m.responses) {
|
|
1087
|
-
if (r.type === 'text' || r.type === 'think' || r.type === 'file' || r.type === 'node' || r.type === 'followUp') {
|
|
1088
|
-
out.push(r);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
return out;
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Plain text from {@link AXPAiDelegatedAgentResultSegment} (text + think only; for provider hints / markdown fallbacks).
|
|
1096
|
-
*/
|
|
1097
|
-
function axpAiDelegatedAgentOutcomeResponsesPlainText(segments) {
|
|
1098
|
-
const parts = [];
|
|
1099
|
-
for (const s of segments) {
|
|
1100
|
-
if (s.type === 'text' || s.type === 'think') {
|
|
1101
|
-
const t = s.content.trim();
|
|
1102
|
-
if (t.length > 0) {
|
|
1103
|
-
parts.push(t);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
1107
|
-
return parts.join('\n\n').trim();
|
|
1108
|
-
}
|
|
1109
|
-
function axpAiChatTextMessage(role, text) {
|
|
1110
|
-
return {
|
|
1111
|
-
role,
|
|
1112
|
-
responses: [{ type: 'text', content: text }],
|
|
1113
|
-
};
|
|
1114
|
-
}
|
|
1115
|
-
function axpAiChatUserMessage(responses) {
|
|
1116
|
-
return {
|
|
1117
|
-
role: 'user',
|
|
1118
|
-
responses,
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
function axpAiChatMessageGetText(message) {
|
|
1122
|
-
const parts = [];
|
|
1123
|
-
for (const r of message.responses) {
|
|
1124
|
-
if (r.type === 'text') {
|
|
1125
|
-
parts.push(r.content);
|
|
1126
|
-
}
|
|
1127
|
-
else if (r.type === 'node' || r.type === 'followUp') {
|
|
1128
|
-
try {
|
|
1129
|
-
parts.push(JSON.stringify({ type: r.type, content: r.content }));
|
|
1130
|
-
}
|
|
1131
|
-
catch {
|
|
1132
|
-
parts.push(`[${r.type}]`);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
return parts.join('\n\n');
|
|
1137
|
-
}
|
|
1138
|
-
/**
|
|
1139
|
-
* Planning-only assistant output is always internal reasoning. Provider text may be untagged;
|
|
1140
|
-
* the engine maps `text` responses to `think` so the transcript and UI are correct without
|
|
1141
|
-
* requiring `<think>` / `<think>` in the model output.
|
|
1142
|
-
*/
|
|
1143
|
-
function axpAiCoercePlanningAssistantTextToThink(planningMessages) {
|
|
1144
|
-
return planningMessages.map((m) => {
|
|
1145
|
-
if (m.role !== 'assistant') {
|
|
1146
|
-
return m;
|
|
1147
|
-
}
|
|
1148
|
-
return {
|
|
1149
|
-
...m,
|
|
1150
|
-
responses: m.responses.map((r) => {
|
|
1151
|
-
if (r.type === 'text') {
|
|
1152
|
-
return { type: 'think', content: r.content };
|
|
1153
|
-
}
|
|
1154
|
-
return r;
|
|
1155
|
-
}),
|
|
1156
|
-
};
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
//#region ---- Assistant plain text: embedded node JSON envelopes ----
|
|
1160
|
-
function isAssistantNodeTypeEnvelope(o) {
|
|
1161
|
-
if (!o || typeof o !== 'object' || Array.isArray(o)) {
|
|
1162
|
-
return false;
|
|
1163
|
-
}
|
|
1164
|
-
const r = o;
|
|
1165
|
-
return r['type'] === 'node' && 'content' in r;
|
|
1166
|
-
}
|
|
1167
|
-
function isAssistantFollowUpTypeEnvelope(o) {
|
|
1168
|
-
return axpAiIsDelegatedAgentResultSegment(o) && o.type === 'followUp';
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Parses one balanced `{ ... }` JSON object starting at `start` (must point at `{`).
|
|
1172
|
-
*/
|
|
1173
|
-
function tryParseBalancedJsonObjectAt$1(s, start) {
|
|
1174
|
-
if (s[start] !== '{') {
|
|
1175
|
-
return null;
|
|
1176
|
-
}
|
|
1177
|
-
let depth = 0;
|
|
1178
|
-
let inString = false;
|
|
1179
|
-
let escape = false;
|
|
1180
|
-
for (let i = start; i < s.length; i++) {
|
|
1181
|
-
const c = s[i];
|
|
1182
|
-
if (inString) {
|
|
1183
|
-
if (escape) {
|
|
1184
|
-
escape = false;
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
if (c === '\\') {
|
|
1188
|
-
escape = true;
|
|
1189
|
-
continue;
|
|
1190
|
-
}
|
|
1191
|
-
if (c === '"') {
|
|
1192
|
-
inString = false;
|
|
1193
|
-
}
|
|
1194
|
-
continue;
|
|
1195
|
-
}
|
|
1196
|
-
if (c === '"') {
|
|
1197
|
-
inString = true;
|
|
1198
|
-
continue;
|
|
1199
|
-
}
|
|
1200
|
-
if (c === '{') {
|
|
1201
|
-
depth++;
|
|
1202
|
-
}
|
|
1203
|
-
else if (c === '}') {
|
|
1204
|
-
depth--;
|
|
1205
|
-
if (depth === 0) {
|
|
1206
|
-
const slice = s.slice(start, i + 1);
|
|
1207
|
-
try {
|
|
1208
|
-
return { value: JSON.parse(slice), end: i + 1 };
|
|
1209
|
-
}
|
|
1210
|
-
catch {
|
|
1211
|
-
return null;
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
return null;
|
|
1217
|
-
}
|
|
1218
|
-
function mergeAdjacentAssistantTextResponses(seq) {
|
|
1219
|
-
const out = [];
|
|
1220
|
-
for (const r of seq) {
|
|
1221
|
-
if (r.type !== 'text') {
|
|
1222
|
-
out.push(r);
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
const prev = out[out.length - 1];
|
|
1226
|
-
const v = r.content.trim();
|
|
1227
|
-
if (!v.length) {
|
|
1228
|
-
continue;
|
|
1229
|
-
}
|
|
1230
|
-
if (prev && prev.type === 'text') {
|
|
1231
|
-
const joined = `${prev.content}\n\n${v}`.trim();
|
|
1232
|
-
out[out.length - 1] = { type: 'text', content: joined };
|
|
1233
|
-
}
|
|
1234
|
-
else {
|
|
1235
|
-
out.push({ type: 'text', content: v });
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
return out;
|
|
1239
|
-
}
|
|
1240
|
-
/**
|
|
1241
|
-
* Splits raw assistant prose into ordered `text` / `node` responses by detecting JSON objects
|
|
1242
|
-
* shaped like `{ "type": "node", "content": <AXPWidgetNode> }` anywhere in the string.
|
|
1243
|
-
*/
|
|
1244
|
-
/** Removes markdown ```json fences so specialist-only JSON replies still parse as envelopes. */
|
|
1245
|
-
function axpAiStripMarkdownJsonFences(raw) {
|
|
1246
|
-
return raw.replace(/```(?:json)?\s*([\s\S]*?)```/gi, (_match, inner) => inner.trim());
|
|
1247
|
-
}
|
|
1248
|
-
function axpAiExpandPlainAssistantTextToResponses(raw) {
|
|
1249
|
-
const normalized = axpAiStripMarkdownJsonFences(raw);
|
|
1250
|
-
const parts = [];
|
|
1251
|
-
let i = 0;
|
|
1252
|
-
const pushText = (value) => {
|
|
1253
|
-
const v = value.trim();
|
|
1254
|
-
if (v.length > 0) {
|
|
1255
|
-
parts.push({ type: 'text', content: v });
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
while (i < normalized.length) {
|
|
1259
|
-
const braceIdx = normalized.indexOf('{', i);
|
|
1260
|
-
if (braceIdx < 0) {
|
|
1261
|
-
pushText(normalized.slice(i));
|
|
1262
|
-
break;
|
|
1263
|
-
}
|
|
1264
|
-
pushText(normalized.slice(i, braceIdx));
|
|
1265
|
-
const parsed = tryParseBalancedJsonObjectAt$1(normalized, braceIdx);
|
|
1266
|
-
if (parsed && isAssistantFollowUpTypeEnvelope(parsed.value)) {
|
|
1267
|
-
const normalized = axpAiNormalizeFollowUpContent(parsed.value.content);
|
|
1268
|
-
if (normalized.length > 0) {
|
|
1269
|
-
parts.push({ type: 'followUp', content: normalized });
|
|
1270
|
-
}
|
|
1271
|
-
i = parsed.end;
|
|
1272
|
-
}
|
|
1273
|
-
else if (parsed && isAssistantNodeTypeEnvelope(parsed.value)) {
|
|
1274
|
-
parts.push({ type: 'node', content: parsed.value.content });
|
|
1275
|
-
i = parsed.end;
|
|
1276
|
-
}
|
|
1277
|
-
else if (parsed) {
|
|
1278
|
-
pushText(normalized.slice(braceIdx, parsed.end));
|
|
1279
|
-
i = parsed.end;
|
|
1280
|
-
}
|
|
1281
|
-
else {
|
|
1282
|
-
pushText(normalized.slice(braceIdx, braceIdx + 1));
|
|
1283
|
-
i = braceIdx + 1;
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
return mergeAdjacentAssistantTextResponses(parts);
|
|
1287
|
-
}
|
|
1288
|
-
//#endregion
|
|
1289
|
-
function axpAiChatMessagesFromProviderAssistant(text, toolCalls) {
|
|
1290
|
-
const normalizeThinkingTags = (raw) => raw
|
|
1291
|
-
.replace(/<(\/?redacted_thinking)>/gi, '<$1>')
|
|
1292
|
-
.replace(/<(\/?think)>/gi, '<$1>');
|
|
1293
|
-
const splitAssistantResponses = (raw) => {
|
|
1294
|
-
const normalized = normalizeThinkingTags(raw ?? '');
|
|
1295
|
-
if (!normalized) {
|
|
1296
|
-
return [];
|
|
1297
|
-
}
|
|
1298
|
-
const out = [];
|
|
1299
|
-
const appendPlainSlice = (value) => {
|
|
1300
|
-
out.push(...axpAiExpandPlainAssistantTextToResponses(value));
|
|
1301
|
-
};
|
|
1302
|
-
const pushThink = (value) => {
|
|
1303
|
-
const v = value.trim();
|
|
1304
|
-
if (v.length > 0) {
|
|
1305
|
-
out.push({ type: 'think', content: v });
|
|
1306
|
-
}
|
|
1307
|
-
};
|
|
1308
|
-
const thinkRe = /<(redacted_thinking|think)>([\s\S]*?)<\/\1>/gi;
|
|
1309
|
-
let last = 0;
|
|
1310
|
-
let m;
|
|
1311
|
-
while ((m = thinkRe.exec(normalized)) !== null) {
|
|
1312
|
-
appendPlainSlice(normalized.slice(last, m.index));
|
|
1313
|
-
pushThink(m[2] ?? '');
|
|
1314
|
-
last = m.index + m[0].length;
|
|
1315
|
-
}
|
|
1316
|
-
appendPlainSlice(normalized.slice(last));
|
|
1317
|
-
return mergeAdjacentAssistantTextResponses(out);
|
|
1318
|
-
};
|
|
1319
|
-
const parsedResponses = splitAssistantResponses(text);
|
|
1320
|
-
const calls = toolCalls ?? [];
|
|
1321
|
-
const out = [];
|
|
1322
|
-
if (parsedResponses.length > 0) {
|
|
1323
|
-
out.push({
|
|
1324
|
-
role: 'assistant',
|
|
1325
|
-
responses: parsedResponses,
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
for (const tc of calls) {
|
|
1329
|
-
const isDelegatedAgent = axpAiParseSupervisorAgentToolName(tc.name) != null;
|
|
1330
|
-
out.push({
|
|
1331
|
-
role: 'assistant',
|
|
1332
|
-
responses: [
|
|
1333
|
-
isDelegatedAgent
|
|
1334
|
-
? {
|
|
1335
|
-
type: 'agent',
|
|
1336
|
-
content: {
|
|
1337
|
-
command: tc.name,
|
|
1338
|
-
arguments: tc.arguments,
|
|
1339
|
-
},
|
|
1340
|
-
callId: tc.id,
|
|
1341
|
-
}
|
|
1342
|
-
: {
|
|
1343
|
-
type: 'tool',
|
|
1344
|
-
content: {
|
|
1345
|
-
command: tc.name,
|
|
1346
|
-
arguments: tc.arguments,
|
|
1347
|
-
},
|
|
1348
|
-
callId: tc.id,
|
|
1349
|
-
},
|
|
1350
|
-
],
|
|
1351
|
-
});
|
|
1352
|
-
}
|
|
1353
|
-
if (out.length === 0) {
|
|
1354
|
-
out.push(axpAiChatTextMessage('assistant', ''));
|
|
1355
|
-
}
|
|
1356
|
-
return out;
|
|
1357
|
-
}
|
|
1358
|
-
function axpAiToolCallFromAssistantToolMessage(message) {
|
|
1359
|
-
if (message.role !== 'assistant') {
|
|
1360
|
-
return null;
|
|
1361
|
-
}
|
|
1362
|
-
for (const r of message.responses) {
|
|
1363
|
-
if (r.type === 'tool' || r.type === 'agent') {
|
|
1364
|
-
const callId = r.callId?.trim() ?? '';
|
|
1365
|
-
const command = r.content.command?.trim() ?? '';
|
|
1366
|
-
if (!command) {
|
|
1367
|
-
return null;
|
|
1368
|
-
}
|
|
1369
|
-
return {
|
|
1370
|
-
id: callId || `call_${command}`,
|
|
1371
|
-
name: command,
|
|
1372
|
-
arguments: r.content.arguments,
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
return null;
|
|
1377
|
-
}
|
|
1378
|
-
function axpAiChatToolResultMessage(callId, content, toolName) {
|
|
1379
|
-
const id = callId.trim();
|
|
1380
|
-
return {
|
|
1381
|
-
role: 'tool',
|
|
1382
|
-
...(toolName?.trim() ? { name: toolName.trim() } : {}),
|
|
1383
|
-
responses: [
|
|
1384
|
-
{
|
|
1385
|
-
type: 'tool_result',
|
|
1386
|
-
callId: id,
|
|
1387
|
-
content,
|
|
1388
|
-
},
|
|
1389
|
-
],
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
function axpAiChatAgentResultMessage(callId, content, toolName) {
|
|
1393
|
-
const id = callId.trim();
|
|
1394
|
-
return {
|
|
1395
|
-
role: 'tool',
|
|
1396
|
-
...(toolName?.trim() ? { name: toolName.trim() } : {}),
|
|
1397
|
-
responses: [
|
|
1398
|
-
{
|
|
1399
|
-
type: 'agent_result',
|
|
1400
|
-
callId: id,
|
|
1401
|
-
content,
|
|
1402
|
-
},
|
|
1403
|
-
],
|
|
1404
|
-
};
|
|
1405
|
-
}
|
|
1406
|
-
function axpAiNormalizeHandlerResultToToolBody(handlerResult) {
|
|
1407
|
-
if (handlerResult === undefined || handlerResult === null) {
|
|
1408
|
-
return { success: false, error: 'Tool returned no result.' };
|
|
1409
|
-
}
|
|
1410
|
-
if (typeof handlerResult === 'string') {
|
|
1411
|
-
const t = handlerResult.trim();
|
|
1412
|
-
if (!t) {
|
|
1413
|
-
return { success: false, error: 'Tool returned an empty string.' };
|
|
1414
|
-
}
|
|
1415
|
-
try {
|
|
1416
|
-
return JSON.parse(t);
|
|
1417
|
-
}
|
|
1418
|
-
catch {
|
|
1419
|
-
return { success: true, data: handlerResult };
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
if (typeof handlerResult === 'object' && !Array.isArray(handlerResult)) {
|
|
1423
|
-
return handlerResult;
|
|
1424
|
-
}
|
|
1425
|
-
return { success: true, data: handlerResult };
|
|
1426
|
-
}
|
|
1427
|
-
function axpAiChatToolOrAgentResultBodyJson(message) {
|
|
1428
|
-
const tr = message.responses.find((r) => r.type === 'tool_result' || r.type === 'agent_result');
|
|
1429
|
-
if (!tr || (tr.type !== 'tool_result' && tr.type !== 'agent_result')) {
|
|
1430
|
-
return '';
|
|
1431
|
-
}
|
|
1432
|
-
try {
|
|
1433
|
-
return JSON.stringify(tr.content);
|
|
1434
|
-
}
|
|
1435
|
-
catch {
|
|
1436
|
-
return String(tr.content);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Compact JSON string of a tool-role message’s `tool_result` body (transcript tail / logs).
|
|
1441
|
-
*/
|
|
1442
|
-
function axpAiChatToolResultBodyJson(message) {
|
|
1443
|
-
return axpAiChatToolOrAgentResultBodyJson(message);
|
|
1444
|
-
}
|
|
1445
|
-
//#endregion
|
|
1446
|
-
//#region ---- Supervisor delegated-agent helpers ----
|
|
1447
|
-
function axpAiSupervisorAgentToolName(agentCatalogId) {
|
|
1448
|
-
return `${AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX}${agentCatalogId.trim()}`;
|
|
1449
|
-
}
|
|
1450
1017
|
function axpAiParseSupervisorAgentToolName(toolName) {
|
|
1451
1018
|
const n = toolName.trim();
|
|
1452
1019
|
if (!n.startsWith(AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX)) {
|
|
1453
|
-
return null;
|
|
1454
|
-
}
|
|
1455
|
-
const id = n.slice(AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX.length).trim();
|
|
1456
|
-
return id.length > 0 ? id : null;
|
|
1457
|
-
}
|
|
1458
|
-
//#endregion
|
|
1459
|
-
//#region ---- Assistant message transforms (delegated sub-run) ----
|
|
1460
|
-
function axpAiMergePlainAssistantTextTurn(messages) {
|
|
1461
|
-
const chunks = [];
|
|
1462
|
-
for (const m of messages) {
|
|
1463
|
-
if (m.role !== 'assistant') {
|
|
1464
|
-
continue;
|
|
1465
|
-
}
|
|
1466
|
-
const textParts = m.responses
|
|
1467
|
-
.filter((r) => r.type === 'text')
|
|
1468
|
-
.map((r) => r.content.trim())
|
|
1469
|
-
.filter((s) => s.length > 0);
|
|
1470
|
-
const thinkParts = m.responses
|
|
1471
|
-
.filter((r) => r.type === 'think')
|
|
1472
|
-
.map((r) => r.content.trim())
|
|
1473
|
-
.filter((s) => s.length > 0);
|
|
1474
|
-
const serialized = axpAiChatMessageGetText(m).trim();
|
|
1475
|
-
const t = textParts.join('\n\n').trim() ||
|
|
1476
|
-
thinkParts.join('\n\n').trim() ||
|
|
1477
|
-
serialized;
|
|
1478
|
-
if (t.length > 0) {
|
|
1479
|
-
chunks.push(t);
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
const joined = chunks.join('\n\n').trim();
|
|
1483
|
-
if (!joined) {
|
|
1484
|
-
return [];
|
|
1485
|
-
}
|
|
1486
|
-
return axpAiChatMessagesFromProviderAssistant(joined, undefined);
|
|
1487
|
-
}
|
|
1488
|
-
//#region ---- Follow-up normalization ----
|
|
1489
|
-
/** Normalizes one follow-up chip command object from model JSON. */
|
|
1490
|
-
function axpAiNormalizeFollowUpCommand(raw) {
|
|
1491
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1492
|
-
return null;
|
|
1493
|
-
}
|
|
1494
|
-
const o = raw;
|
|
1495
|
-
const name = typeof o['name'] === 'string' ? o['name'].trim() : '';
|
|
1496
|
-
if (!name) {
|
|
1497
|
-
return null;
|
|
1498
|
-
}
|
|
1499
|
-
if (!('options' in o)) {
|
|
1500
|
-
return { name };
|
|
1501
|
-
}
|
|
1502
|
-
return { name, options: o['options'] };
|
|
1503
|
-
}
|
|
1504
|
-
/** Normalizes one follow-up chip row (`text` + `command`). */
|
|
1505
|
-
function axpAiNormalizeFollowUpItem(raw) {
|
|
1506
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
1507
|
-
return null;
|
|
1508
|
-
}
|
|
1509
|
-
const o = raw;
|
|
1510
|
-
const text = typeof o['text'] === 'string' ? o['text'].trim() : '';
|
|
1511
|
-
const command = axpAiNormalizeFollowUpCommand(o['command']);
|
|
1512
|
-
if (!text || !command) {
|
|
1513
|
-
return null;
|
|
1514
|
-
}
|
|
1515
|
-
return { text, command };
|
|
1516
|
-
}
|
|
1517
|
-
/** Normalizes follow-up `content` arrays; invalid rows are skipped. */
|
|
1518
|
-
function axpAiNormalizeFollowUpContent(raw) {
|
|
1519
|
-
if (!Array.isArray(raw)) {
|
|
1520
|
-
return [];
|
|
1521
|
-
}
|
|
1522
|
-
const out = [];
|
|
1523
|
-
for (const item of raw) {
|
|
1524
|
-
const row = axpAiNormalizeFollowUpItem(item);
|
|
1525
|
-
if (row) {
|
|
1526
|
-
out.push(row);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
return out;
|
|
1530
|
-
}
|
|
1531
|
-
//#endregion
|
|
1532
|
-
//#region ---- Follow-up auto-invoke (supervisor turn) ----
|
|
1533
|
-
function axpAiLastUserTranscriptTextLocal(messages) {
|
|
1534
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1535
|
-
if (messages[i].role === 'user') {
|
|
1536
|
-
return axpAiChatMessageGetText(messages[i]).trim();
|
|
1537
|
-
}
|
|
1538
|
-
}
|
|
1539
|
-
return '';
|
|
1540
|
-
}
|
|
1541
|
-
function axpAiIndexOfLastUserMessage(messages) {
|
|
1542
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1543
|
-
if (messages[i].role === 'user') {
|
|
1544
|
-
return i;
|
|
1545
|
-
}
|
|
1020
|
+
return null;
|
|
1546
1021
|
}
|
|
1547
|
-
|
|
1022
|
+
const id = n.slice(AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX.length).trim();
|
|
1023
|
+
return id.length > 0 ? id : null;
|
|
1548
1024
|
}
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region ---- Message builders (outbound / debug) ----
|
|
1027
|
+
function axpAiChatTextMessage(role, text) {
|
|
1028
|
+
return {
|
|
1029
|
+
role,
|
|
1030
|
+
responses: [{ type: 'text', content: text }],
|
|
1031
|
+
};
|
|
1552
1032
|
}
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1033
|
+
function axpAiChatUserMessage(responses) {
|
|
1034
|
+
return {
|
|
1035
|
+
role: 'user',
|
|
1036
|
+
responses,
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
//#endregion
|
|
1040
|
+
//#region ---- Transcript read helpers (display only) ----
|
|
1041
|
+
function axpAiChatMessageGetText(message) {
|
|
1042
|
+
const parts = [];
|
|
1043
|
+
for (const r of message.responses) {
|
|
1044
|
+
if (r.type === 'text') {
|
|
1045
|
+
parts.push(r.content);
|
|
1564
1046
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
}
|
|
1569
|
-
const payload = r.content;
|
|
1570
|
-
if (payload.success !== true || payload.data?.responses == null) {
|
|
1571
|
-
continue;
|
|
1572
|
-
}
|
|
1573
|
-
const agentId = payload.data.agentId?.trim() ?? '';
|
|
1574
|
-
const agentName = payload.data.agentName?.trim() ?? '';
|
|
1575
|
-
const isFollowUpAgent = agentId === followUpAgentId.trim() || agentName === AXPAI_CONVERSATION_FOLLOW_UP_AGENT_NAME;
|
|
1576
|
-
if (!isFollowUpAgent) {
|
|
1577
|
-
continue;
|
|
1047
|
+
else if (r.type === 'node' || r.type === 'followUp') {
|
|
1048
|
+
try {
|
|
1049
|
+
parts.push(JSON.stringify({ type: r.type, content: r.content }));
|
|
1578
1050
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1051
|
+
catch {
|
|
1052
|
+
parts.push(`[${r.type}]`);
|
|
1581
1053
|
}
|
|
1582
1054
|
}
|
|
1583
1055
|
}
|
|
1584
|
-
return
|
|
1056
|
+
return parts.join('\n\n');
|
|
1585
1057
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
* Engine may auto-call the follow-up specialist after the supervisor finishes, unless the turn is trivial.
|
|
1589
|
-
*/
|
|
1590
|
-
function axpAiShouldAutoInvokeFollowUpAgent(messages) {
|
|
1591
|
-
const userText = axpAiLastUserTranscriptTextLocal(messages).trim();
|
|
1592
|
-
if (userText.length === 0) {
|
|
1593
|
-
return false;
|
|
1594
|
-
}
|
|
1595
|
-
if (AXPAI_GREETING_ONLY_USER_RE.test(userText)) {
|
|
1596
|
-
return false;
|
|
1597
|
-
}
|
|
1598
|
-
const tail = axpAiTranscriptTailSinceLastUser(messages);
|
|
1599
|
-
for (const m of tail) {
|
|
1600
|
-
if (m.role !== 'assistant' || axpAiChatMessageIsDelegatedReflectionExcluded(m)) {
|
|
1601
|
-
continue;
|
|
1602
|
-
}
|
|
1603
|
-
const plain = m.responses
|
|
1604
|
-
.filter((r) => r.type === 'text')
|
|
1605
|
-
.map((r) => r.content.trim())
|
|
1606
|
-
.join('\n')
|
|
1607
|
-
.trim();
|
|
1608
|
-
const serialized = axpAiChatMessageGetText(m).trim();
|
|
1609
|
-
if (plain.length > 0 || serialized.length > 0) {
|
|
1610
|
-
return true;
|
|
1611
|
-
}
|
|
1612
|
-
if (m.responses.some((r) => r.type === 'node' || r.type === 'file')) {
|
|
1613
|
-
return true;
|
|
1614
|
-
}
|
|
1615
|
-
}
|
|
1616
|
-
return false;
|
|
1058
|
+
function axpAiChatMessageIsDelegatedReflectionExcluded(message) {
|
|
1059
|
+
return message.delegatedReflection === true;
|
|
1617
1060
|
}
|
|
1618
|
-
/**
|
|
1619
|
-
function
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
const answerParts = [];
|
|
1623
|
-
for (const m of tail) {
|
|
1624
|
-
if (m.role !== 'assistant' || axpAiChatMessageIsDelegatedReflectionExcluded(m)) {
|
|
1625
|
-
continue;
|
|
1626
|
-
}
|
|
1627
|
-
const t = axpAiChatMessageGetText(m).trim();
|
|
1628
|
-
if (t.length > 0) {
|
|
1629
|
-
answerParts.push(t);
|
|
1630
|
-
}
|
|
1061
|
+
/** Reads typed specialist segments from a normalized {@code agent_result} payload. */
|
|
1062
|
+
function axpAiDelegatedAgentResultSegments(payload) {
|
|
1063
|
+
if (payload.success !== true || !Array.isArray(payload.data?.responses)) {
|
|
1064
|
+
return [];
|
|
1631
1065
|
}
|
|
1632
|
-
|
|
1633
|
-
return [
|
|
1634
|
-
'Propose 2–5 short next-step chips for the user after this exchange.',
|
|
1635
|
-
'',
|
|
1636
|
-
`User message: ${userText}`,
|
|
1637
|
-
'',
|
|
1638
|
-
answer.length > 0 ? `Assistant answer so far:\n${answer}` : 'Assistant answer so far: (none yet)',
|
|
1639
|
-
].join('\n');
|
|
1066
|
+
return payload.data.responses;
|
|
1640
1067
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
const m = messages[i];
|
|
1650
|
-
if (m.role !== 'assistant' || axpAiChatMessageIsDelegatedReflectionExcluded(m)) {
|
|
1651
|
-
continue;
|
|
1652
|
-
}
|
|
1653
|
-
if (m.responses.some((r) => r.type === 'text' || r.type === 'node' || r.type === 'file' || r.type === 'followUp')) {
|
|
1654
|
-
lastAssistantIdx = i;
|
|
1655
|
-
break;
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
if (lastAssistantIdx < 0) {
|
|
1659
|
-
return messages;
|
|
1660
|
-
}
|
|
1661
|
-
const delegatedStructured = [];
|
|
1662
|
-
for (const m of messages) {
|
|
1663
|
-
if (m.role !== 'tool') {
|
|
1664
|
-
continue;
|
|
1665
|
-
}
|
|
1666
|
-
for (const r of m.responses) {
|
|
1667
|
-
if (r.type !== 'agent_result') {
|
|
1668
|
-
continue;
|
|
1669
|
-
}
|
|
1670
|
-
const payload = r.content;
|
|
1671
|
-
if (payload.success !== true || payload.data?.responses == null) {
|
|
1672
|
-
continue;
|
|
1673
|
-
}
|
|
1674
|
-
for (const seg of payload.data.responses) {
|
|
1675
|
-
if (seg.type === 'followUp' || seg.type === 'node' || seg.type === 'file') {
|
|
1676
|
-
delegatedStructured.push(seg);
|
|
1677
|
-
}
|
|
1068
|
+
/** Plain text from specialist segments (text + think) for debug / preview UI. */
|
|
1069
|
+
function axpAiDelegatedAgentOutcomeResponsesPlainText(segments) {
|
|
1070
|
+
const parts = [];
|
|
1071
|
+
for (const s of segments) {
|
|
1072
|
+
if (s.type === 'text' || s.type === 'think') {
|
|
1073
|
+
const t = s.content.trim();
|
|
1074
|
+
if (t.length > 0) {
|
|
1075
|
+
parts.push(t);
|
|
1678
1076
|
}
|
|
1679
1077
|
}
|
|
1680
1078
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
const toAppend = [];
|
|
1691
|
-
for (const seg of delegatedStructured) {
|
|
1692
|
-
if (seg.type === 'followUp' && !followUpPresent) {
|
|
1693
|
-
toAppend.push(seg);
|
|
1694
|
-
followUpPresent = true;
|
|
1695
|
-
}
|
|
1696
|
-
else if (seg.type === 'node' && !nodePresent) {
|
|
1697
|
-
toAppend.push(seg);
|
|
1698
|
-
nodePresent = true;
|
|
1699
|
-
}
|
|
1700
|
-
else if (seg.type === 'file' && !fileIds.has(seg.content.fileId)) {
|
|
1701
|
-
toAppend.push(seg);
|
|
1702
|
-
fileIds.add(seg.content.fileId);
|
|
1703
|
-
}
|
|
1079
|
+
return parts.join('\n\n').trim();
|
|
1080
|
+
}
|
|
1081
|
+
function axpAiChatToolResultBodyJson(message) {
|
|
1082
|
+
return axpAiChatToolOrAgentResultBodyJson(message);
|
|
1083
|
+
}
|
|
1084
|
+
function axpAiChatToolOrAgentResultBodyJson(message) {
|
|
1085
|
+
const tr = message.responses.find((r) => r.type === 'tool_result' || r.type === 'agent_result');
|
|
1086
|
+
if (!tr || (tr.type !== 'tool_result' && tr.type !== 'agent_result')) {
|
|
1087
|
+
return '';
|
|
1704
1088
|
}
|
|
1705
|
-
|
|
1706
|
-
return
|
|
1089
|
+
try {
|
|
1090
|
+
return JSON.stringify(tr.content);
|
|
1707
1091
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
...last,
|
|
1711
|
-
responses: [...last.responses, ...toAppend],
|
|
1712
|
-
};
|
|
1713
|
-
return out;
|
|
1714
|
-
}
|
|
1715
|
-
function axpAiPruneAssistantPiecesToFirstToolOnly(messages) {
|
|
1716
|
-
const out = [];
|
|
1717
|
-
let keptTool = false;
|
|
1718
|
-
for (const m of messages) {
|
|
1719
|
-
if (m.role !== 'assistant') {
|
|
1720
|
-
out.push(m);
|
|
1721
|
-
continue;
|
|
1722
|
-
}
|
|
1723
|
-
const idx = m.responses.findIndex((r) => r.type === 'tool' || r.type === 'agent');
|
|
1724
|
-
if (idx < 0) {
|
|
1725
|
-
out.push(m);
|
|
1726
|
-
continue;
|
|
1727
|
-
}
|
|
1728
|
-
if (!keptTool) {
|
|
1729
|
-
out.push({
|
|
1730
|
-
...m,
|
|
1731
|
-
responses: m.responses.slice(0, idx + 1),
|
|
1732
|
-
});
|
|
1733
|
-
keptTool = true;
|
|
1734
|
-
}
|
|
1092
|
+
catch {
|
|
1093
|
+
return String(tr.content);
|
|
1735
1094
|
}
|
|
1736
|
-
return out;
|
|
1737
|
-
}
|
|
1738
|
-
function axpAiChatDelegatedReflectionAssistantMessage(reflectionText) {
|
|
1739
|
-
return {
|
|
1740
|
-
role: 'assistant',
|
|
1741
|
-
delegatedReflection: true,
|
|
1742
|
-
responses: [{ type: 'text', content: reflectionText }],
|
|
1743
|
-
};
|
|
1744
|
-
}
|
|
1745
|
-
function axpAiChatMessageIsDelegatedReflectionExcluded(message) {
|
|
1746
|
-
return message.delegatedReflection === true;
|
|
1747
1095
|
}
|
|
1748
1096
|
//#endregion
|
|
1749
1097
|
|
|
@@ -2737,6 +2085,211 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
2737
2085
|
args: [{ providedIn: 'root' }]
|
|
2738
2086
|
}] });
|
|
2739
2087
|
|
|
2088
|
+
//#region ---- Imports ----
|
|
2089
|
+
//#endregion
|
|
2090
|
+
const STRUCTURED_OUTPUT_KINDS$1 = new Set(['object', 'array']);
|
|
2091
|
+
function usesStructuredFinalize(kind) {
|
|
2092
|
+
return STRUCTURED_OUTPUT_KINDS$1.has(kind);
|
|
2093
|
+
}
|
|
2094
|
+
//#endregion
|
|
2095
|
+
//#region ---- Retry defaults ----
|
|
2096
|
+
/** Default structured retry count for a contract kind when `structuredRetries` is omitted. */
|
|
2097
|
+
function defaultRetriesForKind(kind) {
|
|
2098
|
+
if (kind === 'string') {
|
|
2099
|
+
return 0;
|
|
2100
|
+
}
|
|
2101
|
+
return AXPAI_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES;
|
|
2102
|
+
}
|
|
2103
|
+
//#endregion
|
|
2104
|
+
//#region ---- Derive provider format ----
|
|
2105
|
+
function isJsonSchemaLike(value) {
|
|
2106
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
2107
|
+
}
|
|
2108
|
+
function parseResponseFormat(raw) {
|
|
2109
|
+
if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
2110
|
+
return undefined;
|
|
2111
|
+
}
|
|
2112
|
+
const o = raw;
|
|
2113
|
+
const t = o['type'];
|
|
2114
|
+
if (t === 'text') {
|
|
2115
|
+
return { type: 'text' };
|
|
2116
|
+
}
|
|
2117
|
+
if (t === 'json_object') {
|
|
2118
|
+
return { type: 'json_object' };
|
|
2119
|
+
}
|
|
2120
|
+
if (t === 'json_schema' && isJsonSchemaLike(o['schema'])) {
|
|
2121
|
+
const name = typeof o['name'] === 'string' ? o['name'].trim() : undefined;
|
|
2122
|
+
const strict = o['strict'] === true;
|
|
2123
|
+
return {
|
|
2124
|
+
type: 'json_schema',
|
|
2125
|
+
schema: o['schema'],
|
|
2126
|
+
...(name && name.length > 0 ? { name } : {}),
|
|
2127
|
+
...(strict ? { strict: true } : {}),
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
return undefined;
|
|
2131
|
+
}
|
|
2132
|
+
/**
|
|
2133
|
+
* Maps an effective output contract to a provider request hint.
|
|
2134
|
+
* Explicit {@link AXPAiAgentOutputContract.providerResponseFormat} wins; otherwise derived from `kind` + `schema`.
|
|
2135
|
+
*/
|
|
2136
|
+
function deriveProviderResponseFormat(contract) {
|
|
2137
|
+
if (contract.providerResponseFormat) {
|
|
2138
|
+
return contract.providerResponseFormat;
|
|
2139
|
+
}
|
|
2140
|
+
if (!usesStructuredFinalize(contract.kind)) {
|
|
2141
|
+
return undefined;
|
|
2142
|
+
}
|
|
2143
|
+
if (contract.schema && isJsonSchemaLike(contract.schema)) {
|
|
2144
|
+
const segment = resolveOutputTranscriptSegment(contract);
|
|
2145
|
+
const name = contract.schemaName?.trim() ||
|
|
2146
|
+
(segment === 'node' ? 'widget_layout' : segment === 'followUp' ? 'follow_up' : 'structured_output');
|
|
2147
|
+
return {
|
|
2148
|
+
type: 'json_schema',
|
|
2149
|
+
schema: contract.schema,
|
|
2150
|
+
name,
|
|
2151
|
+
strict: contract.schemaStrict === true,
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
return { type: 'json_object' };
|
|
2155
|
+
}
|
|
2156
|
+
function parseJsonSchemaLike(raw) {
|
|
2157
|
+
if (!isJsonSchemaLike(raw)) {
|
|
2158
|
+
return undefined;
|
|
2159
|
+
}
|
|
2160
|
+
return raw;
|
|
2161
|
+
}
|
|
2162
|
+
//#endregion
|
|
2163
|
+
|
|
2164
|
+
//#region ---- Imports ----
|
|
2165
|
+
/** @see {@link AXP_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES} */
|
|
2166
|
+
const AXPAI_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES = AXP_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES;
|
|
2167
|
+
//#endregion
|
|
2168
|
+
//#region ---- Resolve helpers ----
|
|
2169
|
+
const STRUCTURED_OUTPUT_KINDS = new Set(['object', 'array']);
|
|
2170
|
+
function isOutputKind(value) {
|
|
2171
|
+
return value === 'string' || value === 'object' || value === 'array';
|
|
2172
|
+
}
|
|
2173
|
+
function isTranscriptSegment(value) {
|
|
2174
|
+
return value === 'text' || value === 'node' || value === 'followUp';
|
|
2175
|
+
}
|
|
2176
|
+
/** Resolved transcript mapping for an object contract (defaults to `text`). */
|
|
2177
|
+
function resolveOutputTranscriptSegment(contract) {
|
|
2178
|
+
if (contract.kind !== 'object') {
|
|
2179
|
+
return 'text';
|
|
2180
|
+
}
|
|
2181
|
+
return contract.transcriptSegment ?? 'text';
|
|
2182
|
+
}
|
|
2183
|
+
//#endregion
|
|
2184
|
+
//#region ---- Parser ----
|
|
2185
|
+
/**
|
|
2186
|
+
* Normalizes persisted `outputContract` (object or JSON string from entity forms).
|
|
2187
|
+
* Returns `null` when absent or invalid (engine treats as implicit `string`).
|
|
2188
|
+
*/
|
|
2189
|
+
function parseAgentOutputContract(raw) {
|
|
2190
|
+
let value = raw;
|
|
2191
|
+
if (typeof raw === 'string') {
|
|
2192
|
+
const t = raw.trim();
|
|
2193
|
+
if (!t.length) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
try {
|
|
2197
|
+
value = JSON.parse(t);
|
|
2198
|
+
}
|
|
2199
|
+
catch {
|
|
2200
|
+
return null;
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
const o = value;
|
|
2207
|
+
const kind = o['kind'];
|
|
2208
|
+
if (!isOutputKind(kind)) {
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
const instruction = typeof o['instruction'] === 'string' ? o['instruction'].trim() : undefined;
|
|
2212
|
+
const schemaSummary = typeof o['schemaSummary'] === 'string' ? o['schemaSummary'].trim() : undefined;
|
|
2213
|
+
const validationCommandName = typeof o['validationCommandName'] === 'string' ? o['validationCommandName'].trim() : undefined;
|
|
2214
|
+
const schema = parseJsonSchemaLike(o['schema']);
|
|
2215
|
+
const schemaName = typeof o['schemaName'] === 'string' ? o['schemaName'].trim() : undefined;
|
|
2216
|
+
const schemaStrict = o['schemaStrict'] === true;
|
|
2217
|
+
const providerResponseFormat = parseResponseFormat(o['providerResponseFormat']);
|
|
2218
|
+
const transcriptSegment = kind === 'object' && isTranscriptSegment(o['transcriptSegment']) ? o['transcriptSegment'] : undefined;
|
|
2219
|
+
let structuredRetries;
|
|
2220
|
+
if (typeof o['structuredRetries'] === 'number' && !Number.isNaN(o['structuredRetries'])) {
|
|
2221
|
+
structuredRetries = Math.max(0, Math.min(10, Math.floor(o['structuredRetries'])));
|
|
2222
|
+
}
|
|
2223
|
+
return {
|
|
2224
|
+
kind,
|
|
2225
|
+
...(transcriptSegment ? { transcriptSegment } : {}),
|
|
2226
|
+
...(instruction && instruction.length > 0 ? { instruction } : {}),
|
|
2227
|
+
...(schemaSummary && schemaSummary.length > 0 ? { schemaSummary } : {}),
|
|
2228
|
+
...(schema ? { schema } : {}),
|
|
2229
|
+
...(schemaName && schemaName.length > 0 ? { schemaName } : {}),
|
|
2230
|
+
...(schemaStrict ? { schemaStrict: true } : {}),
|
|
2231
|
+
...(validationCommandName && validationCommandName.length > 0 ? { validationCommandName } : {}),
|
|
2232
|
+
...(structuredRetries !== undefined ? { structuredRetries } : {}),
|
|
2233
|
+
...(providerResponseFormat ? { providerResponseFormat } : {}),
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
/** Resolved contract for engine (never null; defaults to string). */
|
|
2237
|
+
function resolveAgentOutputContract(raw) {
|
|
2238
|
+
const parsed = parseAgentOutputContract(raw);
|
|
2239
|
+
if (parsed) {
|
|
2240
|
+
return parsed;
|
|
2241
|
+
}
|
|
2242
|
+
return { kind: 'string' };
|
|
2243
|
+
}
|
|
2244
|
+
function axpAiAgentOutputContractUsesStructuredFinalize(contract) {
|
|
2245
|
+
return STRUCTURED_OUTPUT_KINDS.has(contract.kind);
|
|
2246
|
+
}
|
|
2247
|
+
/** Structured kinds omit the string role baseline (Markdown prose hint) in the mock engine. */
|
|
2248
|
+
function axpAiAgentOutputContractOmitsRoleBaseline(contract) {
|
|
2249
|
+
return axpAiAgentOutputContractUsesStructuredFinalize(contract);
|
|
2250
|
+
}
|
|
2251
|
+
function axpAiAgentOutputContractStructuredRetryCap(contract) {
|
|
2252
|
+
if (!axpAiAgentOutputContractUsesStructuredFinalize(contract)) {
|
|
2253
|
+
return 0;
|
|
2254
|
+
}
|
|
2255
|
+
if (typeof contract.structuredRetries === 'number') {
|
|
2256
|
+
return Math.max(0, Math.min(10, Math.floor(contract.structuredRetries)));
|
|
2257
|
+
}
|
|
2258
|
+
return AXPAI_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES;
|
|
2259
|
+
}
|
|
2260
|
+
//#endregion
|
|
2261
|
+
|
|
2262
|
+
//#region ---- Imports ----
|
|
2263
|
+
//#endregion
|
|
2264
|
+
//#region ---- Legacy → contract ----
|
|
2265
|
+
/**
|
|
2266
|
+
* Maps legacy `expectStructuredOutput` flags to an {@link AXPAiAgentOutputContract}, or returns explicit `outputContract`.
|
|
2267
|
+
*/
|
|
2268
|
+
function outputContractFromAssistRunInput(input) {
|
|
2269
|
+
if (input.outputContract) {
|
|
2270
|
+
const parsed = parseAgentOutputContract(input.outputContract);
|
|
2271
|
+
if (parsed) {
|
|
2272
|
+
return parsed;
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
if (!input.expectStructuredOutput) {
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2278
|
+
const instruction = input.structuredOutputInstruction?.trim();
|
|
2279
|
+
const schemaSummary = input.structuredOutputSchemaSummary?.trim();
|
|
2280
|
+
let structuredRetries;
|
|
2281
|
+
if (typeof input.maxStructuredRetries === 'number' && !Number.isNaN(input.maxStructuredRetries)) {
|
|
2282
|
+
structuredRetries = Math.max(0, Math.min(10, Math.floor(input.maxStructuredRetries)));
|
|
2283
|
+
}
|
|
2284
|
+
return {
|
|
2285
|
+
kind: 'object',
|
|
2286
|
+
...(instruction && instruction.length > 0 ? { instruction } : {}),
|
|
2287
|
+
...(schemaSummary && schemaSummary.length > 0 ? { schemaSummary } : {}),
|
|
2288
|
+
...(structuredRetries !== undefined ? { structuredRetries } : {}),
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
//#endregion
|
|
2292
|
+
|
|
2740
2293
|
//#region ---- Transcript ----
|
|
2741
2294
|
/**
|
|
2742
2295
|
* Text segments from the last assistant message (`type: 'text'` only).
|
|
@@ -3101,82 +2654,248 @@ function parseStructuredAssistantWithLocalRepair(raw) {
|
|
|
3101
2654
|
if (v !== undefined) {
|
|
3102
2655
|
return v;
|
|
3103
2656
|
}
|
|
3104
|
-
return undefined;
|
|
2657
|
+
return undefined;
|
|
2658
|
+
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Tries combined assistant text, then each text segment from last to first (models often put JSON only in the last paragraph).
|
|
2661
|
+
*/
|
|
2662
|
+
function parseStructuredAssistantWithSegmentFallback(combined, segments) {
|
|
2663
|
+
let v = parseStructuredAssistantWithLocalRepair(combined);
|
|
2664
|
+
if (v !== undefined) {
|
|
2665
|
+
return v;
|
|
2666
|
+
}
|
|
2667
|
+
const seen = new Set();
|
|
2668
|
+
const mark = stripAssistantThinkingXml(combined).trim();
|
|
2669
|
+
if (mark.length > 0) {
|
|
2670
|
+
seen.add(mark);
|
|
2671
|
+
}
|
|
2672
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
2673
|
+
const seg = stripAssistantThinkingXml(segments[i] ?? '').trim();
|
|
2674
|
+
if (!seg.length || seen.has(seg)) {
|
|
2675
|
+
continue;
|
|
2676
|
+
}
|
|
2677
|
+
seen.add(seg);
|
|
2678
|
+
v = parseStructuredAssistantWithLocalRepair(seg);
|
|
2679
|
+
if (v !== undefined) {
|
|
2680
|
+
return v;
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
return undefined;
|
|
2684
|
+
}
|
|
2685
|
+
//#endregion
|
|
2686
|
+
|
|
2687
|
+
//#region ---- Imports ----
|
|
2688
|
+
//#endregion
|
|
2689
|
+
//#region ---- Validator ----
|
|
2690
|
+
function typeMatches(schemaType, value) {
|
|
2691
|
+
if (schemaType === undefined) {
|
|
2692
|
+
return true;
|
|
2693
|
+
}
|
|
2694
|
+
switch (schemaType) {
|
|
2695
|
+
case 'object':
|
|
2696
|
+
return value != null && typeof value === 'object' && !Array.isArray(value);
|
|
2697
|
+
case 'array':
|
|
2698
|
+
return Array.isArray(value);
|
|
2699
|
+
case 'string':
|
|
2700
|
+
return typeof value === 'string';
|
|
2701
|
+
case 'number':
|
|
2702
|
+
return typeof value === 'number' && !Number.isNaN(value);
|
|
2703
|
+
case 'integer':
|
|
2704
|
+
return typeof value === 'number' && Number.isInteger(value);
|
|
2705
|
+
case 'boolean':
|
|
2706
|
+
return typeof value === 'boolean';
|
|
2707
|
+
case 'null':
|
|
2708
|
+
return value === null;
|
|
2709
|
+
default:
|
|
2710
|
+
return true;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
function validateNode(schema, value, path, errors) {
|
|
2714
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
2715
|
+
if (!schema.enum.some((e) => Object.is(e, value))) {
|
|
2716
|
+
errors.push({ path, message: `Value is not one of the allowed enum values.` });
|
|
2717
|
+
return;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (!typeMatches(schema.type, value)) {
|
|
2721
|
+
errors.push({
|
|
2722
|
+
path,
|
|
2723
|
+
message: `Expected type "${schema.type ?? 'any'}", got ${value === null ? 'null' : typeof value}.`,
|
|
2724
|
+
});
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
if (schema.type === 'object' && value != null && typeof value === 'object' && !Array.isArray(value)) {
|
|
2728
|
+
const o = value;
|
|
2729
|
+
const props = schema.properties ?? {};
|
|
2730
|
+
const required = schema.required ?? [];
|
|
2731
|
+
for (const key of required) {
|
|
2732
|
+
if (!(key in o) || o[key] === undefined) {
|
|
2733
|
+
errors.push({ path: path ? `${path}.${key}` : key, message: 'Required property is missing.' });
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
for (const [key, propSchema] of Object.entries(props)) {
|
|
2737
|
+
if (key in o && o[key] !== undefined) {
|
|
2738
|
+
validateNode(propSchema, o[key], path ? `${path}.${key}` : key, errors);
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
if (schema.additionalProperties === false) {
|
|
2742
|
+
for (const key of Object.keys(o)) {
|
|
2743
|
+
if (!(key in props)) {
|
|
2744
|
+
errors.push({
|
|
2745
|
+
path: path ? `${path}.${key}` : key,
|
|
2746
|
+
message: 'Additional property is not allowed.',
|
|
2747
|
+
});
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
else if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
2752
|
+
for (const [key, val] of Object.entries(o)) {
|
|
2753
|
+
if (!(key in props)) {
|
|
2754
|
+
validateNode(schema.additionalProperties, val, path ? `${path}.${key}` : key, errors);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
if (schema.type === 'array' && Array.isArray(value) && schema.items) {
|
|
2760
|
+
const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
|
|
2761
|
+
if (itemSchema) {
|
|
2762
|
+
value.forEach((item, index) => {
|
|
2763
|
+
validateNode(itemSchema, item, `${path}[${index}]`, errors);
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
3105
2767
|
}
|
|
3106
2768
|
/**
|
|
3107
|
-
*
|
|
2769
|
+
* Validates a value against a minimal JSON Schema subset (types, required, enum, properties, items).
|
|
3108
2770
|
*/
|
|
3109
|
-
function
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
const seen = new Set();
|
|
3115
|
-
const mark = stripAssistantThinkingXml(combined).trim();
|
|
3116
|
-
if (mark.length > 0) {
|
|
3117
|
-
seen.add(mark);
|
|
3118
|
-
}
|
|
3119
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
3120
|
-
const seg = stripAssistantThinkingXml(segments[i] ?? '').trim();
|
|
3121
|
-
if (!seg.length || seen.has(seg)) {
|
|
3122
|
-
continue;
|
|
3123
|
-
}
|
|
3124
|
-
seen.add(seg);
|
|
3125
|
-
v = parseStructuredAssistantWithLocalRepair(seg);
|
|
3126
|
-
if (v !== undefined) {
|
|
3127
|
-
return v;
|
|
3128
|
-
}
|
|
2771
|
+
function validateJsonSchemaMini(schema, value) {
|
|
2772
|
+
const errors = [];
|
|
2773
|
+
validateNode(schema, value, '', errors);
|
|
2774
|
+
if (errors.length > 0) {
|
|
2775
|
+
return { ok: false, errors };
|
|
3129
2776
|
}
|
|
3130
|
-
return
|
|
2777
|
+
return { ok: true };
|
|
3131
2778
|
}
|
|
3132
2779
|
//#endregion
|
|
3133
2780
|
|
|
3134
2781
|
//#region ---- Imports ----
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
2782
|
+
//#endregion
|
|
2783
|
+
//#region ---- Parse + validate ----
|
|
2784
|
+
function defaultParseCommandEnvelope(raw) {
|
|
2785
|
+
if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
2786
|
+
return { valid: false, errorMessage: 'Validation command returned an invalid envelope.' };
|
|
3138
2787
|
}
|
|
3139
|
-
|
|
3140
|
-
|
|
2788
|
+
const o = raw;
|
|
2789
|
+
if (o['success'] !== true) {
|
|
2790
|
+
const err = typeof o['error'] === 'string' ? o['error'] : 'Validation command failed.';
|
|
2791
|
+
return { valid: false, errorMessage: err };
|
|
3141
2792
|
}
|
|
3142
|
-
|
|
2793
|
+
const data = o['data'];
|
|
2794
|
+
if (!data?.valid) {
|
|
2795
|
+
const errs = Array.isArray(data?.errors) ? data.errors.join('; ') : 'Layout validation failed.';
|
|
2796
|
+
return { valid: false, errorMessage: errs };
|
|
2797
|
+
}
|
|
2798
|
+
const normalized = typeof data.normalizedJson === 'string' ? data.normalizedJson.trim() : '';
|
|
2799
|
+
return { valid: true, ...(normalized.length > 0 ? { normalizedJson: normalized } : {}) };
|
|
3143
2800
|
}
|
|
3144
2801
|
/**
|
|
3145
|
-
* Parses
|
|
3146
|
-
*
|
|
3147
|
-
* @param textSegmentsFallback - When provided, each segment is tried after combined text (last segment first); helps when prose and JSON sit in separate assistant text blocks.
|
|
2802
|
+
* Parses assistant text and validates against contract schema, registry command, and optional custom validator.
|
|
3148
2803
|
*/
|
|
3149
|
-
async function
|
|
3150
|
-
|
|
2804
|
+
async function validateAgainstContract(params) {
|
|
2805
|
+
const { contract, assistantPlainText, textSegmentsFallback } = params;
|
|
2806
|
+
if (!axpAiAgentOutputContractUsesStructuredFinalize(contract)) {
|
|
2807
|
+
const trimmed = stripAssistantThinkingXml(assistantPlainText).trim();
|
|
2808
|
+
if (!trimmed.length && !(textSegmentsFallback?.some((s) => (s ?? '').trim().length > 0))) {
|
|
2809
|
+
return {
|
|
2810
|
+
ok: false,
|
|
2811
|
+
errors: [{ phase: 'parse', message: 'Assistant returned empty output.' }],
|
|
2812
|
+
};
|
|
2813
|
+
}
|
|
2814
|
+
return { ok: true, value: trimmed };
|
|
2815
|
+
}
|
|
2816
|
+
const combined = stripAssistantThinkingXml(assistantPlainText);
|
|
2817
|
+
const parsed = parseStructuredAssistantWithSegmentFallback(combined, textSegmentsFallback ?? []);
|
|
2818
|
+
if (parsed === undefined) {
|
|
3151
2819
|
return {
|
|
3152
2820
|
ok: false,
|
|
3153
|
-
|
|
3154
|
-
|
|
2821
|
+
errors: [
|
|
2822
|
+
{
|
|
2823
|
+
phase: 'parse',
|
|
2824
|
+
message: 'Could not parse assistant output as JSON (expected a JSON object or array; optional markdown fence; segment fallback and local repair also failed).',
|
|
2825
|
+
},
|
|
2826
|
+
],
|
|
3155
2827
|
};
|
|
3156
2828
|
}
|
|
3157
|
-
|
|
3158
|
-
if (
|
|
2829
|
+
let value = parsed;
|
|
2830
|
+
if (contract.kind === 'array' && !Array.isArray(value)) {
|
|
3159
2831
|
return {
|
|
3160
2832
|
ok: false,
|
|
3161
|
-
|
|
3162
|
-
failurePhase: 'parse',
|
|
2833
|
+
errors: [{ phase: 'validator', message: 'Expected a JSON array as the root value.' }],
|
|
3163
2834
|
};
|
|
3164
2835
|
}
|
|
3165
|
-
if (
|
|
3166
|
-
return { ok: true, value: parsed };
|
|
3167
|
-
}
|
|
3168
|
-
try {
|
|
3169
|
-
const value = await structuredValidator(parsed);
|
|
3170
|
-
return { ok: true, value };
|
|
3171
|
-
}
|
|
3172
|
-
catch (err) {
|
|
3173
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2836
|
+
if (contract.kind === 'object' && (value == null || typeof value !== 'object' || Array.isArray(value))) {
|
|
3174
2837
|
return {
|
|
3175
2838
|
ok: false,
|
|
3176
|
-
|
|
3177
|
-
failurePhase: 'validator',
|
|
2839
|
+
errors: [{ phase: 'validator', message: 'Expected a JSON object as the root value.' }],
|
|
3178
2840
|
};
|
|
3179
2841
|
}
|
|
2842
|
+
if (contract.schema) {
|
|
2843
|
+
const schemaResult = validateJsonSchemaMini(contract.schema, value);
|
|
2844
|
+
if (!schemaResult.ok) {
|
|
2845
|
+
const message = schemaResult.errors.map((e) => `${e.path || '(root)'}: ${e.message}`).join('; ');
|
|
2846
|
+
return {
|
|
2847
|
+
ok: false,
|
|
2848
|
+
errors: [{ phase: 'validator', message, details: schemaResult.errors }],
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
const cmd = contract.validationCommandName?.trim();
|
|
2853
|
+
if (cmd && params.commandExecutor) {
|
|
2854
|
+
try {
|
|
2855
|
+
const raw = await params.commandExecutor.execute(cmd, { json: value });
|
|
2856
|
+
const parseEnvelope = params.parseCommandEnvelope ?? defaultParseCommandEnvelope;
|
|
2857
|
+
const envelope = parseEnvelope(raw);
|
|
2858
|
+
if (!envelope.valid) {
|
|
2859
|
+
return {
|
|
2860
|
+
ok: false,
|
|
2861
|
+
errors: [
|
|
2862
|
+
{
|
|
2863
|
+
phase: 'validator',
|
|
2864
|
+
message: envelope.errorMessage ?? 'Validation command failed.',
|
|
2865
|
+
},
|
|
2866
|
+
],
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
if (envelope.normalizedJson) {
|
|
2870
|
+
try {
|
|
2871
|
+
value = JSON.parse(envelope.normalizedJson);
|
|
2872
|
+
}
|
|
2873
|
+
catch {
|
|
2874
|
+
value = envelope.normalizedJson;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
catch (err) {
|
|
2879
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2880
|
+
return {
|
|
2881
|
+
ok: false,
|
|
2882
|
+
errors: [{ phase: 'validator', message: msg.trim().length > 0 ? msg : 'Validation command failed.' }],
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
if (params.structuredValidator) {
|
|
2887
|
+
try {
|
|
2888
|
+
value = await params.structuredValidator(value);
|
|
2889
|
+
}
|
|
2890
|
+
catch (err) {
|
|
2891
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2892
|
+
return {
|
|
2893
|
+
ok: false,
|
|
2894
|
+
errors: [{ phase: 'validator', message: msg.trim().length > 0 ? msg : 'Structured validation failed.' }],
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
return { ok: true, value };
|
|
3180
2899
|
}
|
|
3181
2900
|
//#endregion
|
|
3182
2901
|
|
|
@@ -3224,9 +2943,9 @@ function truncateForStructuredOutputError(text) {
|
|
|
3224
2943
|
* Runs the engine, validates JSON (+ optional custom validator), and appends user retry messages on failure.
|
|
3225
2944
|
*/
|
|
3226
2945
|
async function runStructuredOutputPipeline(params) {
|
|
3227
|
-
const
|
|
3228
|
-
|
|
3229
|
-
|
|
2946
|
+
const retryCap = params.maxStructuredRetries ?? axpAiAgentOutputContractStructuredRetryCap(params.contract);
|
|
2947
|
+
const maxAttempts = params.retryOnInvalidStructure ? 1 + Math.max(0, retryCap) : 1;
|
|
2948
|
+
const schemaSummary = params.structuredOutputSchemaSummary?.trim() || params.contract.schemaSummary?.trim();
|
|
3230
2949
|
let messages = params.initialMessages;
|
|
3231
2950
|
let mergedUsage;
|
|
3232
2951
|
let lastProvider;
|
|
@@ -3261,7 +2980,13 @@ async function runStructuredOutputPipeline(params) {
|
|
|
3261
2980
|
code: 'duplicate_assistant_output',
|
|
3262
2981
|
});
|
|
3263
2982
|
}
|
|
3264
|
-
const validation = await
|
|
2983
|
+
const validation = await validateAgainstContract({
|
|
2984
|
+
contract: params.contract,
|
|
2985
|
+
assistantPlainText: structuredParts.combined,
|
|
2986
|
+
textSegmentsFallback: structuredParts.segments,
|
|
2987
|
+
structuredValidator: params.structuredValidator,
|
|
2988
|
+
commandExecutor: params.commandExecutor,
|
|
2989
|
+
});
|
|
3265
2990
|
if (validation.ok) {
|
|
3266
2991
|
const structuredRunRaw = {
|
|
3267
2992
|
kind: 'structured-output',
|
|
@@ -3271,26 +2996,31 @@ async function runStructuredOutputPipeline(params) {
|
|
|
3271
2996
|
lastProviderResult: lastProvider,
|
|
3272
2997
|
};
|
|
3273
2998
|
return {
|
|
2999
|
+
success: true,
|
|
3000
|
+
outputKind: params.contract.kind,
|
|
3274
3001
|
text: displayText,
|
|
3002
|
+
data: validation.value,
|
|
3275
3003
|
structured: validation.value,
|
|
3004
|
+
messages: result.messages,
|
|
3276
3005
|
structuredRunRaw,
|
|
3277
3006
|
};
|
|
3278
3007
|
}
|
|
3008
|
+
const firstError = validation.errors[0];
|
|
3279
3009
|
prevFailureStructuredFingerprint = structuredFingerprint;
|
|
3280
|
-
prevFailurePhase =
|
|
3010
|
+
prevFailurePhase = firstError?.phase;
|
|
3281
3011
|
const canRetry = params.retryOnInvalidStructure && attempt < maxAttempts;
|
|
3282
3012
|
if (!canRetry) {
|
|
3283
3013
|
throw new AXPAiStructuredOutputError({
|
|
3284
3014
|
message: `Structured output validation failed after ${attempt} attempt(s).`,
|
|
3285
3015
|
attempts: attempt,
|
|
3286
3016
|
lastAssistantText: truncateForStructuredOutputError(displayText),
|
|
3287
|
-
validationMessage:
|
|
3017
|
+
validationMessage: firstError?.message ?? 'Validation failed.',
|
|
3288
3018
|
code: 'validation_exhausted',
|
|
3289
3019
|
});
|
|
3290
3020
|
}
|
|
3291
3021
|
messages = [
|
|
3292
3022
|
...result.messages,
|
|
3293
|
-
axpAiChatTextMessage('user', buildStructuredOutputRetryUserText(
|
|
3023
|
+
axpAiChatTextMessage('user', buildStructuredOutputRetryUserText(firstError?.message ?? 'Validation failed.', schemaSummary, firstError?.phase ?? 'parse')),
|
|
3294
3024
|
];
|
|
3295
3025
|
}
|
|
3296
3026
|
throw new AXPAiStructuredOutputError({
|
|
@@ -3303,6 +3033,76 @@ async function runStructuredOutputPipeline(params) {
|
|
|
3303
3033
|
}
|
|
3304
3034
|
//#endregion
|
|
3305
3035
|
|
|
3036
|
+
//#region ---- Imports ----
|
|
3037
|
+
//#endregion
|
|
3038
|
+
//#region ---- Segment id ----
|
|
3039
|
+
function isOutputContractSegmentId(value) {
|
|
3040
|
+
return value === 'text' || value === 'node' || value === 'followUp';
|
|
3041
|
+
}
|
|
3042
|
+
//#endregion
|
|
3043
|
+
class AXPAiOutputContractResolveService {
|
|
3044
|
+
constructor() {
|
|
3045
|
+
//#region ---- Dependencies ----
|
|
3046
|
+
this.segmentService = inject(AXPOutputContractTranscriptSegmentService);
|
|
3047
|
+
}
|
|
3048
|
+
//#endregion
|
|
3049
|
+
//#region ---- Public API ----
|
|
3050
|
+
/**
|
|
3051
|
+
* Resolves a persisted agent/assist `outputContract` value (segment id string)
|
|
3052
|
+
* or a full contract object (runtime / programmatic).
|
|
3053
|
+
*/
|
|
3054
|
+
async resolveFromPersisted(raw) {
|
|
3055
|
+
if (raw == null) {
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
if (typeof raw === 'string') {
|
|
3059
|
+
const id = raw.trim();
|
|
3060
|
+
if (!id.length) {
|
|
3061
|
+
return null;
|
|
3062
|
+
}
|
|
3063
|
+
if (isOutputContractSegmentId(id)) {
|
|
3064
|
+
return (await this.segmentService.getContractPreset(id)) ?? null;
|
|
3065
|
+
}
|
|
3066
|
+
return parseAgentOutputContract(raw);
|
|
3067
|
+
}
|
|
3068
|
+
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
3069
|
+
return parseAgentOutputContract(raw);
|
|
3070
|
+
}
|
|
3071
|
+
return null;
|
|
3072
|
+
}
|
|
3073
|
+
/**
|
|
3074
|
+
* Effective contract for one engine turn.
|
|
3075
|
+
* Priority: runtime > input > agent > assist > `{ kind: 'string' }`.
|
|
3076
|
+
*/
|
|
3077
|
+
async resolveEffective(params) {
|
|
3078
|
+
const runtime = await this.resolveFromPersisted(params.runtime);
|
|
3079
|
+
if (runtime) {
|
|
3080
|
+
return runtime;
|
|
3081
|
+
}
|
|
3082
|
+
const input = await this.resolveFromPersisted(params.input);
|
|
3083
|
+
if (input) {
|
|
3084
|
+
return input;
|
|
3085
|
+
}
|
|
3086
|
+
const agent = await this.resolveFromPersisted(params.agent);
|
|
3087
|
+
if (agent) {
|
|
3088
|
+
return agent;
|
|
3089
|
+
}
|
|
3090
|
+
const assist = await this.resolveFromPersisted(params.assist);
|
|
3091
|
+
if (assist) {
|
|
3092
|
+
return assist;
|
|
3093
|
+
}
|
|
3094
|
+
return resolveAgentOutputContract(null);
|
|
3095
|
+
}
|
|
3096
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAiOutputContractResolveService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
3097
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAiOutputContractResolveService, providedIn: 'root' }); }
|
|
3098
|
+
}
|
|
3099
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXPAiOutputContractResolveService, decorators: [{
|
|
3100
|
+
type: Injectable,
|
|
3101
|
+
args: [{
|
|
3102
|
+
providedIn: 'root',
|
|
3103
|
+
}]
|
|
3104
|
+
}] });
|
|
3105
|
+
|
|
3306
3106
|
//#region ---- Imports ----
|
|
3307
3107
|
//#endregion
|
|
3308
3108
|
//#region ---- Assist prompt run loading dialog ----
|
|
@@ -3383,6 +3183,7 @@ class AXPAiManagerService extends AXPAiAssistService {
|
|
|
3383
3183
|
super(...arguments);
|
|
3384
3184
|
//#region ---- Services & Dependencies ----
|
|
3385
3185
|
this.aiEngine = inject(AXPAiEngine);
|
|
3186
|
+
this.outputContractResolve = inject(AXPAiOutputContractResolveService);
|
|
3386
3187
|
this.entityService = inject(AXPEntityService);
|
|
3387
3188
|
this.settings = inject(AXPSettingsService);
|
|
3388
3189
|
this.runtimeContextBuilder = inject(AXPAiPlatformRuntimeContextBuilder);
|
|
@@ -3448,6 +3249,22 @@ class AXPAiManagerService extends AXPAiAssistService {
|
|
|
3448
3249
|
}
|
|
3449
3250
|
return this.getEffectiveAssist();
|
|
3450
3251
|
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Primary catalog model transport for an assist (`modelId` row).
|
|
3254
|
+
*/
|
|
3255
|
+
async getAssistPrimaryChatTransport(assistId) {
|
|
3256
|
+
const assist = await this.getAssistForChat(assistId);
|
|
3257
|
+
const modelEntityId = assist.modelId?.trim();
|
|
3258
|
+
if (!modelEntityId) {
|
|
3259
|
+
return undefined;
|
|
3260
|
+
}
|
|
3261
|
+
const model = await this.modelEntityData.byKey(modelEntityId);
|
|
3262
|
+
const raw = String(model?.chatTransport ?? '').toLowerCase();
|
|
3263
|
+
if (raw === 'openai' || raw === 'gemini' || raw === 'demis') {
|
|
3264
|
+
return raw;
|
|
3265
|
+
}
|
|
3266
|
+
return undefined;
|
|
3267
|
+
}
|
|
3451
3268
|
//#endregion
|
|
3452
3269
|
//#region ---- Engine delegation ----
|
|
3453
3270
|
/**
|
|
@@ -3499,32 +3316,48 @@ class AXPAiManagerService extends AXPAiAssistService {
|
|
|
3499
3316
|
const platformRuntimeContext = mergePlatformRuntimeContext(baseCtx, input.platformRuntimeNotes ?? null);
|
|
3500
3317
|
const directAgentId = input.directAgentId?.trim();
|
|
3501
3318
|
let assistId = input.assistId?.trim();
|
|
3319
|
+
const assistRow = !directAgentId && !assistId
|
|
3320
|
+
? await this.getEffectiveAssist()
|
|
3321
|
+
: await this.getAssistForChat(assistId);
|
|
3502
3322
|
if (!directAgentId && !assistId) {
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3323
|
+
assistId = assistRow.id;
|
|
3324
|
+
}
|
|
3325
|
+
const agentRow = directAgentId ? await this.agentEntityData.byKey(directAgentId) : null;
|
|
3326
|
+
const inputContract = outputContractFromAssistRunInput(input);
|
|
3327
|
+
const effectiveOutputContract = await this.outputContractResolve.resolveEffective({
|
|
3328
|
+
input: inputContract,
|
|
3329
|
+
assist: assistRow.outputContract,
|
|
3330
|
+
agent: agentRow?.outputContract,
|
|
3331
|
+
});
|
|
3332
|
+
const usesStructuredPipeline = input.expectStructuredOutput === true ||
|
|
3333
|
+
axpAiAgentOutputContractUsesStructuredFinalize(effectiveOutputContract);
|
|
3506
3334
|
const messages = buildChatMessages(input);
|
|
3507
3335
|
const catalogMax = await this.resolveCatalogMaxOutputTokens(assistId, directAgentId);
|
|
3508
3336
|
const effectiveMaxTokens = input.maxTokens ?? catalogMax ?? AXPAiAssistDefaultStructuredMaxTokens;
|
|
3509
3337
|
const runOptions = {
|
|
3510
3338
|
platformRuntimeContext,
|
|
3339
|
+
outputContract: effectiveOutputContract,
|
|
3511
3340
|
...(directAgentId ? { directAgentId } : { assistId: assistId }),
|
|
3512
3341
|
...(input.maxSteps !== undefined ? { maxSteps: input.maxSteps } : {}),
|
|
3513
3342
|
...(input.temperature !== undefined ? { temperature: input.temperature } : {}),
|
|
3514
3343
|
maxTokens: effectiveMaxTokens,
|
|
3515
3344
|
};
|
|
3516
|
-
if (!
|
|
3345
|
+
if (!usesStructuredPipeline) {
|
|
3517
3346
|
const result = await this.runEngine({ ...runOptions, messages });
|
|
3518
3347
|
const text = extractLastAssistantPlainText(result.messages);
|
|
3519
3348
|
return {
|
|
3349
|
+
success: true,
|
|
3350
|
+
outputKind: effectiveOutputContract.kind,
|
|
3520
3351
|
text,
|
|
3521
3352
|
raw: { usageTotals: result.usageTotals, lastProviderResult: result.lastProviderResult },
|
|
3353
|
+
messages: result.messages,
|
|
3522
3354
|
};
|
|
3523
3355
|
}
|
|
3524
3356
|
const retryOnInvalidStructure = input.retryOnInvalidStructure !== false;
|
|
3525
3357
|
const maxStructuredRetries = input.maxStructuredRetries ?? AXPAiAssistDefaultMaxStructuredRetries;
|
|
3526
3358
|
const structuredMaxTokens = input.structuredOutputMaxTokens ?? effectiveMaxTokens;
|
|
3527
3359
|
const pipelineResult = await runStructuredOutputPipeline({
|
|
3360
|
+
contract: effectiveOutputContract,
|
|
3528
3361
|
initialMessages: messages,
|
|
3529
3362
|
executeRun: (nextMessages) => this.runEngine({ ...runOptions, maxTokens: structuredMaxTokens, messages: nextMessages }),
|
|
3530
3363
|
maxStructuredRetries,
|
|
@@ -3533,8 +3366,13 @@ class AXPAiManagerService extends AXPAiAssistService {
|
|
|
3533
3366
|
structuredOutputSchemaSummary: input.structuredOutputSchemaSummary,
|
|
3534
3367
|
});
|
|
3535
3368
|
return {
|
|
3369
|
+
success: pipelineResult.success,
|
|
3370
|
+
outputKind: pipelineResult.outputKind,
|
|
3536
3371
|
text: pipelineResult.text,
|
|
3537
3372
|
structured: pipelineResult.structured,
|
|
3373
|
+
data: pipelineResult.data,
|
|
3374
|
+
validationErrors: pipelineResult.validationErrors,
|
|
3375
|
+
messages: pipelineResult.messages,
|
|
3538
3376
|
raw: pipelineResult.structuredRunRaw,
|
|
3539
3377
|
};
|
|
3540
3378
|
}
|
|
@@ -3561,6 +3399,7 @@ class AXPAiManagerService extends AXPAiAssistService {
|
|
|
3561
3399
|
maxSteps: options.maxSteps,
|
|
3562
3400
|
temperature: options.temperature,
|
|
3563
3401
|
maxTokens: options.maxTokens,
|
|
3402
|
+
outputContract: options.outputContract,
|
|
3564
3403
|
expectStructuredOutput: options.expectStructuredOutput,
|
|
3565
3404
|
structuredOutputInstruction: options.structuredOutputInstruction,
|
|
3566
3405
|
platformRuntimeNotes: options.platformRuntimeNotes,
|
|
@@ -3795,6 +3634,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3795
3634
|
type: Injectable
|
|
3796
3635
|
}] });
|
|
3797
3636
|
|
|
3637
|
+
//#region ---- Imports ----
|
|
3638
|
+
//#endregion
|
|
3639
|
+
//#region ---- Built-in segment definitions ----
|
|
3640
|
+
/** Single source for entity select options and DI segment presets. */
|
|
3641
|
+
const AXM_BUILTIN_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_DEFINITIONS = [
|
|
3642
|
+
{
|
|
3643
|
+
name: 'text',
|
|
3644
|
+
title: '@ai-management:agents.entities.agent.fields.output-contract.transcript-segment.options.text',
|
|
3645
|
+
applicableKinds: ['object'],
|
|
3646
|
+
contract: {
|
|
3647
|
+
kind: 'object',
|
|
3648
|
+
transcriptSegment: 'text',
|
|
3649
|
+
structuredRetries: 2,
|
|
3650
|
+
},
|
|
3651
|
+
},
|
|
3652
|
+
{
|
|
3653
|
+
name: 'node',
|
|
3654
|
+
title: '@ai-management:agents.entities.agent.fields.output-contract.transcript-segment.options.node',
|
|
3655
|
+
applicableKinds: ['object'],
|
|
3656
|
+
contract: {
|
|
3657
|
+
kind: 'object',
|
|
3658
|
+
transcriptSegment: 'node',
|
|
3659
|
+
structuredRetries: 3,
|
|
3660
|
+
},
|
|
3661
|
+
},
|
|
3662
|
+
{
|
|
3663
|
+
name: 'followUp',
|
|
3664
|
+
title: '@ai-management:agents.entities.agent.fields.output-contract.transcript-segment.options.follow-up',
|
|
3665
|
+
applicableKinds: ['object'],
|
|
3666
|
+
contract: {
|
|
3667
|
+
kind: 'object',
|
|
3668
|
+
transcriptSegment: 'followUp',
|
|
3669
|
+
structuredRetries: 2,
|
|
3670
|
+
},
|
|
3671
|
+
},
|
|
3672
|
+
];
|
|
3673
|
+
//#endregion
|
|
3674
|
+
//#region ---- Provider ----
|
|
3675
|
+
class AXMBuiltinOutputContractTranscriptSegmentProvider {
|
|
3676
|
+
provide() {
|
|
3677
|
+
return AXM_BUILTIN_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_DEFINITIONS;
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
//#endregion
|
|
3681
|
+
|
|
3798
3682
|
//#region ---- Imports ----
|
|
3799
3683
|
//#endregion
|
|
3800
3684
|
//#region ---- Routes ----
|
|
@@ -3837,11 +3721,21 @@ class AXMAiManagementModule {
|
|
|
3837
3721
|
useClass: AXMAiManagementSettingProvider,
|
|
3838
3722
|
multi: true,
|
|
3839
3723
|
},
|
|
3724
|
+
{
|
|
3725
|
+
provide: AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER,
|
|
3726
|
+
useClass: AXMBuiltinOutputContractTranscriptSegmentProvider,
|
|
3727
|
+
multi: true,
|
|
3728
|
+
},
|
|
3840
3729
|
{
|
|
3841
3730
|
provide: AXP_DATASOURCE_DEFINITION_PROVIDER,
|
|
3842
3731
|
useClass: AXMAiModelCatalogDataSourceDefinition,
|
|
3843
3732
|
multi: true,
|
|
3844
3733
|
},
|
|
3734
|
+
{
|
|
3735
|
+
provide: AXP_DATASOURCE_DEFINITION_PROVIDER,
|
|
3736
|
+
useClass: AXMOutputContractTranscriptSegmentDataSourceDefinition,
|
|
3737
|
+
multi: true,
|
|
3738
|
+
},
|
|
3845
3739
|
{
|
|
3846
3740
|
provide: AXP_DATASOURCE_DEFINITION_PROVIDER,
|
|
3847
3741
|
useClass: AXMAiAgentCatalogDataSourceDefinition,
|
|
@@ -3912,11 +3806,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
3912
3806
|
useClass: AXMAiManagementSettingProvider,
|
|
3913
3807
|
multi: true,
|
|
3914
3808
|
},
|
|
3809
|
+
{
|
|
3810
|
+
provide: AXP_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_PROVIDER,
|
|
3811
|
+
useClass: AXMBuiltinOutputContractTranscriptSegmentProvider,
|
|
3812
|
+
multi: true,
|
|
3813
|
+
},
|
|
3915
3814
|
{
|
|
3916
3815
|
provide: AXP_DATASOURCE_DEFINITION_PROVIDER,
|
|
3917
3816
|
useClass: AXMAiModelCatalogDataSourceDefinition,
|
|
3918
3817
|
multi: true,
|
|
3919
3818
|
},
|
|
3819
|
+
{
|
|
3820
|
+
provide: AXP_DATASOURCE_DEFINITION_PROVIDER,
|
|
3821
|
+
useClass: AXMOutputContractTranscriptSegmentDataSourceDefinition,
|
|
3822
|
+
multi: true,
|
|
3823
|
+
},
|
|
3920
3824
|
{
|
|
3921
3825
|
provide: AXP_DATASOURCE_DEFINITION_PROVIDER,
|
|
3922
3826
|
useClass: AXMAiAgentCatalogDataSourceDefinition,
|
|
@@ -4020,82 +3924,6 @@ function axpAiDelegatedAgentPromptPreview(argumentsValue) {
|
|
|
4020
3924
|
}
|
|
4021
3925
|
//#endregion
|
|
4022
3926
|
|
|
4023
|
-
//#region ---- AI tool command args ----
|
|
4024
|
-
/**
|
|
4025
|
-
* Recursively parses string values that look like JSON objects or arrays, then
|
|
4026
|
-
* normalizes nested values the same way.
|
|
4027
|
-
*
|
|
4028
|
-
* LLM tool calls often stringify nested payloads (e.g. `__context__` for
|
|
4029
|
-
* `Entity:Create`). Use this before passing args to the command or query service
|
|
4030
|
-
* from AI paths only — not inside generic command infrastructure.
|
|
4031
|
-
*
|
|
4032
|
-
* @param toolName When set, command-specific AI defaults from command definitions are merged (e.g. `Entity:Create` redirect).
|
|
4033
|
-
*/
|
|
4034
|
-
function normalizeAiToolCommandArgs(value, toolName) {
|
|
4035
|
-
const normalized = normalizeLikelyJsonStringValuesRecursive(value);
|
|
4036
|
-
return applyAiToolInputDefaultsByCommandName(toolName, normalized);
|
|
4037
|
-
}
|
|
4038
|
-
function normalizeLikelyJsonStringValuesRecursive(value) {
|
|
4039
|
-
if (value === null || value === undefined) {
|
|
4040
|
-
return value;
|
|
4041
|
-
}
|
|
4042
|
-
if (typeof value === 'string') {
|
|
4043
|
-
const trimmed = value.trim();
|
|
4044
|
-
if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
4045
|
-
(trimmed.startsWith('[') && trimmed.endsWith(']'))) {
|
|
4046
|
-
try {
|
|
4047
|
-
const parsed = JSON.parse(trimmed);
|
|
4048
|
-
return normalizeLikelyJsonStringValuesRecursive(parsed);
|
|
4049
|
-
}
|
|
4050
|
-
catch {
|
|
4051
|
-
return value;
|
|
4052
|
-
}
|
|
4053
|
-
}
|
|
4054
|
-
return value;
|
|
4055
|
-
}
|
|
4056
|
-
if (Array.isArray(value)) {
|
|
4057
|
-
return value.map((item) => normalizeLikelyJsonStringValuesRecursive(item));
|
|
4058
|
-
}
|
|
4059
|
-
if (typeof value === 'object') {
|
|
4060
|
-
const obj = value;
|
|
4061
|
-
const out = {};
|
|
4062
|
-
for (const key of Object.keys(obj)) {
|
|
4063
|
-
out[key] = normalizeLikelyJsonStringValuesRecursive(obj[key]);
|
|
4064
|
-
}
|
|
4065
|
-
return out;
|
|
4066
|
-
}
|
|
4067
|
-
return value;
|
|
4068
|
-
}
|
|
4069
|
-
function applyAiToolInputDefaultsByCommandName(toolName, payload) {
|
|
4070
|
-
if (toolName !== 'Entity:Create') {
|
|
4071
|
-
return payload;
|
|
4072
|
-
}
|
|
4073
|
-
return mergeEntityCreateAiToolDefaults(payload);
|
|
4074
|
-
}
|
|
4075
|
-
/** Applies `axpCreateEntityAiToolInputDefaults` so AI flows do not trigger post-create navigation. */
|
|
4076
|
-
function mergeEntityCreateAiToolDefaults(payload) {
|
|
4077
|
-
if (payload == null || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
4078
|
-
return payload;
|
|
4079
|
-
}
|
|
4080
|
-
const p = payload;
|
|
4081
|
-
const ctx = p['__context__'] ?? {};
|
|
4082
|
-
const opts = ctx['options'] ?? {};
|
|
4083
|
-
const proc = opts['process'] ?? {};
|
|
4084
|
-
const defProcess = axpCreateEntityAiToolInputDefaults.__context__.options.process;
|
|
4085
|
-
p['__context__'] = {
|
|
4086
|
-
...ctx,
|
|
4087
|
-
options: {
|
|
4088
|
-
...opts,
|
|
4089
|
-
process: {
|
|
4090
|
-
...proc,
|
|
4091
|
-
...defProcess,
|
|
4092
|
-
},
|
|
4093
|
-
},
|
|
4094
|
-
};
|
|
4095
|
-
return p;
|
|
4096
|
-
}
|
|
4097
|
-
//#endregion
|
|
4098
|
-
|
|
4099
3927
|
//#region ---- Imports ----
|
|
4100
3928
|
//#endregion
|
|
4101
3929
|
/** Maximum length for assist initial transcript text after HTML strip (characters). */
|
|
@@ -4558,6 +4386,97 @@ function getAiChatCommandLlmParametersOverride(commandName) {
|
|
|
4558
4386
|
}
|
|
4559
4387
|
//#endregion
|
|
4560
4388
|
|
|
4389
|
+
//#region ---- Imports ----
|
|
4390
|
+
//#endregion
|
|
4391
|
+
|
|
4392
|
+
//#region ---- Imports ----
|
|
4393
|
+
function contractFromUnknown(raw) {
|
|
4394
|
+
if (raw == null) {
|
|
4395
|
+
return null;
|
|
4396
|
+
}
|
|
4397
|
+
if (typeof raw === 'object' && !Array.isArray(raw) && 'kind' in raw) {
|
|
4398
|
+
const parsed = parseAgentOutputContract(raw);
|
|
4399
|
+
if (parsed) {
|
|
4400
|
+
return parsed;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
return parseAgentOutputContract(raw);
|
|
4404
|
+
}
|
|
4405
|
+
/**
|
|
4406
|
+
* Resolves the effective output contract for one engine turn.
|
|
4407
|
+
*
|
|
4408
|
+
* Priority: runtime > input > agent > assist > `{ kind: 'string' }`.
|
|
4409
|
+
*/
|
|
4410
|
+
function resolveEffectiveOutputContract(params) {
|
|
4411
|
+
const runtime = contractFromUnknown(params.runtime);
|
|
4412
|
+
if (runtime) {
|
|
4413
|
+
return runtime;
|
|
4414
|
+
}
|
|
4415
|
+
const input = contractFromUnknown(params.input);
|
|
4416
|
+
if (input) {
|
|
4417
|
+
return input;
|
|
4418
|
+
}
|
|
4419
|
+
const agent = contractFromUnknown(params.agent);
|
|
4420
|
+
if (agent) {
|
|
4421
|
+
return agent;
|
|
4422
|
+
}
|
|
4423
|
+
const assist = contractFromUnknown(params.assist);
|
|
4424
|
+
if (assist) {
|
|
4425
|
+
return assist;
|
|
4426
|
+
}
|
|
4427
|
+
return resolveAgentOutputContract(null);
|
|
4428
|
+
}
|
|
4429
|
+
//#endregion
|
|
4430
|
+
|
|
4431
|
+
//#region ---- Imports ----
|
|
4432
|
+
function hasAnyStructuredContent(combined, segments) {
|
|
4433
|
+
if (combined.trim().length > 0) {
|
|
4434
|
+
return true;
|
|
4435
|
+
}
|
|
4436
|
+
if (!segments?.length) {
|
|
4437
|
+
return false;
|
|
4438
|
+
}
|
|
4439
|
+
return segments.some((s) => (s ?? '').trim().length > 0);
|
|
4440
|
+
}
|
|
4441
|
+
/**
|
|
4442
|
+
* Parses JSON from assistant text (including lightweight local repair), then optionally runs a custom validator.
|
|
4443
|
+
*
|
|
4444
|
+
* @param textSegmentsFallback - When provided, each segment is tried after combined text (last segment first); helps when prose and JSON sit in separate assistant text blocks.
|
|
4445
|
+
*/
|
|
4446
|
+
async function validateStructuredAssistantResponse(assistantPlainText, structuredValidator, textSegmentsFallback) {
|
|
4447
|
+
if (!hasAnyStructuredContent(assistantPlainText, textSegmentsFallback)) {
|
|
4448
|
+
return {
|
|
4449
|
+
ok: false,
|
|
4450
|
+
errorMessage: 'Assistant returned empty output.',
|
|
4451
|
+
failurePhase: 'parse',
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
const parsed = parseStructuredAssistantWithSegmentFallback(assistantPlainText, textSegmentsFallback ?? []);
|
|
4455
|
+
if (parsed === undefined) {
|
|
4456
|
+
return {
|
|
4457
|
+
ok: false,
|
|
4458
|
+
errorMessage: 'Could not parse assistant output as JSON (expected a JSON object or array; optional markdown fence; segment fallback, local repair, and truncation repair also failed).',
|
|
4459
|
+
failurePhase: 'parse',
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
if (!structuredValidator) {
|
|
4463
|
+
return { ok: true, value: parsed };
|
|
4464
|
+
}
|
|
4465
|
+
try {
|
|
4466
|
+
const value = await structuredValidator(parsed);
|
|
4467
|
+
return { ok: true, value };
|
|
4468
|
+
}
|
|
4469
|
+
catch (err) {
|
|
4470
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4471
|
+
return {
|
|
4472
|
+
ok: false,
|
|
4473
|
+
errorMessage: msg.trim().length > 0 ? msg : 'Structured validation failed.',
|
|
4474
|
+
failurePhase: 'validator',
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
//#endregion
|
|
4479
|
+
|
|
4561
4480
|
//#region ---- Imports ----
|
|
4562
4481
|
//#endregion
|
|
4563
4482
|
/**
|
|
@@ -4592,7 +4511,7 @@ class AXPAiAssistChatModelCatalogService {
|
|
|
4592
4511
|
id: x.id,
|
|
4593
4512
|
name: x.name?.trim() ?? '',
|
|
4594
4513
|
title: resolveMultiLanguageString((x.title ?? x.name ?? x.id), this.localeId).trim() || x.id,
|
|
4595
|
-
modelPurposes:
|
|
4514
|
+
modelPurposes: readModelPurposes(x),
|
|
4596
4515
|
chatTransport: x.chatTransport,
|
|
4597
4516
|
maxContextTokens: typeof x.maxContextTokens === 'number' && x.maxContextTokens > 0
|
|
4598
4517
|
? x.maxContextTokens
|
|
@@ -4688,5 +4607,5 @@ function modelItemSupportsChatCatalogPurpose(m, purpose) {
|
|
|
4688
4607
|
* Generated bundle index. Do not edit.
|
|
4689
4608
|
*/
|
|
4690
4609
|
|
|
4691
|
-
export { AIMANAGEMENT_CHAT_GENERATE_IMAGE_COMMAND_KEY, AIMANAGEMENT_CHAT_SYNTHESIZE_SPEECH_COMMAND_KEY, AIMANAGEMENT_CHAT_TRANSCRIBE_SPEECH_COMMAND_KEY, AIMANAGEMENT_EXTRACT_DOCUMENT_TEXT_COMMAND_KEY, AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY, AI_AGENT_CATALOG_DATASOURCE_NAME, AI_CHAT_ATTACHMENT_CATEGORY, AI_CHAT_ATTACHMENT_REF_TYPE, AI_CHAT_GENERATED_IMAGE_REF_TYPE, AI_CHAT_GENERATED_SPEECH_REF_TYPE, AI_COMMAND_REGISTRY_CATALOG_DATASOURCE_NAME, AI_GENERATED_IMAGE_MAX_REMOTE_URL_IN_TOOL, AI_MODEL_CATALOG_DATASOURCE_NAME, AI_MODEL_IMAGE_CATALOG_DATASOURCE_NAME, AI_MODEL_SPEECH_CATALOG_DATASOURCE_NAME, AI_MODEL_TTS_CATALOG_DATASOURCE_NAME, AI_QUERY_REGISTRY_CATALOG_DATASOURCE_NAME, AXMAiAgentCatalogDataSourceDefinition, AXMAiAssistPermissionKeys, AXMAiAssistPromptBoxComponent, AXMAiAssistPromptBoxService, AXMAiCommandRegistryCatalogDataSourceDefinition, AXMAiManagementEntityProvider, AXMAiManagementMenuProvider, AXMAiManagementModule, AXMAiModelCatalogDataSourceDefinition, AXMAiQueryRegistryCatalogDataSourceDefinition, AXMPermissionDefinitionProvider, AXMPermissionsKeys,
|
|
4610
|
+
export { AIMANAGEMENT_CHAT_GENERATE_IMAGE_COMMAND_KEY, AIMANAGEMENT_CHAT_SYNTHESIZE_SPEECH_COMMAND_KEY, AIMANAGEMENT_CHAT_TRANSCRIBE_SPEECH_COMMAND_KEY, AIMANAGEMENT_EXTRACT_DOCUMENT_TEXT_COMMAND_KEY, AIMANAGEMENT_STRUCTURED_TEXT_COMPLETION_COMMAND_KEY, AI_AGENT_CATALOG_DATASOURCE_NAME, AI_CHAT_ATTACHMENT_CATEGORY, AI_CHAT_ATTACHMENT_REF_TYPE, AI_CHAT_GENERATED_IMAGE_REF_TYPE, AI_CHAT_GENERATED_SPEECH_REF_TYPE, AI_COMMAND_REGISTRY_CATALOG_DATASOURCE_NAME, AI_GENERATED_IMAGE_MAX_REMOTE_URL_IN_TOOL, AI_MODEL_CATALOG_DATASOURCE_NAME, AI_MODEL_IMAGE_CATALOG_DATASOURCE_NAME, AI_MODEL_SPEECH_CATALOG_DATASOURCE_NAME, AI_MODEL_TTS_CATALOG_DATASOURCE_NAME, AI_OUTPUT_CONTRACT_TRANSCRIPT_SEGMENT_DATASOURCE_NAME, AI_QUERY_REGISTRY_CATALOG_DATASOURCE_NAME, AXMAiAgentCatalogDataSourceDefinition, AXMAiAssistPermissionKeys, AXMAiAssistPromptBoxComponent, AXMAiAssistPromptBoxService, AXMAiCommandRegistryCatalogDataSourceDefinition, AXMAiManagementEntityProvider, AXMAiManagementMenuProvider, AXMAiManagementModule, AXMAiModelCatalogDataSourceDefinition, AXMAiQueryRegistryCatalogDataSourceDefinition, AXMOutputContractTranscriptSegmentDataSourceDefinition, AXMPermissionDefinitionProvider, AXMPermissionsKeys, AXPAI_AGENT_OUTPUT_CONTRACT_DEFAULT_STRUCTURED_RETRIES, AXPAI_SUPERVISOR_AGENT_TOOL_PREFIX, AXPAiAssistChatModelCatalogService, AXPAiAssistDefaultMaxStructuredRetries, AXPAiAssistDefaultStructuredMaxTokens, AXPAiAssistService, AXPAiChatToolRunContextService, AXPAiDefaultModelPickerService, AXPAiEngine, AXPAiManagementMenuKeys, AXPAiManagementSettings, AXPAiManagerService, AXPAiOutputContractResolveService, AXPAiPlatformRuntimeContextBuilder, AXPAiStructuredOutputError, AiManagementManifest, RootConfig, axpAiAgentOutputContractOmitsRoleBaseline, axpAiAgentOutputContractStructuredRetryCap, axpAiAgentOutputContractUsesStructuredFinalize, axpAiAssistInitialMessagesToTranscript, axpAiAssistStarterPromptLabels, axpAiAssistStarterPromptTexts, axpAiChatMessageGetText, axpAiChatMessageIsDelegatedReflectionExcluded, axpAiChatTextMessage, axpAiChatToolOrAgentResultBodyJson, axpAiChatToolResultBodyJson, axpAiChatUserMessage, axpAiDelegatedAgentOutcomeResponsesPlainText, axpAiDelegatedAgentPromptPreview, axpAiDelegatedAgentResultSegments, axpAiParseSupervisorAgentToolName, axpAiStripAssistInitialHtmlToPlain, blobOrFileFromImageResult, buildStructuredOutputRetryUserText, defaultRetriesForKind, deriveProviderResponseFormat, extractLastAssistantPlainText, extractLastAssistantStructuredParts, extractLastAssistantTextSegments, getAiChatCommandLlmParametersOverride, isChatPurposeModelRow, isImagePurposeModelRow, isSpeechPurposeModelRow, isTtsPurposeModelRow, loadAssistPermissionRows, modelRowHasPurpose, parseAgentOutputContract, parseJsonSchemaLike, parseResponseFormat, persistAiChatAttachmentImage, persistAiGeneratedImage, persistAiGeneratedSpeech, readModelPurposes, resolveAgentOutputContract, resolveEffectiveOutputContract, resolveOutputTranscriptSegment, validateAgainstContract, validateJsonSchemaMini, validateStructuredAssistantResponse };
|
|
4692
4611
|
//# sourceMappingURL=acorex-modules-ai-management.mjs.map
|