#!/usr/bin/env bash set -euo pipefail if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then echo "ANTHROPIC_API_KEY is required" >&2 exit 1 fi if ! command -v jq >/dev/null 2>&1; then echo "jq is required to build the JSON payload and summarize usage" >&2 exit 1 fi MODEL="${MODEL:-claude-opus-4-6}" ANTHROPIC_VERSION="${ANTHROPIC_VERSION:-2023-06-01}" API_URL="${ANTHROPIC_API_URL:-https://lancerouter.ai/v1/messages}" CACHE_SENTENCE_COUNT="${CACHE_SENTENCE_COUNT:-240}" REQUEST_COUNT="${REQUEST_COUNT:-2}" SAME_USER_PROMPT="${SAME_USER_PROMPT:-0}" CACHE_MODE="${CACHE_MODE:-explicit}" DRY_RUN="${DRY_RUN:-0}" case "$CACHE_MODE" in explicit | automatic) ;; *) echo "CACHE_MODE must be 'explicit' or 'automatic'" >&2 exit 1 ;; esac build_payload() { local question="$1" jq -n \ --arg model "$MODEL" \ --arg question "$question" \ --arg cache_mode "$CACHE_MODE" \ --argjson sentence_count "$CACHE_SENTENCE_COUNT" ' def zpad4: tostring as $raw | ("0000" + $raw) | .[-4:]; (reduce range(1; $sentence_count + 1) as $i (""; . + "Cache test invariant sentence " + ($i | zpad4) + ": this exact line must remain unchanged across requests. " )) as $ctx | ({ model: $model, max_tokens: 20, system: [ { type: "text", text: $ctx } ], messages: [ { role: "user", content: $question } ] } | if $cache_mode == "automatic" then . + { cache_control: { type: "ephemeral" } } else .system[0].cache_control = { type: "ephemeral" } end) ' } send_request() { local question="$1" build_payload "$question" | curl --silent --show-error "$API_URL" \ --header "x-api-key: $ANTHROPIC_API_KEY" \ --header "anthropic-version: $ANTHROPIC_VERSION" \ --header "content-type: application/json" \ -D /tmp/cache_test_headers.txt \ --data @- } extract_header() { local header_name="$1" grep -i "^${header_name}:" /tmp/cache_test_headers.txt 2>/dev/null \ | head -1 \ | sed 's/^[^:]*: *//' \ | tr -d '\r\n' \ || echo "N/A" } summarize_response() { jq 'if has("error") then { type, error } elif (.content | type) == "array" then { id, model, stop_reason, usage, text: (.content | map(select(.type == "text") | .text) | join("")) } else . end' } for i in $(seq 1 "$REQUEST_COUNT"); do if [[ "$SAME_USER_PROMPT" == "1" ]]; then question="Reply with only: cache-test" else question="Reply with only: request-$i" fi if [[ "$DRY_RUN" == "1" ]]; then build_payload "$question" | jq else echo "=== Request $i/$REQUEST_COUNT: $question ===" send_request "$question" | tee /tmp/anthropic_cache_response.json | summarize_response echo "x-oneapi-request-id: $(extract_header 'X-Oneapi-Request-Id')" echo "x-request-id: $(extract_header 'X-Request-Id')" echo fi done if [[ "$DRY_RUN" != "1" ]]; then echo "Expected cache signal:" echo "- First response usage.cache_creation_input_tokens should be greater than 0." echo "- Later responses usage.cache_read_input_tokens should be greater than 0." echo echo "Diagnostic mode:" echo "REQUEST_COUNT=10 SAME_USER_PROMPT=1 MODEL=\"$MODEL\" ./test/anthropic_cache_test.sh" echo echo "Automatic caching mode:" echo "CACHE_MODE=automatic MODEL=\"$MODEL\" ./test/anthropic_cache_test.sh" fi