Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The open-source package is useful on its own. Anyone can run it locally or on th

## Product Shape

- OpenAI-compatible HTTP API first, starting with `/v1/chat/completions`.
- OpenAI-compatible chat completions and embeddings APIs.
- One gateway key for clients, many provider keys behind the gateway.
- Bring-your-own-key mode for self-hosted users.
- Routing by model alias, provider allowlist/blocklist, region policy, price ceilings, fallback, capability, and smart cost/quality/latency hints.
Expand Down Expand Up @@ -72,8 +72,9 @@ gateway-mcp --config gateway.config.json
```

`gateway-serve` is the package-level service binary for local and self-hosted
HTTP runtime smoke checks. It exposes `GET /health`, authenticated `GET /ready`,
`GET /version`, `GET /v1/models`, and `POST /v1/chat/completions`.
HTTP runtime smoke checks. It exposes public `GET /health` and `GET /version`,
plus `GET /ready`, `GET /v1/models`, `POST /v1/chat/completions`, and
`POST /v1/embeddings`. The latter endpoints enforce gateway auth when required.

`gateway-mcp` is a stdio MCP server for local agents. It validates and inspects config, explains route choices without provider calls, manages budget definitions, checks remaining budgets, and summarizes the configured usage ledger. Long-running `serve` and live `smoke` checks stay CLI-only. See [Gateway MCP server](docs/mcp.md).

Expand All @@ -86,7 +87,7 @@ Required config examples:
- `gateway.config.no-china.example.json`: OpenAI/OpenRouter-only policy with `cn` blocked.
- `gateway.config.china.example.json`: Chinese provider routes with explicit `cn`/`sg` allowance.

Provider keys are loaded from environment variables only. Do not put provider secrets in config files.
Built-in provider credentials are loaded from environment variables. Custom static headers can contain arbitrary values, but provider secrets should not be stored in config files.

Providers can use `baseUrl`, `baseUrlEnv`, `apiKeyEnv`, custom `auth`, and static or env-derived `headers`. This keeps OpenAI-compatible gateways on the generic adapter instead of adding hardcoded adapter forks. The built-in presets include:

Expand All @@ -95,6 +96,8 @@ Providers can use `baseUrl`, `baseUrlEnv`, `apiKeyEnv`, custom `auth`, and stati

Smart routing is available with route mode `smart` or request `gateway.routing: "smart"`. It filters by policy first, then scores eligible candidates using configured prices, context, capabilities, quality/latency/success/throughput hints, and deterministic fallback ordering when metrics are missing.

Optional non-streaming chat response caching is configured under `server.responseCache`. Cache entries are in-memory and process-local, only successful responses are cached, and clients can bypass lookup with the configured `bypassHeader`. Streaming and embeddings requests are never cached. A cache hit still runs the budget preflight check and still consumes the per-gateway-key request rate limit, but it does not append a usage ledger record and does not consume budget or token rate limit quota.

```json
{
"model": "coding",
Expand Down Expand Up @@ -131,6 +134,8 @@ The companion `open-router` repo is currently documented as the future extractio

## Documentation

- [CLI reference](docs/cli.md)
- [Configuration reference](docs/configuration.md)
- [Product requirements](docs/product-requirements.md)
- [Architecture](docs/architecture.md)
- [API contract](docs/api-contract.md)
Expand All @@ -147,6 +152,6 @@ The companion `open-router` repo is currently documented as the future extractio

## Status

The gateway core is implemented and locally verified for the first release surface: CLI server, MCP server, health/models/chat endpoints, OpenAI-compatible provider adapter, provider presets, routing policy, fallbacks, streaming, usage normalization, optional local ledger, examples, tests, build, and package dry-run.
The gateway core is implemented and locally verified for the current release surface: CLI and standalone HTTP servers, MCP server, health/readiness/version/models/chat/embeddings endpoints, OpenAI-compatible and Anthropic adapters, provider presets, routing policy, fallbacks, streaming, response caching, rate limits, budgets, usage normalization, local and cloud ledgers, examples, tests, build, and package dry-run.

Publication is gated on a passing live smoke check with valid provider credentials.
30 changes: 16 additions & 14 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
# Hasna Gateway Docs

Read these documents before implementation:
Use these pages as the current operator and API reference:

1. [Product requirements](product-requirements.md)
2. [Architecture](architecture.md)
1. [CLI reference](cli.md)
2. [Configuration reference](configuration.md)
3. [API contract](api-contract.md)
4. [Provider adapters](provider-adapters.md)
5. [2026 provider references](provider-references.md)
6. [Routing and policy](routing-and-policy.md)
7. [Gateway MCP server](mcp.md)
8. [Open-core boundary](open-core-boundary.md)
4. [Architecture](architecture.md)
5. [Provider adapters](provider-adapters.md)
6. [2026 provider references](provider-references.md)
7. [Routing and policy](routing-and-policy.md)
8. [Gateway MCP server](mcp.md)
9. [Security and compliance](security-compliance.md)
10. [Implementation plan](implementation-plan.md)
11. [Publishing and release](publishing-and-release.md)
12. [Hasna app migration plan](migration-plan.md)
13. [Codewith handoff prompt](handoff-prompt.md)
10. [Open-core boundary](open-core-boundary.md)

The [product requirements](product-requirements.md), [implementation plan](implementation-plan.md), [publishing and release checklist](publishing-and-release.md), [Hasna app migration plan](migration-plan.md), and [agent handoff prompt](handoff-prompt.md) record product intent and project history. When they describe future work, the current reference pages and implementation take precedence.

## Current Decision

The gateway should be open source as a self-hostable core. The commercial Hasna product should be a hosted wrapper that adds one Hasna API key, billing, pooled provider keys, discounts, dashboards, and enterprise controls.

## Build Contract

The first implementation should prioritize a small working gateway over broad incomplete abstractions:
The implemented gateway surface includes:

- A working CLI server.
- A stdio MCP server for safe local gateway inspection, route explanation, budget maintenance, and ledger summaries.
- OpenAI-compatible chat completions.
- OpenAI-compatible provider adapter.
- OpenAI-compatible embeddings.
- OpenAI-compatible and Anthropic provider adapters.
- Config validation.
- Model aliases.
- Fallback routing.
- Smart cost/quality/latency routing.
- Explicit provider policy.
- Config-driven provider auth and headers.
- Streaming.
- Optional in-memory response caching and per-key rate limits.
- Hard and soft budgets backed by local JSONL, SQLite, or Postgres usage storage.
- Usage normalization.
- Tests.

Expand Down
40 changes: 35 additions & 5 deletions docs/api-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Compatibility Principle

The public API should be OpenAI-compatible where possible. Existing OpenAI SDK clients should be able to point `baseURL` at the gateway and keep using `chat.completions.create`.
The public API is OpenAI-compatible where possible. Existing OpenAI SDK clients can point `baseURL` at the gateway and use chat completions and embeddings.

Compatibility does not mean hiding provider differences. If a provider cannot support a feature, the gateway should return a clear capability error or route to an allowed provider that can support it.

Expand All @@ -20,7 +20,7 @@ Hosted Hasna mode:
Authorization: Bearer <hasna-api-key>
```

Provider keys are never sent by the client unless explicit BYOK request support is added. Initial BYOK should be configured through server-side environment variables.
Built-in provider credentials are configured through server-side environment variables. Custom provider headers may be static or environment-derived, but client request bodies never supply provider credentials.

## `GET /health`

Expand All @@ -29,7 +29,7 @@ Returns service status. In `local` runtime mode this is a lightweight liveness c
```json
{
"status": "ok",
"version": "0.1.0",
"version": "0.1.6",
"runtime": {
"mode": "production-cloud"
},
Expand All @@ -39,7 +39,15 @@ Returns service status. In `local` runtime mode this is a lightweight liveness c
}
```

Readiness responses must not include secret values or environment variable names.
Health responses must not include secret values or environment variable names.

## `GET /version`

Returns `{ "name": "@hasna/gateway", "version": "<current version>" }`. This endpoint is public.

## `GET /ready`

Returns authenticated operational checks for runtime config, gateway auth, providers, routes, and usage-ledger availability. The response status is `200` when ready and `503` when runtime validation fails. A missing cumulative ledger is reported as `deferred` because per-request budgets remain usable.

## `GET /v1/models`

Expand All @@ -62,7 +70,7 @@ Returns configured gateway models and aliases, including provider and capability

## `POST /v1/chat/completions`

The initial critical endpoint. It should support:
Supports:

- `model`
- `messages`
Expand All @@ -73,8 +81,12 @@ The initial critical endpoint. It should support:
- `temperature`
- `top_p`
- `max_tokens`
- `max_completion_tokens`
- `stop`
- `seed` when provider supports it
- `n`, `presence_penalty`, and `frequency_penalty`
- `parallel_tool_calls`, `logprobs`, and `top_logprobs`
- `metadata`, `store`, `reasoning_effort`, `modalities`, `audio`, `prediction`, `service_tier`, and `user`

Example:

Expand Down Expand Up @@ -110,6 +122,24 @@ Unsupported gateway-only fields and secrets are stripped.

Smart routing fields include `task`, `priority`, `cost_quality_tradeoff`, `sticky_session_id`, `min_quality`, `min_context_tokens`, `expected_input_tokens`, `required_capabilities`, `provider_order`, `provider_only`, and `provider_ignore`. Policy is applied before scoring.

Successful non-streaming chat responses can use the optional process-local response cache. Streaming responses, embeddings, and errors are not cached. Sending a truthy value in the configured cache bypass header skips lookup.

## `POST /v1/embeddings`

Accepts OpenAI-compatible `model` and `input` fields. `input` may be a string, string array, token array, or array of token arrays. Optional forwarded fields are `encoding_format`, `dimensions`, and `user`. The gateway-only `gateway` field may narrow route policy but is stripped before provider forwarding.

Embedding route candidates must declare the `embeddings` capability. The gateway applies the same auth, policy, budget, fallback, rate-limit, usage-ledger, and metadata rules as non-streaming chat requests. Route filtering checks the model capability only, never the provider adapter, so a candidate whose adapter cannot embed is selected rather than skipped and then fails the whole request with a non-retryable `400 provider_embeddings_unsupported`; remaining fallback candidates are not attempted. Only the OpenAI-compatible adapter implements embeddings today, so order embeddings routes so that every eligible candidate is served by that adapter. Dynamic `provider/model` passthrough ids get the `embeddings` capability synthesized for any provider and fail the same way. Streaming and response caching do not apply.

```json
{
"model": "embeddings",
"input": ["first document", "second document"],
"encoding_format": "float"
}
```

The response preserves the provider's OpenAI-compatible `data` array, rewrites `model` to the configured gateway model id, normalizes usage to `prompt_tokens` and `total_tokens`, and includes `gateway` metadata when enabled.

## Response Shape

Non-streaming responses should match OpenAI chat completion shape:
Expand Down
48 changes: 48 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# CLI Reference

The package installs three binaries:

- `gateway`: configuration, routing, budget, smoke, and server commands.
- `gateway-serve`: a dedicated HTTP server entrypoint.
- `gateway-mcp`: the stdio MCP server documented in [Gateway MCP server](mcp.md).

All config-aware `gateway` commands default to `gateway.config.json`. Use `--config <path>` to select another file.

## General

| Command | Behavior |
| --- | --- |
| `gateway --help` or `gateway help` | Print command usage. `--help` after a command also prints the top-level usage. |
| `gateway --version` | Print the package version. |
| `gateway validate [--config <path>]` | Validate raw JSON config, print warnings, and exit non-zero on errors. |
| `gateway serve [--config <path>] [--host <host>] [--port <port>]` | Load config, validate runtime secrets, and start the HTTP server. CLI host and port override config. |
| `gateway smoke [--config <path>] [--model <alias>]` | Send one live chat smoke request. The model defaults to `fast`; missing credentials produce a skipped result. |
| `gateway smoke [--config <path>] --all` | Smoke-test every available provider and exit non-zero if any check fails or none pass. |

`gateway-serve` accepts `--config`, `--host`, `--port`, `--help`/`-h`, and `--version`/`-v`. Unlike `gateway serve`, invalid `--port` text is passed to `Bun.serve` as `NaN` rather than falling back to the configured port.

## Routing

| Command | Options and output |
| --- | --- |
| `gateway route --model <alias>` | Dry-run chat route selection without provider traffic. Add `--stream` to require streaming capability, `--json` for the raw decision, or `--json --contract` for `hasna.decision_envelope.v1`. |
| `gateway routes` | List configured route ids. Add `--json` for route summaries or `--json --contract` for `hasna.capability_card.v1` records. |

Route failures exit non-zero. JSON modes include the rejected route decision when one is available.

## Budgets

| Command | Options and output |
| --- | --- |
| `gateway budget-add --id <id>` | Add or replace a config budget. `--window` defaults to `lifetime`; `--mode` defaults to `hard`. Scope with `--gateway-key`, `--tenant`, and `--model`. Limits are `--max-usd`, `--max-input-tokens`, `--max-output-tokens`, `--max-total-tokens`, and `--warning-threshold`. Add `--json` for structured output. |
| `gateway budget-list` | List budget ids, or full normalized definitions with `--json`. |
| `gateway budget-remaining` | Calculate matching statuses. Filter with `--id`, `--tenant`, and `--model`; add `--json`, or `--json --contract` for `hasna.cost_estimate.v1`. |
| `gateway budget-reset --id <id>` | Set the budget's `resetAt` to the current time. Add `--json` for structured output. |

Numeric budget flags must be non-negative. `budget-add` validates the complete config before writing it.

## Local Removal

`gateway uninstall --yes` removes the selected config file and its configured local JSONL usage ledger. `gateway remove --all --yes` is an alias with an additional explicit `--all` safeguard. Both refuse to continue without a bare `--yes`, refuse to remove directories, tolerate an already-absent ledger, and support `--json` output. The selected config must exist so the command can discover the ledger path.

These commands do not remove the npm package, environment variables, SQLite/Postgres data, or any other file referenced by the config.
78 changes: 78 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Configuration Reference

Gateway config is JSON. `loadGatewayConfig` interpolates `${ENV_VAR}` placeholders before normalization and validation; provider credentials are still read from environment variables at runtime and should not be stored directly in config.

Start with one of the checked-in `gateway.config.*.json` examples. Run `gateway validate --config <path>` after changes.

## Defaults

Omitted top-level values normalize to these local-first defaults:

```json
{
"runtime": {
"mode": "local",
"serviceDiscovery": { "allowLocalProviderEndpoints": true },
"health": { "requireRuntimeSecrets": false }
},
"server": {
"host": "127.0.0.1",
"port": 8787,
"requestTimeoutMs": 60000,
"maxRequestBodyBytes": 1000000,
"includeGatewayMetadata": true,
"maxFallbackAttempts": 3,
"corsAllowedOrigins": ["http://127.0.0.1:8787", "http://localhost:8787"],
"responseCache": {
"enabled": false,
"ttlMs": 300000,
"maxEntries": 500,
"bypassHeader": "x-gateway-cache-bypass"
}
},
"auth": { "apiKeyEnv": "GATEWAY_API_KEY", "required": true },
"storage": {},
"policy": {
"allowTraining": false,
"allowLogging": false,
"allowChineseProviders": false,
"byokOnly": true
},
"providers": [],
"models": [],
"routes": [],
"budgets": []
}
```

## Runtime And Server

- `runtime.mode`: `local` or `production-cloud`. Production mode requires gateway auth, non-loopback binding, secret-aware health, and cloud-safe provider URLs.
- `runtime.serviceDiscovery.allowedProviderBaseUrls`: optional exact-origin allowlist for enabled provider base URLs.
- `server.rateLimits.perGatewayKey`: optional `requestsPerMinute` and `tokensPerMinute` limits. Keys are SHA-256 fingerprints of bearer tokens; unauthenticated optional-auth traffic shares an anonymous bucket.
- `server.responseCache`: process-local cache for successful, non-streaming chat completions. It does not cache streams, errors, or embeddings. TTL and maximum entries must be positive. A truthy configured bypass header skips lookup, but successful responses can still populate the cache.
- `server.corsAllowedOrigins`: exact allowed origins. CORS preflight permits `authorization`, `content-type`, `x-gateway-tenant`, and the configured cache bypass header.

`GET /health` and `GET /version` are public. `GET /ready` and every `/v1/*` endpoint enforce gateway auth when `auth.required` is true.

## Providers And Models

Providers declare `id`, `displayName`, `kind`, endpoint/auth settings, regions, and data policy. `baseUrl` takes precedence; `baseUrlEnv` is read only when `baseUrl` is absent, and config validation requires one of the two. `auth` supports bearer, custom-header, and no-auth modes; `headers` supports static values and env-derived values with optional prefixes and required markers.

Models declare a stable gateway `id`, `providerId`, upstream `providerModel`, aliases, and capabilities. Optional context, price, quality, latency, success-rate, and throughput values drive policy filters and smart scoring. The `embeddings` capability is required for embedding routes.

Provider and model presets are appended after explicit user definitions and deduplicated by id, so an explicit definition wins over a preset with the same id. See [Provider adapters](provider-adapters.md) for preset ids and adapter behavior.

## Routes And Request Policy

Routes select one of `explicit`, `fallback`, `cheapest`, `lowest-latency`, `highest-throughput`, `balanced`, or `smart`. They can bind aliases, provider allow/block lists, price and latency ceilings, fallback model ids, and data policy.

Request `gateway` policy can narrow configured policy. It can expand policy only when `policy.allowRequestPolicyExpansion` is true. See [Routing and policy](routing-and-policy.md) for filtering and scoring order.

## Storage And Budgets

- `storage.usageLedgerPath`: append-only local JSONL ledger.
- `storage.cloud.backend: "sqlite"`: SQLite ledger using `sqlitePath`.
- `storage.cloud.backend: "postgres"`: Postgres ledger using `connectionString` or `connectionStringEnv`.

Daily, monthly, and lifetime budgets require cumulative storage. Per-request budgets do not. Budgets support gateway-key, tenant, and model-alias scopes; hard mode rejects exhausted requests, while soft mode records warnings. See [CLI reference](cli.md) for budget maintenance commands.
Loading
Loading