Centralizovaný AnthropicGatewayService wrapper nad MaraExecutor interface — každý Mara turn se zaloguje do persistentní tabulky api_usage_ledger včetně počtů tokenů a vypočtené ceny. Tři existující UseCases se refaktorují na volání přes Gateway místo přímého MaraExecutor.
- Gateway je Spring
@Service— ne UseCase. Jde o infrastructure concern cross-cutting napříč více UC (viz ADR-020, Consequences). - Tracking je konfigurovatelný přes property
talkide.gateway.tracking.enabled(defaulttrue). PřifalseGateway funguje jako pass-through bez zápisu do ledgeru — určeno pro lokální vývoj nebo dočasné vypnutí trackingu. - Tento UC zavádí pouze ledger a cost tracking. Quota enforcement (5h + weekly windows), fatigue state machine a model swap jsou scope UC-08002.
- Pricing model: Sonnet 4.5 — input $3.00/1M, cache read $0.30/1M, cache write $3.75/1M, output $15.00/1M tokenů.
- CLI mode (Max plán) — cena Anthropic callu pro platformu je $0; ledger záznam se vytvoří s
cost_cents = 0aexecutor_type = CLIpro analytics oddělení od sidecar volání. - Network worker mode (production, LIVE od 2026-05-21) — Mara běží jako
talkide-workerpod v tenant-env namespace (ADR-024). Cena se počítá zusageobjektu vráceného Anthropic API skrze gateway-proxy; ledger záznam se vytvoří sexecutor_type = NETWORK_WORKER(historickySIDECAR— DB enum hodnota beze změny pro backward compatibility). - Viz specification/mara-fatigue-fup.md — Sekce 4 Gateway Architecture a specification/worker-runtime.md pro detaily.
sequenceDiagram
actor User
User->>+FE: odešle zprávu v chatu
FE->>+BE: POST /api/v1/projects/{projectId}/conversations/{conversationId}/messages <br> Authorization: Bearer {accessToken} <br> SendMessageRequest
BE->>+SendMessageUseCase: execute(input)
SendMessageUseCase->>SendMessageUseCase: načti konverzaci, sestav prompt kontext
SendMessageUseCase->>+AnthropicGatewayService: callMara(GatewayRequest)
AnthropicGatewayService->>AnthropicGatewayService: zkontroluj tracking.enabled
AnthropicGatewayService->>+MaraExecutor: startProcess(...)
Note over MaraExecutor: ClaudeCliExecutor (CLI) <br> nebo AgentSidecarExecutor (Sidecar)
MaraExecutor->>Anthropic API: API call (claude-sonnet-4-5 nebo jiný model)
Anthropic API-->>MaraExecutor: streaming response + usage object
MaraExecutor-->>-AnthropicGatewayService: onComplete(fullText, usage)
AnthropicGatewayService->>AnthropicGatewayService: PricingConfig.computeCost(usage, executorType)
alt tracking.enabled = true
AnthropicGatewayService->>DB: INSERT api_usage_ledger <br> (userId, conversationId, tokens, cost_cents, executor_type, ...)
end
AnthropicGatewayService-->>-SendMessageUseCase: GatewayResponse(content, usage, costCents)
SendMessageUseCase->>DB: INSERT message (role=PM, content)
SendMessageUseCase->>DB: UPDATE conversation updatedAt
SendMessageUseCase-->>-BE: SendMessageOutput
BE-->>-FE: SSE event: message <br> MessageDto
FE-->>-User: zobraz PM odpověď v chatu
Interní kontrakt — AnthropicGatewayService
Tento UC nepřidává žádný nový HTTP endpoint. Gateway je interní Spring @Service. Níže je popis interního kontraktu:
GatewayRequest
data class GatewayRequest(
val userId: Long,
val conversationId: Long?, // null pokud se gateway volá mimo konverzaci
val projectSlug: String,
val projectId: Long,
val claudeMdContent: String,
val userMessage: String,
val sessionId: UUID,
val isNewSession: Boolean,
val executorType: ExecutorType, // CLI nebo SIDECAR
val onPmMessage: (String) -> Unit,
val onComplete: () -> Unit,
val onError: (String) -> Unit,
)
GatewayResponse
data class GatewayResponse(
val content: String,
val inputTokens: Int,
val outputTokens: Int,
val cacheReadInputTokens: Int,
val cacheCreationInputTokens: Int,
val costCents: Int, // 0 pro CLI mode (Max plán)
val modelUsed: String, // např. "claude-sonnet-4-5"
val executorType: ExecutorType,
val requestStartedAt: Instant,
val requestCompletedAt: Instant,
val errorMessage: String?, // null pokud call proběhl úspěšně
)
Chybové stavy
Pokud MaraExecutor selže (IOException, timeout, neočekávaná chyba), Gateway:
- Zachytí výjimku
- Zapíše ledger záznam s
error_messageacost_cents = 0 - Propaguje výjimku volajícímu UseCase
DB Migration — api_usage_ledger
Pre-production poznámka: Likviduje spring.liquibase.drop-first: true — modifikuj existující Liquibase migration soubory in-place, NEPŘIDÁVEJ nové changesety.
CREATE TABLE api_usage_ledger (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
conversation_id BIGINT REFERENCES conversations(id),
executor_type VARCHAR(20) NOT NULL, -- 'CLI' nebo 'SIDECAR'
model_used VARCHAR(50) NOT NULL, -- např. 'claude-sonnet-4-5'
input_tokens INT NOT NULL DEFAULT 0,
output_tokens INT NOT NULL DEFAULT 0,
cache_read_input_tokens INT NOT NULL DEFAULT 0,
cache_creation_input_tokens INT NOT NULL DEFAULT 0,
cost_cents INT NOT NULL DEFAULT 0, -- 0 pro CLI mode
request_started_at TIMESTAMPTZ NOT NULL,
request_completed_at TIMESTAMPTZ,
error_message TEXT
);
CREATE INDEX idx_api_usage_ledger_user_time
ON api_usage_ledger (user_id, request_completed_at DESC);
CREATE INDEX idx_api_usage_ledger_conversation
ON api_usage_ledger (conversation_id, request_completed_at DESC);
Poznámky k návrhu:
cost_cents INT— celá čísla v centech jsou dostatečně přesná pro průměrný request (~5 centů = 500 db value); NUMERIC(10,4) je alternativa pokud se prokáže potřeba sub-cent přesnosti.conversation_idje nullable — Gateway může být v budoucnu voláno z kontextů mimo konverzaci (project summary, code search assistance).error_messageje nullable — null = úspěšný call, non-null = call selhal.request_completed_atje nullable — umožňuje detekovat nedokončené záznamy (crash mid-call).
Volající UseCases — refactor
Následující tři UseCases se refaktorují tak, aby volaly AnthropicGatewayService.callMara() místo přímého MaraExecutor:
| UseCase | Soubor |
|---|---|
SendMessageUseCase | features/conversation/domain/SendMessageUseCase.kt |
AgentStartConversationUseCase | features/conversation/domain/AgentStartConversationUseCase.kt |
AutoStartConversationUseCase | features/conversation/domain/AutoStartConversationUseCase.kt |
MaraExecutor stále existuje jako interface — Gateway ho obaluje a přidává ledger vrstvu nad ním. Ostatní konzumenti MaraExecutor (zejména CancelConversationUseCase, CloseConversationUseCase, ConversationController pro SSE endpoint) nevolají Anthropic API přímo, takže procházet Gateway nepotřebují.
Frontend
Tento UC nemá žádné frontendové změny. Gateway je čistě BE refactor + tracking infrastruktura.
Validations
Žádné frontendové validace — UC neobsahuje žádný nový HTTP endpoint ani FE stránku.
Poznámka: UX dopad přijde v UC-08002 (fatigue states — vizuální feedback vyčerpané kvóty) a UC-08004 (admin konzola — real-time využití).
Backend
Validations
| Pole | Constraints | Poznámka |
|---|---|---|
userId | not_null, positive, existuje v DB | Vždy musí být přítomno; uživatel bez záznamu je systémová chyba |
conversationId | positive, existuje v DB (pokud předáno) | Nullable — při null se ledger záznam vytvoří bez FK na konverzaci |
projectSlug | not_blank | Potřebný pro MaraExecutor.startProcess (cwd projektu) |
userMessage | not_blank | Prázdný prompt nesmí dojít do Anthropic |
| Pricing konfigurace | načtena při startu (@PostConstruct) | Pokud config chybí, aplikace nesmí nastartovat (fail-fast) |
usage objekt z Anthropic | všechna token pole musí být ≥ 0 | Validace po MaraExecutor return — neočekávaná null = log error + cost = 0 |
Test Cases
| GIVEN | WHEN | THEN |
|---|---|---|
| platný userId, platný conversationId, tracking enabled, executor = NETWORK_WORKER (default/prod) nebo SIDECAR (legacy, zachován pro backward compat) | callMara() proběhne úspěšně | ledger záznam vytvořen s korektním cost_cents, input_tokens, output_tokens, model_used |
tracking disabled (talkide.gateway.tracking.enabled=false) | callMara() proběhne úspěšně | Mara odpověď vrácena volajícímu UseCase; žádný řádek v api_usage_ledger nevznikne |
Anthropic response obsahuje cache_read_input_tokens = 50000 | callMara() proběhne úspěšně | cache read priced at $0.30/1M (ne $3.00/1M jako fresh input); cost_cents odpovídá |
Anthropic response obsahuje cache_creation_input_tokens = 0 | callMara() proběhne úspěšně | cache write contribution = 0; cost_cents neobsahuje cache write složku |
MaraExecutor.startProcess vyhodí výjimku (timeout, IOException) | callMara() je volána | ledger záznam vytvořen s error_message obsahující popis chyby a cost_cents = 0; výjimka propagována volajícímu UseCase |
| konverzace má 5 historických Mara turnů | každý turn volá callMara() | 5 separátních řádků v api_usage_ledger se stejným conversation_id |
| executor = CLI (Max plán) | callMara() proběhne úspěšně | ledger záznam vytvořen s executor_type = CLI, cost_cents = 0; token counts z usage objektu zaznamenány pro analytics |
| executor = SIDECAR (legacy, zachován pro backward compat; default a prod je NETWORK_WORKER) | callMara() proběhne úspěšně | ledger záznam vytvořen s executor_type = SIDECAR; cost_cents vypočteno z pricing tabulky |
conversationId = null (volání mimo konverzaci) | callMara() proběhne úspěšně | ledger záznam vytvořen s conversation_id = null; žádná FK constraint chyba |
| Anthropic pricing pro Haiku 4 — input $0.80/1M | callMara() s model_used = claude-haiku-4 | cost_cents vypočteno dle Haiku pricing (ne Sonnet pricing) |
UX Guidelines
N/A — Gateway je infrastructure layer bez user-facing UI. UX dopad přijde v UC-08002 (fatigue states, sleep screen, Mara avatar degradace) a UC-08004 (admin konzola s real-time Anthropic spend dashboardem).
Reference
- specification/mara-fatigue-fup.md — Sekce 4: Gateway Architecture
- specification/worker-runtime.md — MaraExecutor / NetworkWorkerExecutor a gateway-proxy
- adr/ADR-024 — Workspace runtime worker extraction
- adr/ADR-020 — Mara FUP, fatigue states a founder-gated invite cascade
Thanks for the feedback.