@open-mercato/ai-assistant 0.6.7-develop.6775.1.c2313bb8a3 → 0.6.7-develop.6785.1.1dd7cfac55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +12 -29
  3. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js +80 -0
  4. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.js.map +7 -0
  5. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js +124 -0
  6. package/dist/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.js.map +7 -0
  7. package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js +30 -0
  8. package/dist/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.js.map +2 -2
  9. package/dist/modules/ai_assistant/api/ai/chat/route.js +7 -0
  10. package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
  11. package/dist/modules/ai_assistant/api/moderation-flags/route.js +121 -0
  12. package/dist/modules/ai_assistant/api/moderation-flags/route.js.map +7 -0
  13. package/dist/modules/ai_assistant/api/settings/allowlist/route.js +10 -10
  14. package/dist/modules/ai_assistant/api/settings/allowlist/route.js.map +2 -2
  15. package/dist/modules/ai_assistant/api/settings/route.js +34 -13
  16. package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
  17. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +106 -0
  18. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
  19. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js +139 -0
  20. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.js.map +7 -0
  21. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js +10 -0
  22. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.js.map +7 -0
  23. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js +26 -0
  24. package/dist/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.js.map +7 -0
  25. package/dist/modules/ai_assistant/data/entities.js +42 -0
  26. package/dist/modules/ai_assistant/data/entities.js.map +2 -2
  27. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +3 -1
  28. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
  29. package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js +44 -0
  30. package/dist/modules/ai_assistant/data/repositories/AiModerationFlagRepository.js.map +7 -0
  31. package/dist/modules/ai_assistant/di.js +6 -2
  32. package/dist/modules/ai_assistant/di.js.map +2 -2
  33. package/dist/modules/ai_assistant/events.js +6 -0
  34. package/dist/modules/ai_assistant/events.js.map +2 -2
  35. package/dist/modules/ai_assistant/i18n/de.json +26 -0
  36. package/dist/modules/ai_assistant/i18n/en.json +26 -0
  37. package/dist/modules/ai_assistant/i18n/es.json +26 -0
  38. package/dist/modules/ai_assistant/i18n/pl.json +26 -0
  39. package/dist/modules/ai_assistant/lib/agent-runtime.js +158 -5
  40. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +2 -2
  41. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
  42. package/dist/modules/ai_assistant/lib/auth.js +6 -7
  43. package/dist/modules/ai_assistant/lib/auth.js.map +2 -2
  44. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js +3 -0
  45. package/dist/modules/ai_assistant/lib/llm-adapters/anthropic.js.map +2 -2
  46. package/dist/modules/ai_assistant/lib/llm-adapters/openai.js +8 -1
  47. package/dist/modules/ai_assistant/lib/llm-adapters/openai.js.map +2 -2
  48. package/dist/modules/ai_assistant/lib/model-factory.js +6 -1
  49. package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
  50. package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js +43 -0
  51. package/dist/modules/ai_assistant/lib/moderation-flag-recorder.js.map +7 -0
  52. package/dist/modules/ai_assistant/lib/moderation-policy.js +24 -0
  53. package/dist/modules/ai_assistant/lib/moderation-policy.js.map +7 -0
  54. package/dist/modules/ai_assistant/lib/moderation.js +109 -0
  55. package/dist/modules/ai_assistant/lib/moderation.js.map +7 -0
  56. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js +2 -0
  57. package/dist/modules/ai_assistant/lib/openai-compatible-presets.js.map +2 -2
  58. package/dist/modules/ai_assistant/lib/tool-search.js +4 -12
  59. package/dist/modules/ai_assistant/lib/tool-search.js.map +2 -2
  60. package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js +19 -0
  61. package/dist/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.js.map +7 -0
  62. package/generated/entities/ai_agent_runtime_override/index.ts +1 -0
  63. package/generated/entities/ai_moderation_flag/index.ts +9 -0
  64. package/generated/entities.ids.generated.ts +2 -1
  65. package/generated/entity-fields-registry.ts +13 -1
  66. package/package.json +6 -6
  67. package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-008-settings-and-audit.spec.ts +126 -0
  68. package/src/modules/ai_assistant/__integration__/TC-AI-MODERATION-009-ui.spec.ts +161 -0
  69. package/src/modules/ai_assistant/__integration__/helpers/aiAssistantFixtures.ts +48 -0
  70. package/src/modules/ai_assistant/api/ai/chat/route.ts +10 -0
  71. package/src/modules/ai_assistant/api/moderation-flags/__tests__/route.test.ts +122 -0
  72. package/src/modules/ai_assistant/api/moderation-flags/route.ts +137 -0
  73. package/src/modules/ai_assistant/api/settings/__tests__/route.test.ts +58 -8
  74. package/src/modules/ai_assistant/api/settings/allowlist/route.ts +10 -12
  75. package/src/modules/ai_assistant/api/settings/route.ts +33 -10
  76. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +146 -0
  77. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/AiModerationFlagsPageClient.tsx +186 -0
  78. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.meta.ts +24 -0
  79. package/src/modules/ai_assistant/backend/config/ai-assistant/moderation-flags/page.tsx +12 -0
  80. package/src/modules/ai_assistant/data/entities.ts +56 -0
  81. package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +4 -0
  82. package/src/modules/ai_assistant/data/repositories/AiModerationFlagRepository.ts +79 -0
  83. package/src/modules/ai_assistant/di.ts +5 -1
  84. package/src/modules/ai_assistant/events.ts +21 -0
  85. package/src/modules/ai_assistant/i18n/de.json +26 -0
  86. package/src/modules/ai_assistant/i18n/en.json +26 -0
  87. package/src/modules/ai_assistant/i18n/es.json +26 -0
  88. package/src/modules/ai_assistant/i18n/pl.json +26 -0
  89. package/src/modules/ai_assistant/lib/__tests__/auth.test.ts +2 -5
  90. package/src/modules/ai_assistant/lib/__tests__/input-moderation-gate.test.ts +121 -0
  91. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-anthropic.test.ts +13 -0
  92. package/src/modules/ai_assistant/lib/__tests__/llm-adapters-openai.test.ts +41 -0
  93. package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +60 -0
  94. package/src/modules/ai_assistant/lib/__tests__/moderation-flag-recorder.test.ts +76 -0
  95. package/src/modules/ai_assistant/lib/__tests__/moderation-policy.test.ts +86 -0
  96. package/src/modules/ai_assistant/lib/__tests__/moderation.test.ts +114 -0
  97. package/src/modules/ai_assistant/lib/agent-runtime.ts +259 -0
  98. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +10 -0
  99. package/src/modules/ai_assistant/lib/auth.ts +6 -15
  100. package/src/modules/ai_assistant/lib/llm-adapters/anthropic.ts +10 -0
  101. package/src/modules/ai_assistant/lib/llm-adapters/openai.ts +32 -1
  102. package/src/modules/ai_assistant/lib/model-factory.ts +33 -0
  103. package/src/modules/ai_assistant/lib/moderation-flag-recorder.ts +75 -0
  104. package/src/modules/ai_assistant/lib/moderation-policy.ts +77 -0
  105. package/src/modules/ai_assistant/lib/moderation.ts +179 -0
  106. package/src/modules/ai_assistant/lib/openai-compatible-presets.ts +2 -0
  107. package/src/modules/ai_assistant/lib/tool-search.ts +4 -17
  108. package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +388 -16
  109. package/src/modules/ai_assistant/migrations/Migration20260610134045_ai_assistant.ts +21 -0
