Internal Documentation internal
TalkIDE internal documentation

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 (default true). Při false Gateway 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 = 0 a executor_type = CLI pro analytics oddělení od sidecar volání.
  • Network worker mode (production, LIVE od 2026-05-21) — Mara běží jako talkide-worker pod v tenant-env namespace (ADR-024). Cena se počítá z usage objektu vráceného Anthropic API skrze gateway-proxy; ledger záznam se vytvoří s executor_type = NETWORK_WORKER (historicky SIDECAR — 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:

  1. Zachytí výjimku
  2. Zapíše ledger záznam s error_message a cost_cents = 0
  3. 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_id je nullable — Gateway může být v budoucnu voláno z kontextů mimo konverzaci (project summary, code search assistance).
  • error_message je nullable — null = úspěšný call, non-null = call selhal.
  • request_completed_at je 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:

UseCaseSoubor
SendMessageUseCasefeatures/conversation/domain/SendMessageUseCase.kt
AgentStartConversationUseCasefeatures/conversation/domain/AgentStartConversationUseCase.kt
AutoStartConversationUseCasefeatures/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

PoleConstraintsPoznámka
userIdnot_null, positive, existuje v DBVždy musí být přítomno; uživatel bez záznamu je systémová chyba
conversationIdpositive, existuje v DB (pokud předáno)Nullable — při null se ledger záznam vytvoří bez FK na konverzaci
projectSlugnot_blankPotřebný pro MaraExecutor.startProcess (cwd projektu)
userMessagenot_blankPrázdný prompt nesmí dojít do Anthropic
Pricing konfiguracenačtena při startu (@PostConstruct)Pokud config chybí, aplikace nesmí nastartovat (fail-fast)
usage objekt z Anthropicvšechna token pole musí být ≥ 0Validace po MaraExecutor return — neočekávaná null = log error + cost = 0

Test Cases

GIVENWHENTHEN
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 = 50000callMara() 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 = 0callMara() 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ánaledger 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/1McallMara() s model_used = claude-haiku-4cost_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


Was this page helpful?

Thanks for the feedback.