🤖 AI-агенты для бизнеса

21. HTTP-маршруты (http_route / static_route)

🔵 Агенты и глобальная конфигурация могут добавлять на сервер собственные HTTP-эндпоинты без пересборки — по той же модели, что script_tool / script_hook. Два вида маршрутов:

Это дополняет, а не заменяет фиксированные поверхности: POST /v1/inbound/:template, POST /v1/workflows/<name>/webhook и каналы telegram/email.


21.1 Раскладка

config/
├── routes/                    # ГЛОБАЛЬНЫЕ маршруты — подхватываются сами
│   ├── weather.yaml + weather.rhai
│   └── assets.yaml + assets/  # папка статики для static_route
└── agents/
    └── felix/
        ├── agent.yaml         # route_files: [routes/card.yaml]
        └── routes/
            ├── card.yaml + card.rhai
            └── static/        # статика, добавляемая агентом

Глобальные маршруты читаются из config/routes/*.yaml автоматически. Агентские подключаются явно из agent.yaml:

# config/agents/felix/agent.yaml
route_files:
  - routes/card.yaml

Агентские маршруты регистрируются в тот же роутер, что и глобальные. По соглашению (это warning, не ошибка) путь агентского маршрута начинается с /ext/<agent>/.


21.2 http_route (скриптовый)

kind: http_route
name: weather                 # id маршрута (по умолчанию — имя файла); пишется в access-лог
method: GET                   # GET|POST|PUT|PATCH|DELETE|HEAD|* (по умолчанию GET)
path: /ext/weather/:city      # сегменты :param и *rest; путь должен быть уникальным
script: weather.rhai          # .rhai или .js, относительно этого YAML
auth: public                  # public | bearer | token_env | user
token_env: ""                 # env с bearer-токеном (при auth: token_env)
max_operations: 10000000      # бюджет инструкций скрипта
timeout_ms: 30000             # потолок времени на обработчик
max_body_bytes: 1048576       # лимит тела запроса (по умолчанию 1 МиБ)
# + любые свои поля → доступны в скрипте как request.config.<field>

Контракт скрипта

fn handler(request) {
    // request = #{
    //   method: "GET",
    //   path: "/ext/weather/moscow",
    //   params: #{ city: "moscow" },      // захваты :param / *rest
    //   query: #{ unit: "c" },
    //   headers: #{ "content-type": "application/json" },
    //   body: "...",                       // сырое тело строкой ("" если нет)
    //   json: #{ ... },                    // распарсенное JSON-тело, или ()
    //   user_id: "...",                    // разрешённая идентичность, или ()
    //   user: "...",                       // канонический id пользователя, или ()
    //   config: #{ ... }                   // сырой YAML, включая свои поля
    // }
    if request.user_id == () {
        return #{ status: 401, body: #{ error: "unauthorized" } };
    }
    let r = http_get(`https://api.weather.example/${request.params.city}`);
    if r.status != 200 {
        return #{ status: 502, body: #{ error: r.error } };
    }
    #{ status: 200, body: parse_json(r.body) }
}

Возврат #{ status, headers, body } (все поля опциональны):

Помощники

Тот же движок и набор помощников, что у script_tool: http_get/post/put/patch/delete, http(method, …), env_var, now, sleep_ms, urlencode/urldecode, parse_json/to_json, json_get, extract_script_json, print/debug, а также db_query/db_schema/db_execute для чтения/записи DuckDB (см. §7.3) и shell/exec/fs_read (см. §7.3).

llm(...) в маршрутах недоступен (нет процесса агента, на который списать бюджет) — вызов бросит ошибку. Нет доступа к ядру; состояние между вызовами не сохраняется (используй HTTP или внешнее хранилище/db_*).


21.3 static_route (файлы)

kind: static_route
name: felix_assets
path: /ext/felix/*rest        # должно заканчиваться на *rest
dir: static/                  # папка относительно этого YAML
index: index.html             # отдаётся при обращении к "/" (опционально)
auth: public
cache_ttl_secs: 300           # Cache-Control (опционально)
mime:                         # переопределение content-type по расширению (опционально)
  .wasm: application/wasm

Поведение (только GET/HEAD):

Так агент отдаёт статику: static_route на папку static/ внутри его routes/.


21.4 Auth

Режим Поведение
public открыто (dev по умолчанию)
bearer требует Authorization: Bearer <token>, совпадающий с auth.admin_tokens (в лог пишется имя, не значение)
token_env требует bearer-токен, равный значению env из token_env (не задан/пуст = отклонять всё, fail-closed)
user требует валидную идентичность конечного пользователя: HMAC X-User-Token, когда задан auth.web_user_secret_env, иначе доверяет X-User-Id (dev). Отдаёт request.user_id / request.user

auth: user доступно с первого релиза.


21.5 Роутинг и жизненный цикл


21.6 Access-лог

Каждый запрос к кастомному маршруту пишется в logs/access.jsonl / logs/access.db, как и встроенные, но с именем маршрута и его скоупом (global / <agent>) вместо свободного пути. Тела, заголовки и значения токенов не логируются.


21.7 Тестирование (без сервера)

route_tool config/routes/weather.yaml '{"method":"GET","path":"/ext/weather/moscow"}'
route_tool config/agents/felix/routes/card.yaml --method POST --body '{"user_id":"x"}'
route_tool config/routes/dash.yaml --args-file req.json    # request JSON из файла ('-' = stdin)

route_tool печатает фейковый запрос, результат скрипта (status + headers + body) и ошибки скрипта — аналог hook_tool / site_tool для маршрутов. Для static_route показывает, какие файлы резолвятся под dir. Query-параметры с ?/& удобнее передавать через --args-file, чтобы их не портил шелл.