@open-mercato/ai-assistant 0.6.7 → 0.6.8-develop.6874.1.982d6097d8
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +12 -29
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js +80 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js +124 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js +30 -0
- package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +7 -0
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/moderation-flags/route.js +121 -0
- package/dist/modules/ai_assistant/api/moderation-flags/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js +10 -10
- package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +34 -13
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +121 -15
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +139 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js +26 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +4 -4
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/data/entities.js +42 -0
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +3 -1
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js +44 -0
- package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js.map +7 -0
- package/dist/modules/ai_assistant/di.js +6 -2
- package/dist/modules/ai_assistant/di.js.map +2 -2
- package/dist/modules/ai_assistant/events.js +6 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +42 -0
- package/dist/modules/ai_assistant/i18n/en.json +42 -0
- package/dist/modules/ai_assistant/i18n/es.json +42 -0
- package/dist/modules/ai_assistant/i18n/ko.json +527 -0
- package/dist/modules/ai_assistant/i18n/pl.json +42 -0
- package/dist/modules/ai_assistant/lib/agent-runtime.js +158 -5
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/auth.js +6 -7
- package/dist/modules/ai_assistant/lib/auth.js.map +2 -2
- package/dist/modules/ai_assistant/lib/entity-graph.js +3 -3
- package/dist/modules/ai_assistant/lib/entity-graph.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +3 -0
- package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
- package/dist/modules/ai_assistant/lib/llm-adapters/openai.js +8 -1
- package/dist/modules/ai_assistant/lib/llm-adapters/openai.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +6 -1
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js +43 -0
- package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js.map +7 -0
- package/dist/modules/ai_assistant/lib/moderation-policy.js +24 -0
- package/dist/modules/ai_assistant/lib/moderation-policy.js.map +7 -0
- package/dist/modules/ai_assistant/lib/moderation.js +109 -0
- package/dist/modules/ai_assistant/lib/moderation.js.map +7 -0
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +2 -0
- package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
- package/dist/modules/ai_assistant/lib/tool-search.js +4 -12
- package/dist/modules/ai_assistant/lib/tool-search.js.map +2 -2
- package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js +19 -0
- package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +1 -0
- package/generated/entities/ai_moderation_flag/index.ts +9 -0
- package/generated/entities.ids.generated.ts +2 -1
- package/generated/entity-fields-registry.ts +13 -1
- package/package.json +13 -12
- package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.ts +126 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.ts +161 -0
- package/src/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.ts +48 -0
- package/src/modules/ai_assistant/api/ai/chat/route.ts +10 -0
- package/src/modules/ai_assistant/api/moderation-flags/__tests__/route.test.ts +122 -0
- package/src/modules/ai_assistant/api/moderation-flags/route.ts +137 -0
- package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +58 -8
- package/src/modules/ai_assistant/api/settings/allowlist/route.ts +10 -12
- package/src/modules/ai_assistant/api/settings/route.ts +33 -10
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +161 -15
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +2 -2
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +186 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.ts +24 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.tsx +12 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +4 -4
- package/src/modules/ai_assistant/data/entities.ts +56 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +4 -0
- package/src/modules/ai_assistant/data/repositories/AiModerationFlagRepository.ts +79 -0
- package/src/modules/ai_assistant/di.ts +5 -1
- package/src/modules/ai_assistant/events.ts +21 -0
- package/src/modules/ai_assistant/i18n/de.json +42 -0
- package/src/modules/ai_assistant/i18n/en.json +42 -0
- package/src/modules/ai_assistant/i18n/es.json +42 -0
- package/src/modules/ai_assistant/i18n/ko.json +527 -0
- package/src/modules/ai_assistant/i18n/pl.json +42 -0
- package/src/modules/ai_assistant/lib/__tests__/auth.test.ts +2 -5
- package/src/modules/ai_assistant/lib/__tests__/input-moderation-gate.test.ts +121 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +13 -0
- package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +41 -0
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +60 -0
- package/src/modules/ai_assistant/lib/__tests__/moderation-flag-recorder.test.ts +76 -0
- package/src/modules/ai_assistant/lib/__tests__/moderation-policy.test.ts +86 -0
- package/src/modules/ai_assistant/lib/__tests__/moderation.test.ts +114 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +259 -0
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +10 -0
- package/src/modules/ai_assistant/lib/auth.ts +6 -15
- package/src/modules/ai_assistant/lib/entity-graph.ts +3 -3
- package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +10 -0
- package/src/modules/ai_assistant/lib/llm-adapters/openai.ts +32 -1
- package/src/modules/ai_assistant/lib/model-factory.ts +33 -0
- package/src/modules/ai_assistant/lib/moderation-flag-recorder.ts +75 -0
- package/src/modules/ai_assistant/lib/moderation-policy.ts +77 -0
- package/src/modules/ai_assistant/lib/moderation.ts +179 -0
- package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +2 -0
- package/src/modules/ai_assistant/lib/tool-search.ts +4 -17
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +388 -16
- package/src/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.ts +21 -0
package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx
CHANGED
|
@@ -121,6 +121,14 @@ type AgentResolution = {
|
|
|
121
121
|
modelId: string
|
|
122
122
|
baseURL: string | null
|
|
123
123
|
source: string
|
|
124
|
+
moderation?: {
|
|
125
|
+
/** `untrustedInput` agents enforce moderation (non-editable). */
|
|
126
|
+
enforced: boolean
|
|
127
|
+
/** Per-agent control value: null = inherit, true = on, false = off. */
|
|
128
|
+
override: boolean | null
|
|
129
|
+
/** Resolved runtime policy after the precedence chain. */
|
|
130
|
+
effective: 'enforced' | 'on' | 'off'
|
|
131
|
+
}
|
|
124
132
|
}
|
|
125
133
|
|
|
126
134
|
type TenantAllowlist = {
|
|
@@ -706,7 +714,7 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
706
714
|
</div>
|
|
707
715
|
|
|
708
716
|
<Alert
|
|
709
|
-
|
|
717
|
+
status="information"
|
|
710
718
|
icon={<ShieldAlert aria-hidden="true" />}
|
|
711
719
|
data-ai-agent-mutation-policy-notice
|
|
712
720
|
>
|
|
@@ -732,7 +740,7 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
732
740
|
)}
|
|
733
741
|
/>
|
|
734
742
|
) : query.isError ? (
|
|
735
|
-
<Alert
|
|
743
|
+
<Alert status="error" data-ai-agent-mutation-policy-load-error>
|
|
736
744
|
<AlertTitle>
|
|
737
745
|
{t(
|
|
738
746
|
'ai_assistant.agents.mutation_policy.loadErrorTitle',
|
|
@@ -820,7 +828,7 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
820
828
|
)}
|
|
821
829
|
|
|
822
830
|
{state.kind === 'success' ? (
|
|
823
|
-
<Alert
|
|
831
|
+
<Alert status="success" data-ai-agent-mutation-policy-state="success">
|
|
824
832
|
<AlertTitle>
|
|
825
833
|
{t('ai_assistant.agents.mutation_policy.savedTitle', 'Mutation policy updated')}
|
|
826
834
|
</AlertTitle>
|
|
@@ -828,7 +836,7 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
828
836
|
</Alert>
|
|
829
837
|
) : null}
|
|
830
838
|
{state.kind === 'error' ? (
|
|
831
|
-
<Alert
|
|
839
|
+
<Alert status="error" data-ai-agent-mutation-policy-state="error">
|
|
832
840
|
<AlertTitle>
|
|
833
841
|
{t(
|
|
834
842
|
'ai_assistant.agents.mutation_policy.errorTitle',
|
|
@@ -875,6 +883,142 @@ function MutationPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
875
883
|
)
|
|
876
884
|
}
|
|
877
885
|
|
|
886
|
+
type ModerationControlValue = 'inherit' | 'on' | 'off'
|
|
887
|
+
|
|
888
|
+
function ModerationSection({ agent }: { agent: AgentSettings }) {
|
|
889
|
+
const t = useT()
|
|
890
|
+
const queryClient = useQueryClient()
|
|
891
|
+
const settingsQuery = useQuery<RuntimeSettingsResponse>({
|
|
892
|
+
queryKey: ['ai_assistant', 'agent_settings', 'runtime_settings'],
|
|
893
|
+
queryFn: fetchRuntimeSettings,
|
|
894
|
+
retry: false,
|
|
895
|
+
})
|
|
896
|
+
const agentResolution = settingsQuery.data?.agents.find((entry) => entry.agentId === agent.id) ?? null
|
|
897
|
+
const moderation = agentResolution?.moderation ?? null
|
|
898
|
+
const enforced = moderation?.enforced === true
|
|
899
|
+
const currentValue: ModerationControlValue =
|
|
900
|
+
moderation?.override === true ? 'on' : moderation?.override === false ? 'off' : 'inherit'
|
|
901
|
+
const [isSaving, setIsSaving] = React.useState(false)
|
|
902
|
+
const { runMutation } = useGuardedMutation({ contextId: `ai-agent-moderation-${agent.id}` })
|
|
903
|
+
|
|
904
|
+
const save = React.useCallback(
|
|
905
|
+
async (value: ModerationControlValue) => {
|
|
906
|
+
if (isSaving || enforced) return
|
|
907
|
+
setIsSaving(true)
|
|
908
|
+
try {
|
|
909
|
+
const inputModeration = value === 'inherit' ? null : value === 'on'
|
|
910
|
+
await runMutation({
|
|
911
|
+
operation: async () => {
|
|
912
|
+
const { ok, status, result } = await apiCall<{ error?: string }>(
|
|
913
|
+
'/api/ai_assistant/settings',
|
|
914
|
+
{
|
|
915
|
+
method: 'PUT',
|
|
916
|
+
headers: { 'content-type': 'application/json' },
|
|
917
|
+
credentials: 'include',
|
|
918
|
+
body: JSON.stringify({ agentId: agent.id, inputModeration }),
|
|
919
|
+
},
|
|
920
|
+
)
|
|
921
|
+
if (!ok) {
|
|
922
|
+
throw new Error(result?.error ?? `[internal] Failed to save moderation setting (${status}).`)
|
|
923
|
+
}
|
|
924
|
+
},
|
|
925
|
+
context: {},
|
|
926
|
+
})
|
|
927
|
+
flash(t('ai_assistant.agents.moderation.saved', 'Input moderation setting saved.'), 'success')
|
|
928
|
+
await queryClient.invalidateQueries({
|
|
929
|
+
queryKey: ['ai_assistant', 'agent_settings', 'runtime_settings'],
|
|
930
|
+
})
|
|
931
|
+
} catch (err) {
|
|
932
|
+
flash(err instanceof Error ? err.message : String(err), 'error')
|
|
933
|
+
} finally {
|
|
934
|
+
setIsSaving(false)
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
[agent.id, enforced, isSaving, queryClient, runMutation, t],
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
return (
|
|
941
|
+
<section
|
|
942
|
+
className="rounded-lg border border-border bg-background p-4"
|
|
943
|
+
data-ai-agent-moderation={agent.id}
|
|
944
|
+
>
|
|
945
|
+
<header className="flex flex-wrap items-start justify-between gap-3 border-b border-border pb-3">
|
|
946
|
+
<div className="flex min-w-0 items-start gap-2">
|
|
947
|
+
<ShieldAlert className="size-4 text-muted-foreground" aria-hidden />
|
|
948
|
+
<div className="min-w-0">
|
|
949
|
+
<h3 className="text-sm font-semibold">
|
|
950
|
+
{t('ai_assistant.agents.moderation.title', 'Input moderation')}
|
|
951
|
+
</h3>
|
|
952
|
+
<p className="text-xs text-muted-foreground">
|
|
953
|
+
{t(
|
|
954
|
+
'ai_assistant.agents.moderation.subtitle',
|
|
955
|
+
'Screen user input for unsafe content before the model runs. Applies only when the resolved provider supports moderation.',
|
|
956
|
+
)}
|
|
957
|
+
</p>
|
|
958
|
+
</div>
|
|
959
|
+
</div>
|
|
960
|
+
{moderation ? (
|
|
961
|
+
<StatusBadge
|
|
962
|
+
variant={moderation.effective === 'off' ? 'neutral' : 'info'}
|
|
963
|
+
dot
|
|
964
|
+
data-ai-agent-moderation-effective
|
|
965
|
+
>
|
|
966
|
+
{moderation.effective === 'enforced'
|
|
967
|
+
? t('ai_assistant.agents.moderation.effectiveEnforced', 'Enforced')
|
|
968
|
+
: moderation.effective === 'on'
|
|
969
|
+
? t('ai_assistant.agents.moderation.effectiveOn', 'On')
|
|
970
|
+
: t('ai_assistant.agents.moderation.effectiveOff', 'Off')}
|
|
971
|
+
</StatusBadge>
|
|
972
|
+
) : null}
|
|
973
|
+
</header>
|
|
974
|
+
|
|
975
|
+
<div className="mt-3">
|
|
976
|
+
{enforced ? (
|
|
977
|
+
<div className="flex flex-wrap items-center gap-2" data-ai-agent-moderation-enforced>
|
|
978
|
+
<StatusBadge variant="warning" dot>
|
|
979
|
+
{t('ai_assistant.agents.moderation.enforcedBadge', 'Enforced')}
|
|
980
|
+
</StatusBadge>
|
|
981
|
+
<p className="text-xs text-muted-foreground">
|
|
982
|
+
{t(
|
|
983
|
+
'ai_assistant.agents.moderation.enforcedHint',
|
|
984
|
+
'This agent accepts untrusted input, so moderation is always on and cannot be disabled here.',
|
|
985
|
+
)}
|
|
986
|
+
</p>
|
|
987
|
+
</div>
|
|
988
|
+
) : (
|
|
989
|
+
<div className="max-w-xs">
|
|
990
|
+
<span className="text-overline font-semibold uppercase tracking-wider text-muted-foreground">
|
|
991
|
+
{t('ai_assistant.agents.moderation.controlLabel', 'Moderation')}
|
|
992
|
+
</span>
|
|
993
|
+
<div className="mt-1">
|
|
994
|
+
<Select
|
|
995
|
+
value={currentValue}
|
|
996
|
+
onValueChange={(value) => save(value as ModerationControlValue)}
|
|
997
|
+
disabled={isSaving || !agentResolution}
|
|
998
|
+
>
|
|
999
|
+
<SelectTrigger aria-label={t('ai_assistant.agents.moderation.controlLabel', 'Moderation')}>
|
|
1000
|
+
<SelectValue />
|
|
1001
|
+
</SelectTrigger>
|
|
1002
|
+
<SelectContent>
|
|
1003
|
+
<SelectItem value="inherit">
|
|
1004
|
+
{t('ai_assistant.agents.moderation.inheritLabel', 'Inherit')}
|
|
1005
|
+
</SelectItem>
|
|
1006
|
+
<SelectItem value="on">
|
|
1007
|
+
{t('ai_assistant.agents.moderation.onLabel', 'On')}
|
|
1008
|
+
</SelectItem>
|
|
1009
|
+
<SelectItem value="off">
|
|
1010
|
+
{t('ai_assistant.agents.moderation.offLabel', 'Off')}
|
|
1011
|
+
</SelectItem>
|
|
1012
|
+
</SelectContent>
|
|
1013
|
+
</Select>
|
|
1014
|
+
</div>
|
|
1015
|
+
</div>
|
|
1016
|
+
)}
|
|
1017
|
+
</div>
|
|
1018
|
+
</section>
|
|
1019
|
+
)
|
|
1020
|
+
}
|
|
1021
|
+
|
|
878
1022
|
function AgentModelOverrideSection({ agent }: { agent: AgentSettings }) {
|
|
879
1023
|
const t = useT()
|
|
880
1024
|
const queryClient = useQueryClient()
|
|
@@ -1190,7 +1334,7 @@ function AgentModelOverrideSection({ agent }: { agent: AgentSettings }) {
|
|
|
1190
1334
|
)}
|
|
1191
1335
|
/>
|
|
1192
1336
|
) : settingsQuery.isError ? (
|
|
1193
|
-
<Alert
|
|
1337
|
+
<Alert status="error" data-ai-agent-model-override-load-error>
|
|
1194
1338
|
<AlertTitle>
|
|
1195
1339
|
{t(
|
|
1196
1340
|
'ai_assistant.agents.model_override.loadErrorTitle',
|
|
@@ -1400,12 +1544,12 @@ function AgentModelOverrideSection({ agent }: { agent: AgentSettings }) {
|
|
|
1400
1544
|
) : null}
|
|
1401
1545
|
|
|
1402
1546
|
{state.kind === 'success' ? (
|
|
1403
|
-
<Alert
|
|
1547
|
+
<Alert status="success" data-ai-agent-model-override-state="success">
|
|
1404
1548
|
<AlertDescription>{state.message}</AlertDescription>
|
|
1405
1549
|
</Alert>
|
|
1406
1550
|
) : null}
|
|
1407
1551
|
{state.kind === 'error' ? (
|
|
1408
|
-
<Alert
|
|
1552
|
+
<Alert status="error" data-ai-agent-model-override-state="error">
|
|
1409
1553
|
<AlertDescription>{state.message}</AlertDescription>
|
|
1410
1554
|
</Alert>
|
|
1411
1555
|
) : null}
|
|
@@ -1649,7 +1793,7 @@ function LoopPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
1649
1793
|
message={t('ai_assistant.agents.loop_policy.loading', 'Loading loop policy...')}
|
|
1650
1794
|
/>
|
|
1651
1795
|
) : query.isError ? (
|
|
1652
|
-
<Alert
|
|
1796
|
+
<Alert status="error" data-ai-agent-loop-policy-load-error>
|
|
1653
1797
|
<AlertTitle>
|
|
1654
1798
|
{t('ai_assistant.agents.loop_policy.loadErrorTitle', 'Failed to load loop policy')}
|
|
1655
1799
|
</AlertTitle>
|
|
@@ -1747,7 +1891,7 @@ function LoopPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
1747
1891
|
</div>
|
|
1748
1892
|
|
|
1749
1893
|
{state.kind === 'success' ? (
|
|
1750
|
-
<Alert
|
|
1894
|
+
<Alert status="success" data-ai-agent-loop-policy-state="success">
|
|
1751
1895
|
<AlertTitle>
|
|
1752
1896
|
{t('ai_assistant.agents.loop_policy.savedTitle', 'Loop policy updated')}
|
|
1753
1897
|
</AlertTitle>
|
|
@@ -1755,7 +1899,7 @@ function LoopPolicySection({ agent }: { agent: AgentSettings }) {
|
|
|
1755
1899
|
</Alert>
|
|
1756
1900
|
) : null}
|
|
1757
1901
|
{state.kind === 'error' ? (
|
|
1758
|
-
<Alert
|
|
1902
|
+
<Alert status="error" data-ai-agent-loop-policy-state="error">
|
|
1759
1903
|
<AlertTitle>
|
|
1760
1904
|
{t(
|
|
1761
1905
|
'ai_assistant.agents.loop_policy.errorTitle',
|
|
@@ -2063,6 +2207,8 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
2063
2207
|
|
|
2064
2208
|
<AgentModelOverrideSection agent={agent} />
|
|
2065
2209
|
|
|
2210
|
+
<ModerationSection agent={agent} />
|
|
2211
|
+
|
|
2066
2212
|
<MutationPolicySection agent={agent} />
|
|
2067
2213
|
|
|
2068
2214
|
<LoopPolicySection agent={agent} />
|
|
@@ -2100,7 +2246,7 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
2100
2246
|
</header>
|
|
2101
2247
|
<div className="mt-3">
|
|
2102
2248
|
<Alert
|
|
2103
|
-
|
|
2249
|
+
status="information"
|
|
2104
2250
|
icon={<Wand2 aria-hidden="true" />}
|
|
2105
2251
|
data-ai-agent-prompt-notice
|
|
2106
2252
|
>
|
|
@@ -2116,7 +2262,7 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
2116
2262
|
</Alert>
|
|
2117
2263
|
</div>
|
|
2118
2264
|
{saveState.kind === 'success' ? (
|
|
2119
|
-
<Alert
|
|
2265
|
+
<Alert status="success" className="mt-3" data-ai-agent-prompt-state="success">
|
|
2120
2266
|
<AlertTitle>
|
|
2121
2267
|
{saveState.version > 0
|
|
2122
2268
|
? t('ai_assistant.agents.override.savedTitle', 'Prompt override saved')
|
|
@@ -2130,7 +2276,7 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
2130
2276
|
</Alert>
|
|
2131
2277
|
) : null}
|
|
2132
2278
|
{saveState.kind === 'error' ? (
|
|
2133
|
-
<Alert
|
|
2279
|
+
<Alert status="error" className="mt-3" data-ai-agent-prompt-state="error">
|
|
2134
2280
|
<AlertTitle>
|
|
2135
2281
|
{t('ai_assistant.agents.override.errorTitle', 'Failed to save prompt override')}
|
|
2136
2282
|
</AlertTitle>
|
|
@@ -2250,7 +2396,7 @@ function AgentDetailPanel({ agent }: { agent: AgentSettings }) {
|
|
|
2250
2396
|
)}
|
|
2251
2397
|
/>
|
|
2252
2398
|
) : overrideQuery.isError ? (
|
|
2253
|
-
<Alert
|
|
2399
|
+
<Alert status="error" data-ai-agent-override-history-error>
|
|
2254
2400
|
<AlertTitle>
|
|
2255
2401
|
{t(
|
|
2256
2402
|
'ai_assistant.agents.override.history.errorTitle',
|
|
@@ -2359,7 +2505,7 @@ export function AiAgentSettingsPageClient() {
|
|
|
2359
2505
|
|
|
2360
2506
|
if (isError) {
|
|
2361
2507
|
return (
|
|
2362
|
-
<Alert
|
|
2508
|
+
<Alert status="error" data-ai-agent-settings-error>
|
|
2363
2509
|
<AlertTitle>
|
|
2364
2510
|
{t('ai_assistant.agents.loadErrorTitle', 'Failed to load AI agents')}
|
|
2365
2511
|
</AlertTitle>
|
|
@@ -129,7 +129,7 @@ export function AiTenantAllowlistPageClient(): React.JSX.Element {
|
|
|
129
129
|
return (
|
|
130
130
|
<div className="flex max-w-3xl flex-col gap-4">
|
|
131
131
|
{pageHeader}
|
|
132
|
-
<Alert
|
|
132
|
+
<Alert status="error">
|
|
133
133
|
<AlertTitle>{t('ai_assistant.allowlist.loadError.title', 'Failed to load allowlist')}</AlertTitle>
|
|
134
134
|
<AlertDescription>
|
|
135
135
|
{settingsQuery.error instanceof Error
|
|
@@ -311,7 +311,7 @@ export function AiTenantAllowlistPageClient(): React.JSX.Element {
|
|
|
311
311
|
{envBanner}
|
|
312
312
|
|
|
313
313
|
{feedback ? (
|
|
314
|
-
<Alert
|
|
314
|
+
<Alert status={feedback.kind === 'error' ? 'error' : undefined}>
|
|
315
315
|
<AlertDescription>{feedback.text}</AlertDescription>
|
|
316
316
|
</Alert>
|
|
317
317
|
) : null}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useQuery } from '@tanstack/react-query'
|
|
5
|
+
import type { ColumnDef } from '@tanstack/react-table'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { DataTable } from '@open-mercato/ui/backend/DataTable'
|
|
8
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
10
|
+
import { Label } from '@open-mercato/ui/primitives/label'
|
|
11
|
+
import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
|
|
12
|
+
import { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
13
|
+
|
|
14
|
+
const PAGE_SIZE = 50
|
|
15
|
+
|
|
16
|
+
type ModerationCategory = { flagged: boolean; score: number }
|
|
17
|
+
|
|
18
|
+
type ModerationFlagRow = {
|
|
19
|
+
id: string
|
|
20
|
+
tenantId: string
|
|
21
|
+
organizationId: string | null
|
|
22
|
+
agentId: string
|
|
23
|
+
userId: string
|
|
24
|
+
providerId: string
|
|
25
|
+
modelId: string
|
|
26
|
+
categories: Record<string, ModerationCategory>
|
|
27
|
+
createdAt: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type ModerationFlagsResponse = {
|
|
31
|
+
items: ModerationFlagRow[]
|
|
32
|
+
total: number
|
|
33
|
+
page: number
|
|
34
|
+
pageSize: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function fetchModerationFlags(params: {
|
|
38
|
+
page: number
|
|
39
|
+
from: string
|
|
40
|
+
to: string
|
|
41
|
+
agentId: string
|
|
42
|
+
userId: string
|
|
43
|
+
}): Promise<ModerationFlagsResponse> {
|
|
44
|
+
const search = new URLSearchParams()
|
|
45
|
+
search.set('page', String(params.page))
|
|
46
|
+
search.set('pageSize', String(PAGE_SIZE))
|
|
47
|
+
if (params.from) search.set('from', params.from)
|
|
48
|
+
if (params.to) search.set('to', params.to)
|
|
49
|
+
if (params.agentId) search.set('agentId', params.agentId)
|
|
50
|
+
if (params.userId) search.set('userId', params.userId)
|
|
51
|
+
const { result, status } = await apiCallOrThrow<ModerationFlagsResponse>(
|
|
52
|
+
`/api/ai_assistant/moderation-flags?${search.toString()}`,
|
|
53
|
+
undefined,
|
|
54
|
+
{ errorMessage: 'Failed to load moderation flags' },
|
|
55
|
+
)
|
|
56
|
+
if (!result) throw new Error(`Failed to load moderation flags (${status})`)
|
|
57
|
+
return result
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function flaggedCategoryNames(categories: Record<string, ModerationCategory>): string[] {
|
|
61
|
+
return Object.entries(categories)
|
|
62
|
+
.filter(([, value]) => value.flagged)
|
|
63
|
+
.map(([name]) => name)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function AiModerationFlagsPageClient() {
|
|
67
|
+
const t = useT()
|
|
68
|
+
const [page, setPage] = React.useState(1)
|
|
69
|
+
const [from, setFrom] = React.useState('')
|
|
70
|
+
const [to, setTo] = React.useState('')
|
|
71
|
+
const [agentId, setAgentId] = React.useState('')
|
|
72
|
+
const [userId, setUserId] = React.useState('')
|
|
73
|
+
const [applied, setApplied] = React.useState({ from: '', to: '', agentId: '', userId: '' })
|
|
74
|
+
|
|
75
|
+
const query = useQuery<ModerationFlagsResponse>({
|
|
76
|
+
queryKey: ['ai_assistant', 'moderation_flags', page, applied],
|
|
77
|
+
queryFn: () => fetchModerationFlags({ page, ...applied }),
|
|
78
|
+
retry: false,
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const applyFilters = React.useCallback(() => {
|
|
82
|
+
setPage(1)
|
|
83
|
+
setApplied({ from, to, agentId, userId })
|
|
84
|
+
}, [from, to, agentId, userId])
|
|
85
|
+
|
|
86
|
+
const columns = React.useMemo<ColumnDef<ModerationFlagRow, unknown>[]>(
|
|
87
|
+
() => [
|
|
88
|
+
{
|
|
89
|
+
accessorKey: 'agentId',
|
|
90
|
+
header: t('ai_assistant.moderationFlags.columns.agent', 'Agent'),
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
accessorKey: 'userId',
|
|
94
|
+
header: t('ai_assistant.moderationFlags.columns.user', 'User'),
|
|
95
|
+
meta: { truncate: true, maxWidth: 220 },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
accessorKey: 'categories',
|
|
99
|
+
header: t('ai_assistant.moderationFlags.columns.categories', 'Categories'),
|
|
100
|
+
cell: ({ row }) => {
|
|
101
|
+
const names = flaggedCategoryNames(row.original.categories)
|
|
102
|
+
if (names.length === 0) {
|
|
103
|
+
return <span className="text-muted-foreground">—</span>
|
|
104
|
+
}
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex flex-wrap gap-1">
|
|
107
|
+
{names.map((name) => (
|
|
108
|
+
<StatusBadge key={name} variant="error" dot>
|
|
109
|
+
{name}
|
|
110
|
+
</StatusBadge>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
accessorKey: 'createdAt',
|
|
118
|
+
header: t('ai_assistant.moderationFlags.columns.createdAt', 'Flagged at'),
|
|
119
|
+
cell: ({ row }) => new Date(row.original.createdAt).toLocaleString(),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
[t],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const total = query.data?.total ?? 0
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="flex flex-col gap-4">
|
|
129
|
+
<div>
|
|
130
|
+
<h2 className="text-base font-semibold">
|
|
131
|
+
{t('ai_assistant.moderationFlags.title', 'Moderation flags')}
|
|
132
|
+
</h2>
|
|
133
|
+
<p className="text-xs text-muted-foreground">
|
|
134
|
+
{t(
|
|
135
|
+
'ai_assistant.moderationFlags.subtitle',
|
|
136
|
+
'Inputs blocked by the content safety filter. Category flags and scores only — no prompt content is stored.',
|
|
137
|
+
)}
|
|
138
|
+
</p>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className="flex flex-wrap items-end gap-3">
|
|
142
|
+
<div className="flex flex-col gap-1">
|
|
143
|
+
<Label htmlFor="moderation-from">{t('ai_assistant.moderationFlags.filters.from', 'From')}</Label>
|
|
144
|
+
<Input id="moderation-from" type="date" value={from} onChange={(e) => setFrom(e.target.value)} />
|
|
145
|
+
</div>
|
|
146
|
+
<div className="flex flex-col gap-1">
|
|
147
|
+
<Label htmlFor="moderation-to">{t('ai_assistant.moderationFlags.filters.to', 'To')}</Label>
|
|
148
|
+
<Input id="moderation-to" type="date" value={to} onChange={(e) => setTo(e.target.value)} />
|
|
149
|
+
</div>
|
|
150
|
+
<div className="flex flex-col gap-1">
|
|
151
|
+
<Label htmlFor="moderation-agent">{t('ai_assistant.moderationFlags.columns.agent', 'Agent')}</Label>
|
|
152
|
+
<Input
|
|
153
|
+
id="moderation-agent"
|
|
154
|
+
value={agentId}
|
|
155
|
+
onChange={(e) => setAgentId(e.target.value)}
|
|
156
|
+
placeholder={t('ai_assistant.moderationFlags.filters.agentPlaceholder', 'module.agent')}
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
<div className="flex flex-col gap-1">
|
|
160
|
+
<Label htmlFor="moderation-user">{t('ai_assistant.moderationFlags.columns.user', 'User')}</Label>
|
|
161
|
+
<Input id="moderation-user" value={userId} onChange={(e) => setUserId(e.target.value)} />
|
|
162
|
+
</div>
|
|
163
|
+
<Button type="button" variant="outline" onClick={applyFilters}>
|
|
164
|
+
{t('ai_assistant.moderationFlags.filters.apply', 'Apply')}
|
|
165
|
+
</Button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<DataTable<ModerationFlagRow>
|
|
169
|
+
columns={columns}
|
|
170
|
+
data={query.data?.items ?? []}
|
|
171
|
+
isLoading={query.isLoading}
|
|
172
|
+
error={query.error ? (query.error as Error).message : undefined}
|
|
173
|
+
emptyState={t('ai_assistant.moderationFlags.empty', 'No flagged messages.')}
|
|
174
|
+
pagination={{
|
|
175
|
+
page,
|
|
176
|
+
pageSize: PAGE_SIZE,
|
|
177
|
+
total,
|
|
178
|
+
totalPages: Math.max(1, Math.ceil(total / PAGE_SIZE)),
|
|
179
|
+
onPageChange: setPage,
|
|
180
|
+
}}
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default AiModerationFlagsPageClient
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const moderationIcon = React.createElement(
|
|
4
|
+
'svg',
|
|
5
|
+
{ width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
|
6
|
+
React.createElement('path', { d: 'M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z' }),
|
|
7
|
+
React.createElement('path', { d: 'M12 8v4' }),
|
|
8
|
+
React.createElement('path', { d: 'M12 16h.01' }),
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
requireAuth: true,
|
|
13
|
+
requireFeatures: ['ai_assistant.settings.manage'],
|
|
14
|
+
pageTitle: 'Moderation flags',
|
|
15
|
+
pageTitleKey: 'ai_assistant.moderationFlags.navTitle',
|
|
16
|
+
pageGroup: 'Module Configs',
|
|
17
|
+
pageGroupKey: 'settings.sections.moduleConfigs',
|
|
18
|
+
pageOrder: 432,
|
|
19
|
+
icon: moderationIcon,
|
|
20
|
+
pageContext: 'settings' as const,
|
|
21
|
+
breadcrumb: [
|
|
22
|
+
{ label: 'Moderation flags', labelKey: 'ai_assistant.moderationFlags.navTitle' },
|
|
23
|
+
],
|
|
24
|
+
} as const
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
|
+
import { AiModerationFlagsPageClient } from './AiModerationFlagsPageClient'
|
|
3
|
+
|
|
4
|
+
export default async function AiModerationFlagsPage() {
|
|
5
|
+
return (
|
|
6
|
+
<Page>
|
|
7
|
+
<PageBody>
|
|
8
|
+
<AiModerationFlagsPageClient />
|
|
9
|
+
</PageBody>
|
|
10
|
+
</Page>
|
|
11
|
+
)
|
|
12
|
+
}
|
package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx
CHANGED
|
@@ -318,7 +318,7 @@ function ChatLane({ agent, debug }: { agent: PlaygroundAgent; debug: boolean })
|
|
|
318
318
|
|
|
319
319
|
if (agent.executionMode !== 'chat') {
|
|
320
320
|
return (
|
|
321
|
-
<Alert
|
|
321
|
+
<Alert status="information" data-ai-playground-unsupported="chat">
|
|
322
322
|
<AlertTitle>
|
|
323
323
|
{t(
|
|
324
324
|
'ai_assistant.playground.chat.notSupportedTitle',
|
|
@@ -413,7 +413,7 @@ function ObjectLane({ agent }: { agent: PlaygroundAgent }) {
|
|
|
413
413
|
|
|
414
414
|
if (!isSupported) {
|
|
415
415
|
return (
|
|
416
|
-
<Alert
|
|
416
|
+
<Alert status="information" data-ai-playground-unsupported="object">
|
|
417
417
|
<AlertTitle>
|
|
418
418
|
{t(
|
|
419
419
|
'ai_assistant.playground.object.notSupportedTitle',
|
|
@@ -474,7 +474,7 @@ function ObjectLane({ agent }: { agent: PlaygroundAgent }) {
|
|
|
474
474
|
</div>
|
|
475
475
|
|
|
476
476
|
{error ? (
|
|
477
|
-
<Alert
|
|
477
|
+
<Alert status="error" data-ai-playground-object-error={error.code ?? 'unknown'}>
|
|
478
478
|
<AlertTitle>
|
|
479
479
|
{t('ai_assistant.playground.object.errorTitle', 'Object run failed')}
|
|
480
480
|
</AlertTitle>
|
|
@@ -582,7 +582,7 @@ export function AiPlaygroundPageClient() {
|
|
|
582
582
|
|
|
583
583
|
if (isError) {
|
|
584
584
|
return (
|
|
585
|
-
<Alert
|
|
585
|
+
<Alert status="error" data-ai-playground-error>
|
|
586
586
|
<AlertTitle>
|
|
587
587
|
{t('ai_assistant.playground.loadErrorTitle', 'Failed to load AI agents')}
|
|
588
588
|
</AlertTitle>
|
|
@@ -361,6 +361,17 @@ export class AiAgentRuntimeOverride {
|
|
|
361
361
|
*/
|
|
362
362
|
@Property({ name: 'loop_active_tools_json', type: 'jsonb', nullable: true })
|
|
363
363
|
loopActiveToolsJson?: unknown | null
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Per-agent (or tenant-wide when `agent_id` is null) input-moderation
|
|
367
|
+
* override. `true` = on, `false` = off, `null` = inherit (env default →
|
|
368
|
+
* off). Additive nullable column; existing rows inherit. The `untrustedInput`
|
|
369
|
+
* agent flag still forces moderation regardless of this value.
|
|
370
|
+
*
|
|
371
|
+
* Spec `2026-06-04-ai-input-moderation-and-safety-identifiers`.
|
|
372
|
+
*/
|
|
373
|
+
@Property({ name: 'input_moderation', type: 'boolean', nullable: true })
|
|
374
|
+
inputModeration?: boolean | null
|
|
364
375
|
}
|
|
365
376
|
|
|
366
377
|
/**
|
|
@@ -933,3 +944,48 @@ export class AiChatMessage {
|
|
|
933
944
|
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
934
945
|
deletedAt?: Date | null
|
|
935
946
|
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Append-only audit record of an input that the moderation gate blocked.
|
|
950
|
+
*
|
|
951
|
+
* Stores ONLY category flags + numeric scores — never the prompt content or
|
|
952
|
+
* any PII (Q4 data-minimization decision). Enough to identify repeat abusers
|
|
953
|
+
* and tune policy. No `updated_at` / `deleted_at`: rows are immutable. Two
|
|
954
|
+
* indexes back the audit listing (`tenant_id, created_at`) and the per-user
|
|
955
|
+
* abuse review (`tenant_id, user_id`); every read filters `tenant_id`.
|
|
956
|
+
*
|
|
957
|
+
* Spec `2026-06-04-ai-input-moderation-and-safety-identifiers`.
|
|
958
|
+
*/
|
|
959
|
+
@Entity({ tableName: 'ai_moderation_flags' })
|
|
960
|
+
@Index({ name: 'ai_moderation_flags_tenant_created_idx', properties: ['tenantId', 'createdAt'] })
|
|
961
|
+
@Index({ name: 'ai_moderation_flags_tenant_user_idx', properties: ['tenantId', 'userId'] })
|
|
962
|
+
export class AiModerationFlag {
|
|
963
|
+
[OptionalProps]?: 'id' | 'createdAt' | 'organizationId'
|
|
964
|
+
|
|
965
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
966
|
+
id!: string
|
|
967
|
+
|
|
968
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
969
|
+
tenantId!: string
|
|
970
|
+
|
|
971
|
+
@Property({ name: 'organization_id', type: 'uuid', nullable: true })
|
|
972
|
+
organizationId?: string | null
|
|
973
|
+
|
|
974
|
+
@Property({ name: 'agent_id', type: 'text' })
|
|
975
|
+
agentId!: string
|
|
976
|
+
|
|
977
|
+
@Property({ name: 'user_id', type: 'text' })
|
|
978
|
+
userId!: string
|
|
979
|
+
|
|
980
|
+
@Property({ name: 'provider_id', type: 'text' })
|
|
981
|
+
providerId!: string
|
|
982
|
+
|
|
983
|
+
@Property({ name: 'model_id', type: 'text' })
|
|
984
|
+
modelId!: string
|
|
985
|
+
|
|
986
|
+
@Property({ name: 'categories', type: 'jsonb' })
|
|
987
|
+
categories!: Record<string, { flagged: boolean; score: number }>
|
|
988
|
+
|
|
989
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
990
|
+
createdAt: Date = new Date()
|
|
991
|
+
}
|
|
@@ -35,6 +35,8 @@ export interface AiAgentRuntimeOverrideInput extends AiAgentRuntimeOverrideLoopI
|
|
|
35
35
|
baseURL?: string | null
|
|
36
36
|
allowedOverrideProviders?: string[] | null
|
|
37
37
|
allowedOverrideModelsByProvider?: Record<string, string[]>
|
|
38
|
+
/** Input-moderation override: true = on, false = off, null = inherit. */
|
|
39
|
+
inputModeration?: boolean | null
|
|
38
40
|
/**
|
|
39
41
|
* Optional: the agent's declared allowedTools. When provided, loopActiveToolsJson
|
|
40
42
|
* is validated to be a subset. When omitted, allowlist validation is skipped
|
|
@@ -270,6 +272,7 @@ export class AiAgentRuntimeOverrideRepository {
|
|
|
270
272
|
if ('loopMaxTokens' in input) existing.loopMaxTokens = input.loopMaxTokens ?? null
|
|
271
273
|
if ('loopStopWhenJson' in input) existing.loopStopWhenJson = input.loopStopWhenJson ?? null
|
|
272
274
|
if ('loopActiveToolsJson' in input) existing.loopActiveToolsJson = input.loopActiveToolsJson ?? null
|
|
275
|
+
if ('inputModeration' in input) existing.inputModeration = input.inputModeration ?? null
|
|
273
276
|
await tx.persist(existing).flush()
|
|
274
277
|
return existing
|
|
275
278
|
}
|
|
@@ -295,6 +298,7 @@ export class AiAgentRuntimeOverrideRepository {
|
|
|
295
298
|
loopMaxTokens: input.loopMaxTokens ?? null,
|
|
296
299
|
loopStopWhenJson: input.loopStopWhenJson ?? null,
|
|
297
300
|
loopActiveToolsJson: input.loopActiveToolsJson ?? null,
|
|
301
|
+
inputModeration: input.inputModeration ?? null,
|
|
298
302
|
} as unknown as AiAgentRuntimeOverride)
|
|
299
303
|
await tx.persist(row).flush()
|
|
300
304
|
return row
|