Skip to content
Open
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Gini is not just a chat box, CLI, messaging bot, or pile of tools. Chat is an in
- [Memory](docs/memory.md): retain, recall, embeddings, reranking, review, and storage
- [Skill Learning From Skills](docs/skill-learning.md): how Gini improves its own skills from task outcomes (two-tier reward, attribution, the daily review, the human gate)
- [Runtime Capabilities](docs/runtime-capabilities.md): current CLI/API capability map and verification commands
- [Model Providers](docs/providers/README.md): per-provider setup guides (credentials, prerequisites, CLI/web config) for OpenAI, Anthropic, Bedrock, Azure, OpenRouter, DeepSeek, Codex, and Local
- [Model Providers](docs/providers/README.md): per-provider setup guides (credentials, prerequisites, CLI/web config) for OpenAI, Anthropic, Bedrock, Azure, OpenRouter, Requesty, DeepSeek, Codex, and Local
- [Operations](docs/operations.md): install, start, stop, smoke, diagnostics, and cleanup
- [Remote Access](docs/remote-access.md): tunnel modes and confirmation, plus a self-contained guide per tunnel provider — [Gini Relay](docs/remote-access/gini-relay.md), [Tailscale](docs/remote-access/tailscale.md), [ngrok](docs/remote-access/ngrok.md), [Cloudflare](docs/remote-access/cloudflare.md) — the same pages the app opens inline
- [Releases](docs/releases.md): versioning, CHANGELOG conventions, and the release process
Expand Down Expand Up @@ -45,7 +45,7 @@ Gini's **runtime is the gateway**: a single Bun process per instance owns state
- Authenticated localhost gateway and a Next.js + Tailwind + shadcn/ui control plane
- Persistent chat, runs, tasks, approvals, traces, audit events, jobs, memories, and skills
- Approval-gated file, terminal, and code tools
- Provider support: Codex OAuth; OpenAI / Azure OpenAI / DeepSeek / OpenRouter API keys; the first-party Anthropic Claude API; Amazon Bedrock (model-agnostic Converse, AWS SigV4 — Claude, Nova, Llama, Mistral, DeepSeek); and any OpenAI-compatible local server
- Provider support: Codex OAuth; OpenAI / Azure OpenAI / DeepSeek / OpenRouter / Requesty API keys; the first-party Anthropic Claude API; Amazon Bedrock (model-agnostic Converse, AWS SigV4 — Claude, Nova, Llama, Mistral, DeepSeek); and any OpenAI-compatible local server
- Local embeddings, reranking, and voice-message speech-to-text by default
- Parallel instances with isolated state, ports, and logs

