Files
Lucas Oliveira 53180c8d51 feat(llm): send operator headers and identify ai-memory on every request
OpenCode Zen/Go notified operators that requests missing an
`x-opencode-session` header may start erroring, and reported ai-memory's
traffic as "Unknown client — your requests carry no user agent, so we
can't tell what sends them". Both halves are real: `reqwest` sends no
`User-Agent` unless one is configured, and nothing in the crate could
attach a caller-supplied header.

Add `AI_MEMORY_LLM_HEADERS` (`llm_headers` in config.toml): comma-separated
`Name=Value` / `Name: Value` entries, parsed and validated once at the
configuration boundary into a typed `ExtraHeaders`, then sent on every chat
request. This follows the rule provider auth already obeys — providers
consume typed material and never re-parse operator strings — and means a
malformed entry fails at startup rather than on the first consolidation
pass. Headers ai-memory sets itself are refused rather than duplicated:
`RequestBuilder::header` appends, so a second `authorization` would break
the request instead of overriding it. Values are marked sensitive and never
logged; `Debug` prints names only.

Two defaults ride on the same mechanism, layered *under* the operator's so
an explicit entry always wins:

- `User-Agent: ai-memory/<version>` on every provider. Not opencode-specific:
  an unattributable request is the one a rate limiter throttles first, and
  any gateway benefits from knowing what called it. Copilot is excluded — it
  keeps `GitHubCopilotChat/<version>`, the agent GitHub's API expects.
- `x-opencode-session` on the `opencode` provider, one id per process, since
  that header is Zen/Go's own request-correlation field.

Zen/Go permits this. Its documentation ("Where can I use it?", docs/go.mdx)
says Go "is designed to be used with OpenCode and other popular coding
agents that produce a similar types of requests", documents the
`https://opencode.ai/zen/go/v1/...` endpoints for direct use, and asks that
the calling tool "does not generate abusive traffic" and "properly
identifies itself (no broad user agents)". Hence naming ai-memory in the
agent string rather than copying OpenCode's own — identifying the caller is
the requirement, and impersonation would defeat it.

Integration tests drive `build_provider` against wiremock: asserting the
header is on the struct is not the same claim as asserting the right single
value is on the wire.
2026-09-03 12:02:07 +00:00

15 lines
596 B
Bash

# Environment file read by the system ai-memory.service.
#
# Keep one KEY=value assignment per line. Do not quote values unless the value
# itself contains quotes; systemd EnvironmentFile is not a shell script.
#
# Common choices:
# AI_MEMORY_AUTH_TOKEN=<generated with: ai-memory generate-auth-token>
# AI_MEMORY_ALLOWED_HOSTS=localhost,127.0.0.1,::1
# AI_MEMORY_LLM_PROVIDER=anthropic
# ANTHROPIC_API_KEY=sk-ant-...
# AI_MEMORY_LLM_REASONING_EFFORT=low
# AI_MEMORY_LLM_HEADERS=x-opencode-session=host-01,x-opencode-client=ai-memory
# AI_MEMORY_EMBEDDING_PROVIDER=openai
# OPENAI_API_KEY=sk-...