@open-mercato/ai-assistant 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1

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 (135) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +30 -4
  3. package/dist/frontend/components/AiChatButton.js +3 -2
  4. package/dist/frontend/components/AiChatButton.js.map +2 -2
  5. package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js +364 -0
  6. package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js.map +7 -0
  7. package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +7 -7
  8. package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +2 -2
  9. package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +182 -0
  10. package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +7 -0
  11. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +316 -0
  12. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +7 -0
  13. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +8 -7
  14. package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
  15. package/dist/modules/ai_assistant/api/ai/chat/route.js +43 -20
  16. package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
  17. package/dist/modules/ai_assistant/api/settings/route.js +4 -3
  18. package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
  19. package/dist/modules/ai_assistant/api/usage/daily/route.js +111 -0
  20. package/dist/modules/ai_assistant/api/usage/daily/route.js.map +7 -0
  21. package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +108 -0
  22. package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +7 -0
  23. package/dist/modules/ai_assistant/api/usage/sessions/route.js +153 -0
  24. package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +7 -0
  25. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +335 -38
  26. package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
  27. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +2 -7
  28. package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
  29. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +44 -35
  30. package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
  31. package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js +282 -0
  32. package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js.map +7 -0
  33. package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js +10 -0
  34. package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js.map +7 -0
  35. package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js +25 -0
  36. package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js.map +7 -0
  37. package/dist/modules/ai_assistant/cli.js +12 -0
  38. package/dist/modules/ai_assistant/cli.js.map +2 -2
  39. package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +1 -1
  40. package/dist/modules/ai_assistant/data/entities.js +177 -1
  41. package/dist/modules/ai_assistant/data/entities.js.map +2 -2
  42. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +104 -2
  43. package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
  44. package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js +168 -0
  45. package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js.map +7 -0
  46. package/dist/modules/ai_assistant/events.js +8 -0
  47. package/dist/modules/ai_assistant/events.js.map +2 -2
  48. package/dist/modules/ai_assistant/i18n/de.json +74 -1
  49. package/dist/modules/ai_assistant/i18n/en.json +74 -1
  50. package/dist/modules/ai_assistant/i18n/es.json +75 -2
  51. package/dist/modules/ai_assistant/i18n/pl.json +74 -1
  52. package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
  53. package/dist/modules/ai_assistant/lib/agent-runtime.js +588 -23
  54. package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
  55. package/dist/modules/ai_assistant/lib/agent-tools.js +6 -1
  56. package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
  57. package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
  58. package/dist/modules/ai_assistant/lib/model-factory.js +63 -22
  59. package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
  60. package/dist/modules/ai_assistant/lib/token-usage-recorder.js +78 -0
  61. package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +7 -0
  62. package/dist/modules/ai_assistant/lib/usage-serialization.js +33 -0
  63. package/dist/modules/ai_assistant/lib/usage-serialization.js.map +7 -0
  64. package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js +25 -0
  65. package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js.map +7 -0
  66. package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js +88 -0
  67. package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js.map +7 -0
  68. package/dist/modules/ai_assistant/setup.js +34 -0
  69. package/dist/modules/ai_assistant/setup.js.map +2 -2
  70. package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +114 -0
  71. package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +7 -0
  72. package/generated/entities/ai_agent_runtime_override/index.ts +7 -0
  73. package/generated/entities/ai_token_usage_daily/index.ts +16 -0
  74. package/generated/entities/ai_token_usage_event/index.ts +19 -0
  75. package/generated/entities.ids.generated.ts +2 -0
  76. package/generated/entity-fields-registry.ts +47 -1
  77. package/package.json +15 -7
  78. package/src/frontend/components/AiChatButton.tsx +3 -2
  79. package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.ts +521 -0
  80. package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +8 -8
  81. package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +231 -0
  82. package/src/modules/ai_assistant/__tests__/events.test.ts +4 -3
  83. package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +5 -5
  84. package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +109 -0
  85. package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +388 -0
  86. package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +5 -0
  87. package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +8 -7
  88. package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +102 -5
  89. package/src/modules/ai_assistant/api/ai/chat/route.ts +55 -18
  90. package/src/modules/ai_assistant/api/settings/route.ts +5 -3
  91. package/src/modules/ai_assistant/api/usage/daily/__tests__/route.test.ts +159 -0
  92. package/src/modules/ai_assistant/api/usage/daily/route.ts +126 -0
  93. package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/__tests__/route.test.ts +143 -0
  94. package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +130 -0
  95. package/src/modules/ai_assistant/api/usage/sessions/__tests__/route.test.ts +123 -0
  96. package/src/modules/ai_assistant/api/usage/sessions/route.ts +184 -0
  97. package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +372 -16
  98. package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +1 -4
  99. package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +26 -9
  100. package/src/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.tsx +469 -0
  101. package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.ts +23 -0
  102. package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.tsx +12 -0
  103. package/src/modules/ai_assistant/cli.ts +18 -0
  104. package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +1 -1
  105. package/src/modules/ai_assistant/data/entities.ts +237 -0
  106. package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +135 -3
  107. package/src/modules/ai_assistant/data/repositories/AiTokenUsageRepository.ts +213 -0
  108. package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +223 -0
  109. package/src/modules/ai_assistant/data/repositories/__tests__/AiTokenUsageRepository.test.ts +58 -0
  110. package/src/modules/ai_assistant/events.ts +8 -0
  111. package/src/modules/ai_assistant/i18n/de.json +74 -1
  112. package/src/modules/ai_assistant/i18n/en.json +74 -1
  113. package/src/modules/ai_assistant/i18n/es.json +75 -2
  114. package/src/modules/ai_assistant/i18n/pl.json +74 -1
  115. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +439 -0
  116. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase1.test.ts +243 -0
  117. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase2.test.ts +388 -0
  118. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase3.test.ts +359 -0
  119. package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +2 -2
  120. package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +2 -1
  121. package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +12 -13
  122. package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +77 -14
  123. package/src/modules/ai_assistant/lib/agent-policy.ts +9 -0
  124. package/src/modules/ai_assistant/lib/agent-runtime.ts +1148 -43
  125. package/src/modules/ai_assistant/lib/agent-tools.ts +5 -1
  126. package/src/modules/ai_assistant/lib/ai-agent-definition.ts +289 -2
  127. package/src/modules/ai_assistant/lib/model-factory.ts +128 -43
  128. package/src/modules/ai_assistant/lib/token-usage-recorder.ts +122 -0
  129. package/src/modules/ai_assistant/lib/usage-serialization.ts +29 -0
  130. package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +791 -0
  131. package/src/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.ts +25 -0
  132. package/src/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.ts +89 -0
  133. package/src/modules/ai_assistant/setup.ts +49 -0
  134. package/src/modules/ai_assistant/workers/__tests__/ai-token-usage-prune.test.ts +144 -0
  135. package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +188 -0
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useQuery, useQueryClient } from "@tanstack/react-query";
5
- import { Loader2, Save, Shield, Trash2, Info, AlertCircle, ShieldCheck } from "lucide-react";
5
+ import { Loader2, Save, Shield, Trash2, ShieldCheck } from "lucide-react";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
8
8
  import { Badge } from "@open-mercato/ui/primitives/badge";
@@ -74,7 +74,6 @@ function AiTenantAllowlistPageClient() {
74
74
  return /* @__PURE__ */ jsxs("div", { className: "flex max-w-3xl flex-col gap-4", children: [
75
75
  pageHeader,
76
76
  /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
77
- /* @__PURE__ */ jsx(AlertCircle, { className: "size-4" }),
78
77
  /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.allowlist.loadError.title", "Failed to load allowlist") }),
79
78
  /* @__PURE__ */ jsx(AlertDescription, { children: settingsQuery.error instanceof Error ? settingsQuery.error.message : t("ai_assistant.allowlist.loadError.body", "Try refreshing the page.") })
80
79
  ] })
@@ -209,7 +208,6 @@ function AiTenantAllowlistPageClient() {
209
208
  }
210
209
  };
