{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://ayagmar.github.io/llm-usage-metrics/machine-export.v1.schema.json",
  "title": "llm-usage-metrics machine export line (v1)",
  "description": "One NDJSON line of `llm-usage machine export`. A bundle is a header line, zero or more file lines, then an end line; a bundle without its end line is incomplete.",
  "oneOf": [{ "$ref": "#/$defs/header" }, { "$ref": "#/$defs/file" }, { "$ref": "#/$defs/end" }],
  "$defs": {
    "header": {
      "type": "object",
      "properties": {
        "type": { "const": "header" },
        "format": { "const": "llm-usage-metrics.machine-export" },
        "version": {
          "description": "Bundle format version; readers reject versions they do not know.",
          "const": 1
        },
        "cliVersion": {
          "description": "llm-usage-metrics version that wrote the bundle.",
          "type": "string"
        },
        "hostname": { "description": "Host name of the exporting machine.", "type": "string" },
        "exportedAt": { "description": "ISO 8601 time of the export.", "type": "string" },
        "parserVersions": {
          "description": "Source id to the parser version the export read it with. Optional: older exports leave it out, and readers that predate it ignore it.",
          "type": "object",
          "additionalProperties": { "type": "string", "minLength": 1 }
        },
        "singleRequestFlag": {
          "description": "Events carry singleRequest. Optional: older exports leave it out, and readers take the flag from the event's source (antigravity, claude, gemini, opencode, pi).",
          "const": true
        }
      },
      "required": ["type", "format", "version", "cliVersion", "hostname", "exportedAt"],
      "additionalProperties": true
    },
    "file": {
      "description": "The events of one counted file the reader does not hold at this revision.",
      "type": "object",
      "properties": {
        "type": { "const": "file" },
        "source": { "type": "string", "minLength": 1 },
        "filePath": {
          "description": "Path of the file on the exporting machine.",
          "type": "string",
          "minLength": 1
        },
        "revision": {
          "description": "Opaque; changes exactly when the file's events, tool calls or messages change.",
          "type": "string",
          "minLength": 1
        },
        "events": {
          "description": "Unpriced usage events: costUsd is present only when the source recorded it (costMode explicit).",
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "https://ayagmar.github.io/llm-usage-metrics/events-line.v1.schema.json"
          }
        },
        "sessionEvents": {
          "description": "The file's tool calls and messages, in the order the source logged them. Optional: left out when the file has none and by older exports; readers that predate it ignore it. Never holds message text or tool input and output.",
          "type": "array",
          "items": { "oneOf": [{ "$ref": "#/$defs/toolCall" }, { "$ref": "#/$defs/message" }] }
        }
      },
      "required": ["type", "source", "filePath", "revision", "events"],
      "additionalProperties": false
    },
    "end": {
      "description": "Every counted file, including those the reader already holds; a file it holds that is not listed is no longer counted.",
      "type": "object",
      "properties": {
        "type": { "const": "end" },
        "files": {
          "type": "array",
          "items": {
            "description": "[source, filePath, revision]",
            "type": "array",
            "prefixItems": [
              { "type": "string", "minLength": 1 },
              { "type": "string", "minLength": 1 },
              { "type": "string", "minLength": 1 }
            ],
            "minItems": 3,
            "maxItems": 3
          }
        },
        "eventCount": {
          "description": "Total usage events across all listed files; tool calls and messages are not counted.",
          "type": "integer",
          "minimum": 0
        }
      },
      "required": ["type", "files", "eventCount"],
      "additionalProperties": false
    },
    "toolCall": {
      "description": "A tool call: its name, how it ended and when, without its input or output.",
      "type": "object",
      "properties": {
        "kind": { "const": "tool_call" },
        "source": { "type": "string", "minLength": 1 },
        "sessionId": { "type": "string", "minLength": 1 },
        "timestamp": { "description": "ISO 8601 time of the call.", "type": "string" },
        "toolCallId": { "type": "string", "minLength": 1 },
        "name": { "type": "string", "minLength": 1 },
        "status": { "enum": ["ok", "error", "unknown"] },
        "completedAt": { "description": "ISO 8601 time of the result.", "type": "string" },
        "durationMs": { "type": "integer", "minimum": 0 },
        "messageId": {
          "description": "The assistant message that made the call.",
          "type": "string",
          "minLength": 1
        }
      },
      "required": ["kind", "source", "sessionId", "timestamp", "name", "status"],
      "additionalProperties": false
    },
    "message": {
      "description": "A user, assistant or system turn: its role, ids, model and text length, never its text.",
      "type": "object",
      "properties": {
        "kind": { "const": "message" },
        "source": { "type": "string", "minLength": 1 },
        "sessionId": { "type": "string", "minLength": 1 },
        "timestamp": { "description": "ISO 8601 time of the turn.", "type": "string" },
        "role": { "enum": ["user", "assistant", "system"] },
        "messageId": { "type": "string", "minLength": 1 },
        "model": { "type": "string", "minLength": 1 },
        "textLength": { "type": "integer", "minimum": 0 }
      },
      "required": ["kind", "source", "sessionId", "timestamp", "role", "textLength"],
      "additionalProperties": false
    }
  }
}
