Ticketnummer bis ins Router-Log durchreichen (42i/intern#1291)

Der Log wusste bisher WER gerufen hat (Key, Rolle), aber nicht WORAN gearbeitet
wurde. Damit liess sich "was kostet ein Vorgang" gar nicht bilden -- die
Voraussetzung fuer die Frage, ob die teuren Tickets die sind, die ein Mensch
wollte.

- server.ts liest den Header x-42i-ticket und legt ihn in den Log-Eintrag
- log.ts: Feld `ticket` in JSONL und SQLite; die Spalte wird per ALTER
  nachgezogen (Bestandstabellen haben sie nicht), dazu ein Index darauf.
  Das INSERT nennt jetzt Spaltennamen statt Positionen -- sonst haette die
  neue Spalte die Reihenfolge zerschossen.
- opencode.json und der Container-Entrypoint schicken den Header aus
  {env:AGENT_TICKET}; job.sh exportiert die Variable ohnehin seit 2026-09-02.

Wer keines mitschickt (Chat, Handbetrieb), steht als null im Log -- auch das
ist eine Auskunft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-07 22:22:19 +02:00
committed by andreas
co-authored by Claude Opus 5
parent 2355981c67
commit 16a6b98c51
2 changed files with 18 additions and 3 deletions
+16 -2
View File
@@ -10,6 +10,13 @@ import { Database } from "bun:sqlite";
export type Eintrag = { export type Eintrag = {
ts: string; ts: string;
key: string; key: string;
/**
* Woran gearbeitet wurde, z.B. "live/live#1573" -- aus dem Header
* x-42i-ticket. Der Key allein sagt nur, WER gerufen hat; ohne das Ticket
* laesst sich "was kostet ein Vorgang" gar nicht bilden (42i/intern#1291).
* null, wenn der Aufrufer keines mitschickt (Chat, Handbetrieb).
*/
ticket: string | null;
alias: string; alias: string;
angefragt: string; angefragt: string;
model: string; model: string;
@@ -37,14 +44,21 @@ export class Log {
ts TEXT, key TEXT, alias TEXT, angefragt TEXT, model TEXT, upstream TEXT, effort TEXT, pfad TEXT, ts TEXT, key TEXT, alias TEXT, angefragt TEXT, model TEXT, upstream TEXT, effort TEXT, pfad TEXT,
status INTEGER, ms INTEGER, stream INTEGER, prompt_tokens INTEGER, completion_tokens INTEGER, status INTEGER, ms INTEGER, stream INTEGER, prompt_tokens INTEGER, completion_tokens INTEGER,
cached_tokens INTEGER, cost REAL, fehler TEXT)`); cached_tokens INTEGER, cost REAL, fehler TEXT)`);
// Nachtraeglich ergaenzt (42i/intern#1291): Bestandstabellen haben die
// Spalte nicht. ALTER schlaegt fehl, wenn sie schon da ist -- das ist der
// Normalfall und kein Grund, den Start abzubrechen.
try { this.db.run(`ALTER TABLE requests ADD COLUMN ticket TEXT`); } catch { /* Spalte existiert bereits */ }
this.db.run(`CREATE INDEX IF NOT EXISTS requests_ts_key ON requests (ts, key)`); this.db.run(`CREATE INDEX IF NOT EXISTS requests_ts_key ON requests (ts, key)`);
this.db.run(`CREATE INDEX IF NOT EXISTS requests_ticket ON requests (ticket)`);
} }
schreiben(e: Eintrag): void { schreiben(e: Eintrag): void {
appendFileSync(this.jsonl, JSON.stringify(e) + "\n"); appendFileSync(this.jsonl, JSON.stringify(e) + "\n");
this.db.run( this.db.run(
`INSERT INTO requests VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, `INSERT INTO requests (ts,key,alias,angefragt,model,upstream,effort,pfad,status,ms,stream,
prompt_tokens,completion_tokens,cached_tokens,cost,fehler,ticket)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
[e.ts, e.key, e.alias, e.angefragt, e.model, e.upstream, e.effort, e.pfad, e.status, e.ms, e.stream ? 1 : 0, [e.ts, e.key, e.alias, e.angefragt, e.model, e.upstream, e.effort, e.pfad, e.status, e.ms, e.stream ? 1 : 0,
e.prompt_tokens, e.completion_tokens, e.cached_tokens, e.cost, e.fehler ?? null], e.prompt_tokens, e.completion_tokens, e.cached_tokens, e.cost, e.fehler ?? null, e.ticket ?? null],
); );
} }
/** Je Tag und Key: Anfragen, Tokens, Kosten. */ /** Je Tag und Key: Anfragen, Tokens, Kosten. */
+2 -1
View File
@@ -333,7 +333,8 @@ Bun.serve({
const accept = req.headers.get("accept"); if (accept) headers.set("accept", accept); const accept = req.headers.get("accept"); if (accept) headers.set("accept", accept);
const eintrag: Eintrag = { const eintrag: Eintrag = {
ts: jetzt(), key: keyName, alias: ziel.alias, angefragt, model: ziel.model, upstream: ziel.upstreamName, effort: ziel.effort, ts: jetzt(), key: keyName, ticket: req.headers.get("x-42i-ticket") || null,
alias: ziel.alias, angefragt, model: ziel.model, upstream: ziel.upstreamName, effort: ziel.effort,
pfad, status: 0, ms: 0, stream, prompt_tokens: null, completion_tokens: null, cached_tokens: null, cost: null, pfad, status: 0, ms: 0, stream, prompt_tokens: null, completion_tokens: null, cached_tokens: null, cost: null,
}; };
const abschliessen = (usage?: unknown) => { const abschliessen = (usage?: unknown) => {