211
210
  const envBanner = envAllowedProviders === null && Object.keys(envModelsByProvider).length === 0 ? null : /* @__PURE__ */ jsxs(Alert, { children: [
212
- /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }),
213
211
  /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.allowlist.envBanner.title", "Env allowlist is in effect") }),
214
212
  /* @__PURE__ */ jsxs(AlertDescription, { className: "space-y-1", children: [
215
213
  envAllowedProviders ? /* @__PURE__ */ jsxs("div", { children: [
@@ -231,10 +229,7 @@ function AiTenantAllowlistPageClient() {
231
229
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 max-w-3xl", children: [
232
230
  pageHeader,
233
231
  envBanner,
234
- feedback ? /* @__PURE__ */ jsxs(Alert, { variant: feedback.kind === "error" ? "destructive" : void 0, children: [
235
- feedback.kind === "error" ? /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }),
236
- /* @__PURE__ */ jsx(AlertDescription, { children: feedback.text })
237
- ] }) : null,
232
+ feedback ? /* @__PURE__ */ jsx(Alert, { variant: feedback.kind === "error" ? "destructive" : void 0, children: /* @__PURE__ */ jsx(AlertDescription, { children: feedback.text }) }) : null,
238
233
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6 space-y-6", children: [
239
234
  /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
240
235
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Loader2, Save, Shield, Trash2, Info, AlertCircle, ShieldCheck } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\ntype EnvAllowlistConfig = {\n providers: string[] | null\n modelsByProvider: Record<string, string[]>\n hasRestrictions: boolean\n}\n\ntype TenantAllowlist = {\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\ntype EffectiveAllowlist = {\n providers: string[] | null\n modelsByProvider: Record<string, string[]>\n hasRestrictions: boolean\n tenantOverridesActive: boolean\n}\n\ntype ProviderEntry = {\n id: string\n name: string\n defaultModel: string\n envKey: string | null\n configured: boolean\n defaultModels: Array<{ id: string; name: string; contextWindow?: number; tags?: string[] }>\n}\n\ntype SettingsResponse = {\n availableProviders: ProviderEntry[]\n allowlistProviders?: ProviderEntry[]\n allowlist: EnvAllowlistConfig\n tenantAllowlist: TenantAllowlist | null\n effectiveAllowlist: EffectiveAllowlist\n}\n\nasync function fetchSettings(): Promise<SettingsResponse> {\n const { result, status } = await apiCallOrThrow<SettingsResponse>(\n '/api/ai_assistant/settings',\n { method: 'GET', credentials: 'include' },\n { errorMessage: 'Failed to load AI settings' },\n )\n if (!result) throw new Error(`Failed to load settings (${status})`)\n return result\n}\n\ntype EditState = {\n /** null = \"no tenant restriction (inherit env)\"; array = explicit tenant pick */\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\nfunction snapshotToEditState(snapshot: TenantAllowlist | null): EditState {\n return {\n allowedProviders: snapshot?.allowedProviders ?? null,\n allowedModelsByProvider: { ...(snapshot?.allowedModelsByProvider ?? {}) },\n }\n}\n\nexport function AiTenantAllowlistPageClient(): React.JSX.Element {\n const t = useT()\n const queryClient = useQueryClient()\n const settingsQuery = useQuery({ queryKey: ['ai_assistant', 'settings'], queryFn: fetchSettings, staleTime: 0 })\n\n const [editState, setEditState] = React.useState<EditState>({\n allowedProviders: null,\n allowedModelsByProvider: {},\n })\n const [dirty, setDirty] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [clearing, setClearing] = React.useState(false)\n const [feedback, setFeedback] = React.useState<{ kind: 'ok' | 'error'; text: string } | null>(null)\n const { runMutation: runSaveAllowlistMutation } = useGuardedMutation({\n contextId: 'ai-tenant-allowlist-save',\n })\n const { runMutation: runClearAllowlistMutation } = useGuardedMutation({\n contextId: 'ai-tenant-allowlist-clear',\n })\n\n React.useEffect(() => {\n if (settingsQuery.data) {\n setEditState(snapshotToEditState(settingsQuery.data.tenantAllowlist))\n setDirty(false)\n }\n }, [settingsQuery.data])\n\n const pageHeader = (\n <div className=\"space-y-1\">\n <h1 className=\"flex items-center gap-2 text-2xl font-bold\">\n <Shield className=\"size-6\" />\n {t('ai_assistant.allowlist.title', 'AI provider & model allowlist')}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.allowlist.subtitle',\n 'Limit which providers and models the runtime, settings, and chat picker may use for this tenant. The env allowlist is the outer constraint \u2014 tenant picks narrow it further.',\n )}\n </p>\n </div>\n )\n\n if (settingsQuery.isLoading) {\n return (\n <div className=\"flex max-w-3xl flex-col gap-4\">\n {pageHeader}\n <div className=\"flex w-fit items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-muted-foreground\" role=\"status\">\n <Loader2 className=\"size-4 animate-spin\" />\n {t('ai_assistant.allowlist.loading', 'Loading allowlist\u2026')}\n </div>\n </div>\n )\n }\n\n if (settingsQuery.isError || !settingsQuery.data) {\n return (\n <div className=\"flex max-w-3xl flex-col gap-4\">\n {pageHeader}\n <Alert variant=\"destructive\">\n <AlertCircle className=\"size-4\" />\n <AlertTitle>{t('ai_assistant.allowlist.loadError.title', 'Failed to load allowlist')}</AlertTitle>\n <AlertDescription>\n {settingsQuery.error instanceof Error\n ? settingsQuery.error.message\n : t('ai_assistant.allowlist.loadError.body', 'Try refreshing the page.')}\n </AlertDescription>\n </Alert>\n </div>\n )\n }\n\n const settings = settingsQuery.data\n const envAllowedProviders = settings.allowlist.providers\n const envModelsByProvider = settings.allowlist.modelsByProvider\n\n // Provider universe to render: env-allowed providers (or all configured if env unset).\n const editableProviders = settings.allowlistProviders ?? settings.availableProviders\n const candidateProviders = editableProviders.filter((p) => {\n if (envAllowedProviders === null) return true\n return envAllowedProviders.some((id) => id.toLowerCase() === p.id.toLowerCase())\n })\n\n const tenantPickedProviders = editState.allowedProviders\n const isProviderEnabled = (id: string): boolean => {\n if (tenantPickedProviders === null) return true\n return tenantPickedProviders.includes(id)\n }\n\n const toggleProvider = (id: string, next: boolean): void => {\n setDirty(true)\n setFeedback(null)\n setEditState((prev) => {\n const current = prev.allowedProviders\n if (next) {\n const list = current === null ? [id] : Array.from(new Set([...current, id]))\n return { ...prev, allowedProviders: list }\n }\n const list = current === null\n ? candidateProviders.map((p) => p.id).filter((pid) => pid !== id)\n : current.filter((pid) => pid !== id)\n return { ...prev, allowedProviders: list }\n })\n }\n\n const isModelEnabled = (providerId: string, modelId: string): boolean => {\n const list = editState.allowedModelsByProvider[providerId]\n if (list === undefined) return true\n return list.includes(modelId)\n }\n\n const toggleModel = (providerId: string, modelId: string, next: boolean): void => {\n setDirty(true)\n setFeedback(null)\n const provider = candidateProviders.find((p) => p.id === providerId)\n const allModelIds = provider?.defaultModels.map((m) => m.id) ?? []\n setEditState((prev) => {\n const current = prev.allowedModelsByProvider[providerId]\n const allowedModelsByProvider = { ...prev.allowedModelsByProvider }\n if (next) {\n const list = current === undefined ? [modelId] : Array.from(new Set([...current, modelId]))\n allowedModelsByProvider[providerId] = list\n } else {\n const baseline = current === undefined ? allModelIds : current\n const list = baseline.filter((id) => id !== modelId)\n allowedModelsByProvider[providerId] = list\n }\n return { ...prev, allowedModelsByProvider }\n })\n }\n\n const resetTenantPicks = (): void => {\n setDirty(true)\n setFeedback(null)\n setEditState({ allowedProviders: null, allowedModelsByProvider: {} })\n }\n\n const handleSave = async (): Promise<void> => {\n setSaving(true)\n setFeedback(null)\n try {\n await runSaveAllowlistMutation({\n operation: async () => {\n const { ok, status, result } = await apiCall<{ error?: string; code?: string }>(\n '/api/ai_assistant/settings/allowlist',\n {\n method: 'PUT',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n allowedProviders: editState.allowedProviders,\n allowedModelsByProvider: editState.allowedModelsByProvider,\n }),\n },\n )\n if (!ok) {\n throw new Error(\n result?.error ?? t('ai_assistant.allowlist.save.error', `Save failed (${status})`),\n )\n }\n },\n context: {},\n })\n const successText = t('ai_assistant.allowlist.save.success', 'Allowlist saved.')\n setFeedback({ kind: 'ok', text: successText })\n flash(successText, 'success')\n setDirty(false)\n await queryClient.invalidateQueries({ queryKey: ['ai_assistant', 'settings'] })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n setFeedback({ kind: 'error', text: message })\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }\n\n const handleClear = async (): Promise<void> => {\n setClearing(true)\n setFeedback(null)\n try {\n await runClearAllowlistMutation({\n operation: async () => {\n const { ok, status, result } = await apiCall<{ error?: string; cleared?: boolean }>(\n '/api/ai_assistant/settings/allowlist',\n { method: 'DELETE', credentials: 'include' },\n )\n if (!ok) {\n throw new Error(\n result?.error ?? t('ai_assistant.allowlist.clear.error', `Clear failed (${status})`),\n )\n }\n },\n context: {},\n })\n const successText = t(\n 'ai_assistant.allowlist.clear.success',\n 'Tenant allowlist cleared. Env-only enforcement applies.',\n )\n setFeedback({ kind: 'ok', text: successText })\n flash(successText, 'success')\n setDirty(false)\n await queryClient.invalidateQueries({ queryKey: ['ai_assistant', 'settings'] })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n setFeedback({ kind: 'error', text: message })\n flash(message, 'error')\n } finally {\n setClearing(false)\n }\n }\n\n const envBanner = envAllowedProviders === null && Object.keys(envModelsByProvider).length === 0\n ? null\n : (\n <Alert>\n <Info className=\"h-4 w-4\" />\n <AlertTitle>{t('ai_assistant.allowlist.envBanner.title', 'Env allowlist is in effect')}</AlertTitle>\n <AlertDescription className=\"space-y-1\">\n {envAllowedProviders ? (\n <div>\n {t('ai_assistant.allowlist.envBanner.providers', 'OM_AI_AVAILABLE_PROVIDERS')}: <code className=\"font-mono text-xs\">{envAllowedProviders.join(', ')}</code>\n </div>\n ) : null}\n {Object.keys(envModelsByProvider).map((pid) => (\n <div key={pid}>\n <code className=\"font-mono text-xs\">OM_AI_AVAILABLE_MODELS_{pid.toUpperCase()}</code>: {envModelsByProvider[pid].join(', ')}\n </div>\n ))}\n <p className=\"text-xs text-muted-foreground mt-1\">\n {t('ai_assistant.allowlist.envBanner.note', 'Tenant picks may not widen the env list \u2014 values outside it are hidden.')}\n </p>\n </AlertDescription>\n </Alert>\n )\n\n return (\n <div className=\"flex flex-col gap-6 max-w-3xl\">\n {pageHeader}\n\n {envBanner}\n\n {feedback ? (\n <Alert variant={feedback.kind === 'error' ? 'destructive' : undefined}>\n {feedback.kind === 'error' ? <AlertCircle className=\"h-4 w-4\" /> : <Info className=\"h-4 w-4\" />}\n <AlertDescription>{feedback.text}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"rounded-lg border bg-card p-6 space-y-6\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">{t('ai_assistant.allowlist.providers.title', 'Providers')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'ai_assistant.allowlist.providers.help',\n 'Untick to forbid the runtime from using a provider for this tenant. Tick all to inherit the env allowlist.',\n )}\n </p>\n </div>\n <span\n className={\n settings.effectiveAllowlist.tenantOverridesActive\n ? 'inline-flex size-8 items-center justify-center rounded-md text-status-success-icon'\n : 'inline-flex size-8 items-center justify-center rounded-md text-status-warning-icon'\n }\n role=\"img\"\n aria-label={\n settings.effectiveAllowlist.tenantOverridesActive\n ? t('ai_assistant.allowlist.badge.active', 'Tenant rules active')\n : t('ai_assistant.allowlist.badge.envOnly', 'Env-only')\n }\n title={\n settings.effectiveAllowlist.tenantOverridesActive\n ? t('ai_assistant.allowlist.badge.active', 'Tenant rules active')\n : t('ai_assistant.allowlist.badge.envOnly', 'Env-only')\n }\n >\n {settings.effectiveAllowlist.tenantOverridesActive ? (\n <ShieldCheck className=\"size-5\" aria-hidden />\n ) : (\n <Shield className=\"size-5\" aria-hidden />\n )}\n </span>\n </div>\n\n {candidateProviders.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('ai_assistant.allowlist.providers.empty', 'No configured providers within the env allowlist.')}\n </p>\n ) : (\n <div className=\"space-y-4\">\n {candidateProviders.map((provider) => {\n const enabled = isProviderEnabled(provider.id)\n const envModels = envModelsByProvider[provider.id]\n const candidateModels = envModels\n ? provider.defaultModels.filter((m) => envModels.includes(m.id))\n : provider.defaultModels\n return (\n <div key={provider.id} className=\"rounded-md border p-4 space-y-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <Checkbox\n id={`provider-${provider.id}`}\n checked={enabled}\n onCheckedChange={(value) => toggleProvider(provider.id, value === true)}\n />\n <Label htmlFor={`provider-${provider.id}`} className=\"font-medium\">\n {provider.name}\n </Label>\n {provider.configured ? (\n <Badge variant=\"outline\" className=\"text-xs\">\n {t('ai_assistant.allowlist.providers.configured', 'configured')}\n </Badge>\n ) : (\n <Badge variant=\"outline\" className=\"text-xs text-muted-foreground\">\n {t('ai_assistant.allowlist.providers.notConfigured', 'not configured')}\n </Badge>\n )}\n </div>\n </div>\n\n {enabled && candidateModels.length > 0 ? (\n <div className=\"ml-7 space-y-2\">\n <div className=\"text-xs text-muted-foreground\">\n {t('ai_assistant.allowlist.models.help', 'Tick the models tenants may pick. Empty = no model restriction (inherit env).')}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {candidateModels.map((model) => {\n const checked = isModelEnabled(provider.id, model.id)\n return (\n <label\n key={`${provider.id}-${model.id}`}\n className=\"flex items-center gap-2 text-sm\"\n >\n <Checkbox\n checked={checked}\n onCheckedChange={(value) => toggleModel(provider.id, model.id, value === true)}\n />\n <span className=\"font-mono text-xs\">{model.id}</span>\n {model.id === provider.defaultModel ? (\n <Badge variant=\"outline\" className=\"text-xs\">\n {t('ai_assistant.allowlist.models.default', 'default')}\n </Badge>\n ) : null}\n </label>\n )\n })}\n </div>\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void handleSave()} disabled={!dirty || saving} className=\"gap-2\">\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {t('ai_assistant.allowlist.actions.save', 'Save allowlist')}\n </Button>\n <Button\n variant=\"outline\"\n onClick={resetTenantPicks}\n disabled={saving || clearing}\n className=\"gap-2\"\n >\n {t('ai_assistant.allowlist.actions.reset', 'Reset to env defaults')}\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => void handleClear()}\n disabled={clearing || saving || !settings.tenantAllowlist}\n className=\"gap-2\"\n >\n {clearing ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Trash2 className=\"h-4 w-4\" />}\n {t('ai_assistant.allowlist.actions.clearStored', 'Clear stored allowlist')}\n </Button>\n </div>\n </div>\n )\n}\n\nexport default AiTenantAllowlistPageClient\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Loader2, Save, Shield, Trash2, ShieldCheck } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\ntype EnvAllowlistConfig = {\n providers: string[] | null\n modelsByProvider: Record<string, string[]>\n hasRestrictions: boolean\n}\n\ntype TenantAllowlist = {\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\ntype EffectiveAllowlist = {\n providers: string[] | null\n modelsByProvider: Record<string, string[]>\n hasRestrictions: boolean\n tenantOverridesActive: boolean\n}\n\ntype ProviderEntry = {\n id: string\n name: string\n defaultModel: string\n envKey: string | null\n configured: boolean\n defaultModels: Array<{ id: string; name: string; contextWindow?: number; tags?: string[] }>\n}\n\ntype SettingsResponse = {\n availableProviders: ProviderEntry[]\n allowlistProviders?: ProviderEntry[]\n allowlist: EnvAllowlistConfig\n tenantAllowlist: TenantAllowlist | null\n effectiveAllowlist: EffectiveAllowlist\n}\n\nasync function fetchSettings(): Promise<SettingsResponse> {\n const { result, status } = await apiCallOrThrow<SettingsResponse>(\n '/api/ai_assistant/settings',\n { method: 'GET', credentials: 'include' },\n { errorMessage: 'Failed to load AI settings' },\n )\n if (!result) throw new Error(`Failed to load settings (${status})`)\n return result\n}\n\ntype EditState = {\n /** null = \"no tenant restriction (inherit env)\"; array = explicit tenant pick */\n allowedProviders: string[] | null\n allowedModelsByProvider: Record<string, string[]>\n}\n\nfunction snapshotToEditState(snapshot: TenantAllowlist | null): EditState {\n return {\n allowedProviders: snapshot?.allowedProviders ?? null,\n allowedModelsByProvider: { ...(snapshot?.allowedModelsByProvider ?? {}) },\n }\n}\n\nexport function AiTenantAllowlistPageClient(): React.JSX.Element {\n const t = useT()\n const queryClient = useQueryClient()\n const settingsQuery = useQuery({ queryKey: ['ai_assistant', 'settings'], queryFn: fetchSettings, staleTime: 0 })\n\n const [editState, setEditState] = React.useState<EditState>({\n allowedProviders: null,\n allowedModelsByProvider: {},\n })\n const [dirty, setDirty] = React.useState(false)\n const [saving, setSaving] = React.useState(false)\n const [clearing, setClearing] = React.useState(false)\n const [feedback, setFeedback] = React.useState<{ kind: 'ok' | 'error'; text: string } | null>(null)\n const { runMutation: runSaveAllowlistMutation } = useGuardedMutation({\n contextId: 'ai-tenant-allowlist-save',\n })\n const { runMutation: runClearAllowlistMutation } = useGuardedMutation({\n contextId: 'ai-tenant-allowlist-clear',\n })\n\n React.useEffect(() => {\n if (settingsQuery.data) {\n setEditState(snapshotToEditState(settingsQuery.data.tenantAllowlist))\n setDirty(false)\n }\n }, [settingsQuery.data])\n\n const pageHeader = (\n <div className=\"space-y-1\">\n <h1 className=\"flex items-center gap-2 text-2xl font-bold\">\n <Shield className=\"size-6\" />\n {t('ai_assistant.allowlist.title', 'AI provider & model allowlist')}\n </h1>\n <p className=\"text-muted-foreground\">\n {t(\n 'ai_assistant.allowlist.subtitle',\n 'Limit which providers and models the runtime, settings, and chat picker may use for this tenant. The env allowlist is the outer constraint \u2014 tenant picks narrow it further.',\n )}\n </p>\n </div>\n )\n\n if (settingsQuery.isLoading) {\n return (\n <div className=\"flex max-w-3xl flex-col gap-4\">\n {pageHeader}\n <div className=\"flex w-fit items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-muted-foreground\" role=\"status\">\n <Loader2 className=\"size-4 animate-spin\" />\n {t('ai_assistant.allowlist.loading', 'Loading allowlist\u2026')}\n </div>\n </div>\n )\n }\n\n if (settingsQuery.isError || !settingsQuery.data) {\n return (\n <div className=\"flex max-w-3xl flex-col gap-4\">\n {pageHeader}\n <Alert variant=\"destructive\">\n <AlertTitle>{t('ai_assistant.allowlist.loadError.title', 'Failed to load allowlist')}</AlertTitle>\n <AlertDescription>\n {settingsQuery.error instanceof Error\n ? settingsQuery.error.message\n : t('ai_assistant.allowlist.loadError.body', 'Try refreshing the page.')}\n </AlertDescription>\n </Alert>\n </div>\n )\n }\n\n const settings = settingsQuery.data\n const envAllowedProviders = settings.allowlist.providers\n const envModelsByProvider = settings.allowlist.modelsByProvider\n\n // Provider universe to render: env-allowed providers (or all configured if env unset).\n const editableProviders = settings.allowlistProviders ?? settings.availableProviders\n const candidateProviders = editableProviders.filter((p) => {\n if (envAllowedProviders === null) return true\n return envAllowedProviders.some((id) => id.toLowerCase() === p.id.toLowerCase())\n })\n\n const tenantPickedProviders = editState.allowedProviders\n const isProviderEnabled = (id: string): boolean => {\n if (tenantPickedProviders === null) return true\n return tenantPickedProviders.includes(id)\n }\n\n const toggleProvider = (id: string, next: boolean): void => {\n setDirty(true)\n setFeedback(null)\n setEditState((prev) => {\n const current = prev.allowedProviders\n if (next) {\n const list = current === null ? [id] : Array.from(new Set([...current, id]))\n return { ...prev, allowedProviders: list }\n }\n const list = current === null\n ? candidateProviders.map((p) => p.id).filter((pid) => pid !== id)\n : current.filter((pid) => pid !== id)\n return { ...prev, allowedProviders: list }\n })\n }\n\n const isModelEnabled = (providerId: string, modelId: string): boolean => {\n const list = editState.allowedModelsByProvider[providerId]\n if (list === undefined) return true\n return list.includes(modelId)\n }\n\n const toggleModel = (providerId: string, modelId: string, next: boolean): void => {\n setDirty(true)\n setFeedback(null)\n const provider = candidateProviders.find((p) => p.id === providerId)\n const allModelIds = provider?.defaultModels.map((m) => m.id) ?? []\n setEditState((prev) => {\n const current = prev.allowedModelsByProvider[providerId]\n const allowedModelsByProvider = { ...prev.allowedModelsByProvider }\n if (next) {\n const list = current === undefined ? [modelId] : Array.from(new Set([...current, modelId]))\n allowedModelsByProvider[providerId] = list\n } else {\n const baseline = current === undefined ? allModelIds : current\n const list = baseline.filter((id) => id !== modelId)\n allowedModelsByProvider[providerId] = list\n }\n return { ...prev, allowedModelsByProvider }\n })\n }\n\n const resetTenantPicks = (): void => {\n setDirty(true)\n setFeedback(null)\n setEditState({ allowedProviders: null, allowedModelsByProvider: {} })\n }\n\n const handleSave = async (): Promise<void> => {\n setSaving(true)\n setFeedback(null)\n try {\n await runSaveAllowlistMutation({\n operation: async () => {\n const { ok, status, result } = await apiCall<{ error?: string; code?: string }>(\n '/api/ai_assistant/settings/allowlist',\n {\n method: 'PUT',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n allowedProviders: editState.allowedProviders,\n allowedModelsByProvider: editState.allowedModelsByProvider,\n }),\n },\n )\n if (!ok) {\n throw new Error(\n result?.error ?? t('ai_assistant.allowlist.save.error', `Save failed (${status})`),\n )\n }\n },\n context: {},\n })\n const successText = t('ai_assistant.allowlist.save.success', 'Allowlist saved.')\n setFeedback({ kind: 'ok', text: successText })\n flash(successText, 'success')\n setDirty(false)\n await queryClient.invalidateQueries({ queryKey: ['ai_assistant', 'settings'] })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n setFeedback({ kind: 'error', text: message })\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }\n\n const handleClear = async (): Promise<void> => {\n setClearing(true)\n setFeedback(null)\n try {\n await runClearAllowlistMutation({\n operation: async () => {\n const { ok, status, result } = await apiCall<{ error?: string; cleared?: boolean }>(\n '/api/ai_assistant/settings/allowlist',\n { method: 'DELETE', credentials: 'include' },\n )\n if (!ok) {\n throw new Error(\n result?.error ?? t('ai_assistant.allowlist.clear.error', `Clear failed (${status})`),\n )\n }\n },\n context: {},\n })\n const successText = t(\n 'ai_assistant.allowlist.clear.success',\n 'Tenant allowlist cleared. Env-only enforcement applies.',\n )\n setFeedback({ kind: 'ok', text: successText })\n flash(successText, 'success')\n setDirty(false)\n await queryClient.invalidateQueries({ queryKey: ['ai_assistant', 'settings'] })\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n setFeedback({ kind: 'error', text: message })\n flash(message, 'error')\n } finally {\n setClearing(false)\n }\n }\n\n const envBanner = envAllowedProviders === null && Object.keys(envModelsByProvider).length === 0\n ? null\n : (\n <Alert>\n <AlertTitle>{t('ai_assistant.allowlist.envBanner.title', 'Env allowlist is in effect')}</AlertTitle>\n <AlertDescription className=\"space-y-1\">\n {envAllowedProviders ? (\n <div>\n {t('ai_assistant.allowlist.envBanner.providers', 'OM_AI_AVAILABLE_PROVIDERS')}: <code className=\"font-mono text-xs\">{envAllowedProviders.join(', ')}</code>\n </div>\n ) : null}\n {Object.keys(envModelsByProvider).map((pid) => (\n <div key={pid}>\n <code className=\"font-mono text-xs\">OM_AI_AVAILABLE_MODELS_{pid.toUpperCase()}</code>: {envModelsByProvider[pid].join(', ')}\n </div>\n ))}\n <p className=\"text-xs text-muted-foreground mt-1\">\n {t('ai_assistant.allowlist.envBanner.note', 'Tenant picks may not widen the env list \u2014 values outside it are hidden.')}\n </p>\n </AlertDescription>\n </Alert>\n )\n\n return (\n <div className=\"flex flex-col gap-6 max-w-3xl\">\n {pageHeader}\n\n {envBanner}\n\n {feedback ? (\n <Alert variant={feedback.kind === 'error' ? 'destructive' : undefined}>\n <AlertDescription>{feedback.text}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"rounded-lg border bg-card p-6 space-y-6\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">{t('ai_assistant.allowlist.providers.title', 'Providers')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'ai_assistant.allowlist.providers.help',\n 'Untick to forbid the runtime from using a provider for this tenant. Tick all to inherit the env allowlist.',\n )}\n </p>\n </div>\n <span\n className={\n settings.effectiveAllowlist.tenantOverridesActive\n ? 'inline-flex size-8 items-center justify-center rounded-md text-status-success-icon'\n : 'inline-flex size-8 items-center justify-center rounded-md text-status-warning-icon'\n }\n role=\"img\"\n aria-label={\n settings.effectiveAllowlist.tenantOverridesActive\n ? t('ai_assistant.allowlist.badge.active', 'Tenant rules active')\n : t('ai_assistant.allowlist.badge.envOnly', 'Env-only')\n }\n title={\n settings.effectiveAllowlist.tenantOverridesActive\n ? t('ai_assistant.allowlist.badge.active', 'Tenant rules active')\n : t('ai_assistant.allowlist.badge.envOnly', 'Env-only')\n }\n >\n {settings.effectiveAllowlist.tenantOverridesActive ? (\n <ShieldCheck className=\"size-5\" aria-hidden />\n ) : (\n <Shield className=\"size-5\" aria-hidden />\n )}\n </span>\n </div>\n\n {candidateProviders.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('ai_assistant.allowlist.providers.empty', 'No configured providers within the env allowlist.')}\n </p>\n ) : (\n <div className=\"space-y-4\">\n {candidateProviders.map((provider) => {\n const enabled = isProviderEnabled(provider.id)\n const envModels = envModelsByProvider[provider.id]\n const candidateModels = envModels\n ? provider.defaultModels.filter((m) => envModels.includes(m.id))\n : provider.defaultModels\n return (\n <div key={provider.id} className=\"rounded-md border p-4 space-y-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-3\">\n <Checkbox\n id={`provider-${provider.id}`}\n checked={enabled}\n onCheckedChange={(value) => toggleProvider(provider.id, value === true)}\n />\n <Label htmlFor={`provider-${provider.id}`} className=\"font-medium\">\n {provider.name}\n </Label>\n {provider.configured ? (\n <Badge variant=\"outline\" className=\"text-xs\">\n {t('ai_assistant.allowlist.providers.configured', 'configured')}\n </Badge>\n ) : (\n <Badge variant=\"outline\" className=\"text-xs text-muted-foreground\">\n {t('ai_assistant.allowlist.providers.notConfigured', 'not configured')}\n </Badge>\n )}\n </div>\n </div>\n\n {enabled && candidateModels.length > 0 ? (\n <div className=\"ml-7 space-y-2\">\n <div className=\"text-xs text-muted-foreground\">\n {t('ai_assistant.allowlist.models.help', 'Tick the models tenants may pick. Empty = no model restriction (inherit env).')}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {candidateModels.map((model) => {\n const checked = isModelEnabled(provider.id, model.id)\n return (\n <label\n key={`${provider.id}-${model.id}`}\n className=\"flex items-center gap-2 text-sm\"\n >\n <Checkbox\n checked={checked}\n onCheckedChange={(value) => toggleModel(provider.id, model.id, value === true)}\n />\n <span className=\"font-mono text-xs\">{model.id}</span>\n {model.id === provider.defaultModel ? (\n <Badge variant=\"outline\" className=\"text-xs\">\n {t('ai_assistant.allowlist.models.default', 'default')}\n </Badge>\n ) : null}\n </label>\n )\n })}\n </div>\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button onClick={() => void handleSave()} disabled={!dirty || saving} className=\"gap-2\">\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n {t('ai_assistant.allowlist.actions.save', 'Save allowlist')}\n </Button>\n <Button\n variant=\"outline\"\n onClick={resetTenantPicks}\n disabled={saving || clearing}\n className=\"gap-2\"\n >\n {t('ai_assistant.allowlist.actions.reset', 'Reset to env defaults')}\n </Button>\n <Button\n variant=\"ghost\"\n onClick={() => void handleClear()}\n disabled={clearing || saving || !settings.tenantAllowlist}\n className=\"gap-2\"\n >\n {clearing ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Trash2 className=\"h-4 w-4\" />}\n {t('ai_assistant.allowlist.actions.clearStored', 'Clear stored allowlist')}\n </Button>\n </div>\n </div>\n )\n}\n\nexport default AiTenantAllowlistPageClient\n"],
5
+ "mappings": "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",
6
6
  "names": ["list"]
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useQuery } from "@tanstack/react-query";
5
- import { AlertCircle, Bot, BookOpen, Loader2, Play, RefreshCcw } from "lucide-react";
5
+ import { Bot, BookOpen, Loader2, Play, RefreshCcw } from "lucide-react";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
8
8
  import { Button } from "@open-mercato/ui/primitives/button";
@@ -13,7 +13,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@open-mercato/ui/primi
13
13
  import { Textarea } from "@open-mercato/ui/primitives/textarea";
14
14
  import { EmptyState } from "@open-mercato/ui/backend/EmptyState";
15
15
  import { apiCall, apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
16
- import { AiChat, createAiUiPartRegistry, useAiShortcuts } from "@open-mercato/ui/ai";
16
+ import { AiChat, createAiUiPartRegistry, LoopDisabledBanner, useAiShortcuts } from "@open-mercato/ui/ai";
17
17
  async function fetchAgents() {
18
18
  const { result, status } = await apiCallOrThrow(
19
19
  "/api/ai_assistant/ai/agents",
@@ -62,35 +62,28 @@ function PlaygroundNoAgents() {
62
62
  }
63
63
  function AgentDetails({ agent }) {
64
64
  const t = useT();
65
- return /* @__PURE__ */ jsxs(
66
- "div",
67
- {
68
- className: "rounded-md border border-border bg-muted/30 p-3 text-sm",
69
- "data-ai-playground-agent": agent.id,
70
- children: [
71
- /* @__PURE__ */ jsx("div", { className: "font-semibold", children: agent.label }),
72
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: agent.description }),
73
- /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid grid-cols-2 gap-2 text-xs", children: [
74
- /* @__PURE__ */ jsxs("div", { children: [
75
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.module", "Module") }),
76
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.moduleId })
77
- ] }),
78
- /* @__PURE__ */ jsxs("div", { children: [
79
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.executionMode", "Execution mode") }),
80
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.executionMode })
81
- ] }),
82
- /* @__PURE__ */ jsxs("div", { children: [
83
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.mutationPolicy", "Mutation policy") }),
84
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.mutationPolicy })
85
- ] }),
86
- /* @__PURE__ */ jsxs("div", { children: [
87
- /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.tools", "Allowed tools") }),
88
- /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.allowedTools.length })
89
- ] })
90
- ] })
91
- ]
92
- }
93
- );
65
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border bg-muted/30 p-3 text-sm", children: [
66
+ /* @__PURE__ */ jsx("div", { className: "font-semibold", children: agent.label }),
67
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: agent.description }),
68
+ /* @__PURE__ */ jsxs("dl", { className: "mt-3 grid grid-cols-2 gap-2 text-xs", children: [
69
+ /* @__PURE__ */ jsxs("div", { children: [
70
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.module", "Module") }),
71
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.moduleId })
72
+ ] }),
73
+ /* @__PURE__ */ jsxs("div", { children: [
74
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.executionMode", "Execution mode") }),
75
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.executionMode })
76
+ ] }),
77
+ /* @__PURE__ */ jsxs("div", { children: [
78
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.mutationPolicy", "Mutation policy") }),
79
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.mutationPolicy })
80
+ ] }),
81
+ /* @__PURE__ */ jsxs("div", { children: [
82
+ /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.meta.tools", "Allowed tools") }),
83
+ /* @__PURE__ */ jsx("dd", { className: "font-mono", children: agent.allowedTools.length })
84
+ ] })
85
+ ] })
86
+ ] });
94
87
  }
