OpenAI's chat completions response includes prompt_tokens_details.
cached_tokens when prompt caching kicks in - previously dropped
entirely. Now extracted in LlmClient and forwarded to Langfuse as the
flat cache_read_input_tokens usage key.
Used the flat key rather than OpenAI's nested prompt_tokens_details
shape: the installed @langfuse/tracing SDK's usageDetails type is
Record<string, number> | OpenAiUsage (a 3-field camelCase shape, no
_details nesting) even though the wire schema in @langfuse/core
supports nesting - a gap in the SDK's public types, not something
worth fighting with a cast.
Verified: full suite (82/82) passing, tsc/eslint clean.
LangfuseTracer was passing our internal camelCase usage fields
(promptTokens/completionTokens/totalTokens) straight through as
usageDetails. Langfuse only recognizes a flat schema (input/output/...)
or OpenAI's native snake_case schema (prompt_tokens/completion_tokens/
total_tokens) for usage/cost calculation - anything else is silently
ignored, so every generation was showing zero usage regardless of
what LlmClient actually reported.
Note: cost still requires a price-list entry for the model name.
Langfuse's built-in list only covers hosted providers (OpenAI,
Anthropic, Google); self-hosted models (e.g. LLM_MODEL=llama3 via
Ollama) need a custom model price added in Project Settings > Models
for a $ figure to show up, even with token counts now correct.
Verified: full suite (80/80) passing, tsc/eslint clean.
Each pipeline invocation (runAsync/searchAsync) now gets a fresh
sessionId, wrapped via the new Tracer.runWithSession() (implemented
with @langfuse/tracing's propagateAttributes, since v5 has no direct
sessionId/tags option on startObservation) so it shows up in
Langfuse's Sessions view. The session is also tagged with what
triggered the run: "cron", "api-list", "api-trigger", "api-search".
Verified: full test suite (79/79) passing, tsc/eslint clean, real
docker compose build + container smoke test against /health.
approval (needed by the new OTEL-based Langfuse SDK) actually takes
effect inside the image, not just locally.
Verified with docker compose build --no-cache + a real container run
against /health.
The TypeScript/OOP rewrite has reached full functional parity and is
now the only implementation:
- Delete cmd/, internal/, go.mod, go.sum.
- Promote Dockerfile.node -> Dockerfile and docker-compose.node.yml ->
docker-compose.yml (service renamed sugarr-node -> sugarr).
- Update README and .gitignore to drop the "two implementations"
framing and Go-specific entries.
Verified after the rename: tsc --noEmit clean, full test suite
passing (75/75), docker compose build succeeds.
- ApiClient: strip query string from ApiError messages so API keys
(e.g. TMDb's api_key query param) never leak into 500 response
bodies or logs.
- LlmTool: accept an optional defaultModel, wired from AppConfig's
LLM_MODEL, so Langfuse prompts that omit a model config fall back
to the configured LLM rather than silently requesting gpt-4o-mini.
- RecommendationPipeline: restore the Go original's graceful
degradation — library/Radarr fetch failures, per-suggestion TMDb
resolution failures, and per-signal TMDb similarity/recommendation
failures are now logged and skipped instead of aborting the whole
run (applies to both runInternal and searchAsync).
- RecommendationsController: validate the ?count= query param
(must be a positive integer) instead of silently producing NaN.
- Commit the Langfuse prompt definitions (langfuse/*.prompt.json) and
add a README covering setup, prompt seeding, cache invalidation,
and the HTTP API surface — the branch was not otherwise runnable by
anyone but the author.
Matches the Go original's behavior: a lookup/addMovie failure for one
recommendation is logged and skipped rather than aborting the whole
auto-request batch.
- authHook: use crypto.timingSafeEqual for the bearer token comparison
instead of !==, avoiding a timing side-channel on the secret key.
- .eslintrc.cjs: add argsIgnorePattern for underscore-prefixed unused
params, fixing false-positive no-unused-vars errors on intentional
_ctx/_params placeholders used throughout the LlmTool hierarchy.