@@ -11,6 +11,18 @@ import {
11
11
  Experimental_Agent as ToolLoopAgent
12
12
  } from "ai";
13
13
  import { createModelFactory, resolveAllowRuntimeOverride } from "./model-factory.js";
14
+ import { computeEndUserIdentifier } from "@open-mercato/shared/lib/ai/safety-identifier";
15
+ import { llmProviderRegistry } from "@open-mercato/shared/lib/ai/llm-provider-registry";
16
+ import {
17
+ AiModerationBlockedError,
18
+ AiModerationUnavailableError
19
+ } from "./moderation.js";
20
+ import {
21
+ isModerationActive,
22
+ resolveModerationPolicy,
23
+ shouldFailClosed
24
+ } from "./moderation-policy.js";
25
+ import { recordModerationFlag } from "./moderation-flag-recorder.js";
14
26
  import {
15
27
  resolveAiAgentTools,
16
28
  AgentPolicyError,
@@ -397,7 +409,7 @@ function assertLoopObjectModeCompatible(loopConfig) {
397
409
  );
398
410
  }
399
411
  }
400
- function resolveAgentModel(agent, modelOverride, providerOverride, container, baseUrlOverride, tenantOverride, requestOverride, tenantAllowlist) {
412
+ function resolveAgentModel(agent, modelOverride, providerOverride, container, baseUrlOverride, tenantOverride, requestOverride, tenantAllowlist, endUserIdentifier) {
401
413
  const effectiveContainer = container ?? createContainer();
402
414
  const resolution = createModelFactory(effectiveContainer).resolveModel({
403
415
  moduleId: agent.moduleId,
@@ -410,14 +422,88 @@ function resolveAgentModel(agent, modelOverride, providerOverride, container, ba
410
422
  allowRuntimeOverride: resolveAllowRuntimeOverride(agent),
411
423
  tenantOverride: tenantOverride ?? void 0,
412
424
  requestOverride: requestOverride ?? void 0,
413
- tenantAllowlist: tenantAllowlist ?? null
425
+ tenantAllowlist: tenantAllowlist ?? null,
426
+ endUserIdentifier
414
427
  });
415
428
  return {
416
429
  model: resolution.model,
417
430
  modelId: resolution.modelId,
418
- providerId: resolution.providerId
431
+ providerId: resolution.providerId,
432
+ providerOptions: resolution.providerOptions,
433
+ supportsInputModeration: resolution.supportsInputModeration === true,
434
+ baseURL: resolution.baseURL
419
435
  };
420
436
  }
437
+ function extractLatestUserText(messages) {
438
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
439
+ const raw = messages[index];
440
+ if (raw.role !== "user") continue;
441
+ if (Array.isArray(raw.parts)) {
442
+ return raw.parts.filter((part) => part?.type === "text" && typeof part.text === "string").map((part) => part.text).join("\n");
443
+ }
444
+ return typeof raw.content === "string" ? raw.content : "";
445
+ }
446
+ return "";
447
+ }
448
+ async function runInputModerationGate(params) {
449
+ const policy = resolveModerationPolicy({
450
+ untrustedInput: params.untrustedInput,
451
+ perAgentOverride: params.perAgentOverride,
452
+ tenantWideOverride: params.tenantWideOverride,
453
+ env: params.env
454
+ });
455
+ if (!isModerationActive(policy)) return;
456
+ if (!params.supportsInputModeration) return;
457
+ const text = params.userText.trim();
458
+ if (!text) return;
459
+ const failClosed = shouldFailClosed(policy);
460
+ const failOpenOrThrow = (reason, error) => {
461
+ if (failClosed) {
462
+ if (error instanceof AiModerationUnavailableError) throw error;
463
+ throw new AiModerationUnavailableError(reason, error);
464
+ }
465
+ logger.warn("Input moderation unavailable; failing open for opt-in surface", { reason });
466
+ };
467
+ if (!params.service) {
468
+ return failOpenOrThrow("moderation service is not available in the container");
469
+ }
470
+ const apiKey = params.resolveApiKey();
471
+ if (!apiKey) {
472
+ return failOpenOrThrow("no API key available for the moderation provider");
473
+ }
474
+ let result;
475
+ try {
476
+ result = await params.service.checkInput({
477
+ text,
478
+ apiKey,
479
+ baseURL: params.baseURL,
480
+ model: params.moderationModel
481
+ });
482
+ } catch (error) {
483
+ if (error instanceof AiModerationUnavailableError) {
484
+ return failOpenOrThrow(error.message, error);
485
+ }
486
+ throw error;
487
+ }
488
+ if (result.flagged) {
489
+ if (params.onFlagged) {
490
+ try {
491
+ await params.onFlagged(result.categories);
492
+ } catch (error) {
493
+ logger.error("Moderation flag side effect failed (rejection still applies)", { err: error });
494
+ }
495
+ }
496
+ throw new AiModerationBlockedError(result.categories);
497
+ }
498
+ }
499
+ function resolveEndUserIdentifier(authContext) {
500
+ try {
501
+ return computeEndUserIdentifier(authContext.tenantId, authContext.userId);
502
+ } catch (error) {
503
+ logger.warn("Failed to derive end-user safety identifier", { err: error });
504
+ return void 0;
505
+ }
506
+ }
421
507
  async function resolveTenantAllowlistSnapshot(container, tenantId, organizationId) {
422
508
  if (!tenantId || !container) return null;
423
509
  let em = null;
@@ -550,6 +636,31 @@ async function resolveRuntimeModelOverride(agentId, container, tenantId, organiz
550
636
  return null;
551
637
  }
552
638
  }
639
+ async function resolveModerationOverrideValues(container, tenantId, organizationId, agentId) {
640
+ const empty = { perAgentOverride: null, tenantWideOverride: null };
641
+ if (!tenantId || !container) return empty;
642
+ let em = null;
643
+ try {
644
+ em = container.resolve("em");
645
+ } catch {
646
+ em = null;
647
+ }
648
+ if (!em) return empty;
649
+ try {
650
+ const repo = new AiAgentRuntimeOverrideRepository(em);
651
+ const [agentRow, tenantRow] = await Promise.all([
652
+ repo.getExact({ tenantId, organizationId: organizationId ?? null, agentId }),
653
+ repo.getDefault({ tenantId, organizationId: organizationId ?? null, agentId: null })
654
+ ]);
655
+ return {
656
+ perAgentOverride: agentRow?.inputModeration ?? null,
657
+ tenantWideOverride: tenantRow?.inputModeration ?? null
658
+ };
659
+ } catch (error) {
660
+ logger.warn("Moderation override lookup failed; falling back to env default", { agentId, err: error });
661
+ return empty;
662
+ }
663
+ }
553
664
  function ensureUiMessageShape(messages) {
554
665
  return messages.map((message, index) => {
555
666
  const raw = message;
@@ -750,10 +861,47 @@ async function runAiAgentText(input) {
750
861
  input.baseUrlOverride,
751
862
  tenantRuntimeOverride,
752
863
  input.requestOverride,
753
- tenantAllowlistSnapshot
864
+ tenantAllowlistSnapshot,
865
+ resolveEndUserIdentifier(input.authContext)
754
866
  );
755
867
  const { model } = resolvedModel;
868
+ const modelProviderOptions = resolvedModel.providerOptions;
756
869
  const normalizedMessages = ensureUiMessageShape(input.messages);
870
+ let moderationService = null;
871
+ try {
872
+ moderationService = input.container?.resolve("moderationService") ?? null;
873
+ } catch {
874
+ moderationService = null;
875
+ }
876
+ const moderationOverrides = resolvedModel.supportsInputModeration && agent.untrustedInput !== true ? await resolveModerationOverrideValues(
877
+ input.container,
878
+ input.authContext.tenantId,
879
+ input.authContext.organizationId,
880
+ agent.id
881
+ ) : { perAgentOverride: null, tenantWideOverride: null };
882
+ await runInputModerationGate({
883
+ untrustedInput: agent.untrustedInput,
884
+ supportsInputModeration: resolvedModel.supportsInputModeration,
885
+ perAgentOverride: moderationOverrides.perAgentOverride,
886
+ tenantWideOverride: moderationOverrides.tenantWideOverride,
887
+ userText: extractLatestUserText(normalizedMessages),
888
+ service: moderationService,
889
+ resolveApiKey: () => llmProviderRegistry.get(resolvedModel.providerId)?.resolveApiKey() ?? null,
890
+ baseURL: resolvedModel.baseURL,
891
+ moderationModel: process.env.OM_AI_MODERATION_MODEL,
892
+ onFlagged: (categories) => recordModerationFlag(
893
+ {
894
+ tenantId: input.authContext.tenantId,
895
+ organizationId: input.authContext.organizationId,
896
+ agentId: agent.id,
897
+ userId: input.authContext.userId,
898
+ providerId: resolvedModel.providerId,
899
+ modelId: resolvedModel.modelId,
900
+ categories
901
+ },
902
+ input.container
903
+ )
904
+ });
757
905
  const hydratedMessages = attachAttachmentsToMessages(normalizedMessages, resolvedAttachments);
758
906
  const modelMessages = await convertToModelMessages(hydratedMessages);
759
907
  const effectiveLoop = resolveEffectiveLoopConfig(agent, input.loop, WRAPPER_DEFAULT_LOOP_CHAT);
@@ -814,7 +962,8 @@ async function runAiAgentText(input) {
814
962
  onStepFinish: wiredOnStepFinish,
815
963
  ...effectiveLoop.repairToolCall !== void 0 ? { experimental_repairToolCall: effectiveLoop.repairToolCall } : {},
816
964
  ...sdkActiveTools !== void 0 ? { activeTools: sdkActiveTools } : {},
817
- ...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {}
965
+ ...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {},
966
+ ...modelProviderOptions !== void 0 ? { providerOptions: modelProviderOptions } : {}
818
967
  };
819
968
  builtToolLoopAgent = new ToolLoopAgent(agentSettings);
820
969
  }
@@ -835,6 +984,7 @@ async function runAiAgentText(input) {
835
984
  toolChoice: sdkToolChoice,
836
985
  abortSignal: abortController.signal,
837
986
  finalizeLoopTrace: () => loopTraceCollector.finalize(budgetEnforcer.abortReason),
987
+ ...modelProviderOptions !== void 0 ? { providerOptions: modelProviderOptions } : {},
838
988
  ...builtToolLoopAgent !== void 0 ? { toolLoopAgent: builtToolLoopAgent } : {}
839
989
  };
840
990
  const taskPlanId = `turn_${turnId}`;
@@ -894,6 +1044,7 @@ async function runAiAgentText(input) {
894
1044
  experimental_repairToolCall: effectiveLoop.repairToolCall,
895
1045
  ...sdkActiveTools !== void 0 ? { activeTools: sdkActiveTools } : {},
896
1046
  ...sdkToolChoice !== void 0 ? { toolChoice: sdkToolChoice } : {},
1047
+ ...modelProviderOptions !== void 0 ? { providerOptions: modelProviderOptions } : {},
897
1048
  abortSignal: abortController.signal
898
1049
  });
899
1050
  if (wallClockTimer !== void 0) {
@@ -1101,11 +1252,13 @@ export {
1101
1252
  buildLoopTraceCollector,
1102
1253
  buildWrapperPrepareStep,
1103
1254
  composeSystemPrompt,
1255
+ extractLatestUserText,
1104
1256
  mergeStepOverrides,
1105
1257
  resolveEffectiveLoopConfig,
1106
1258
  resolveLoopBudgetPreset,
1107
1259
  runAiAgentObject,
1108
1260
  runAiAgentText,
1261
+ runInputModerationGate,
1109
1262
  translateStopConditions
1110
1263
  };
1111
1264
  //# sourceMappingURL=agent-runtime.js.map