@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.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +30 -4
- package/dist/frontend/components/AiChatButton.js +3 -2
- package/dist/frontend/components/AiChatButton.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js +364 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.js.map +7 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js +7 -7
- package/dist/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.js.map +2 -2
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js +182 -0
- package/dist/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js +316 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js +8 -7
- package/dist/modules/ai_assistant/api/ai/agents/[agentId]/models/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/ai/chat/route.js +43 -20
- package/dist/modules/ai_assistant/api/ai/chat/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/settings/route.js +4 -3
- package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
- package/dist/modules/ai_assistant/api/usage/daily/route.js +111 -0
- package/dist/modules/ai_assistant/api/usage/daily/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js +108 -0
- package/dist/modules/ai_assistant/api/usage/sessions/[sessionId]/route.js.map +7 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js +153 -0
- package/dist/modules/ai_assistant/api/usage/sessions/route.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js +335 -38
- package/dist/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js +2 -7
- package/dist/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js +44 -35
- package/dist/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.js.map +2 -2
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js +282 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js +10 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.js.map +7 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js +25 -0
- package/dist/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.js.map +7 -0
- package/dist/modules/ai_assistant/cli.js +12 -0
- package/dist/modules/ai_assistant/cli.js.map +2 -2
- package/dist/modules/ai_assistant/components/AiAssistantSettingsPageClient.js.map +1 -1
- package/dist/modules/ai_assistant/data/entities.js +177 -1
- package/dist/modules/ai_assistant/data/entities.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js +104 -2
- package/dist/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.js.map +2 -2
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js +168 -0
- package/dist/modules/ai_assistant/data/repositories/AiTokenUsageRepository.js.map +7 -0
- package/dist/modules/ai_assistant/events.js +8 -0
- package/dist/modules/ai_assistant/events.js.map +2 -2
- package/dist/modules/ai_assistant/i18n/de.json +74 -1
- package/dist/modules/ai_assistant/i18n/en.json +74 -1
- package/dist/modules/ai_assistant/i18n/es.json +75 -2
- package/dist/modules/ai_assistant/i18n/pl.json +74 -1
- package/dist/modules/ai_assistant/lib/agent-policy.js.map +2 -2
- package/dist/modules/ai_assistant/lib/agent-runtime.js +588 -23
- package/dist/modules/ai_assistant/lib/agent-runtime.js.map +3 -3
- package/dist/modules/ai_assistant/lib/agent-tools.js +6 -1
- package/dist/modules/ai_assistant/lib/agent-tools.js.map +2 -2
- package/dist/modules/ai_assistant/lib/ai-agent-definition.js.map +2 -2
- package/dist/modules/ai_assistant/lib/model-factory.js +63 -22
- package/dist/modules/ai_assistant/lib/model-factory.js.map +2 -2
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js +78 -0
- package/dist/modules/ai_assistant/lib/token-usage-recorder.js.map +7 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js +33 -0
- package/dist/modules/ai_assistant/lib/usage-serialization.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js +25 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.js.map +7 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js +88 -0
- package/dist/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.js.map +7 -0
- package/dist/modules/ai_assistant/setup.js +34 -0
- package/dist/modules/ai_assistant/setup.js.map +2 -2
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js +114 -0
- package/dist/modules/ai_assistant/workers/ai-token-usage-prune.js.map +7 -0
- package/generated/entities/ai_agent_runtime_override/index.ts +7 -0
- package/generated/entities/ai_token_usage_daily/index.ts +16 -0
- package/generated/entities/ai_token_usage_event/index.ts +19 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +47 -1
- package/package.json +15 -7
- package/src/frontend/components/AiChatButton.tsx +3 -2
- package/src/modules/ai_assistant/__integration__/TC-AI-AGENT-LOOP-001-006.spec.ts +521 -0
- package/src/modules/ai_assistant/__integration__/TC-AI-RUNTIME-OVERRIDES-006-model-picker.spec.ts +8 -8
- package/src/modules/ai_assistant/__integration__/TC-AI-TOKEN-USAGE-001-005.spec.ts +231 -0
- package/src/modules/ai_assistant/__tests__/events.test.ts +4 -3
- package/src/modules/ai_assistant/__tests__/settings-page-logic.test.ts +5 -5
- package/src/modules/ai_assistant/__tests__/token-usage-recorder.test.ts +109 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/loop-override/route.ts +388 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/__tests__/route.test.ts +5 -0
- package/src/modules/ai_assistant/api/ai/agents/[agentId]/models/route.ts +8 -7
- package/src/modules/ai_assistant/api/ai/chat/__tests__/route.test.ts +102 -5
- package/src/modules/ai_assistant/api/ai/chat/route.ts +55 -18
- package/src/modules/ai_assistant/api/settings/route.ts +5 -3
- package/src/modules/ai_assistant/api/usage/daily/__tests__/route.test.ts +159 -0
- package/src/modules/ai_assistant/api/usage/daily/route.ts +126 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/__tests__/route.test.ts +143 -0
- package/src/modules/ai_assistant/api/usage/sessions/[sessionId]/route.ts +130 -0
- package/src/modules/ai_assistant/api/usage/sessions/__tests__/route.test.ts +123 -0
- package/src/modules/ai_assistant/api/usage/sessions/route.ts +184 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/agents/AiAgentSettingsPageClient.tsx +372 -16
- package/src/modules/ai_assistant/backend/config/ai-assistant/allowlist/AiTenantAllowlistPageClient.tsx +1 -4
- package/src/modules/ai_assistant/backend/config/ai-assistant/playground/AiPlaygroundPageClient.tsx +26 -9
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/AiUsageStatsPageClient.tsx +469 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.meta.ts +23 -0
- package/src/modules/ai_assistant/backend/config/ai-assistant/usage/page.tsx +12 -0
- package/src/modules/ai_assistant/cli.ts +18 -0
- package/src/modules/ai_assistant/components/AiAssistantSettingsPageClient.tsx +1 -1
- package/src/modules/ai_assistant/data/entities.ts +237 -0
- package/src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts +135 -3
- package/src/modules/ai_assistant/data/repositories/AiTokenUsageRepository.ts +213 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiAgentRuntimeOverrideRepository.test.ts +223 -0
- package/src/modules/ai_assistant/data/repositories/__tests__/AiTokenUsageRepository.test.ts +58 -0
- package/src/modules/ai_assistant/events.ts +8 -0
- package/src/modules/ai_assistant/i18n/de.json +74 -1
- package/src/modules/ai_assistant/i18n/en.json +74 -1
- package/src/modules/ai_assistant/i18n/es.json +75 -2
- package/src/modules/ai_assistant/i18n/pl.json +74 -1
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase0.test.ts +439 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase1.test.ts +243 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase2.test.ts +388 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-loop-phase3.test.ts +359 -0
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime-phase4a.test.ts +2 -2
- package/src/modules/ai_assistant/lib/__tests__/agent-runtime.test.ts +2 -1
- package/src/modules/ai_assistant/lib/__tests__/max-steps-budget.integration.test.ts +12 -13
- package/src/modules/ai_assistant/lib/__tests__/model-factory.test.ts +77 -14
- package/src/modules/ai_assistant/lib/agent-policy.ts +9 -0
- package/src/modules/ai_assistant/lib/agent-runtime.ts +1148 -43
- package/src/modules/ai_assistant/lib/agent-tools.ts +5 -1
- package/src/modules/ai_assistant/lib/ai-agent-definition.ts +289 -2
- package/src/modules/ai_assistant/lib/model-factory.ts +128 -43
- package/src/modules/ai_assistant/lib/token-usage-recorder.ts +122 -0
- package/src/modules/ai_assistant/lib/usage-serialization.ts +29 -0
- package/src/modules/ai_assistant/migrations/.snapshot-open-mercato.json +791 -0
- package/src/modules/ai_assistant/migrations/Migration20260508160000_ai_agent_loop_overrides.ts +25 -0
- package/src/modules/ai_assistant/migrations/Migration20260508170000_ai_token_usage.ts +89 -0
- package/src/modules/ai_assistant/setup.ts +49 -0
- package/src/modules/ai_assistant/workers/__tests__/ai-token-usage-prune.test.ts +144 -0
- package/src/modules/ai_assistant/workers/ai-token-usage-prune.ts +188 -0
|
@@ -43,6 +43,87 @@ class AiAgentRuntimeOverrideRepository {
|
|
|
43
43
|
});
|
|
44
44
|
return row ?? null;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Validates and normalizes the loop override fields from an input object.
|
|
48
|
+
* Throws `AiAgentRuntimeOverrideValidationError` with code
|
|
49
|
+
* `invalid_loop_override` for any validation failure.
|
|
50
|
+
*
|
|
51
|
+
* Validation rules (Phase 3 — R5 mitigation):
|
|
52
|
+
* - `loopStopWhenJson`: all items must have kind `stepCount` or `hasToolCall`.
|
|
53
|
+
* Items with kind `custom` are rejected — they cannot be stored as JSON.
|
|
54
|
+
* - `loopActiveToolsJson`: when `agentAllowedTools` is provided, every entry
|
|
55
|
+
* must be in that allowlist.
|
|
56
|
+
*/
|
|
57
|
+
validateLoopInput(input) {
|
|
58
|
+
if (input.loopStopWhenJson != null) {
|
|
59
|
+
if (!Array.isArray(input.loopStopWhenJson)) {
|
|
60
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
61
|
+
"loopStopWhenJson must be an array of stop condition objects.",
|
|
62
|
+
"invalid_loop_override"
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
for (const item of input.loopStopWhenJson) {
|
|
66
|
+
if (!item || typeof item !== "object" || !("kind" in item)) {
|
|
67
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
68
|
+
'loopStopWhenJson items must have a "kind" field.',
|
|
69
|
+
"invalid_loop_override"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const kind = item.kind;
|
|
73
|
+
if (kind === "custom") {
|
|
74
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
75
|
+
'loopStopWhenJson does not support kind "custom" \u2014 only "stepCount" and "hasToolCall" are JSON-safe and storable.',
|
|
76
|
+
"invalid_loop_override"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
if (kind !== "stepCount" && kind !== "hasToolCall") {
|
|
80
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
81
|
+
`loopStopWhenJson contains unknown kind "${String(kind)}". Allowed: "stepCount", "hasToolCall".`,
|
|
82
|
+
"invalid_loop_override"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (kind === "stepCount" && typeof item.count !== "number") {
|
|
86
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
87
|
+
'loopStopWhenJson stepCount item must have a numeric "count" field.',
|
|
88
|
+
"invalid_loop_override"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (kind === "hasToolCall" && typeof item.toolName !== "string") {
|
|
92
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
93
|
+
'loopStopWhenJson hasToolCall item must have a string "toolName" field.',
|
|
94
|
+
"invalid_loop_override"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (input.loopActiveToolsJson != null) {
|
|
100
|
+
if (!Array.isArray(input.loopActiveToolsJson)) {
|
|
101
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
102
|
+
"loopActiveToolsJson must be an array of tool name strings.",
|
|
103
|
+
"invalid_loop_override"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
for (const name of input.loopActiveToolsJson) {
|
|
107
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
108
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
109
|
+
"loopActiveToolsJson entries must be non-empty strings.",
|
|
110
|
+
"invalid_loop_override"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (input.agentAllowedTools && input.agentAllowedTools.length > 0) {
|
|
115
|
+
const outsideAllowlist = input.loopActiveToolsJson.filter(
|
|
116
|
+
(name) => !input.agentAllowedTools.includes(name)
|
|
117
|
+
);
|
|
118
|
+
if (outsideAllowlist.length > 0) {
|
|
119
|
+
throw new AiAgentRuntimeOverrideValidationError(
|
|
120
|
+
`loopActiveToolsJson contains tools outside the agent's allowedTools: ${outsideAllowlist.join(", ")}.`,
|
|
121
|
+
"invalid_loop_override"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
46
127
|
/**
|
|
47
128
|
* Inserts or updates the runtime override for the given context.
|
|
48
129
|
*
|
|
@@ -50,6 +131,11 @@ class AiAgentRuntimeOverrideRepository {
|
|
|
50
131
|
* cannot save a typo (Phase 1.4 contract re-applied per spec §Data Models).
|
|
51
132
|
* An unknown provider id throws a typed error.
|
|
52
133
|
*
|
|
134
|
+
* Also validates loop override fields (R5 mitigation — Phase 3):
|
|
135
|
+
* - `loopStopWhenJson` items must use only JSON-safe kinds.
|
|
136
|
+
* - `loopActiveToolsJson` items must be a subset of `agentAllowedTools`
|
|
137
|
+
* when that is provided.
|
|
138
|
+
*
|
|
53
139
|
* The R6 base-URL allowlist check is intentionally NOT performed here —
|
|
54
140
|
* that enforcement lives at the HTTP layer (PUT settings route). The
|
|
55
141
|
* repository trusts that callers have already validated the value.
|
|
@@ -67,6 +153,7 @@ class AiAgentRuntimeOverrideRepository {
|
|
|
67
153
|
);
|
|
68
154
|
}
|
|
69
155
|
}
|
|
156
|
+
this.validateLoopInput(input);
|
|
70
157
|
const orgFilter = ctx.organizationId ?? null;
|
|
71
158
|
const agentIdFilter = input.agentId ?? null;
|
|
72
159
|
const hasProviderId = Object.prototype.hasOwnProperty.call(input, "providerId");
|
|
@@ -93,6 +180,13 @@ class AiAgentRuntimeOverrideRepository {
|
|
|
93
180
|
}
|
|
94
181
|
existing.updatedByUserId = ctx.userId ?? null;
|
|
95
182
|
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
183
|
+
if ("loopDisabled" in input) existing.loopDisabled = input.loopDisabled ?? null;
|
|
184
|
+
if ("loopMaxSteps" in input) existing.loopMaxSteps = input.loopMaxSteps ?? null;
|
|
185
|
+
if ("loopMaxToolCalls" in input) existing.loopMaxToolCalls = input.loopMaxToolCalls ?? null;
|
|
186
|
+
if ("loopMaxWallClockMs" in input) existing.loopMaxWallClockMs = input.loopMaxWallClockMs ?? null;
|
|
187
|
+
if ("loopMaxTokens" in input) existing.loopMaxTokens = input.loopMaxTokens ?? null;
|
|
188
|
+
if ("loopStopWhenJson" in input) existing.loopStopWhenJson = input.loopStopWhenJson ?? null;
|
|
189
|
+
if ("loopActiveToolsJson" in input) existing.loopActiveToolsJson = input.loopActiveToolsJson ?? null;
|
|
96
190
|
await tx.persist(existing).flush();
|
|
97
191
|
return existing;
|
|
98
192
|
}
|
|
@@ -105,7 +199,14 @@ class AiAgentRuntimeOverrideRepository {
|
|
|
105
199
|
baseUrl: hasBaseURL ? input.baseURL ?? null : null,
|
|
106
200
|
allowedOverrideProviders: hasAllowedOverrideProviders ? input.allowedOverrideProviders ?? null : null,
|
|
107
201
|
allowedOverrideModelsByProvider: hasAllowedOverrideModels ? input.allowedOverrideModelsByProvider ?? {} : {},
|
|
108
|
-
updatedByUserId: ctx.userId ?? null
|
|
202
|
+
updatedByUserId: ctx.userId ?? null,
|
|
203
|
+
loopDisabled: input.loopDisabled ?? null,
|
|
204
|
+
loopMaxSteps: input.loopMaxSteps ?? null,
|
|
205
|
+
loopMaxToolCalls: input.loopMaxToolCalls ?? null,
|
|
206
|
+
loopMaxWallClockMs: input.loopMaxWallClockMs ?? null,
|
|
207
|
+
loopMaxTokens: input.loopMaxTokens ?? null,
|
|
208
|
+
loopStopWhenJson: input.loopStopWhenJson ?? null,
|
|
209
|
+
loopActiveToolsJson: input.loopActiveToolsJson ?? null
|
|
109
210
|
});
|
|
110
211
|
await tx.persist(row).flush();
|
|
111
212
|
return row;
|
|
@@ -143,9 +244,10 @@ class AiAgentRuntimeOverrideRepository {
|
|
|
143
244
|
}
|
|
144
245
|
}
|
|
145
246
|
class AiAgentRuntimeOverrideValidationError extends Error {
|
|
146
|
-
constructor(message) {
|
|
247
|
+
constructor(message, code = "invalid_override") {
|
|
147
248
|
super(message);
|
|
148
249
|
this.name = "AiAgentRuntimeOverrideValidationError";
|
|
250
|
+
this.code = code;
|
|
149
251
|
}
|
|
150
252
|
}
|
|
151
253
|
var AiAgentRuntimeOverrideRepository_default = AiAgentRuntimeOverrideRepository;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/ai_assistant/data/repositories/AiAgentRuntimeOverrideRepository.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { canonicalProviderId } from '../../lib/model-allowlist'\nimport { AiAgentRuntimeOverride } from '../entities'\n\nexport interface AiAgentRuntimeOverrideContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface AiAgentRuntimeOverrideInput {\n /** null means tenant-wide default (no agent pinning). */\n agentId?: string | null\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n allowedOverrideProviders?: string[] | null\n allowedOverrideModelsByProvider?: Record<string, string[]>\n}\n\n/**\n * Repository for per-tenant AI runtime overrides (Phase 4a of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * All reads MUST filter by `tenant_id` first. The three public methods follow\n * the same fail-safe pattern as sibling repositories: `getDefault` returns\n * null on missing tenant context; `upsertDefault` validates provider id at\n * write time; `clearDefault` soft-deletes via `deleted_at`.\n *\n * Resolution precedence returned by `getDefault`:\n * 1. Agent-specific row (non-null `agent_id`) when `agentId` is provided.\n * 2. Tenant-wide row (`agent_id IS NULL`) for the same `(tenant_id, org_id)`.\n * Both rows are always scoped to the caller's `tenant_id` \u2014 cross-tenant\n * reads are impossible because every query filters `WHERE tenant_id = ?`.\n */\nexport class AiAgentRuntimeOverrideRepository {\n constructor(private readonly em: EntityManager) {}\n\n /**\n * Returns the most-specific active runtime override for the given context.\n *\n * When `agentId` is provided, an agent-specific row takes precedence over a\n * tenant-wide null-agent row. Returns null when neither exists.\n *\n * Never returns a row with `deleted_at IS NOT NULL`.\n */\n async getDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n\n const orgFilter = ctx.organizationId ?? null\n\n // Try agent-specific first when caller provided an agentId.\n if (ctx.agentId) {\n const agentRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: ctx.agentId,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (agentRow) return agentRow\n }\n\n // Fall back to tenant-wide row (agentId = null).\n const tenantRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return tenantRow ?? null\n }\n\n async getExact(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n const row = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: ctx.agentId ?? null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return row ?? null\n }\n\n /**\n * Inserts or updates the runtime override for the given context.\n *\n * Validates `providerId` against the registry at write time so an admin\n * cannot save a typo (Phase 1.4 contract re-applied per spec \u00A7Data Models).\n * An unknown provider id throws a typed error.\n *\n * The R6 base-URL allowlist check is intentionally NOT performed here \u2014\n * that enforcement lives at the HTTP layer (PUT settings route). The\n * repository trusts that callers have already validated the value.\n */\n async upsertDefault(\n input: AiAgentRuntimeOverrideInput,\n ctx: AiAgentRuntimeOverrideContext,\n ): Promise<AiAgentRuntimeOverride> {\n if (!ctx?.tenantId) {\n throw new Error('AiAgentRuntimeOverrideRepository.upsertDefault requires tenantId')\n }\n\n const normalizedProviderId = input.providerId\n ? canonicalProviderId(input.providerId, llmProviderRegistry.list().map((p) => p.id))\n : null\n if (input.providerId) {\n const knownProvider = normalizedProviderId ? llmProviderRegistry.get(normalizedProviderId) : null\n if (!knownProvider) {\n throw new AiAgentRuntimeOverrideValidationError(\n `Unknown provider id \"${input.providerId}\". Registered provider ids: ${llmProviderRegistry.list().map((p) => p.id).join(', ')}.`,\n )\n }\n }\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = input.agentId ?? null\n const hasProviderId = Object.prototype.hasOwnProperty.call(input, 'providerId')\n const hasModelId = Object.prototype.hasOwnProperty.call(input, 'modelId')\n const hasBaseURL = Object.prototype.hasOwnProperty.call(input, 'baseURL')\n const hasAllowedOverrideProviders = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideProviders')\n const hasAllowedOverrideModels = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideModelsByProvider')\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n\n if (existing) {\n if (hasProviderId) existing.providerId = normalizedProviderId\n if (hasModelId) existing.modelId = input.modelId ?? null\n if (hasBaseURL) existing.baseUrl = input.baseURL ?? null\n if (hasAllowedOverrideProviders) {\n existing.allowedOverrideProviders = input.allowedOverrideProviders ?? null\n }\n if (hasAllowedOverrideModels) {\n existing.allowedOverrideModelsByProvider = input.allowedOverrideModelsByProvider ?? {}\n }\n existing.updatedByUserId = ctx.userId ?? null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return existing\n }\n\n const row = tx.create(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n providerId: hasProviderId ? normalizedProviderId : null,\n modelId: hasModelId ? (input.modelId ?? null) : null,\n baseUrl: hasBaseURL ? (input.baseURL ?? null) : null,\n allowedOverrideProviders: hasAllowedOverrideProviders\n ? (input.allowedOverrideProviders ?? null)\n : null,\n allowedOverrideModelsByProvider: hasAllowedOverrideModels\n ? (input.allowedOverrideModelsByProvider ?? {})\n : {},\n updatedByUserId: ctx.userId ?? null,\n } as unknown as AiAgentRuntimeOverride)\n await tx.persist(row).flush()\n return row\n })\n }\n\n /**\n * Soft-deletes the active override matching the given context by setting\n * `deleted_at = now()`. Returns true when a row was found and cleared,\n * false when no active row existed.\n */\n async clearDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<boolean> {\n if (!ctx?.tenantId) return false\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = ctx.agentId ?? null\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (!existing) return false\n if (\n existing.allowedOverrideProviders != null ||\n Object.keys(existing.allowedOverrideModelsByProvider ?? {}).length > 0\n ) {\n existing.providerId = null\n existing.modelId = null\n existing.baseUrl = null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return true\n }\n existing.deletedAt = new Date()\n await tx.persist(existing).flush()\n return true\n })\n }\n}\n\n/**\n * Thrown by `upsertDefault` when an unknown provider id is submitted.\n */\nexport class AiAgentRuntimeOverrideValidationError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'AiAgentRuntimeOverrideValidationError'\n }\n}\n\nexport default AiAgentRuntimeOverrideRepository\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { llmProviderRegistry } from '@open-mercato/shared/lib/ai/llm-provider-registry'\nimport { canonicalProviderId } from '../../lib/model-allowlist'\nimport { AiAgentRuntimeOverride } from '../entities'\nimport type { AiAgentLoopStopCondition } from '../../lib/ai-agent-definition'\n\nexport interface AiAgentRuntimeOverrideContext {\n tenantId: string\n organizationId?: string | null\n userId?: string | null\n}\n\nexport interface AiAgentRuntimeOverrideLoopInput {\n /** Kill switch \u2014 when true, runtime forces stepCountIs(1). */\n loopDisabled?: boolean | null\n /** Override loop.maxSteps. */\n loopMaxSteps?: number | null\n /** Override loop.budget.maxToolCalls. */\n loopMaxToolCalls?: number | null\n /** Override loop.budget.maxWallClockMs. */\n loopMaxWallClockMs?: number | null\n /** Override loop.budget.maxTokens. */\n loopMaxTokens?: number | null\n /** Override loop.stopWhen \u2014 JSON-safe variants only (stepCount, hasToolCall). */\n loopStopWhenJson?: AiAgentLoopStopCondition[] | null\n /** Override loop.activeTools \u2014 must be a subset of agent.allowedTools. */\n loopActiveToolsJson?: string[] | null\n}\n\nexport interface AiAgentRuntimeOverrideInput extends AiAgentRuntimeOverrideLoopInput {\n /** null means tenant-wide default (no agent pinning). */\n agentId?: string | null\n providerId?: string | null\n modelId?: string | null\n baseURL?: string | null\n allowedOverrideProviders?: string[] | null\n allowedOverrideModelsByProvider?: Record<string, string[]>\n /**\n * Optional: the agent's declared allowedTools. When provided, loopActiveToolsJson\n * is validated to be a subset. When omitted, allowlist validation is skipped\n * (write-time defense only; the runtime re-validates at read time).\n */\n agentAllowedTools?: string[]\n}\n\n/**\n * Repository for per-tenant AI runtime overrides (Phase 4a of spec\n * `2026-04-27-ai-agents-provider-model-baseurl-overrides`).\n *\n * All reads MUST filter by `tenant_id` first. The three public methods follow\n * the same fail-safe pattern as sibling repositories: `getDefault` returns\n * null on missing tenant context; `upsertDefault` validates provider id at\n * write time; `clearDefault` soft-deletes via `deleted_at`.\n *\n * Resolution precedence returned by `getDefault`:\n * 1. Agent-specific row (non-null `agent_id`) when `agentId` is provided.\n * 2. Tenant-wide row (`agent_id IS NULL`) for the same `(tenant_id, org_id)`.\n * Both rows are always scoped to the caller's `tenant_id` \u2014 cross-tenant\n * reads are impossible because every query filters `WHERE tenant_id = ?`.\n */\nexport class AiAgentRuntimeOverrideRepository {\n constructor(private readonly em: EntityManager) {}\n\n /**\n * Returns the most-specific active runtime override for the given context.\n *\n * When `agentId` is provided, an agent-specific row takes precedence over a\n * tenant-wide null-agent row. Returns null when neither exists.\n *\n * Never returns a row with `deleted_at IS NOT NULL`.\n */\n async getDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n\n const orgFilter = ctx.organizationId ?? null\n\n // Try agent-specific first when caller provided an agentId.\n if (ctx.agentId) {\n const agentRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: ctx.agentId,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (agentRow) return agentRow\n }\n\n // Fall back to tenant-wide row (agentId = null).\n const tenantRow = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return tenantRow ?? null\n }\n\n async getExact(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<AiAgentRuntimeOverride | null> {\n if (!ctx?.tenantId) return null\n const row = await this.em.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId ?? null,\n agentId: ctx.agentId ?? null,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n return row ?? null\n }\n\n /**\n * Validates and normalizes the loop override fields from an input object.\n * Throws `AiAgentRuntimeOverrideValidationError` with code\n * `invalid_loop_override` for any validation failure.\n *\n * Validation rules (Phase 3 \u2014 R5 mitigation):\n * - `loopStopWhenJson`: all items must have kind `stepCount` or `hasToolCall`.\n * Items with kind `custom` are rejected \u2014 they cannot be stored as JSON.\n * - `loopActiveToolsJson`: when `agentAllowedTools` is provided, every entry\n * must be in that allowlist.\n */\n private validateLoopInput(input: AiAgentRuntimeOverrideInput): void {\n if (input.loopStopWhenJson != null) {\n if (!Array.isArray(input.loopStopWhenJson)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson must be an array of stop condition objects.',\n 'invalid_loop_override',\n )\n }\n for (const item of input.loopStopWhenJson) {\n if (!item || typeof item !== 'object' || !('kind' in item)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson items must have a \"kind\" field.',\n 'invalid_loop_override',\n )\n }\n const kind = (item as AiAgentLoopStopCondition).kind\n if (kind === 'custom') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson does not support kind \"custom\" \u2014 only \"stepCount\" and \"hasToolCall\" are JSON-safe and storable.',\n 'invalid_loop_override',\n )\n }\n if (kind !== 'stepCount' && kind !== 'hasToolCall') {\n throw new AiAgentRuntimeOverrideValidationError(\n `loopStopWhenJson contains unknown kind \"${String(kind)}\". Allowed: \"stepCount\", \"hasToolCall\".`,\n 'invalid_loop_override',\n )\n }\n if (kind === 'stepCount' && typeof (item as { count?: unknown }).count !== 'number') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson stepCount item must have a numeric \"count\" field.',\n 'invalid_loop_override',\n )\n }\n if (kind === 'hasToolCall' && typeof (item as { toolName?: unknown }).toolName !== 'string') {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopStopWhenJson hasToolCall item must have a string \"toolName\" field.',\n 'invalid_loop_override',\n )\n }\n }\n }\n\n if (input.loopActiveToolsJson != null) {\n if (!Array.isArray(input.loopActiveToolsJson)) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopActiveToolsJson must be an array of tool name strings.',\n 'invalid_loop_override',\n )\n }\n for (const name of input.loopActiveToolsJson) {\n if (typeof name !== 'string' || name.length === 0) {\n throw new AiAgentRuntimeOverrideValidationError(\n 'loopActiveToolsJson entries must be non-empty strings.',\n 'invalid_loop_override',\n )\n }\n }\n if (input.agentAllowedTools && input.agentAllowedTools.length > 0) {\n const outsideAllowlist = input.loopActiveToolsJson.filter(\n (name) => !input.agentAllowedTools!.includes(name),\n )\n if (outsideAllowlist.length > 0) {\n throw new AiAgentRuntimeOverrideValidationError(\n `loopActiveToolsJson contains tools outside the agent's allowedTools: ${outsideAllowlist.join(', ')}.`,\n 'invalid_loop_override',\n )\n }\n }\n }\n }\n\n /**\n * Inserts or updates the runtime override for the given context.\n *\n * Validates `providerId` against the registry at write time so an admin\n * cannot save a typo (Phase 1.4 contract re-applied per spec \u00A7Data Models).\n * An unknown provider id throws a typed error.\n *\n * Also validates loop override fields (R5 mitigation \u2014 Phase 3):\n * - `loopStopWhenJson` items must use only JSON-safe kinds.\n * - `loopActiveToolsJson` items must be a subset of `agentAllowedTools`\n * when that is provided.\n *\n * The R6 base-URL allowlist check is intentionally NOT performed here \u2014\n * that enforcement lives at the HTTP layer (PUT settings route). The\n * repository trusts that callers have already validated the value.\n */\n async upsertDefault(\n input: AiAgentRuntimeOverrideInput,\n ctx: AiAgentRuntimeOverrideContext,\n ): Promise<AiAgentRuntimeOverride> {\n if (!ctx?.tenantId) {\n throw new Error('AiAgentRuntimeOverrideRepository.upsertDefault requires tenantId')\n }\n\n const normalizedProviderId = input.providerId\n ? canonicalProviderId(input.providerId, llmProviderRegistry.list().map((p) => p.id))\n : null\n if (input.providerId) {\n const knownProvider = normalizedProviderId ? llmProviderRegistry.get(normalizedProviderId) : null\n if (!knownProvider) {\n throw new AiAgentRuntimeOverrideValidationError(\n `Unknown provider id \"${input.providerId}\". Registered provider ids: ${llmProviderRegistry.list().map((p) => p.id).join(', ')}.`,\n )\n }\n }\n\n this.validateLoopInput(input)\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = input.agentId ?? null\n const hasProviderId = Object.prototype.hasOwnProperty.call(input, 'providerId')\n const hasModelId = Object.prototype.hasOwnProperty.call(input, 'modelId')\n const hasBaseURL = Object.prototype.hasOwnProperty.call(input, 'baseURL')\n const hasAllowedOverrideProviders = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideProviders')\n const hasAllowedOverrideModels = Object.prototype.hasOwnProperty.call(input, 'allowedOverrideModelsByProvider')\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n\n if (existing) {\n if (hasProviderId) existing.providerId = normalizedProviderId\n if (hasModelId) existing.modelId = input.modelId ?? null\n if (hasBaseURL) existing.baseUrl = input.baseURL ?? null\n if (hasAllowedOverrideProviders) {\n existing.allowedOverrideProviders = input.allowedOverrideProviders ?? null\n }\n if (hasAllowedOverrideModels) {\n existing.allowedOverrideModelsByProvider = input.allowedOverrideModelsByProvider ?? {}\n }\n existing.updatedByUserId = ctx.userId ?? null\n existing.updatedAt = new Date()\n if ('loopDisabled' in input) existing.loopDisabled = input.loopDisabled ?? null\n if ('loopMaxSteps' in input) existing.loopMaxSteps = input.loopMaxSteps ?? null\n if ('loopMaxToolCalls' in input) existing.loopMaxToolCalls = input.loopMaxToolCalls ?? null\n if ('loopMaxWallClockMs' in input) existing.loopMaxWallClockMs = input.loopMaxWallClockMs ?? null\n if ('loopMaxTokens' in input) existing.loopMaxTokens = input.loopMaxTokens ?? null\n if ('loopStopWhenJson' in input) existing.loopStopWhenJson = input.loopStopWhenJson ?? null\n if ('loopActiveToolsJson' in input) existing.loopActiveToolsJson = input.loopActiveToolsJson ?? null\n await tx.persist(existing).flush()\n return existing\n }\n\n const row = tx.create(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n providerId: hasProviderId ? normalizedProviderId : null,\n modelId: hasModelId ? (input.modelId ?? null) : null,\n baseUrl: hasBaseURL ? (input.baseURL ?? null) : null,\n allowedOverrideProviders: hasAllowedOverrideProviders\n ? (input.allowedOverrideProviders ?? null)\n : null,\n allowedOverrideModelsByProvider: hasAllowedOverrideModels\n ? (input.allowedOverrideModelsByProvider ?? {})\n : {},\n updatedByUserId: ctx.userId ?? null,\n loopDisabled: input.loopDisabled ?? null,\n loopMaxSteps: input.loopMaxSteps ?? null,\n loopMaxToolCalls: input.loopMaxToolCalls ?? null,\n loopMaxWallClockMs: input.loopMaxWallClockMs ?? null,\n loopMaxTokens: input.loopMaxTokens ?? null,\n loopStopWhenJson: input.loopStopWhenJson ?? null,\n loopActiveToolsJson: input.loopActiveToolsJson ?? null,\n } as unknown as AiAgentRuntimeOverride)\n await tx.persist(row).flush()\n return row\n })\n }\n\n /**\n * Soft-deletes the active override matching the given context by setting\n * `deleted_at = now()`. Returns true when a row was found and cleared,\n * false when no active row existed.\n */\n async clearDefault(ctx: {\n tenantId: string\n organizationId?: string | null\n agentId?: string | null\n }): Promise<boolean> {\n if (!ctx?.tenantId) return false\n\n const orgFilter = ctx.organizationId ?? null\n const agentIdFilter = ctx.agentId ?? null\n\n return this.em.transactional(async (tx) => {\n const existing = await tx.findOne(AiAgentRuntimeOverride, {\n tenantId: ctx.tenantId,\n organizationId: orgFilter,\n agentId: agentIdFilter,\n deletedAt: null,\n } satisfies FilterQuery<AiAgentRuntimeOverride>)\n if (!existing) return false\n if (\n existing.allowedOverrideProviders != null ||\n Object.keys(existing.allowedOverrideModelsByProvider ?? {}).length > 0\n ) {\n existing.providerId = null\n existing.modelId = null\n existing.baseUrl = null\n existing.updatedAt = new Date()\n await tx.persist(existing).flush()\n return true\n }\n existing.deletedAt = new Date()\n await tx.persist(existing).flush()\n return true\n })\n }\n}\n\n/**\n * Thrown by `upsertDefault` when validation fails (unknown provider id,\n * invalid loop override JSON).\n */\nexport class AiAgentRuntimeOverrideValidationError extends Error {\n readonly code: string\n\n constructor(message: string, code = 'invalid_override') {\n super(message)\n this.name = 'AiAgentRuntimeOverrideValidationError'\n this.code = code\n }\n}\n\nexport default AiAgentRuntimeOverrideRepository\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { AiTokenUsageEvent, AiTokenUsageDaily } from "../entities.js";
|
|
2
|
+
class AiTokenUsageRepository {
|
|
3
|
+
constructor(em) {
|
|
4
|
+
this.em = em;
|
|
5
|
+
}
|
|
6
|
+
async createEvent(input) {
|
|
7
|
+
const event = this.em.create(AiTokenUsageEvent, {
|
|
8
|
+
tenantId: input.tenantId,
|
|
9
|
+
organizationId: input.organizationId ?? null,
|
|
10
|
+
userId: input.userId,
|
|
11
|
+
agentId: input.agentId,
|
|
12
|
+
moduleId: input.moduleId,
|
|
13
|
+
sessionId: input.sessionId,
|
|
14
|
+
turnId: input.turnId,
|
|
15
|
+
stepIndex: input.stepIndex,
|
|
16
|
+
providerId: input.providerId,
|
|
17
|
+
modelId: input.modelId,
|
|
18
|
+
inputTokens: input.inputTokens,
|
|
19
|
+
outputTokens: input.outputTokens,
|
|
20
|
+
cachedInputTokens: input.cachedInputTokens ?? null,
|
|
21
|
+
reasoningTokens: input.reasoningTokens ?? null,
|
|
22
|
+
finishReason: input.finishReason ?? null,
|
|
23
|
+
loopAbortReason: input.loopAbortReason ?? null
|
|
24
|
+
});
|
|
25
|
+
this.em.persist(event);
|
|
26
|
+
await this.em.flush();
|
|
27
|
+
return event;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Upserts the daily rollup row, incrementing counters atomically via
|
|
31
|
+
* `INSERT ... ON CONFLICT DO UPDATE`. The `session_count` column is
|
|
32
|
+
* incremented only when this is the first event observed for the
|
|
33
|
+
* `(tenant_id, session_id, day, agent_id, model_id)` tuple — a LATERAL
|
|
34
|
+
* NOT EXISTS check prevents double-counting.
|
|
35
|
+
*
|
|
36
|
+
* The query handles the two partial unique indexes (org IS NOT NULL vs
|
|
37
|
+
* IS NULL) by encoding `organization_id` in the EXCLUDED row and relying
|
|
38
|
+
* on the appropriate partial index the planner selects.
|
|
39
|
+
*/
|
|
40
|
+
async upsertDaily(input) {
|
|
41
|
+
const connection = this.em.getConnection();
|
|
42
|
+
const now = /* @__PURE__ */ new Date();
|
|
43
|
+
const orgValue = input.organizationId ?? null;
|
|
44
|
+
const sessionCheckSql = `
|
|
45
|
+
select exists (
|
|
46
|
+
select 1 from ai_token_usage_events
|
|
47
|
+
where tenant_id = ?
|
|
48
|
+
and session_id = ?::uuid
|
|
49
|
+
and agent_id = ?
|
|
50
|
+
and model_id = ?
|
|
51
|
+
and date_trunc('day', created_at) = ?::date
|
|
52
|
+
${orgValue !== null ? "and organization_id = ?" : "and organization_id is null"}
|
|
53
|
+
) as already_seen
|
|
54
|
+
`;
|
|
55
|
+
const sessionCheckParams = [
|
|
56
|
+
input.tenantId,
|
|
57
|
+
input.sessionId,
|
|
58
|
+
input.agentId,
|
|
59
|
+
input.modelId,
|
|
60
|
+
input.day
|
|
61
|
+
];
|
|
62
|
+
if (orgValue !== null) sessionCheckParams.push(orgValue);
|
|
63
|
+
const sessionRows = await connection.execute(sessionCheckSql, sessionCheckParams, "all");
|
|
64
|
+
const alreadySeen = Array.isArray(sessionRows) && sessionRows.length > 0 && sessionRows[0].already_seen === true;
|
|
65
|
+
const sessionDelta = alreadySeen ? 0 : 1;
|
|
66
|
+
if (orgValue !== null) {
|
|
67
|
+
await connection.execute(
|
|
68
|
+
`
|
|
69
|
+
insert into ai_token_usage_daily (
|
|
70
|
+
id, tenant_id, organization_id, day, agent_id, model_id, provider_id,
|
|
71
|
+
input_tokens, output_tokens, cached_input_tokens, reasoning_tokens,
|
|
72
|
+
step_count, turn_count, session_count, created_at, updated_at
|
|
73
|
+
) values (
|
|
74
|
+
gen_random_uuid(), ?, ?, ?::date, ?, ?, ?,
|
|
75
|
+
?, ?, ?, ?,
|
|
76
|
+
1, 1, ?, ?, ?
|
|
77
|
+
)
|
|
78
|
+
on conflict (tenant_id, day, agent_id, model_id, organization_id)
|
|
79
|
+
where organization_id is not null
|
|
80
|
+
do update set
|
|
81
|
+
input_tokens = ai_token_usage_daily.input_tokens + excluded.input_tokens,
|
|
82
|
+
output_tokens = ai_token_usage_daily.output_tokens + excluded.output_tokens,
|
|
83
|
+
cached_input_tokens = ai_token_usage_daily.cached_input_tokens + excluded.cached_input_tokens,
|
|
84
|
+
reasoning_tokens = ai_token_usage_daily.reasoning_tokens + excluded.reasoning_tokens,
|
|
85
|
+
step_count = ai_token_usage_daily.step_count + 1,
|
|
86
|
+
turn_count = ai_token_usage_daily.turn_count + 1,
|
|
87
|
+
session_count = ai_token_usage_daily.session_count + excluded.session_count,
|
|
88
|
+
updated_at = excluded.updated_at
|
|
89
|
+
`,
|
|
90
|
+
[
|
|
91
|
+
input.tenantId,
|
|
92
|
+
orgValue,
|
|
93
|
+
input.day,
|
|
94
|
+
input.agentId,
|
|
95
|
+
input.modelId,
|
|
96
|
+
input.providerId,
|
|
97
|
+
input.inputTokens,
|
|
98
|
+
input.outputTokens,
|
|
99
|
+
input.cachedInputTokens,
|
|
100
|
+
input.reasoningTokens,
|
|
101
|
+
sessionDelta,
|
|
102
|
+
now,
|
|
103
|
+
now
|
|
104
|
+
],
|
|
105
|
+
"run"
|
|
106
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
await connection.execute(
|
|
109
|
+
`
|
|
110
|
+
insert into ai_token_usage_daily (
|
|
111
|
+
id, tenant_id, organization_id, day, agent_id, model_id, provider_id,
|
|
112
|
+
input_tokens, output_tokens, cached_input_tokens, reasoning_tokens,
|
|
113
|
+
step_count, turn_count, session_count, created_at, updated_at
|
|
114
|
+
) values (
|
|
115
|
+
gen_random_uuid(), ?, null, ?::date, ?, ?, ?,
|
|
116
|
+
?, ?, ?, ?,
|
|
117
|
+
1, 1, ?, ?, ?
|
|
118
|
+
)
|
|
119
|
+
on conflict (tenant_id, day, agent_id, model_id)
|
|
120
|
+
where organization_id is null
|
|
121
|
+
do update set
|
|
122
|
+
input_tokens = ai_token_usage_daily.input_tokens + excluded.input_tokens,
|
|
123
|
+
output_tokens = ai_token_usage_daily.output_tokens + excluded.output_tokens,
|
|
124
|
+
cached_input_tokens = ai_token_usage_daily.cached_input_tokens + excluded.cached_input_tokens,
|
|
125
|
+
reasoning_tokens = ai_token_usage_daily.reasoning_tokens + excluded.reasoning_tokens,
|
|
126
|
+
step_count = ai_token_usage_daily.step_count + 1,
|
|
127
|
+
turn_count = ai_token_usage_daily.turn_count + 1,
|
|
128
|
+
session_count = ai_token_usage_daily.session_count + excluded.session_count,
|
|
129
|
+
updated_at = excluded.updated_at
|
|
130
|
+
`,
|
|
131
|
+
[
|
|
132
|
+
input.tenantId,
|
|
133
|
+
input.day,
|
|
134
|
+
input.agentId,
|
|
135
|
+
input.modelId,
|
|
136
|
+
input.providerId,
|
|
137
|
+
input.inputTokens,
|
|
138
|
+
input.outputTokens,
|
|
139
|
+
input.cachedInputTokens,
|
|
140
|
+
input.reasoningTokens,
|
|
141
|
+
sessionDelta,
|
|
142
|
+
now,
|
|
143
|
+
now
|
|
144
|
+
],
|
|
145
|
+
"run"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async listEventsForSession(tenantId, sessionId, limit = 200) {
|
|
150
|
+
return this.em.find(
|
|
151
|
+
AiTokenUsageEvent,
|
|
152
|
+
{ tenantId, sessionId },
|
|
153
|
+
{ orderBy: { createdAt: "ASC", stepIndex: "ASC" }, limit }
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
async listDailyRollup(tenantId, from, to, filters = {}) {
|
|
157
|
+
const where = { tenantId, day: { $gte: from, $lte: to } };
|
|
158
|
+
if (filters.agentId) where.agentId = filters.agentId;
|
|
159
|
+
if (filters.modelId) where.modelId = filters.modelId;
|
|
160
|
+
return this.em.find(AiTokenUsageDaily, where, {
|
|
161
|
+
orderBy: { day: "ASC", agentId: "ASC", modelId: "ASC" }
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
AiTokenUsageRepository
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=AiTokenUsageRepository.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/ai_assistant/data/repositories/AiTokenUsageRepository.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { AiTokenUsageEvent, AiTokenUsageDaily } from '../entities'\n\nexport interface CreateTokenUsageEventInput {\n tenantId: string\n organizationId?: string | null\n userId: string\n agentId: string\n moduleId: string\n sessionId: string\n turnId: string\n stepIndex: number\n providerId: string\n modelId: string\n inputTokens: number\n outputTokens: number\n cachedInputTokens?: number | null\n reasoningTokens?: number | null\n finishReason?: string | null\n loopAbortReason?: string | null\n}\n\nexport interface UpsertTokenUsageDailyInput {\n tenantId: string\n organizationId?: string | null\n day: string\n agentId: string\n modelId: string\n providerId: string\n sessionId: string\n inputTokens: number\n outputTokens: number\n cachedInputTokens: number\n reasoningTokens: number\n}\n\n/**\n * Repository for the Phase 6 token-usage event log and daily rollup tables.\n *\n * `upsertDaily` uses raw SQL to perform the CONFLICT-based incremental update\n * because MikroORM does not expose `INSERT ... ON CONFLICT DO UPDATE` for\n * arbitrary expressions. The LATERAL session-count check guards against\n * double-counting a session within the same `(tenant, day, agent, model)` tuple.\n *\n * All writes are fail-open \u2014 callers MUST wrap invocations in try/catch and\n * log at `warn` rather than rethrowing (R12: recorder must never break a turn).\n *\n * Phase 6.1 + 6.3 of spec `2026-04-28-ai-agents-agentic-loop-controls`.\n */\nexport class AiTokenUsageRepository {\n constructor(private readonly em: EntityManager) {}\n\n async createEvent(input: CreateTokenUsageEventInput): Promise<AiTokenUsageEvent> {\n const event = this.em.create(AiTokenUsageEvent, {\n tenantId: input.tenantId,\n organizationId: input.organizationId ?? null,\n userId: input.userId,\n agentId: input.agentId,\n moduleId: input.moduleId,\n sessionId: input.sessionId,\n turnId: input.turnId,\n stepIndex: input.stepIndex,\n providerId: input.providerId,\n modelId: input.modelId,\n inputTokens: input.inputTokens,\n outputTokens: input.outputTokens,\n cachedInputTokens: input.cachedInputTokens ?? null,\n reasoningTokens: input.reasoningTokens ?? null,\n finishReason: input.finishReason ?? null,\n loopAbortReason: input.loopAbortReason ?? null,\n })\n this.em.persist(event)\n await this.em.flush()\n return event\n }\n\n /**\n * Upserts the daily rollup row, incrementing counters atomically via\n * `INSERT ... ON CONFLICT DO UPDATE`. The `session_count` column is\n * incremented only when this is the first event observed for the\n * `(tenant_id, session_id, day, agent_id, model_id)` tuple \u2014 a LATERAL\n * NOT EXISTS check prevents double-counting.\n *\n * The query handles the two partial unique indexes (org IS NOT NULL vs\n * IS NULL) by encoding `organization_id` in the EXCLUDED row and relying\n * on the appropriate partial index the planner selects.\n */\n async upsertDaily(input: UpsertTokenUsageDailyInput): Promise<void> {\n const connection = this.em.getConnection()\n const now = new Date()\n const orgValue = input.organizationId ?? null\n\n // Determine if this is the first event for this session in the window\n // (used to guard the session_count increment).\n const sessionCheckSql = `\n select exists (\n select 1 from ai_token_usage_events\n where tenant_id = ?\n and session_id = ?::uuid\n and agent_id = ?\n and model_id = ?\n and date_trunc('day', created_at) = ?::date\n ${orgValue !== null ? 'and organization_id = ?' : 'and organization_id is null'}\n ) as already_seen\n `\n const sessionCheckParams: unknown[] = [\n input.tenantId,\n input.sessionId,\n input.agentId,\n input.modelId,\n input.day,\n ]\n if (orgValue !== null) sessionCheckParams.push(orgValue)\n\n const sessionRows = await connection.execute(sessionCheckSql, sessionCheckParams, 'all')\n const alreadySeen =\n Array.isArray(sessionRows) &&\n sessionRows.length > 0 &&\n (sessionRows[0] as Record<string, unknown>).already_seen === true\n\n const sessionDelta = alreadySeen ? 0 : 1\n\n if (orgValue !== null) {\n await connection.execute(\n `\n insert into ai_token_usage_daily (\n id, tenant_id, organization_id, day, agent_id, model_id, provider_id,\n input_tokens, output_tokens, cached_input_tokens, reasoning_tokens,\n step_count, turn_count, session_count, created_at, updated_at\n ) values (\n gen_random_uuid(), ?, ?, ?::date, ?, ?, ?,\n ?, ?, ?, ?,\n 1, 1, ?, ?, ?\n )\n on conflict (tenant_id, day, agent_id, model_id, organization_id)\n where organization_id is not null\n do update set\n input_tokens = ai_token_usage_daily.input_tokens + excluded.input_tokens,\n output_tokens = ai_token_usage_daily.output_tokens + excluded.output_tokens,\n cached_input_tokens = ai_token_usage_daily.cached_input_tokens + excluded.cached_input_tokens,\n reasoning_tokens = ai_token_usage_daily.reasoning_tokens + excluded.reasoning_tokens,\n step_count = ai_token_usage_daily.step_count + 1,\n turn_count = ai_token_usage_daily.turn_count + 1,\n session_count = ai_token_usage_daily.session_count + excluded.session_count,\n updated_at = excluded.updated_at\n `,\n [\n input.tenantId, orgValue, input.day, input.agentId, input.modelId, input.providerId,\n input.inputTokens, input.outputTokens, input.cachedInputTokens, input.reasoningTokens,\n sessionDelta, now, now,\n ],\n 'run',\n )\n } else {\n await connection.execute(\n `\n insert into ai_token_usage_daily (\n id, tenant_id, organization_id, day, agent_id, model_id, provider_id,\n input_tokens, output_tokens, cached_input_tokens, reasoning_tokens,\n step_count, turn_count, session_count, created_at, updated_at\n ) values (\n gen_random_uuid(), ?, null, ?::date, ?, ?, ?,\n ?, ?, ?, ?,\n 1, 1, ?, ?, ?\n )\n on conflict (tenant_id, day, agent_id, model_id)\n where organization_id is null\n do update set\n input_tokens = ai_token_usage_daily.input_tokens + excluded.input_tokens,\n output_tokens = ai_token_usage_daily.output_tokens + excluded.output_tokens,\n cached_input_tokens = ai_token_usage_daily.cached_input_tokens + excluded.cached_input_tokens,\n reasoning_tokens = ai_token_usage_daily.reasoning_tokens + excluded.reasoning_tokens,\n step_count = ai_token_usage_daily.step_count + 1,\n turn_count = ai_token_usage_daily.turn_count + 1,\n session_count = ai_token_usage_daily.session_count + excluded.session_count,\n updated_at = excluded.updated_at\n `,\n [\n input.tenantId, input.day, input.agentId, input.modelId, input.providerId,\n input.inputTokens, input.outputTokens, input.cachedInputTokens, input.reasoningTokens,\n sessionDelta, now, now,\n ],\n 'run',\n )\n }\n }\n\n async listEventsForSession(\n tenantId: string,\n sessionId: string,\n limit = 200,\n ): Promise<AiTokenUsageEvent[]> {\n return this.em.find(\n AiTokenUsageEvent,\n { tenantId, sessionId },\n { orderBy: { createdAt: 'ASC', stepIndex: 'ASC' }, limit },\n )\n }\n\n async listDailyRollup(\n tenantId: string,\n from: string,\n to: string,\n filters: { agentId?: string; modelId?: string } = {},\n ): Promise<AiTokenUsageDaily[]> {\n const where: Record<string, unknown> = { tenantId, day: { $gte: from, $lte: to } }\n if (filters.agentId) where.agentId = filters.agentId\n if (filters.modelId) where.modelId = filters.modelId\n return this.em.find(AiTokenUsageDaily, where, {\n orderBy: { day: 'ASC', agentId: 'ASC', modelId: 'ASC' },\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,mBAAmB,yBAAyB;AAgD9C,MAAM,uBAAuB;AAAA,EAClC,YAA6B,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAEjD,MAAM,YAAY,OAA+D;AAC/E,UAAM,QAAQ,KAAK,GAAG,OAAO,mBAAmB;AAAA,MAC9C,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM,kBAAkB;AAAA,MACxC,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,UAAU,MAAM;AAAA,MAChB,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,MACd,WAAW,MAAM;AAAA,MACjB,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM;AAAA,MACf,aAAa,MAAM;AAAA,MACnB,cAAc,MAAM;AAAA,MACpB,mBAAmB,MAAM,qBAAqB;AAAA,MAC9C,iBAAiB,MAAM,mBAAmB;AAAA,MAC1C,cAAc,MAAM,gBAAgB;AAAA,MACpC,iBAAiB,MAAM,mBAAmB;AAAA,IAC5C,CAAC;AACD,SAAK,GAAG,QAAQ,KAAK;AACrB,UAAM,KAAK,GAAG,MAAM;AACpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,YAAY,OAAkD;AAClE,UAAM,aAAa,KAAK,GAAG,cAAc;AACzC,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,WAAW,MAAM,kBAAkB;AAIzC,UAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAQhB,aAAa,OAAO,4BAA4B,6BAA6B;AAAA;AAAA;AAGrF,UAAM,qBAAgC;AAAA,MACpC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,IACR;AACA,QAAI,aAAa,KAAM,oBAAmB,KAAK,QAAQ;AAEvD,UAAM,cAAc,MAAM,WAAW,QAAQ,iBAAiB,oBAAoB,KAAK;AACvF,UAAM,cACJ,MAAM,QAAQ,WAAW,KACzB,YAAY,SAAS,KACpB,YAAY,CAAC,EAA8B,iBAAiB;AAE/D,UAAM,eAAe,cAAc,IAAI;AAEvC,QAAI,aAAa,MAAM;AACrB,YAAM,WAAW;AAAA,QACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAsBA;AAAA,UACE,MAAM;AAAA,UAAU;AAAA,UAAU,MAAM;AAAA,UAAK,MAAM;AAAA,UAAS,MAAM;AAAA,UAAS,MAAM;AAAA,UACzE,MAAM;AAAA,UAAa,MAAM;AAAA,UAAc,MAAM;AAAA,UAAmB,MAAM;AAAA,UACtE;AAAA,UAAc;AAAA,UAAK;AAAA,QACrB;AAAA,QACA;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,WAAW;AAAA,QACf;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAsBA;AAAA,UACE,MAAM;AAAA,UAAU,MAAM;AAAA,UAAK,MAAM;AAAA,UAAS,MAAM;AAAA,UAAS,MAAM;AAAA,UAC/D,MAAM;AAAA,UAAa,MAAM;AAAA,UAAc,MAAM;AAAA,UAAmB,MAAM;AAAA,UACtE;AAAA,UAAc;AAAA,UAAK;AAAA,QACrB;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,qBACJ,UACA,WACA,QAAQ,KACsB;AAC9B,WAAO,KAAK,GAAG;AAAA,MACb;AAAA,MACA,EAAE,UAAU,UAAU;AAAA,MACtB,EAAE,SAAS,EAAE,WAAW,OAAO,WAAW,MAAM,GAAG,MAAM;AAAA,IAC3D;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,UACA,MACA,IACA,UAAkD,CAAC,GACrB;AAC9B,UAAM,QAAiC,EAAE,UAAU,KAAK,EAAE,MAAM,MAAM,MAAM,GAAG,EAAE;AACjF,QAAI,QAAQ,QAAS,OAAM,UAAU,QAAQ;AAC7C,QAAI,QAAQ,QAAS,OAAM,UAAU,QAAQ;AAC7C,WAAO,KAAK,GAAG,KAAK,mBAAmB,OAAO;AAAA,MAC5C,SAAS,EAAE,KAAK,OAAO,SAAS,OAAO,SAAS,MAAM;AAAA,IACxD,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -17,6 +17,14 @@ const events = [
|
|
|
17
17
|
label: "AI Pending Action Expired",
|
|
18
18
|
entity: "ai_pending_action",
|
|
19
19
|
category: "system"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "ai.token_usage.recorded",
|
|
23
|
+
label: "AI Token Usage Recorded",
|
|
24
|
+
entity: "token_usage",
|
|
25
|
+
category: "system",
|
|
26
|
+
clientBroadcast: false,
|
|
27
|
+
portalBroadcast: false
|
|
20
28
|
}
|
|
21
29
|
];
|
|
22
30
|
const eventsConfig = createModuleEvents({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/ai_assistant/events.ts"],
|
|
4
|
-
"sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * AI Assistant Module Events\n *\n * Typed declarations for the pending-action lifecycle events emitted by\n * the Phase 3 WS-C mutation approval flow. The event IDs are FROZEN per\n * `BACKWARD_COMPATIBILITY.md` \u00A75 (contract surface #5) and MUST NOT be\n * renamed; additive payload changes are allowed.\n *\n * - `ai.action.confirmed` \u2014 emitted by `executePendingActionConfirm`\n * (Step 5.8) after the `pending \u2192 confirmed \u2192 executing \u2192 {confirmed|\n * failed}` transition. The handler's outcome lives in\n * `executionResult`; partial-stale rows carry the surviving stale\n * records via `failedRecords`.\n * - `ai.action.cancelled` \u2014 emitted by `executePendingActionCancel`\n * (Step 5.9) after the atomic `pending \u2192 cancelled` transition.\n * - `ai.action.expired` \u2014 emitted by the Step 5.9 expired short-circuit\n * AND by the Step 5.12 cleanup worker when the TTL elapses. The\n * worker is the actor in that path, so `resolvedByUserId` is NOT part\n * of the payload.\n */\nconst events = [\n {\n id: 'ai.action.confirmed',\n label: 'AI Pending Action Confirmed',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.cancelled',\n label: 'AI Pending Action Cancelled',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.expired',\n label: 'AI Pending Action Expired',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'ai_assistant',\n events,\n})\n\n/** Type-safe event emitter for the ai_assistant module. */\nexport const emitAiAssistantEvent = eventsConfig.emit\n\n/** Event IDs declared by the ai_assistant module. */\nexport type AiAssistantEventId = (typeof events)[number]['id']\n\n/**\n * Typed payload contracts for each ai_assistant event. Payloads are\n * additive-only \u2014 extend existing fields rather than renaming/removing.\n */\nexport interface AiActionFailedRecordPayload {\n recordId: string\n error: { code: string; message: string }\n}\n\nexport interface AiActionExecutionResultPayload {\n recordId?: string\n commandName?: string\n error?: { code: string; message: string }\n}\n\nexport interface AiActionConfirmedPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n failedRecords?: AiActionFailedRecordPayload[] | null\n}\n\nexport interface AiActionCancelledPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n reason?: string\n}\n\nexport interface AiActionExpiredPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string | null\n resolvedByUserId: null\n resolvedAt: string\n expiresAt?: string\n expiredAt?: string\n}\n\nexport default eventsConfig\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,0BAA0B;AAsBnC,MAAM,SAAS;AAAA,EACb;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AACF;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAGM,MAAM,uBAAuB,aAAa;AA8DjD,IAAO,iBAAQ;",
|
|
4
|
+
"sourcesContent": ["import { createModuleEvents } from '@open-mercato/shared/modules/events'\n\n/**\n * AI Assistant Module Events\n *\n * Typed declarations for the pending-action lifecycle events emitted by\n * the Phase 3 WS-C mutation approval flow. The event IDs are FROZEN per\n * `BACKWARD_COMPATIBILITY.md` \u00A75 (contract surface #5) and MUST NOT be\n * renamed; additive payload changes are allowed.\n *\n * - `ai.action.confirmed` \u2014 emitted by `executePendingActionConfirm`\n * (Step 5.8) after the `pending \u2192 confirmed \u2192 executing \u2192 {confirmed|\n * failed}` transition. The handler's outcome lives in\n * `executionResult`; partial-stale rows carry the surviving stale\n * records via `failedRecords`.\n * - `ai.action.cancelled` \u2014 emitted by `executePendingActionCancel`\n * (Step 5.9) after the atomic `pending \u2192 cancelled` transition.\n * - `ai.action.expired` \u2014 emitted by the Step 5.9 expired short-circuit\n * AND by the Step 5.12 cleanup worker when the TTL elapses. The\n * worker is the actor in that path, so `resolvedByUserId` is NOT part\n * of the payload.\n */\nconst events = [\n {\n id: 'ai.action.confirmed',\n label: 'AI Pending Action Confirmed',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.cancelled',\n label: 'AI Pending Action Cancelled',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.action.expired',\n label: 'AI Pending Action Expired',\n entity: 'ai_pending_action',\n category: 'system' as const,\n },\n {\n id: 'ai.token_usage.recorded',\n label: 'AI Token Usage Recorded',\n entity: 'token_usage',\n category: 'system' as const,\n clientBroadcast: false,\n portalBroadcast: false,\n },\n] as const\n\nexport const eventsConfig = createModuleEvents({\n moduleId: 'ai_assistant',\n events,\n})\n\n/** Type-safe event emitter for the ai_assistant module. */\nexport const emitAiAssistantEvent = eventsConfig.emit\n\n/** Event IDs declared by the ai_assistant module. */\nexport type AiAssistantEventId = (typeof events)[number]['id']\n\n/**\n * Typed payload contracts for each ai_assistant event. Payloads are\n * additive-only \u2014 extend existing fields rather than renaming/removing.\n */\nexport interface AiActionFailedRecordPayload {\n recordId: string\n error: { code: string; message: string }\n}\n\nexport interface AiActionExecutionResultPayload {\n recordId?: string\n commandName?: string\n error?: { code: string; message: string }\n}\n\nexport interface AiActionConfirmedPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n failedRecords?: AiActionFailedRecordPayload[] | null\n}\n\nexport interface AiActionCancelledPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string\n resolvedByUserId: string\n resolvedAt: string\n executionResult: AiActionExecutionResultPayload | null\n reason?: string\n}\n\nexport interface AiActionExpiredPayload {\n pendingActionId: string\n agentId: string\n toolName: string\n status: string\n tenantId: string | null\n organizationId: string | null\n userId: string | null\n resolvedByUserId: null\n resolvedAt: string\n expiresAt?: string\n expiredAt?: string\n}\n\nexport default eventsConfig\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,0BAA0B;AAsBnC,MAAM,SAAS;AAAA,EACb;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiB;AAAA,IACjB,iBAAiB;AAAA,EACnB;AACF;AAEO,MAAM,eAAe,mBAAmB;AAAA,EAC7C,UAAU;AAAA,EACV;AACF,CAAC;AAGM,MAAM,uBAAuB,aAAa;AA8DjD,IAAO,iBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,6 +8,24 @@
|
|
|
8
8
|
"ai_assistant.agents.empty.title": "Für Ihre Rolle sind noch keine KI-Agenten registriert.",
|
|
9
9
|
"ai_assistant.agents.loadErrorTitle": "KI-Agenten konnten nicht geladen werden",
|
|
10
10
|
"ai_assistant.agents.loadingAgents": "KI-Agenten werden geladen...",
|
|
11
|
+
"ai_assistant.agents.loop_policy.clear": "Überschreibung löschen",
|
|
12
|
+
"ai_assistant.agents.loop_policy.clearedMessage": "Loop-Richtlinien-Überschreibung gelöscht; Agent verwendet seine deklarierten Standardwerte.",
|
|
13
|
+
"ai_assistant.agents.loop_policy.disabledBadge": "Loop deaktiviert",
|
|
14
|
+
"ai_assistant.agents.loop_policy.errorTitle": "Loop-Richtlinie konnte nicht aktualisiert werden",
|
|
15
|
+
"ai_assistant.agents.loop_policy.killSwitchDescription": "Wenn aktiviert, läuft der Agent als einzelner Modellaufruf ohne Toolschleife.",
|
|
16
|
+
"ai_assistant.agents.loop_policy.killSwitchLabel": "Kill-Schalter",
|
|
17
|
+
"ai_assistant.agents.loop_policy.loadErrorTitle": "Loop-Richtlinie konnte nicht geladen werden",
|
|
18
|
+
"ai_assistant.agents.loop_policy.loading": "Loop-Richtlinie wird geladen...",
|
|
19
|
+
"ai_assistant.agents.loop_policy.maxStepsLabel": "Max. Schritte",
|
|
20
|
+
"ai_assistant.agents.loop_policy.maxTokensLabel": "Max. Tokens",
|
|
21
|
+
"ai_assistant.agents.loop_policy.maxToolCallsLabel": "Max. Tool-Aufrufe",
|
|
22
|
+
"ai_assistant.agents.loop_policy.maxWallClockMsLabel": "Max. Echtzeit (ms)",
|
|
23
|
+
"ai_assistant.agents.loop_policy.noOverridePlaceholder": "Keine Überschreibung",
|
|
24
|
+
"ai_assistant.agents.loop_policy.save": "Überschreibung speichern",
|
|
25
|
+
"ai_assistant.agents.loop_policy.savedMessage": "Loop-Richtlinien-Überschreibung gespeichert.",
|
|
26
|
+
"ai_assistant.agents.loop_policy.savedTitle": "Loop-Richtlinie aktualisiert",
|
|
27
|
+
"ai_assistant.agents.loop_policy.subtitle": "Legen Sie mandantenweite Budget-Limits fest oder deaktivieren Sie den agentischen Loop für diesen Agenten.",
|
|
28
|
+
"ai_assistant.agents.loop_policy.title": "Loop-Richtlinie",
|
|
11
29
|
"ai_assistant.agents.meta.executionMode": "Ausführungsmodus",
|
|
12
30
|
"ai_assistant.agents.meta.id": "Agenten-ID",
|
|
13
31
|
"ai_assistant.agents.meta.maxSteps": "Max. Schritte",
|
|
@@ -17,6 +35,22 @@
|
|
|
17
35
|
"ai_assistant.agents.meta.readOnlyNo": "Nein",
|
|
18
36
|
"ai_assistant.agents.meta.readOnlyYes": "Ja",
|
|
19
37
|
"ai_assistant.agents.meta.unlimited": "Unbegrenzt",
|
|
38
|
+
"ai_assistant.agents.model_override.allowlistCustom": "benutzerdefiniert",
|
|
39
|
+
"ai_assistant.agents.model_override.allowlistInherited": "geerbt",
|
|
40
|
+
"ai_assistant.agents.model_override.allowlistReset": "Erben",
|
|
41
|
+
"ai_assistant.agents.model_override.allowlistSave": "Auswahl speichern",
|
|
42
|
+
"ai_assistant.agents.model_override.anyProvider": "erste konfigurierte",
|
|
43
|
+
"ai_assistant.agents.model_override.clear": "Überschreibung löschen",
|
|
44
|
+
"ai_assistant.agents.model_override.codeDefault": "Im Code deklarierter Standard",
|
|
45
|
+
"ai_assistant.agents.model_override.defaultBadge": "Standard",
|
|
46
|
+
"ai_assistant.agents.model_override.model": "Modell",
|
|
47
|
+
"ai_assistant.agents.model_override.noOverride": "Keine agentenspezifische Überschreibung",
|
|
48
|
+
"ai_assistant.agents.model_override.provider": "Anbieter",
|
|
49
|
+
"ai_assistant.agents.model_override.providerDefault": "Anbieter-Standard",
|
|
50
|
+
"ai_assistant.agents.model_override.save": "Überschreibung speichern",
|
|
51
|
+
"ai_assistant.agents.model_override.saved": "Modell-Überschreibung gespeichert.",
|
|
52
|
+
"ai_assistant.agents.model_override.tenantOverride": "Mandanten-Überschreibung",
|
|
53
|
+
"ai_assistant.agents.model_override.title": "Anbieter und Modell",
|
|
20
54
|
"ai_assistant.agents.mutation_policy.clear": "Überschreibung löschen",
|
|
21
55
|
"ai_assistant.agents.mutation_policy.clearedMessage": "Überschreibung der Mutationsrichtlinie gelöscht; Agent verwendet die im Code deklarierte Richtlinie.",
|
|
22
56
|
"ai_assistant.agents.mutation_policy.codeDeclared": "Im Code deklariert",
|
|
@@ -112,6 +146,7 @@
|
|
|
112
146
|
"ai_assistant.allowlist.save.success": "Liste gespeichert.",
|
|
113
147
|
"ai_assistant.allowlist.subtitle": "Schränke Anbieter und Modelle ein, die für diesen Mandanten verwendet werden dürfen. Die ENV-Liste ist die äußere Beschränkung — die Mandantenauswahl engt sie weiter ein.",
|
|
114
148
|
"ai_assistant.allowlist.title": "Anbieter- & Modell-Allowlist",
|
|
149
|
+
"ai_assistant.chat.agentTasksTitle": "Agent-Aufgaben",
|
|
115
150
|
"ai_assistant.chat.assistantRoleLabel": "Assistent",
|
|
116
151
|
"ai_assistant.chat.attachFile": "Attach file",
|
|
117
152
|
"ai_assistant.chat.betaChip": "beta",
|
|
@@ -257,6 +292,8 @@
|
|
|
257
292
|
"ai_assistant.launcher.welcome.suggestion3": "Suggest things to try",
|
|
258
293
|
"ai_assistant.launcher.welcome.suggestion4": "How do I use this assistant?",
|
|
259
294
|
"ai_assistant.launcher.writesBadge": "Can write",
|
|
295
|
+
"ai_assistant.loop.disabledBanner.description": "Der agentische Loop wurde für diesen Agenten von einem Mandantenadministrator deaktiviert. Jeder Durchlauf wird als einzelner Modellaufruf ausgeführt. Um den Loop wieder zu aktivieren, aktualisieren Sie die Loop-Richtlinie in den KI-Assistenten-Einstellungen.",
|
|
296
|
+
"ai_assistant.loop.disabledBanner.title": "Agenten-Loop durch Mandantenrichtlinie deaktiviert",
|
|
260
297
|
"ai_assistant.mcp.apiKeyLabel": "API-Schlüssel:",
|
|
261
298
|
"ai_assistant.mcp.close": "Schließen",
|
|
262
299
|
"ai_assistant.mcp.copied": "Kopiert!",
|
|
@@ -272,9 +309,11 @@
|
|
|
272
309
|
"ai_assistant.modelPicker.activeBadge": "aktiv",
|
|
273
310
|
"ai_assistant.modelPicker.defaultBadge": "Standard",
|
|
274
311
|
"ai_assistant.modelPicker.defaultLabel": "Modell: Standard",
|
|
312
|
+
"ai_assistant.modelPicker.defaultWithModelLabel": "Standard: {{model}}",
|
|
275
313
|
"ai_assistant.modelPicker.listAriaLabel": "Verfügbare Modelle",
|
|
276
314
|
"ai_assistant.modelPicker.triggerAriaLabel": "KI-Modell auswählen",
|
|
277
315
|
"ai_assistant.modelPicker.useDefault": "Standard des Agenten verwenden",
|
|
316
|
+
"ai_assistant.modelPicker.useDefaultWithModel": "Standard des Agenten verwenden: {{model}}",
|
|
278
317
|
"ai_assistant.playground.agentPickerLabel": "Agent",
|
|
279
318
|
"ai_assistant.playground.chat.notSupportedBody": "Wählen Sie einen Agenten, dessen Ausführungsmodus \"chat\" ist, oder wechseln Sie zum Objekt-Modus-Tab.",
|
|
280
319
|
"ai_assistant.playground.chat.notSupportedTitle": "Chat-Modus ist für diesen Agenten nicht verfügbar.",
|
|
@@ -346,6 +385,7 @@
|
|
|
346
385
|
"ai_assistant.settings.envKeyMissing": "{{key}} nicht gesetzt",
|
|
347
386
|
"ai_assistant.settings.generateMcpConfig": "MCP-Konfiguration generieren",
|
|
348
387
|
"ai_assistant.settings.generateSessionKey": "Sitzungsschlüssel generieren",
|
|
388
|
+
"ai_assistant.settings.launchDescription": "Öffnen Sie den KI-Assistenten von dieser Seite.",
|
|
349
389
|
"ai_assistant.settings.llmProviderLabel": "LLM-Anbieter:",
|
|
350
390
|
"ai_assistant.settings.loading": "Einstellungen werden geladen...",
|
|
351
391
|
"ai_assistant.settings.mcpAuthLabel": "MCP-Authentifizierung:",
|
|
@@ -362,6 +402,7 @@
|
|
|
362
402
|
"ai_assistant.settings.notAvailable": "Nicht verfügbar",
|
|
363
403
|
"ai_assistant.settings.notConfigured": "Nicht konfiguriert",
|
|
364
404
|
"ai_assistant.settings.openButton": "KI-Assistent öffnen",
|
|
405
|
+
"ai_assistant.settings.openSelectorButton": "KI-Assistenten öffnen",
|
|
365
406
|
"ai_assistant.settings.pageDescription": "KI-Assistent konfigurieren und überwachen",
|
|
366
407
|
"ai_assistant.settings.pageTitle": "KI-Assistent-Einstellungen",
|
|
367
408
|
"ai_assistant.settings.providerColumn": "Anbieter",
|
|
@@ -382,5 +423,37 @@
|
|
|
382
423
|
"ai_assistant.status.executing": "Tools werden ausgeführt...",
|
|
383
424
|
"ai_assistant.status.responding": "Antwort wird erstellt...",
|
|
384
425
|
"ai_assistant.status.thinking": "Denkt nach...",
|
|
385
|
-
"ai_assistant.status.working": "Arbeitet..."
|
|
426
|
+
"ai_assistant.status.working": "Arbeitet...",
|
|
427
|
+
"ai_assistant.usage.apply": "Anwenden",
|
|
428
|
+
"ai_assistant.usage.col.agent": "Agent",
|
|
429
|
+
"ai_assistant.usage.col.day": "Tag",
|
|
430
|
+
"ai_assistant.usage.col.finishReason": "Abschluss",
|
|
431
|
+
"ai_assistant.usage.col.inputTokens": "Eingabe",
|
|
432
|
+
"ai_assistant.usage.col.model": "Modell",
|
|
433
|
+
"ai_assistant.usage.col.outputTokens": "Ausgabe",
|
|
434
|
+
"ai_assistant.usage.col.session": "Sitzung",
|
|
435
|
+
"ai_assistant.usage.col.sessions": "Sitzungen",
|
|
436
|
+
"ai_assistant.usage.col.startedAt": "Gestartet",
|
|
437
|
+
"ai_assistant.usage.col.step": "Schritt",
|
|
438
|
+
"ai_assistant.usage.col.steps": "Schritte",
|
|
439
|
+
"ai_assistant.usage.dailyBreakdown": "Tägliche Aufschlüsselung",
|
|
440
|
+
"ai_assistant.usage.error": "Nutzungsdaten konnten nicht geladen werden.",
|
|
441
|
+
"ai_assistant.usage.errorDetail": "Sitzungsereignisse konnten nicht geladen werden.",
|
|
442
|
+
"ai_assistant.usage.errorSessions": "Sitzungen konnten nicht geladen werden.",
|
|
443
|
+
"ai_assistant.usage.from": "Von",
|
|
444
|
+
"ai_assistant.usage.inputTokens": "Eingabe-Tokens",
|
|
445
|
+
"ai_assistant.usage.loading": "Nutzungsdaten werden geladen...",
|
|
446
|
+
"ai_assistant.usage.loadingDetail": "Sitzungsereignisse werden geladen...",
|
|
447
|
+
"ai_assistant.usage.loadingSessions": "Sitzungen werden geladen...",
|
|
448
|
+
"ai_assistant.usage.navTitle": "KI-Nutzung",
|
|
449
|
+
"ai_assistant.usage.next": "Weiter",
|
|
450
|
+
"ai_assistant.usage.noSessions": "Für den ausgewählten Zeitraum wurden keine Sitzungen gefunden.",
|
|
451
|
+
"ai_assistant.usage.outputTokens": "Ausgabe-Tokens",
|
|
452
|
+
"ai_assistant.usage.prev": "Zurück",
|
|
453
|
+
"ai_assistant.usage.sessionDetail": "Sitzungsdetails",
|
|
454
|
+
"ai_assistant.usage.sessions": "Sitzungen",
|
|
455
|
+
"ai_assistant.usage.sessionsList": "Sitzungen",
|
|
456
|
+
"ai_assistant.usage.steps": "Schritte",
|
|
457
|
+
"ai_assistant.usage.title": "Token-Nutzungsstatistik",
|
|
458
|
+
"ai_assistant.usage.to": "Bis"
|
|
386
459
|
}
|