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
18 changes: 11 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ When starting any new work (feature, fix, refactor, chore), always create a feat

## Observability

See [hawk/docs/OTEL-CONVENTIONS.md](https://github.com/GrayCodeAI/hawk/blob/main/docs/OTEL-CONVENTIONS.md) for the shared OpenTelemetry attribute vocabulary (`gen_ai.*`, `cost.usd`, etc.) used across all GrayCodeAI repos.
See [graycode/docs/OTEL-CONVENTIONS.md](https://github.com/GrayCodeAI/graycode-cli/blob/main/docs/OTEL-CONVENTIONS.md) for the shared OpenTelemetry attribute vocabulary (`gen_ai.*`, `cost.usd`, etc.) used across all GrayCodeAI repos.

## Build & Test

Expand Down Expand Up @@ -49,13 +49,17 @@ make ci # Full CI suite

## Common Pitfalls

- `engine` is Hawk's product boundary; Hawk must not assemble lower-level
`client`, `catalog`, `config`, `credentials`, `router`, or `runtime` packages
- `engine`, `llm`, `graph` and `tools` are the host contract surface. Graycode
must not assemble `client`, `catalog`, `config`, `credentials`, `router` or
`runtime`. Six symbols Graycode needs (`ChatOptions`, `ContinuationConfig`,
`StreamResult`, `ResponseFormat`, `ImageURLPart`, `InputAudioPart`) live in
`llm` with no `engine` alias; widening the facade to cover them is a
deliberate API change, not an incidental one.
- `client.Provider` remains the lower-level compatibility boundary for other
consumers; preserve its method set and the facade's type identity
- Streaming tests need careful goroutine management
- `go.work` here should stay minimal; the parent `graycode-eco/go.work`
connects this independent `graycode-router` checkout beside Hawk for local development.
connects this independent `graycode-router` checkout beside Graycode for local development.
Do not add extra local `replace` directives here without coordinating with
the parent workspace.

Expand Down Expand Up @@ -141,7 +145,7 @@ make ci # Full CI suite
| Main test file | `client/client_test.go` (httptest servers, provider detection) |
| Linter config | `.golangci.yml` (govet, ineffassign, misspell — minimal) |

This is an independent repository consumed by Hawk. In the local
`graycode-eco` parent workspace it is checked out beside `hawk` as `../graycode-router`
This is an independent repository consumed by Graycode. In the local
`graycode-eco` parent workspace it is checked out beside `graycode` as `../graycode-router`
and connected through the parent `go.work`; publish changes here, then update
Hawk's module pin through a separate PR.
Graycode's module pin through a separate PR.
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Contributing to graycode-router

Thanks for your interest! This guide covers the conventions used across the
hawk-eco. The eco-wide standards (versioning, release tooling, repo layout)
are defined in <https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md>.
graycode-eco. The eco-wide standards (versioning, release tooling, repo layout)
are defined in <https://github.com/GrayCodeAI/graycode-cli/blob/main/VERSIONING.md>.

## Quick start

Expand All @@ -20,7 +20,7 @@ are defined in <https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md>.

## Build & test

This repo uses the standardised hawk-eco Makefile targets. Run `make help`
This repo uses the standardised graycode-eco Makefile targets. Run `make help`
for the full list. The most common targets:

| Target | What it does |
Expand Down
37 changes: 25 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,28 +30,41 @@

## What is graycode-router

graycode-router is the LLM provider runtime that powers the [hawk](https://github.com/GrayCodeAI/hawk) coding agent. It handles everything between your application and LLM APIs — authentication, model resolution, streaming, retries, rate limiting, and caching.
graycode-router is the LLM provider runtime that powers the [graycode](https://github.com/GrayCodeAI/graycode-cli) coding agent. It handles everything between your application and LLM APIs — authentication, model resolution, streaming, retries, rate limiting, and caching.

When your app calls a model, graycode-router figures out which provider to use, how to talk to it, and how to stream the response back. Switch from Anthropic to Ollama? graycode-router handles the translation. API returns 529? graycode-router retries with backoff. Response hits `max_tokens`? graycode-router continues automatically.

**Your app never talks to an LLM API directly. graycode-router does.**

Hawk is the product face: it owns UX, agent orchestration, tools, permissions,
Graycode is the product face: it owns UX, agent orchestration, tools, permissions,
sessions, and product semantics. GraycodeRouter is the provider engine: it owns
credentials, catalog and route resolution, provider transports, normalized
streams, retry/fallback, usage, and provider telemetry. Hawk integrates through
streams, retry/fallback, usage, and provider telemetry. Graycode integrates through
the stable [`engine`](engine/) facade rather than assembling GraycodeRouter's internal
provider packages.

## Ecosystem Boundaries

graycode-router is a Hawk support engine. Keep the dependency edge one-way:
graycode-router is a Graycode support engine. Keep the dependency edge one-way.

- host-facing DTOs and the `Provider` port live in `eagle/llm`; `engine/` re-exports them as aliases (`*Engine` implements `llm.Provider`)
- internal provider/transport types stay graycode-router-scoped (not shared contracts)
- do not import `hawk/internal/*`
- do not import removed legacy path `hawk/shared/types`
- do not import other engines (`harrier`, `shrike`, `swift`, `kestrel`, `merlin`) — engines are peers, not dependencies
Hosts may import exactly four packages:

| Package | Carries |
|---|---|
| `engine` | the stable host-facing facade |
| `llm` | host-facing DTOs and the `Provider` port that `engine` re-exports as aliases |
| `graph` | the portable execution-graph vocabulary |
| `tools` | tool-call and tool-result contracts |

Everything else is engine-internal: `client`, `catalog`, `config`,
`credentials`, `router`, `runtime`, and their subpackages are not shared
contracts. Enforced by `graycode-cli/scripts/check-graycode-router-engine-boundary.sh`
and two Go AST tests in `graycode-cli/internal/testaudit/`.

- do not import `graycode-cli/internal/*`
- do not import the removed legacy path `graycode/shared/types`
- do not import other engines (`harrier`, `shrike`, `swift`, `kestrel`,
`merlin`) — engines are peers, not dependencies

## Quick Start

Expand Down Expand Up @@ -174,7 +187,7 @@ ANTHROPIC_API_KEY=sk-... go run ./examples/basic/

## Supported Providers

22 provider gateways in `catalog/registry/providers.go` (hawk `/config` uses the same list), listed in registry `SortOrder`:
22 provider gateways in `catalog/registry/providers.go` (graycode `/config` uses the same list), listed in registry `SortOrder`:

| Provider | ID | Env variable |
|---|---|---|
Expand Down Expand Up @@ -302,11 +315,11 @@ tool-call count, and deployment-routing state remain queryable.

## Ecosystem

graycode-router is part of the hawk-eco:
graycode-router is part of the graycode-eco:

| Component | Repository | Purpose |
|---|---|---|
| **hawk** | [GrayCodeAI/hawk](https://github.com/GrayCodeAI/hawk) | AI coding agent |
| **graycode** | [GrayCodeAI/graycode-cli](https://github.com/GrayCodeAI/graycode-cli) | AI coding agent |
| **graycode-router** | This repo | LLM provider runtime |
| **shrike** | [GrayCodeAI/shrike](https://github.com/GrayCodeAI/shrike) | Tokenizer & compression |
| **harrier** | [GrayCodeAI/harrier](https://github.com/GrayCodeAI/harrier) | Graph-based memory |
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ minor versions once `1.x` ships. Older versions receive critical-severity
fixes only on a best-effort basis.

The current canonical version is the contents of the [`VERSION`](./VERSION)
file at the repo root. See [`VERSIONING.md`](https://github.com/GrayCodeAI/hawk/blob/main/VERSIONING.md)
file at the repo root. See [`VERSIONING.md`](https://github.com/GrayCodeAI/graycode-cli/blob/main/VERSIONING.md)
for the eco-wide versioning scheme.

## Reporting a vulnerability
Expand Down
2 changes: 1 addition & 1 deletion catalog/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package catalog

// Credentials carries API keys and related env (base URLs) for provider-backed catalog discovery.
// Keys use standard env var names (e.g. OPENROUTER_API_KEY). Populate via config.DiscoveryCredentials.
// or pass an explicit map from hawk — do not hardcode provider lists in hawk.
// or pass an explicit map from graycode — do not hardcode provider lists in graycode.
type Credentials struct {
APIKeys map[string]string
}
Expand Down
2 changes: 1 addition & 1 deletion catalog/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package catalog
import "errors"

// ErrCatalogCacheRequired is returned when no valid ~/.graycode-router/model_catalog.json exists.
// Run catalog discovery (hawk models refresh / graycode-router catalog discover) to populate the cache.
// Run catalog discovery (graycode models refresh / graycode-router catalog discover) to populate the cache.
var ErrCatalogCacheRequired = errors.New("model catalog cache required")
2 changes: 1 addition & 1 deletion catalog/live/fetchers_providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,7 +977,7 @@ func FetchConcentrate(env map[string]string) ([]Entry, error) {
// capabilities but omits the provider-level supports.tools field.
// The Responses API and model-details endpoint advertise function
// calling for these routed models, so preserve that capability for
// Hawk's tool-enabled coding loop.
// Graycode's tool-enabled coding loop.
entry.Features = append(entry.Features, "function_calling")
entries = append(entries, entry)
}
Expand Down
2 changes: 1 addition & 1 deletion catalog/provider_credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func apiKeyEnvFromDeployment(dep Deployment) string {
}

// CredentialStatusForProvider reports whether a provider needs an API key (local vs required).
// For set/empty status use hawk config.EnvKeyStatus or credentials.HasSecret — catalog does not read env.
// For set/empty status use graycode config.EnvKeyStatus or credentials.HasSecret — catalog does not read env.
func CredentialStatusForProvider(compiled *CompiledCatalog, providerID string) string {
providerID = canonicalProviderID(providerID)
if providerID == "" {
Expand Down
2 changes: 1 addition & 1 deletion catalog/refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
// RefreshCatalog fetches the published catalog, validates it, and writes the cache.
// Unlike LoadCatalog with RefreshRemote, this fails when the remote fetch fails so
// callers never treat a stale cache as a successful refresh.
func RefreshCatalog(ctx context.Context, opts LoadCatalogOptions) (*RefreshResult, error) {

Check failure on line 53 in catalog/refresh.go

View workflow job for this annotation

GitHub Actions / deadcode

unreachable func: RefreshCatalog
if opts.CachePath == "" {
opts.CachePath = DefaultCachePath()
}
Expand Down Expand Up @@ -96,7 +96,7 @@
)
}

// DiscoverReport returns a multi-line report for `hawk models refresh` / `graycode-router catalog discover`.
// DiscoverReport returns a multi-line report for `graycode models refresh` / `graycode-router catalog discover`.
func (r *RefreshResult) DiscoverReport() string {
if r == nil || r.Compiled == nil {
return "Catalog discovery: no data"
Expand Down
6 changes: 3 additions & 3 deletions catalog/registry/protocol_matrix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// agnes, kimi, openai, grok, openrouter, groq, canopywave, poolside,
// clinepass, ollama, azure, gemini (native/Gemini protocol), concentrate (Responses)
//
// Vendors documenting both OpenAI + Anthropic (hawk uses exactly one — OpenAI):
// Vendors documenting both OpenAI + Anthropic (graycode uses exactly one — OpenAI):
// deepseek, zai_*, xiaomi_mimo_*, minimax_*, longcat
// opencodego keeps both clients only for per-model routing (one protocol per call;
// never cross-protocol fallback on the same request)
Expand All @@ -21,7 +21,7 @@ import (
// anthropic, bedrock
//
// Rule: if a vendor is OpenAI-compatible only, do not invent an Anthropic client.
// If a vendor documents both, hawk uses OpenAI only — never both protocols for the
// If a vendor documents both, graycode uses OpenAI only — never both protocols for the
// same provider request (no OpenAI→Anthropic error fallback).

func TestProviderProtocolMatrix_OpenAIOnlyHaveNoAnthropicTransport(t *testing.T) {
Expand Down Expand Up @@ -50,7 +50,7 @@ func TestProviderProtocolMatrix_OpenAIOnlyHaveNoAnthropicTransport(t *testing.T)

func TestProviderProtocolMatrix_DualOfficialStayOpenAIPrimary(t *testing.T) {
t.Parallel()
// Catalog primary protocol is OpenAI chat completions. Hawk clients for these
// Catalog primary protocol is OpenAI chat completions. Graycode clients for these
// providers use OpenAI only (OpenCode Go is the exception: per-model single
// protocol, still no cross-protocol fallback).
dualPrimaryOpenAI := []string{
Expand Down
2 changes: 1 addition & 1 deletion catalog/registry/provider_spec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestProviderSpecs_AgnesOpenAIOnlyLongCatOpenAIPrimary(t *testing.T) {
}

// LongCat: official docs expose BOTH OpenAI (/openai) and Anthropic (/anthropic).
// Hawk uses the OpenAI primary only — Anthropic is not required when OpenAI works.
// Graycode uses the OpenAI primary only — Anthropic is not required when OpenAI works.
longcat, ok := registry.SpecByProviderID("longcat")
if !ok {
t.Fatal("missing longcat")
Expand Down
2 changes: 1 addition & 1 deletion catalog/testdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"testing"
)

// Run with EXPORT_HAWK_FIXTURE=1 to refresh hawk/internal/catalogtest/testdata/minimal_v1.json
// Run with EXPORT_HAWK_FIXTURE=1 to refresh graycode/internal/catalogtest/testdata/minimal_v1.json
func TestExportHawkCatalogFixture(t *testing.T) {
t.Parallel()
if os.Getenv("EXPORT_HAWK_FIXTURE") != "1" {
Expand Down
4 changes: 2 additions & 2 deletions catalog/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ func LoadCatalog(ctx context.Context, opts LoadCatalogOptions) (*CompiledCatalog
return compiled, nil
}
if opts.RequireCache {
return nil, fmt.Errorf("%w (%s missing or invalid; run: hawk models refresh)", ErrCatalogCacheRequired, opts.CachePath)
return nil, fmt.Errorf("%w (%s missing or invalid; run: graycode models refresh)", ErrCatalogCacheRequired, opts.CachePath)
}
bootstrap := BootstrapCatalog()
compiled, err := CompileCatalog(&bootstrap)
Expand All @@ -639,7 +639,7 @@ func LoadCatalog(ctx context.Context, opts LoadCatalogOptions) (*CompiledCatalog
}
compiled.Diagnostics = append(compiled.Diagnostics, CatalogDiagnostic{
Code: "bootstrap_only",
Message: "no model catalog cache; run hawk models refresh or graycode-router catalog discover",
Message: "no model catalog cache; run graycode models refresh or graycode-router catalog discover",
})
return compiled, nil
}
Expand Down
2 changes: 1 addition & 1 deletion catalog/zai/endpoints.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package zai resolves Z.AI (Zhipu GLM) API base URLs for General (pay-as-you-go)
// and Coding Plan subscriptions across International vs China regions.
// Hawk uses the OpenAI-compatible surface only.
// Graycode uses the OpenAI-compatible surface only.
package zai

import (
Expand Down
2 changes: 1 addition & 1 deletion client/adapters/poolside.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (c *PoolsideClient) StreamChat(ctx context.Context, messages []core.Graycod
}

func (c *PoolsideClient) reasoningOnlyFallbackChat(ctx context.Context, messages []core.GraycodeRouterMessage, opts core.ChatOptions) (*core.GraycodeRouterResponse, error) {
// A Laguna stream can exhaust itself in reasoning when Hawk's large tool
// A Laguna stream can exhaust itself in reasoning when Graycode's large tool
// catalog is attached. Preserve tools on the primary request, but make the
// one-shot recovery text-only so the model emits its final answer.
opts.Tools = nil
Expand Down
4 changes: 2 additions & 2 deletions client/continuation.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,15 @@ func ChatWithContinuation(ctx context.Context, p Provider, messages []GraycodeRo
// It returns a StreamResult whose Events channel transparently continues across
// multiple LLM calls, emitting a "continuation" event at each boundary.
//
// DEPRECATION NOTE: hawk's Session loop has its own max_tokens recovery
// DEPRECATION NOTE: graycode's Session loop has its own max_tokens recovery
// (internal/engine/stream.go around the `recoveryCount` loop) that doesn't
// add a synthetic "Continue." user message, and the graycode-router conversation
// engine (graycode-router/conversation.Engine) has its own OutputGroupID-based
// engine-level continuation. The two engine-level paths produce cleaner
// conversation shapes (no synthetic user turns) and are the recommended
// pattern for new code. This client-level helper remains for
// backwards-compatibility with the embedded graycode-router HTTP server and
// non-hawk consumers; new code should implement continuation at the
// non-graycode consumers; new code should implement continuation at the
// engine or call-site level instead.
//
// Will be removed in graycode-router v0.3.0. See graycode-router/CHANGELOG.md for the
Expand Down
2 changes: 1 addition & 1 deletion client/core/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var extToMediaType = map[string]string{
// encoded → (mediaType, data, true)
//
// It is the single entry point for image handling so the provider clients and
// hawk no longer each carry their own divergent encoder. Local files and
// graycode no longer each carry their own divergent encoder. Local files and
// data-URLs are validated against supportedImageMediaTypes; HTTP URLs are left
// for the provider to fetch (avoiding an SSRF surface inside graycode-router).
func NormalizeImageSource(src string) (mediaType, data string, isBase64 bool, err error) {
Expand Down
2 changes: 1 addition & 1 deletion client/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
//
// These are provider-agnostic backends for the two well-defined, broadly
// supported public APIs (OpenAI Images: POST /v1/images/generations; OpenAI
// Audio: POST /v1/audio/transcriptions). They give hawk's pluggable
// Audio: POST /v1/audio/transcriptions). They give graycode's pluggable
// MediaEngine / Transcriber seams a concrete default backend while staying
// testable against an httptest server. A future provider (xAI image-gen,
// etc.) can replace the endpoint/credentials without touching callers.
Expand Down
2 changes: 1 addition & 1 deletion config/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func DefaultCategories() map[ModelCategory]CategoryConfig {
}

// GetCategoryRegistry returns the global category registry.
// It loads overrides from Hawk user config if present.
// It loads overrides from Graycode user config if present.
func GetCategoryRegistry() *CategoryRegistry {
registryOnce.Do(func() {
globalRegistry = &CategoryRegistry{
Expand Down
2 changes: 1 addition & 1 deletion config/discovery_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// DiscoveryCredentials loads API keys from the OS secret store (not process env or .env files),
// merged with non-secret routing from ~/.hawk/provider.json (e.g. MiMo Token Plan region/base URL).
// merged with non-secret routing from ~/.graycode/provider.json (e.g. MiMo Token Plan region/base URL).
func DiscoveryCredentials(ctx context.Context) catalog.Credentials {
if ctx == nil {
ctx = context.Background()
Expand Down
2 changes: 1 addition & 1 deletion config/provider_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/GrayCodeAI/graycode-router/catalog/registry"
)

// ProviderConfig mirrors the Hawk provider.json file.
// ProviderConfig mirrors the Graycode provider.json file.
type ProviderConfig struct {
ConfigVersion int `json:"config_version,omitempty"`
Version string `json:"_version,omitempty"`
Expand Down
4 changes: 2 additions & 2 deletions config/routing_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

// BuildRoutingPolicyFromDeployments builds deployment routing from configured deployments.
// Hawk should not author routing rules — consume this JSON from graycode-router only.
// Graycode should not author routing rules — consume this JSON from graycode-router only.
func BuildRoutingPolicyFromDeployments(deployments map[string]DeploymentConfig) *RoutingPolicy {
if len(deployments) == 0 {
return &RoutingPolicy{}
Expand Down Expand Up @@ -109,7 +109,7 @@ func longcatProviderStages(deployments map[string]DeploymentConfig) []RoutingSta
return nil
}
// Single OpenAI-compatible endpoint only (longcat-direct).
// Official LongCat also documents /anthropic; hawk does not require it when OpenAI works.
// Official LongCat also documents /anthropic; graycode does not require it when OpenAI works.
return singleDeploymentStages("longcat-direct", 1)
}

Expand Down
4 changes: 2 additions & 2 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@

## <img src="https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/icons/target.svg" width="16" height="16" alt="target" /> Overview

graycode-router is the LLM provider runtime for the hawk ecosystem. It sits between the application and LLM APIs, handling **authentication**, **model resolution**, **streaming**, **retries**, **rate limiting**, and **caching**.
graycode-router is the LLM provider runtime for the graycode ecosystem. It sits between the application and LLM APIs, handling **authentication**, **model resolution**, **streaming**, **retries**, **rate limiting**, and **caching**.

> <img src="https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/icons/lightbulb.svg" width="16" height="16" alt="lightbulb" /> No hawk ecosystem component talks to an LLM API directly — all communication goes through graycode-router.
> <img src="https://cdn.jsdelivr.net/gh/lucide-icons/lucide@latest/icons/lightbulb.svg" width="16" height="16" alt="lightbulb" /> No graycode ecosystem component talks to an LLM API directly — all communication goes through graycode-router.

---

Expand Down
Loading
Loading