95
88
  function buildDebugTools(agent) {
96
89
  if (agent.tools && agent.tools.length > 0) {
@@ -127,6 +120,23 @@ async function fetchAgentResolutions() {
127
120
  if (!result.ok || !result.result) return { agents: [] };
128
121
  return { agents: result.result.agents ?? [] };
129
122
  }
123
+ async function fetchLoopOverrideForAgent(agentId) {
124
+ const result = await apiCall(
125
+ `/api/ai_assistant/ai/agents/${encodeURIComponent(agentId)}/loop-override`,
126
+ { method: "GET", credentials: "include" }
127
+ );
128
+ if (!result.ok || !result.result) return { agentId, override: null };
129
+ return result.result;
130
+ }
131
+ function LoopDisabledPlaygroundBanner({ agentId }) {
132
+ const { data } = useQuery({
133
+ queryKey: ["ai_assistant", "loop_override", agentId],
134
+ queryFn: () => fetchLoopOverrideForAgent(agentId),
135
+ staleTime: 3e4
136
+ });
137
+ if (!data?.override?.loopDisabled) return null;
138
+ return /* @__PURE__ */ jsx(LoopDisabledBanner, { agentId });
139
+ }
130
140
  function ModelResolutionPanel({ agentId }) {
131
141
  const t = useT();
132
142
  const { data } = useQuery({
@@ -141,7 +151,6 @@ function ModelResolutionPanel({ agentId }) {
141
151
  {
142
152
  className: "grid grid-cols-2 gap-x-4 gap-y-1 rounded-md border border-border bg-muted/30 p-3 text-xs sm:grid-cols-4",
143
153
  "data-ai-playground-model-resolution": agentId,
144
- "data-ai-playground-resolution-panel": agentId,
145
154
  children: [
146
155
  /* @__PURE__ */ jsxs("div", { children: [
147
156
  /* @__PURE__ */ jsx("dt", { className: "font-medium text-muted-foreground", children: t("ai_assistant.playground.resolution.provider", "Provider") }),
@@ -203,7 +212,7 @@ function ChatLane({ agent, debug }) {
203
212
  ) })
204
213
  ] });
205
214
  }
206
- return /* @__PURE__ */ jsx("div", { "data-ai-playground-chat": agent.id, children: /* @__PURE__ */ jsx(
215
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", "data-ai-playground-chat": agent.id, children: /* @__PURE__ */ jsx(
207
216
  AiChat,
208
217
  {
209
218
  agent: agent.id,
@@ -401,7 +410,6 @@ function AiPlaygroundPageClient() {
401
410
  }
402
411
  if (isError) {
403
412
  return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", "data-ai-playground-error": true, children: [
404
- /* @__PURE__ */ jsx(AlertCircle, { className: "size-4", "aria-hidden": true }),
405
413
  /* @__PURE__ */ jsx(AlertTitle, { children: t("ai_assistant.playground.loadErrorTitle", "Failed to load AI agents") }),
406
414
  /* @__PURE__ */ jsxs(AlertDescription, { children: [
407
415
  /* @__PURE__ */ jsx("span", { children: error instanceof Error ? error.message : String(error) }),
@@ -484,7 +492,8 @@ function AiPlaygroundPageClient() {
484
492
  ] })
485
493
  ] }),
486
494
  selectedAgent ? /* @__PURE__ */ jsx(AgentDetails, { agent: selectedAgent }) : null,
487
- selectedAgent ? /* @__PURE__ */ jsx(ModelResolutionPanel, { agentId: selectedAgent.id }) : null
495
+ selectedAgent ? /* @__PURE__ */ jsx(ModelResolutionPanel, { agentId: selectedAgent.id }) : null,
496
+ selectedAgent ? /* @__PURE__ */ jsx(LoopDisabledPlaygroundBanner, { agentId: selectedAgent.id }) : null
488
497
  ] }),
489
498
  selectedAgent ? /* @__PURE__ */ jsxs(
490
499
  Tabs,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { AlertCircle, Bot, BookOpen, Loader2, Play, RefreshCcw } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primitives/tabs'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { AiChat, createAiUiPartRegistry, useAiShortcuts } from '@open-mercato/ui/ai'\nimport type { AiChatDebugPromptSection, AiChatDebugTool } from '@open-mercato/ui/ai'\n\ntype PlaygroundAgentTool = {\n name: string\n displayName?: string\n isMutation?: boolean\n registered?: boolean\n requiredFeatures?: string[]\n}\n\ntype PlaygroundAgent = {\n id: string\n moduleId: string\n label: string\n description: string\n executionMode: 'chat' | 'object'\n mutationPolicy: string\n allowedTools: string[]\n requiredFeatures: string[]\n acceptedMediaTypes: string[]\n hasOutputSchema: boolean\n systemPrompt?: string\n readOnly?: boolean\n maxSteps?: number | null\n tools?: PlaygroundAgentTool[]\n}\n\ntype AgentsResponse = {\n agents: PlaygroundAgent[]\n total: number\n}\n\ntype RunObjectResponse = {\n object: unknown\n finishReason?: string\n usage?: { inputTokens?: number; outputTokens?: number }\n}\n\ntype RunObjectError = {\n error: string\n code?: string\n issues?: unknown\n}\n\nasync function fetchAgents(): Promise<AgentsResponse> {\n const { result, status } = await apiCallOrThrow<AgentsResponse>(\n '/api/ai_assistant/ai/agents',\n { method: 'GET', credentials: 'include' },\n { errorMessage: 'Failed to load agents' },\n )\n if (!result) throw new Error(`Failed to load agents (${status})`)\n return result\n}\n\nfunction PlaygroundLoading({ message }: { message: string }) {\n return (\n <div\n className=\"flex items-center gap-2 rounded-lg border border-border bg-background p-4 text-sm text-muted-foreground\"\n role=\"status\"\n >\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n <span>{message}</span>\n </div>\n )\n}\n\nfunction PlaygroundNoAgents() {\n const t = useT()\n return (\n <EmptyState\n icon={<Bot className=\"size-6\" aria-hidden />}\n title={t(\n 'ai_assistant.playground.empty.title',\n 'No AI agents are registered for your role yet.',\n )}\n description={t(\n 'ai_assistant.playground.empty.description',\n 'Declare agents inside `packages/<module>/src/modules/<module>/ai-agents.ts`, run `yarn generate`, and ensure the caller holds the agent\\'s required features.',\n )}\n >\n <div className=\"mt-2 inline-flex items-center gap-2 text-xs text-muted-foreground\">\n <BookOpen className=\"size-3\" aria-hidden />\n <span>\n {t(\n 'ai_assistant.playground.empty.docLabel',\n 'See packages/ai-assistant/AGENTS.md for the agent definition reference.',\n )}\n </span>\n </div>\n </EmptyState>\n )\n}\n\nfunction AgentDetails({ agent }: { agent: PlaygroundAgent }) {\n const t = useT()\n return (\n <div\n className=\"rounded-md border border-border bg-muted/30 p-3 text-sm\"\n data-ai-playground-agent={agent.id}\n >\n <div className=\"font-semibold\">{agent.label}</div>\n <p className=\"mt-1 text-xs text-muted-foreground\">{agent.description}</p>\n <dl className=\"mt-3 grid grid-cols-2 gap-2 text-xs\">\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.module', 'Module')}\n </dt>\n <dd className=\"font-mono\">{agent.moduleId}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.executionMode', 'Execution mode')}\n </dt>\n <dd className=\"font-mono\">{agent.executionMode}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.mutationPolicy', 'Mutation policy')}\n </dt>\n <dd className=\"font-mono\">{agent.mutationPolicy}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.tools', 'Allowed tools')}\n </dt>\n <dd className=\"font-mono\">{agent.allowedTools.length}</dd>\n </div>\n </dl>\n </div>\n )\n}\n\nfunction buildDebugTools(agent: PlaygroundAgent): AiChatDebugTool[] {\n if (agent.tools && agent.tools.length > 0) {\n return agent.tools.map((tool) => ({\n name: tool.name,\n displayName: tool.displayName ?? tool.name,\n isMutation: Boolean(tool.isMutation),\n requiredFeatures: tool.requiredFeatures ?? [],\n }))\n }\n return agent.allowedTools.map((toolName) => ({ name: toolName }))\n}\n\nfunction buildDebugPromptSections(agent: PlaygroundAgent): AiChatDebugPromptSection[] {\n const sections: AiChatDebugPromptSection[] = []\n if (agent.systemPrompt) {\n sections.push({ id: 'role', source: 'default', text: agent.systemPrompt })\n }\n const placeholderIds = [\n 'scope',\n 'data',\n 'tools',\n 'attachments',\n 'mutationPolicy',\n 'responseStyle',\n 'overrides',\n ] as const\n for (const id of placeholderIds) {\n sections.push({ id, source: 'placeholder' })\n }\n return sections\n}\n\ntype AgentModelResolution = {\n agentId: string\n providerId: string\n modelId: string\n baseURL: string | null\n source: string\n}\n\ntype SettingsAgentResolutionResponse = {\n agents: AgentModelResolution[]\n}\n\nasync function fetchAgentResolutions(): Promise<SettingsAgentResolutionResponse> {\n const result = await apiCall<SettingsAgentResolutionResponse>('/api/ai_assistant/settings')\n if (!result.ok || !result.result) return { agents: [] }\n return { agents: result.result.agents ?? [] }\n}\n\nfunction ModelResolutionPanel({ agentId }: { agentId: string }) {\n const t = useT()\n const { data } = useQuery({\n queryKey: ['ai_assistant', 'settings', 'agents'],\n queryFn: fetchAgentResolutions,\n staleTime: 30000,\n })\n\n const resolution = data?.agents.find((agent) => agent.agentId === agentId)\n if (!resolution) return null\n\n return (\n <dl\n className=\"grid grid-cols-2 gap-x-4 gap-y-1 rounded-md border border-border bg-muted/30 p-3 text-xs sm:grid-cols-4\"\n data-ai-playground-model-resolution={agentId}\n data-ai-playground-resolution-panel={agentId}\n >\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.provider', 'Provider')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-provider>\n {resolution.providerId}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.model', 'Model')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-model>\n {resolution.modelId}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.baseUrl', 'Base URL')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-base-url>\n {resolution.baseURL ?? t('ai_assistant.playground.resolution.none', '\u2014')}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.source', 'Source')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-source>\n {resolution.source}\n </dd>\n </div>\n </dl>\n )\n}\n\ntype PlaygroundUiPartSeed = {\n componentId: string\n pendingActionId?: string\n payload?: unknown\n}\n\nfunction readPlaygroundUiPartSeeds(): PlaygroundUiPartSeed[] {\n if (typeof window === 'undefined') return []\n try {\n const params = new URLSearchParams(window.location.search)\n const componentId = params.get('uiPart')\n if (!componentId) return []\n const pendingActionId = params.get('pendingActionId') ?? undefined\n return [{ componentId, pendingActionId }]\n } catch {\n return []\n }\n}\n\nfunction ChatLane({ agent, debug }: { agent: PlaygroundAgent; debug: boolean }) {\n const t = useT()\n // Scoped registry so repeated mounts do not share state with other pages.\n // Step 5.10: opt in to the LIVE mutation-approval cards so the playground\n // exercises the real cards when the chat response surfaces a pending\n // action (via the `?uiPart=...` debug seed for Playwright).\n const registry = React.useMemo(\n () => createAiUiPartRegistry({ seedLiveApprovalCards: true }),\n [],\n )\n const debugTools = React.useMemo(() => buildDebugTools(agent), [agent])\n const debugPromptSections = React.useMemo(\n () => buildDebugPromptSections(agent),\n [agent],\n )\n const [uiParts, setUiParts] = React.useState<PlaygroundUiPartSeed[]>([])\n\n // Step 5.10: the dispatcher does not yet surface `AiUiPart` entries through\n // the plain-text stream consumed by `useAiChat`. For now the playground\n // reads a `?uiPart=<componentId>&pendingActionId=...` seed from the URL\n // so Playwright + operator debug flows can render the approval cards\n // against a stubbed `/api/ai_assistant/ai/actions/:id` endpoint. When the\n // dispatcher switches to the UIMessageChunk format this effect swaps over\n // to the streamed `uiParts` payload.\n React.useEffect(() => {\n const seeds = readPlaygroundUiPartSeeds()\n if (seeds.length > 0) setUiParts(seeds)\n }, [])\n\n if (agent.executionMode !== 'chat') {\n return (\n <Alert variant=\"info\" data-ai-playground-unsupported=\"chat\">\n <AlertTitle>\n {t(\n 'ai_assistant.playground.chat.notSupportedTitle',\n 'Chat mode is not available for this agent.',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.playground.chat.notSupportedBody',\n 'Pick an agent whose execution mode is \"chat\", or switch to the object-mode tab.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div data-ai-playground-chat={agent.id}>\n <AiChat\n key={agent.id}\n agent={agent.id}\n pageContext={{ source: 'playground', pageId: 'ai_assistant.playground' }}\n debug={debug}\n registry={registry}\n className=\"min-h-96\"\n debugTools={debugTools}\n debugPromptSections={debugPromptSections}\n uiParts={uiParts}\n />\n </div>\n )\n}\n\nfunction ObjectLane({ agent }: { agent: PlaygroundAgent }) {\n const t = useT()\n const [prompt, setPrompt] = React.useState('')\n const [isRunning, setIsRunning] = React.useState(false)\n const [result, setResult] = React.useState<RunObjectResponse | null>(null)\n const [error, setError] = React.useState<RunObjectError | null>(null)\n const [lastRequest, setLastRequest] = React.useState<unknown>(null)\n\n const isSupported = agent.executionMode === 'object'\n const canRun = isSupported && prompt.trim().length > 0 && !isRunning\n\n const runObject = React.useCallback(async () => {\n if (!canRun) return\n const body = {\n agent: agent.id,\n messages: [{ role: 'user' as const, content: prompt }],\n pageContext: { source: 'playground', pageId: 'ai_assistant.playground' },\n }\n setLastRequest(body)\n setIsRunning(true)\n setResult(null)\n setError(null)\n try {\n const { ok, status, result } = await apiCall<RunObjectResponse | RunObjectError>(\n '/api/ai_assistant/ai/run-object',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body),\n },\n )\n if (!ok) {\n const payload = (result as RunObjectError | null) ?? { error: `HTTP ${status}` }\n setError(payload)\n return\n }\n setResult((result as RunObjectResponse | null) ?? { object: null })\n } catch (err) {\n setError({\n error: err instanceof Error ? err.message : String(err),\n code: 'network_error',\n })\n } finally {\n setIsRunning(false)\n }\n }, [agent.id, canRun, prompt])\n\n const { handleKeyDown } = useAiShortcuts({\n onSubmit: () => {\n void runObject()\n },\n onCancel: () => {\n setError(null)\n },\n })\n\n if (!isSupported) {\n return (\n <Alert variant=\"info\" data-ai-playground-unsupported=\"object\">\n <AlertTitle>\n {t(\n 'ai_assistant.playground.object.notSupportedTitle',\n 'Object mode is not available for this agent.',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.playground.object.notSupportedBody',\n 'This agent declares executionMode = \"chat\". Pick an object-mode agent to preview structured output, or switch to the chat tab.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div className=\"flex flex-col gap-3\" data-ai-playground-object>\n <div className=\"flex flex-col gap-2\">\n <Label htmlFor=\"ai-playground-object-input\">\n {t('ai_assistant.playground.object.inputLabel', 'Prompt')}\n </Label>\n <Textarea\n id=\"ai-playground-object-input\"\n rows={4}\n value={prompt}\n placeholder={t(\n 'ai_assistant.playground.object.inputPlaceholder',\n 'Describe what the agent should produce...',\n )}\n onChange={(event) => setPrompt(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"resize-none\"\n aria-label={t('ai_assistant.playground.object.inputLabel', 'Prompt')}\n />\n <div className=\"flex items-center justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'ai_assistant.playground.object.shortcutHint',\n 'Press Cmd/Ctrl+Enter to run.',\n )}\n </p>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void runObject()}\n disabled={!canRun}\n data-ai-playground-object-run\n >\n {isRunning ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : (\n <Play className=\"size-4\" aria-hidden />\n )}\n <span>{t('ai_assistant.playground.object.run', 'Run object')}</span>\n </Button>\n </div>\n </div>\n\n {error ? (\n <Alert variant=\"destructive\" data-ai-playground-object-error={error.code ?? 'unknown'}>\n <AlertTitle>\n {t('ai_assistant.playground.object.errorTitle', 'Object run failed')}\n </AlertTitle>\n <AlertDescription>\n {error.code ? <span className=\"mr-2 font-mono text-xs\">{error.code}</span> : null}\n {error.error}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {result ? (\n <section\n className=\"rounded-md border border-border bg-muted/30 p-3\"\n data-ai-playground-object-result\n >\n <h3 className=\"text-sm font-semibold\">\n {t('ai_assistant.playground.object.resultTitle', 'Generated object')}\n </h3>\n <pre className=\"mt-2 max-h-96 overflow-auto rounded bg-background p-2 text-xs font-mono\">\n {JSON.stringify(result.object, null, 2)}\n </pre>\n {result.usage || result.finishReason ? (\n <dl className=\"mt-3 grid grid-cols-3 gap-2 text-xs\">\n {result.finishReason ? (\n <div>\n <dt className=\"text-muted-foreground\">\n {t('ai_assistant.playground.object.finishReason', 'Finish reason')}\n </dt>\n <dd className=\"font-mono\">{result.finishReason}</dd>\n </div>\n ) : null}\n {result.usage?.inputTokens !== undefined ? (\n <div>\n <dt className=\"text-muted-foreground\">\n {t('ai_assistant.playground.object.inputTokens', 'Input tokens')}\n </dt>\n <dd className=\"font-mono\">{result.usage.inputTokens}</dd>\n </div>\n ) : null}\n {result.usage?.outputTokens !== undefined ? (\n <div>\n <dt className=\"text-muted-foreground\">\n {t('ai_assistant.playground.object.outputTokens', 'Output tokens')}\n </dt>\n <dd className=\"font-mono\">{result.usage.outputTokens}</dd>\n </div>\n ) : null}\n </dl>\n ) : null}\n </section>\n ) : null}\n\n {lastRequest && (error || result) ? (\n <details\n className=\"rounded-md border border-border bg-muted/20 p-2 text-xs\"\n data-ai-playground-object-debug\n >\n <summary className=\"cursor-pointer font-semibold\">\n {t('ai_assistant.playground.object.debugTitle', 'Last request payload')}\n </summary>\n <pre className=\"mt-2 max-h-64 overflow-auto whitespace-pre-wrap font-mono\">\n {JSON.stringify(lastRequest, null, 2)}\n </pre>\n </details>\n ) : null}\n </div>\n )\n}\n\nexport function AiPlaygroundPageClient() {\n const t = useT()\n const [selectedAgentId, setSelectedAgentId] = React.useState<string | null>(null)\n const [debugEnabled, setDebugEnabled] = React.useState(false)\n const [tab, setTab] = React.useState<'chat' | 'object'>('chat')\n\n const { data, isLoading, isError, error, refetch, isFetching } = useQuery<AgentsResponse>({\n queryKey: ['ai_assistant', 'playground', 'agents'],\n queryFn: fetchAgents,\n })\n\n const agents = React.useMemo<PlaygroundAgent[]>(() => data?.agents ?? [], [data])\n\n React.useEffect(() => {\n if (!agents.length) {\n if (selectedAgentId !== null) setSelectedAgentId(null)\n return\n }\n if (!selectedAgentId || !agents.some((agent) => agent.id === selectedAgentId)) {\n setSelectedAgentId(agents[0].id)\n }\n }, [agents, selectedAgentId])\n\n const selectedAgent = React.useMemo<PlaygroundAgent | null>(() => {\n if (!selectedAgentId) return null\n return agents.find((agent) => agent.id === selectedAgentId) ?? null\n }, [agents, selectedAgentId])\n\n if (isLoading) {\n return (\n <PlaygroundLoading\n message={t('ai_assistant.playground.loadingAgents', 'Loading AI agents...')}\n />\n )\n }\n\n if (isError) {\n return (\n <Alert variant=\"destructive\" data-ai-playground-error>\n <AlertCircle className=\"size-4\" aria-hidden />\n <AlertTitle>\n {t('ai_assistant.playground.loadErrorTitle', 'Failed to load AI agents')}\n </AlertTitle>\n <AlertDescription>\n <span>{error instanceof Error ? error.message : String(error)}</span>\n <div className=\"mt-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n void refetch()\n }}\n >\n <RefreshCcw className=\"size-4\" aria-hidden />\n <span>{t('ai_assistant.playground.retry', 'Retry')}</span>\n </Button>\n </div>\n </AlertDescription>\n </Alert>\n )\n }\n\n if (!agents.length) {\n return <PlaygroundNoAgents />\n }\n\n return (\n <div className=\"flex flex-col gap-4\" data-ai-playground>\n <header className=\"flex flex-col gap-1\">\n <h1 className=\"text-2xl font-bold tracking-tight\">\n {t('ai_assistant.playground.title', 'AI Playground')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'ai_assistant.playground.subtitle',\n 'Exercise every registered AI agent end-to-end. Use the debug panel to inspect request and response payloads, and the object-mode tab to preview structured output.',\n )}\n </p>\n </header>\n\n <section className=\"flex flex-col gap-3 rounded-lg border border-border bg-background p-3\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between\">\n <div className=\"flex flex-col gap-2 sm:flex-1\">\n <Label htmlFor=\"ai-playground-agent-picker\">\n {t('ai_assistant.playground.agentPickerLabel', 'Agent')}\n </Label>\n <select\n id=\"ai-playground-agent-picker\"\n data-ai-playground-agent-picker\n className=\"h-9 rounded-md border border-input bg-background px-3 text-sm\"\n value={selectedAgentId ?? ''}\n onChange={(event) => setSelectedAgentId(event.target.value)}\n >\n {agents.map((agent) => (\n <option key={agent.id} value={agent.id}>\n {agent.label} ({agent.id})\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-3 sm:flex-shrink-0\">\n <Label htmlFor=\"ai-playground-debug\" className=\"text-sm\">\n {t('ai_assistant.playground.debugToggle', 'Debug panel')}\n </Label>\n <Switch\n id=\"ai-playground-debug\"\n checked={debugEnabled}\n onCheckedChange={(next: boolean) => setDebugEnabled(next)}\n aria-label={t('ai_assistant.playground.debugToggle', 'Debug panel')}\n data-ai-playground-debug-toggle\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n void refetch()\n }}\n aria-label={t('ai_assistant.playground.refresh', 'Refresh agents')}\n disabled={isFetching}\n >\n <RefreshCcw className=\"size-4\" aria-hidden />\n </IconButton>\n </div>\n </div>\n {selectedAgent ? <AgentDetails agent={selectedAgent} /> : null}\n {selectedAgent ? <ModelResolutionPanel agentId={selectedAgent.id} /> : null}\n </section>\n\n {selectedAgent ? (\n <Tabs\n value={tab}\n onValueChange={(next: string) => setTab(next === 'object' ? 'object' : 'chat')}\n >\n <TabsList>\n <TabsTrigger value=\"chat\">\n {t('ai_assistant.playground.tabs.chat', 'Chat')}\n </TabsTrigger>\n <TabsTrigger value=\"object\">\n {t('ai_assistant.playground.tabs.object', 'Object mode')}\n </TabsTrigger>\n </TabsList>\n <TabsContent value=\"chat\">\n <ChatLane agent={selectedAgent} debug={debugEnabled} />\n </TabsContent>\n <TabsContent value=\"object\">\n <ObjectLane agent={selectedAgent} />\n </TabsContent>\n </Tabs>\n ) : null}\n </div>\n )\n}\n\nexport default AiPlaygroundPageClient\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { Bot, BookOpen, Loader2, Play, RefreshCcw } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primitives/tabs'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { EmptyState } from '@open-mercato/ui/backend/EmptyState'\nimport { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { AiChat, createAiUiPartRegistry, LoopDisabledBanner, useAiShortcuts } from '@open-mercato/ui/ai'\nimport type { AiChatDebugPromptSection, AiChatDebugTool } from '@open-mercato/ui/ai'\n\ntype PlaygroundAgentTool = {\n name: string\n displayName?: string\n isMutation?: boolean\n registered?: boolean\n requiredFeatures?: string[]\n}\n\ntype PlaygroundAgent = {\n id: string\n moduleId: string\n label: string\n description: string\n executionMode: 'chat' | 'object'\n mutationPolicy: string\n allowedTools: string[]\n requiredFeatures: string[]\n acceptedMediaTypes: string[]\n hasOutputSchema: boolean\n systemPrompt?: string\n readOnly?: boolean\n maxSteps?: number | null\n tools?: PlaygroundAgentTool[]\n}\n\ntype AgentsResponse = {\n agents: PlaygroundAgent[]\n total: number\n}\n\ntype RunObjectResponse = {\n object: unknown\n finishReason?: string\n usage?: { inputTokens?: number; outputTokens?: number }\n}\n\ntype RunObjectError = {\n error: string\n code?: string\n issues?: unknown\n}\n\nasync function fetchAgents(): Promise<AgentsResponse> {\n const { result, status } = await apiCallOrThrow<AgentsResponse>(\n '/api/ai_assistant/ai/agents',\n { method: 'GET', credentials: 'include' },\n { errorMessage: 'Failed to load agents' },\n )\n if (!result) throw new Error(`Failed to load agents (${status})`)\n return result\n}\n\nfunction PlaygroundLoading({ message }: { message: string }) {\n return (\n <div\n className=\"flex items-center gap-2 rounded-lg border border-border bg-background p-4 text-sm text-muted-foreground\"\n role=\"status\"\n >\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n <span>{message}</span>\n </div>\n )\n}\n\nfunction PlaygroundNoAgents() {\n const t = useT()\n return (\n <EmptyState\n icon={<Bot className=\"size-6\" aria-hidden />}\n title={t(\n 'ai_assistant.playground.empty.title',\n 'No AI agents are registered for your role yet.',\n )}\n description={t(\n 'ai_assistant.playground.empty.description',\n 'Declare agents inside `packages/<module>/src/modules/<module>/ai-agents.ts`, run `yarn generate`, and ensure the caller holds the agent\\'s required features.',\n )}\n >\n <div className=\"mt-2 inline-flex items-center gap-2 text-xs text-muted-foreground\">\n <BookOpen className=\"size-3\" aria-hidden />\n <span>\n {t(\n 'ai_assistant.playground.empty.docLabel',\n 'See packages/ai-assistant/AGENTS.md for the agent definition reference.',\n )}\n </span>\n </div>\n </EmptyState>\n )\n}\n\nfunction AgentDetails({ agent }: { agent: PlaygroundAgent }) {\n const t = useT()\n return (\n <div className=\"rounded-md border border-border bg-muted/30 p-3 text-sm\">\n <div className=\"font-semibold\">{agent.label}</div>\n <p className=\"mt-1 text-xs text-muted-foreground\">{agent.description}</p>\n <dl className=\"mt-3 grid grid-cols-2 gap-2 text-xs\">\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.module', 'Module')}\n </dt>\n <dd className=\"font-mono\">{agent.moduleId}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.executionMode', 'Execution mode')}\n </dt>\n <dd className=\"font-mono\">{agent.executionMode}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.mutationPolicy', 'Mutation policy')}\n </dt>\n <dd className=\"font-mono\">{agent.mutationPolicy}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.meta.tools', 'Allowed tools')}\n </dt>\n <dd className=\"font-mono\">{agent.allowedTools.length}</dd>\n </div>\n </dl>\n </div>\n )\n}\n\nfunction buildDebugTools(agent: PlaygroundAgent): AiChatDebugTool[] {\n if (agent.tools && agent.tools.length > 0) {\n return agent.tools.map((tool) => ({\n name: tool.name,\n displayName: tool.displayName ?? tool.name,\n isMutation: Boolean(tool.isMutation),\n requiredFeatures: tool.requiredFeatures ?? [],\n }))\n }\n return agent.allowedTools.map((toolName) => ({ name: toolName }))\n}\n\nfunction buildDebugPromptSections(agent: PlaygroundAgent): AiChatDebugPromptSection[] {\n const sections: AiChatDebugPromptSection[] = []\n if (agent.systemPrompt) {\n sections.push({ id: 'role', source: 'default', text: agent.systemPrompt })\n }\n const placeholderIds = [\n 'scope',\n 'data',\n 'tools',\n 'attachments',\n 'mutationPolicy',\n 'responseStyle',\n 'overrides',\n ] as const\n for (const id of placeholderIds) {\n sections.push({ id, source: 'placeholder' })\n }\n return sections\n}\n\ntype AgentModelResolution = {\n agentId: string\n providerId: string\n modelId: string\n baseURL: string | null\n source: string\n}\n\ntype SettingsAgentResolutionResponse = {\n agents: AgentModelResolution[]\n}\n\nasync function fetchAgentResolutions(): Promise<SettingsAgentResolutionResponse> {\n const result = await apiCall<SettingsAgentResolutionResponse>('/api/ai_assistant/settings')\n if (!result.ok || !result.result) return { agents: [] }\n return { agents: result.result.agents ?? [] }\n}\n\nasync function fetchLoopOverrideForAgent(\n agentId: string,\n): Promise<{ agentId: string; override: { loopDisabled?: boolean | null } | null }> {\n const result = await apiCall<{ agentId: string; override: { loopDisabled?: boolean | null } | null }>(\n `/api/ai_assistant/ai/agents/${encodeURIComponent(agentId)}/loop-override`,\n { method: 'GET', credentials: 'include' },\n )\n if (!result.ok || !result.result) return { agentId, override: null }\n return result.result\n}\n\nfunction LoopDisabledPlaygroundBanner({ agentId }: { agentId: string }) {\n const { data } = useQuery({\n queryKey: ['ai_assistant', 'loop_override', agentId],\n queryFn: () => fetchLoopOverrideForAgent(agentId),\n staleTime: 30000,\n })\n if (!data?.override?.loopDisabled) return null\n return <LoopDisabledBanner agentId={agentId} />\n}\n\nfunction ModelResolutionPanel({ agentId }: { agentId: string }) {\n const t = useT()\n const { data } = useQuery({\n queryKey: ['ai_assistant', 'settings', 'agents'],\n queryFn: fetchAgentResolutions,\n staleTime: 30000,\n })\n\n const resolution = data?.agents.find((agent) => agent.agentId === agentId)\n if (!resolution) return null\n\n return (\n <dl\n className=\"grid grid-cols-2 gap-x-4 gap-y-1 rounded-md border border-border bg-muted/30 p-3 text-xs sm:grid-cols-4\"\n data-ai-playground-model-resolution={agentId}\n >\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.provider', 'Provider')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-provider>\n {resolution.providerId}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.model', 'Model')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-model>\n {resolution.modelId}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.baseUrl', 'Base URL')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-base-url>\n {resolution.baseURL ?? t('ai_assistant.playground.resolution.none', '\u2014')}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('ai_assistant.playground.resolution.source', 'Source')}\n </dt>\n <dd className=\"font-mono\" data-ai-playground-resolution-source>\n {resolution.source}\n </dd>\n </div>\n </dl>\n )\n}\n\ntype PlaygroundUiPartSeed = {\n componentId: string\n pendingActionId?: string\n payload?: unknown\n}\n\nfunction readPlaygroundUiPartSeeds(): PlaygroundUiPartSeed[] {\n if (typeof window === 'undefined') return []\n try {\n const params = new URLSearchParams(window.location.search)\n const componentId = params.get('uiPart')\n if (!componentId) return []\n const pendingActionId = params.get('pendingActionId') ?? undefined\n return [{ componentId, pendingActionId }]\n } catch {\n return []\n }\n}\n\nfunction ChatLane({ agent, debug }: { agent: PlaygroundAgent; debug: boolean }) {\n const t = useT()\n // Scoped registry so repeated mounts do not share state with other pages.\n // Step 5.10: opt in to the LIVE mutation-approval cards so the playground\n // exercises the real cards when the chat response surfaces a pending\n // action (via the `?uiPart=...` debug seed for Playwright).\n const registry = React.useMemo(\n () => createAiUiPartRegistry({ seedLiveApprovalCards: true }),\n [],\n )\n const debugTools = React.useMemo(() => buildDebugTools(agent), [agent])\n const debugPromptSections = React.useMemo(\n () => buildDebugPromptSections(agent),\n [agent],\n )\n const [uiParts, setUiParts] = React.useState<PlaygroundUiPartSeed[]>([])\n\n // Step 5.10: the dispatcher does not yet surface `AiUiPart` entries through\n // the plain-text stream consumed by `useAiChat`. For now the playground\n // reads a `?uiPart=<componentId>&pendingActionId=...` seed from the URL\n // so Playwright + operator debug flows can render the approval cards\n // against a stubbed `/api/ai_assistant/ai/actions/:id` endpoint. When the\n // dispatcher switches to the UIMessageChunk format this effect swaps over\n // to the streamed `uiParts` payload.\n React.useEffect(() => {\n const seeds = readPlaygroundUiPartSeeds()\n if (seeds.length > 0) setUiParts(seeds)\n }, [])\n\n if (agent.executionMode !== 'chat') {\n return (\n <Alert variant=\"info\" data-ai-playground-unsupported=\"chat\">\n <AlertTitle>\n {t(\n 'ai_assistant.playground.chat.notSupportedTitle',\n 'Chat mode is not available for this agent.',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.playground.chat.notSupportedBody',\n 'Pick an agent whose execution mode is \"chat\", or switch to the object-mode tab.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div className=\"flex flex-col gap-2\" data-ai-playground-chat={agent.id}>\n <AiChat\n key={agent.id}\n agent={agent.id}\n pageContext={{ source: 'playground', pageId: 'ai_assistant.playground' }}\n debug={debug}\n registry={registry}\n className=\"min-h-96\"\n debugTools={debugTools}\n debugPromptSections={debugPromptSections}\n uiParts={uiParts}\n />\n </div>\n )\n}\n\nfunction ObjectLane({ agent }: { agent: PlaygroundAgent }) {\n const t = useT()\n const [prompt, setPrompt] = React.useState('')\n const [isRunning, setIsRunning] = React.useState(false)\n const [result, setResult] = React.useState<RunObjectResponse | null>(null)\n const [error, setError] = React.useState<RunObjectError | null>(null)\n const [lastRequest, setLastRequest] = React.useState<unknown>(null)\n\n const isSupported = agent.executionMode === 'object'\n const canRun = isSupported && prompt.trim().length > 0 && !isRunning\n\n const runObject = React.useCallback(async () => {\n if (!canRun) return\n const body = {\n agent: agent.id,\n messages: [{ role: 'user' as const, content: prompt }],\n pageContext: { source: 'playground', pageId: 'ai_assistant.playground' },\n }\n setLastRequest(body)\n setIsRunning(true)\n setResult(null)\n setError(null)\n try {\n const { ok, status, result } = await apiCall<RunObjectResponse | RunObjectError>(\n '/api/ai_assistant/ai/run-object',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify(body),\n },\n )\n if (!ok) {\n const payload = (result as RunObjectError | null) ?? { error: `HTTP ${status}` }\n setError(payload)\n return\n }\n setResult((result as RunObjectResponse | null) ?? { object: null })\n } catch (err) {\n setError({\n error: err instanceof Error ? err.message : String(err),\n code: 'network_error',\n })\n } finally {\n setIsRunning(false)\n }\n }, [agent.id, canRun, prompt])\n\n const { handleKeyDown } = useAiShortcuts({\n onSubmit: () => {\n void runObject()\n },\n onCancel: () => {\n setError(null)\n },\n })\n\n if (!isSupported) {\n return (\n <Alert variant=\"info\" data-ai-playground-unsupported=\"object\">\n <AlertTitle>\n {t(\n 'ai_assistant.playground.object.notSupportedTitle',\n 'Object mode is not available for this agent.',\n )}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'ai_assistant.playground.object.notSupportedBody',\n 'This agent declares executionMode = \"chat\". Pick an object-mode agent to preview structured output, or switch to the chat tab.',\n )}\n </AlertDescription>\n </Alert>\n )\n }\n\n return (\n <div className=\"flex flex-col gap-3\" data-ai-playground-object>\n <div className=\"flex flex-col gap-2\">\n <Label htmlFor=\"ai-playground-object-input\">\n {t('ai_assistant.playground.object.inputLabel', 'Prompt')}\n </Label>\n <Textarea\n id=\"ai-playground-object-input\"\n rows={4}\n value={prompt}\n placeholder={t(\n 'ai_assistant.playground.object.inputPlaceholder',\n 'Describe what the agent should produce...',\n )}\n onChange={(event) => setPrompt(event.target.value)}\n onKeyDown={handleKeyDown}\n className=\"resize-none\"\n aria-label={t('ai_assistant.playground.object.inputLabel', 'Prompt')}\n />\n <div className=\"flex items-center justify-between\">\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'ai_assistant.playground.object.shortcutHint',\n 'Press Cmd/Ctrl+Enter to run.',\n )}\n </p>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void runObject()}\n disabled={!canRun}\n data-ai-playground-object-run\n >\n {isRunning ? (\n <Loader2 className=\"size-4 animate-spin\" aria-hidden />\n ) : (\n <Play className=\"size-4\" aria-hidden />\n )}\n <span>{t('ai_assistant.playground.object.run', 'Run object')}</span>\n </Button>\n </div>\n </div>\n\n {error ? (\n <Alert variant=\"destructive\" data-ai-playground-object-error={error.code ?? 'unknown'}>\n <AlertTitle>\n {t('ai_assistant.playground.object.errorTitle', 'Object run failed')}\n </AlertTitle>\n <AlertDescription>\n {error.code ? <span className=\"mr-2 font-mono text-xs\">{error.code}</span> : null}\n {error.error}\n </AlertDescription>\n </Alert>\n ) : null}\n\n {result ? (\n <section\n className=\"rounded-md border border-border bg-muted/30 p-3\"\n data-ai-playground-object-result\n >\n <h3 className=\"text-sm font-semibold\">\n {t('ai_assistant.playground.object.resultTitle', 'Generated object')}\n </h3>\n <pre className=\"mt-2 max-h-96 overflow-auto rounded bg-background p-2 text-xs font-mono\">\n {JSON.stringify(result.object, null, 2)}\n </pre>\n {result.usage || result.finishReason ? (\n <dl className=\"mt-3 grid grid-cols-3 gap-2 text-xs\">\n {result.finishReason ? (\n <div>\n <dt className=\"text-muted-foreground\">\n {t('ai_assistant.playground.object.finishReason', 'Finish reason')}\n </dt>\n <dd className=\"font-mono\">{result.finishReason}</dd>\n </div>\n ) : null}\n {result.usage?.inputTokens !== undefined ? (\n <div>\n <dt className=\"text-muted-foreground\">\n {t('ai_assistant.playground.object.inputTokens', 'Input tokens')}\n </dt>\n <dd className=\"font-mono\">{result.usage.inputTokens}</dd>\n </div>\n ) : null}\n {result.usage?.outputTokens !== undefined ? (\n <div>\n <dt className=\"text-muted-foreground\">\n {t('ai_assistant.playground.object.outputTokens', 'Output tokens')}\n </dt>\n <dd className=\"font-mono\">{result.usage.outputTokens}</dd>\n </div>\n ) : null}\n </dl>\n ) : null}\n </section>\n ) : null}\n\n {lastRequest && (error || result) ? (\n <details\n className=\"rounded-md border border-border bg-muted/20 p-2 text-xs\"\n data-ai-playground-object-debug\n >\n <summary className=\"cursor-pointer font-semibold\">\n {t('ai_assistant.playground.object.debugTitle', 'Last request payload')}\n </summary>\n <pre className=\"mt-2 max-h-64 overflow-auto whitespace-pre-wrap font-mono\">\n {JSON.stringify(lastRequest, null, 2)}\n </pre>\n </details>\n ) : null}\n </div>\n )\n}\n\nexport function AiPlaygroundPageClient() {\n const t = useT()\n const [selectedAgentId, setSelectedAgentId] = React.useState<string | null>(null)\n const [debugEnabled, setDebugEnabled] = React.useState(false)\n const [tab, setTab] = React.useState<'chat' | 'object'>('chat')\n\n const { data, isLoading, isError, error, refetch, isFetching } = useQuery<AgentsResponse>({\n queryKey: ['ai_assistant', 'playground', 'agents'],\n queryFn: fetchAgents,\n })\n\n const agents = React.useMemo<PlaygroundAgent[]>(() => data?.agents ?? [], [data])\n\n React.useEffect(() => {\n if (!agents.length) {\n if (selectedAgentId !== null) setSelectedAgentId(null)\n return\n }\n if (!selectedAgentId || !agents.some((agent) => agent.id === selectedAgentId)) {\n setSelectedAgentId(agents[0].id)\n }\n }, [agents, selectedAgentId])\n\n const selectedAgent = React.useMemo<PlaygroundAgent | null>(() => {\n if (!selectedAgentId) return null\n return agents.find((agent) => agent.id === selectedAgentId) ?? null\n }, [agents, selectedAgentId])\n\n if (isLoading) {\n return (\n <PlaygroundLoading\n message={t('ai_assistant.playground.loadingAgents', 'Loading AI agents...')}\n />\n )\n }\n\n if (isError) {\n return (\n <Alert variant=\"destructive\" data-ai-playground-error>\n <AlertTitle>\n {t('ai_assistant.playground.loadErrorTitle', 'Failed to load AI agents')}\n </AlertTitle>\n <AlertDescription>\n <span>{error instanceof Error ? error.message : String(error)}</span>\n <div className=\"mt-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n void refetch()\n }}\n >\n <RefreshCcw className=\"size-4\" aria-hidden />\n <span>{t('ai_assistant.playground.retry', 'Retry')}</span>\n </Button>\n </div>\n </AlertDescription>\n </Alert>\n )\n }\n\n if (!agents.length) {\n return <PlaygroundNoAgents />\n }\n\n return (\n <div className=\"flex flex-col gap-4\" data-ai-playground>\n <header className=\"flex flex-col gap-1\">\n <h1 className=\"text-2xl font-bold tracking-tight\">\n {t('ai_assistant.playground.title', 'AI Playground')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'ai_assistant.playground.subtitle',\n 'Exercise every registered AI agent end-to-end. Use the debug panel to inspect request and response payloads, and the object-mode tab to preview structured output.',\n )}\n </p>\n </header>\n\n <section className=\"flex flex-col gap-3 rounded-lg border border-border bg-background p-3\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between\">\n <div className=\"flex flex-col gap-2 sm:flex-1\">\n <Label htmlFor=\"ai-playground-agent-picker\">\n {t('ai_assistant.playground.agentPickerLabel', 'Agent')}\n </Label>\n <select\n id=\"ai-playground-agent-picker\"\n data-ai-playground-agent-picker\n className=\"h-9 rounded-md border border-input bg-background px-3 text-sm\"\n value={selectedAgentId ?? ''}\n onChange={(event) => setSelectedAgentId(event.target.value)}\n >\n {agents.map((agent) => (\n <option key={agent.id} value={agent.id}>\n {agent.label} ({agent.id})\n </option>\n ))}\n </select>\n </div>\n <div className=\"flex items-center gap-3 sm:flex-shrink-0\">\n <Label htmlFor=\"ai-playground-debug\" className=\"text-sm\">\n {t('ai_assistant.playground.debugToggle', 'Debug panel')}\n </Label>\n <Switch\n id=\"ai-playground-debug\"\n checked={debugEnabled}\n onCheckedChange={(next: boolean) => setDebugEnabled(next)}\n aria-label={t('ai_assistant.playground.debugToggle', 'Debug panel')}\n data-ai-playground-debug-toggle\n />\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => {\n void refetch()\n }}\n aria-label={t('ai_assistant.playground.refresh', 'Refresh agents')}\n disabled={isFetching}\n >\n <RefreshCcw className=\"size-4\" aria-hidden />\n </IconButton>\n </div>\n </div>\n {selectedAgent ? <AgentDetails agent={selectedAgent} /> : null}\n {selectedAgent ? <ModelResolutionPanel agentId={selectedAgent.id} /> : null}\n {selectedAgent ? <LoopDisabledPlaygroundBanner agentId={selectedAgent.id} /> : null}\n </section>\n\n {selectedAgent ? (\n <Tabs\n value={tab}\n onValueChange={(next: string) => setTab(next === 'object' ? 'object' : 'chat')}\n >\n <TabsList>\n <TabsTrigger value=\"chat\">\n {t('ai_assistant.playground.tabs.chat', 'Chat')}\n </TabsTrigger>\n <TabsTrigger value=\"object\">\n {t('ai_assistant.playground.tabs.object', 'Object mode')}\n </TabsTrigger>\n </TabsList>\n <TabsContent value=\"chat\">\n <ChatLane agent={selectedAgent} debug={debugEnabled} />\n </TabsContent>\n <TabsContent value=\"object\">\n <ObjectLane agent={selectedAgent} />\n </TabsContent>\n </Tabs>\n ) : null}\n </div>\n )\n}\n\nexport default AiPlaygroundPageClient\n"],
5
+ "mappings": "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",
6
6
  "names": ["result"]
7
7
  }