Expand All @@ -57,7 +57,7 @@ See the [Whitepaper](docs/whitepaper.md) and [Architecture Overview](docs/archit
curl -fsSL https://raw.githubusercontent.com/Lilac-Labs/gini-agent/main/scripts/install.sh | bash
```

On macOS the installer enables autostart (per-user LaunchAgents for the runtime and webapp), waits for the webapp to come up, and opens the `/setup` page in your browser. The form offers the full provider catalog — OpenAI, Codex, Anthropic, Amazon Bedrock, Azure OpenAI, OpenRouter, DeepSeek, and Local — and prompts for whatever the one you pick needs (an API key, the AWS access key pair for Bedrock, the resource endpoint for Azure, or your existing `codex login` auth). Save it and you land on the running app. The runtime stays alive across reboots and crashes until you explicitly run `gini stop` or `gini autostart disable`.
On macOS the installer enables autostart (per-user LaunchAgents for the runtime and webapp), waits for the webapp to come up, and opens the `/setup` page in your browser. The form offers the full provider catalog — OpenAI, Codex, Anthropic, Amazon Bedrock, Azure OpenAI, OpenRouter, Requesty, DeepSeek, and Local — and prompts for whatever the one you pick needs (an API key, the AWS access key pair for Bedrock, the resource endpoint for Azure, or your existing `codex login` auth). Save it and you land on the running app. The runtime stays alive across reboots and crashes until you explicitly run `gini stop` or `gini autostart disable`.

If the browser doesn't open automatically (or you want to navigate manually), run `gini status` to print the actual web URL. The installed `default` instance always lives at `:7777`; other instances get hash-derived ports, so check `gini status` rather than guessing. The installer also prints the URL right before opening the browser.

Expand Down Expand Up @@ -114,6 +114,7 @@ Run `gini setup` for an interactive picker, or configure directly:
gini provider set codex gpt-5.5 # Codex OAuth (reads ~/.codex/auth.json)
gini provider set openai gpt-5.4-mini # uses $OPENAI_API_KEY
gini provider set openrouter <model> # uses $OPENROUTER_API_KEY
gini provider set requesty <model> # uses $REQUESTY_API_KEY
gini provider set local <model> --base-url http://127.0.0.1:8000/v1
gini provider set anthropic claude-opus-4-8 # first-party Claude API, uses $ANTHROPIC_API_KEY
# Amazon Bedrock: model-agnostic Converse, SigV4-signed with AWS keys you enter via the web form or `gini setup`
Expand All @@ -124,11 +125,11 @@ gini provider set azure gpt-4o \
--deployment <deployment> --api-version 2024-10-21 --auth-scheme api-key # uses $AZURE_OPENAI_API_KEY
```

For step-by-step setup of each provider — getting credentials, installing any prerequisite tooling (Ollama, …), and configuring it in the CLI or web — see the per-provider guides: [OpenAI](docs/providers/openai.md), [Anthropic](docs/providers/anthropic.md), [Amazon Bedrock](docs/providers/bedrock.md), [Azure OpenAI](docs/providers/azure.md), [OpenRouter](docs/providers/openrouter.md), [DeepSeek](docs/providers/deepseek.md), [Codex](docs/providers/codex.md), and [Local](docs/providers/local.md). The [providers index](docs/providers/README.md) lists them all with their auth model at a glance.
For step-by-step setup of each provider — getting credentials, installing any prerequisite tooling (Ollama, …), and configuring it in the CLI or web — see the per-provider guides: [OpenAI](docs/providers/openai.md), [Anthropic](docs/providers/anthropic.md), [Amazon Bedrock](docs/providers/bedrock.md), [Azure OpenAI](docs/providers/azure.md), [OpenRouter](docs/providers/openrouter.md), [Requesty](docs/providers/requesty.md), [DeepSeek](docs/providers/deepseek.md), [Codex](docs/providers/codex.md), and [Local](docs/providers/local.md). The [providers index](docs/providers/README.md) lists them all with their auth model at a glance.

The `local` provider works with any OpenAI-compatible server (oMLX, vLLM, LM Studio, llama.cpp). The `azure` provider targets an Azure OpenAI resource: set `--base-url` to `https://<resource>.openai.azure.com` and pick a deployment; `--api-version` defaults to a GA value and `--auth-scheme` defaults to `api-key` (a resource key), with `bearer` available for an Entra token. API keys are read from environment variables, and Codex OAuth is read from `~/.codex/auth.json` (or `CODEX_AUTH_JSON`) — nothing is written to Gini config. Run `gini --help` for the full flag set, or see [provider-extra-body.md](docs/adr/provider-extra-body.md) for the `--extra-body` contract and [Azure OpenAI As A First-Class Provider](docs/adr/azure-provider.md) for the Azure routing contract. When a credential fails mid-chat, see [Codex re-authentication](docs/providers/codex.md#re-authentication) and [Provider Re-Authentication Guidance](docs/adr/provider-reauth-guidance.md).

`gini setup`'s interactive picker covers every provider — OpenAI, Codex, Anthropic, Amazon Bedrock, Azure OpenAI, OpenRouter, DeepSeek, and Local — prompting for whatever each one needs (an API key, the AWS access key + secret for Bedrock, the resource endpoint and deployment for Azure, the base URL for Local). `gini provider set …` (above) and the web **Settings → Add provider** form remain available for scripted or non-interactive configuration.
`gini setup`'s interactive picker covers every provider — OpenAI, Codex, Anthropic, Amazon Bedrock, Azure OpenAI, OpenRouter, Requesty, DeepSeek, and Local — prompting for whatever each one needs (an API key, the AWS access key + secret for Bedrock, the resource endpoint and deployment for Azure, the base URL for Local). `gini provider set …` (above) and the web **Settings → Add provider** form remain available for scripted or non-interactive configuration.

## Parallel Instances

Expand Down
1 change: 1 addition & 0 deletions docs/providers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ provider in Gini (both the CLI and the web Add Provider form).
| Amazon Bedrock | AWS SigV4 | AWS access key + secret you enter (stored in `~/.gini/secrets.env`) | [bedrock.md](bedrock.md) |
| Azure OpenAI | API key / Entra | `AZURE_OPENAI_API_KEY` | [azure.md](azure.md) |
| OpenRouter | API key | `OPENROUTER_API_KEY` | [openrouter.md](openrouter.md) |
| Requesty | API key | `REQUESTY_API_KEY` | [requesty.md](requesty.md) |
| DeepSeek | API key | `DEEPSEEK_API_KEY` | [deepseek.md](deepseek.md) |
| Codex (OpenAI OAuth) | OAuth / CLI | `~/.codex/auth.json` (no key) | [codex.md](codex.md) |
| Local (OpenAI-compatible) | none / optional key | `GINI_LOCAL_API_KEY` (optional) | [local.md](local.md) |
Expand Down
50 changes: 50 additions & 0 deletions docs/providers/requesty.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Requesty

Requesty is an OpenAI-compatible API-key provider that routes a single key to
models from many vendors. Gini talks to `https://router.requesty.ai/v1` and
authenticates with a Bearer key.

## Step 1 — Get an API key

1. Sign in at [requesty.ai](https://requesty.ai/).
2. Create a key on the [API keys page](https://app.requesty.ai/api-keys) and copy it.
3. Add credit (Requesty is pay-as-you-go and bills per model). See the
[Requesty docs](https://docs.requesty.ai/) for the model catalog.

See the [Requesty docs](https://docs.requesty.ai/) for the full API reference.

## Step 2 — Set the key

Gini reads the key from the `REQUESTY_API_KEY` environment variable. Set it in
your shell or in `~/.gini/secrets.env` for persistence:

```bash
# ~/.gini/secrets.env (created mode 0600)
REQUESTY_API_KEY=rqsty-sk-...
```

The web Add Provider form writes this for you.

## Step 3 — Configure the provider in Gini

### CLI

```bash
gini provider set requesty openai/gpt-4o-mini
```

Requesty uses `provider/model` slugs (e.g. `openai/gpt-4o-mini`,
`anthropic/claude-opus-4-8`). The base URL defaults to
`https://router.requesty.ai/v1`; override it only for a proxy with `--base-url`.

### Web

Open **Settings → Add provider → Requesty**, paste the key, and pick or type a
model slug.

## Re-authentication

Requesty is an API-key provider, so a credential failure surfaces Requesty's own
message and links to **Settings → Providers** to paste a new key. Rotate keys on
the [API keys page](https://app.requesty.ai/api-keys). See ADR
[provider-reauth-guidance.md](../adr/provider-reauth-guidance.md).
3 changes: 2 additions & 1 deletion src/cli/commands/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export async function provider(ctx: CliContext): Promise<void> {
name !== "openai" &&
name !== "codex" &&
name !== "openrouter" &&
name !== "requesty" &&
name !== "local" &&
name !== "deepseek" &&
name !== "anthropic" &&
Expand Down Expand Up @@ -169,7 +170,7 @@ export async function provider(ctx: CliContext): Promise<void> {

config.provider = normalizeProvider({
name,
model: model ?? (name === "echo" ? "gini-echo-v0" : name === "codex" ? "gpt-5.5" : name === "openrouter" ? "openrouter/auto" : name === "local" ? "local/default" : name === "deepseek" ? "deepseek-v4-flash" : name === "anthropic" ? "claude-opus-4-8" : name === "bedrock" ? "us.anthropic.claude-opus-4-8" : name === "azure" ? "gpt-5.5" : "gpt-5.4-mini"),
model: model ?? (name === "echo" ? "gini-echo-v0" : name === "codex" ? "gpt-5.5" : name === "openrouter" ? "openrouter/auto" : name === "requesty" ? "openai/gpt-4o-mini" : name === "local" ? "local/default" : name === "deepseek" ? "deepseek-v4-flash" : name === "anthropic" ? "claude-opus-4-8" : name === "bedrock" ? "us.anthropic.claude-opus-4-8" : name === "azure" ? "gpt-5.5" : "gpt-5.4-mini"),
...(baseUrl ? { baseUrl } : {}),
...(apiKeyEnv ? { apiKeyEnv } : {}),
...(extraBody ? { extraBody } : {}),
Expand Down
15 changes: 14 additions & 1 deletion src/cli/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export interface ProviderExtraConfig {
}

export interface ProviderModule {
id: "openai" | "codex" | "anthropic" | "bedrock" | "azure" | "openrouter" | "deepseek" | "local";
id: "openai" | "codex" | "anthropic" | "bedrock" | "azure" | "openrouter" | "requesty" | "deepseek" | "local";
kind: ProviderKind;
label: string;
description: string;
Expand Down Expand Up @@ -231,6 +231,16 @@ const openrouterProvider: ProviderModule = apiKeyProvider({
keyHint: "sk-or-"
});

const requestyProvider: ProviderModule = apiKeyProvider({
id: "requesty",
label: "Requesty",
description: "Multi-model router — rqsty-sk-...",
apiKeyEnv: "REQUESTY_API_KEY",
defaultModel: "openai/gpt-4o-mini",
suggestedModels: ["openai/gpt-4o-mini", "openai/gpt-4o", "anthropic/claude-opus-4-8"],
keyHint: "rqsty-sk-"
});

const deepseekProvider: ProviderModule = apiKeyProvider({
id: "deepseek",
label: "DeepSeek",
Expand Down Expand Up @@ -460,6 +470,7 @@ const PROVIDERS: ProviderModule[] = [
bedrockProvider,
azureProvider,
openrouterProvider,
requestyProvider,
deepseekProvider,
localProvider
];
Expand Down Expand Up @@ -528,6 +539,7 @@ const AUTO_CONFIGURABLE: ProviderModule[] = [
openaiProvider,
anthropicProvider,
openrouterProvider,
requestyProvider,
deepseekProvider,
bedrockProvider
];
Expand Down Expand Up @@ -722,6 +734,7 @@ export const __testing = {
bedrockProvider,
azureProvider,
openrouterProvider,
requestyProvider,
deepseekProvider,
localProvider,
PROVIDERS,
Expand Down
6 changes: 3 additions & 3 deletions src/cli/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,16 @@ Usage:
bun run gini notifications list|queue|send|ack
bun run gini promotions list|propose|approve|reject
bun run gini snapshots list|create|restore
bun run gini provider show|catalog|set echo|openai|codex|openrouter|local|deepseek|azure [model]
bun run gini provider show|catalog|set echo|openai|codex|openrouter|requesty|local|deepseek|azure [model]
[--base-url <url>] [--api-key-env <NAME>] [--extra-body <JSON>]
[--api-version <V>] [--deployment <NAME>] [--auth-scheme bearer|api-key]
--base-url and --api-key-env work for every OpenAI-compatible
provider (local / openai / openrouter / deepseek / azure —
provider (local / openai / openrouter / requesty / deepseek / azure —
point at servers like oMLX, vLLM, LM Studio) AND for codex
(override the backend URL or auth-file env var). --extra-body
forwards server-specific request fields like
\`chat_template_kwargs\` and applies to the chat-completions
calls of local / openai / openrouter / deepseek / azure;
calls of local / openai / openrouter / requesty / deepseek / azure;
codex (/responses) and echo ignore it. --api-version,
--deployment, and --auth-scheme configure the azure provider
(Azure OpenAI): set --base-url to https://<resource>.openai.azure.com;
Expand Down
2 changes: 1 addition & 1 deletion src/embeddings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ async function embedOpenAIBatch(config: RuntimeConfig, texts: string[]): Promise
// the request and leak the chat key to that host (e.g. the Anthropic key to
// api.anthropic.com/embeddings). Those fall back to the canonical OpenAI
// endpoint + OPENAI_API_KEY instead.
const OPENAI_EMBEDDING_COMPATIBLE = new Set(["openai", "openrouter", "deepseek", "local"]);
const OPENAI_EMBEDDING_COMPATIBLE = new Set(["openai", "openrouter", "requesty", "deepseek", "local"]);

function openAIEmbeddingBaseUrl(config: RuntimeConfig): string {
const reuse = OPENAI_EMBEDDING_COMPATIBLE.has(config.provider.name) && config.provider.baseUrl;
Expand Down
2 changes: 1 addition & 1 deletion src/model-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const MODEL_ALIASES: Record<string, Record<string, { id: string; qualifie
// revisions and features first), codex's OAuth bundle beats the metered
// clouds, and openrouter/local are deliberate opt-ins that should never
// win a tie. Unknown providers sort last in catalog order.
const ROUTE_PRIORITY = ["openai", "anthropic", "deepseek", "codex", "azure", "bedrock", "openrouter", "local"] as const;
const ROUTE_PRIORITY = ["openai", "anthropic", "deepseek", "codex", "azure", "bedrock", "openrouter", "requesty", "local"] as const;

function routePriority(provider: ProviderCatalogItem["name"]): number {
const index = (ROUTE_PRIORITY as readonly string[]).indexOf(provider);
Expand Down
7 changes: 7 additions & 0 deletions src/provider-capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ export function resolveProviderContextWindowTokens(provider: ProviderConfig): nu
return Math.min(CODEX_BACKEND_CONTEXT_WINDOW_TOKENS, openaiContextWindowTokens(model));
case "openrouter":
return openrouterContextWindowTokens(model);
case "requesty":
// Requesty uses the same <vendor>/<model> slug scheme as OpenRouter,
// so the per-vendor context-window lookup applies unchanged.
return openrouterContextWindowTokens(model);
case "deepseek":
return deepseekContextWindowTokens(model);
case "anthropic":
Expand Down Expand Up @@ -187,6 +191,9 @@ export function resolveProviderModality(provider: ProviderConfig): ProviderModal
: { vision: false, nativeDocs: false };
case "openrouter":
return openrouterModality(model);
case "requesty":
// Same <vendor>/<model> slug scheme as OpenRouter; reuse its modality map.
return openrouterModality(model);
case "deepseek":
// Confirmed text-only API — no image/file content part.
return { vision: false, nativeDocs: false };
Expand Down
25 changes: 25 additions & 0 deletions src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export function providerHealth(config: RuntimeConfig) {
const PROVIDER_API_KEY_ENV: Record<string, string> = {
openai: "OPENAI_API_KEY",
openrouter: "OPENROUTER_API_KEY",
requesty: "REQUESTY_API_KEY",
deepseek: "DEEPSEEK_API_KEY",
local: "GINI_LOCAL_API_KEY",
anthropic: "ANTHROPIC_API_KEY",
Expand Down Expand Up @@ -376,6 +377,16 @@ export function providerCatalog(): ProviderCatalogItem[] {
capabilities: ["chat-completions", "model-routing"],
costHint: "external"
},
{
id: "requesty",
name: "requesty",
displayName: "Requesty Compatible",
baseUrl: "https://router.requesty.ai/v1",
auth: "env",
models: ["openai/gpt-4o-mini"],
capabilities: ["chat-completions", "model-routing"],
costHint: "external"
},
{
id: "deepseek",
name: "deepseek",
Expand Down Expand Up @@ -429,6 +440,8 @@ export function providerDisplayLabel(name: ProviderName): string {
return "OpenAI";
case "openrouter":
return "OpenRouter";
case "requesty":
return "Requesty";
case "deepseek":
return "DeepSeek";
case "anthropic":
Expand Down Expand Up @@ -3082,6 +3095,7 @@ export async function generateTaskSummary(
}
if (
provider.name === "openrouter" ||
provider.name === "requesty" ||
provider.name === "local" ||
provider.name === "deepseek" ||
// Azure OpenAI exposes deployment-scoped chat/completions, not the flat
Expand Down Expand Up @@ -3225,6 +3239,7 @@ export async function generateStructured<T>(
// many compat providers reject the field. Validator re-checks shape.
if (
provider.name === "openrouter" ||
provider.name === "requesty" ||
provider.name === "local" ||
provider.name === "openai" ||
provider.name === "deepseek" ||
Expand Down Expand Up @@ -3436,6 +3451,15 @@ export function normalizeProvider(provider: ProviderConfig): ProviderConfig {
...(provider.extraBody ? { extraBody: provider.extraBody } : {})
};
}
if (provider.name === "requesty") {
return {
name: "requesty",
model: provider.model || "openai/gpt-4o-mini",
baseUrl: pickBaseUrl(provider.baseUrl, "https://router.requesty.ai/v1"),
apiKeyEnv: provider.apiKeyEnv ?? "REQUESTY_API_KEY",
...(provider.extraBody ? { extraBody: provider.extraBody } : {})
};
}
if (provider.name === "local") {
return {
name: "local",
Expand Down Expand Up @@ -4329,6 +4353,7 @@ function resolveBaseUrl(baseUrl: string | undefined, fallback: string): string {
function defaultBaseUrl(provider: ProviderConfig): string {
if (provider.name === "codex") return DEFAULT_CODEX_BASE_URL;
if (provider.name === "openrouter") return "https://openrouter.ai/api/v1";
if (provider.name === "requesty") return "https://router.requesty.ai/v1";
if (provider.name === "local") return "http://127.0.0.1:11434/v1";
if (provider.name === "deepseek") return DEFAULT_DEEPSEEK_BASE_URL;
if (provider.name === "anthropic") return DEFAULT_ANTHROPIC_BASE_URL;
Expand Down
Loading