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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
229 changes: 117 additions & 112 deletions .env.example

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .env.selfhost.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ REQUIRE_API_KEY=false
# 1024 = dashboard + light chat. Coding agents (long POST /v1/responses
# bodies) need more — see SELF_HOST_GUIDE.md "sizing". 2048 is a safe
# default for a single user who runs Claude Code / Codex through it.
OMNIROUTE_MEMORY_MB=2048
AGENTPROXY_MEMORY_MB=2048

# ── Browser-facing origin (optional) ───────────────────────────────────
# Set ONLY if you expose AgentProxy behind a domain via a reverse proxy.
Expand Down
8 changes: 4 additions & 4 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Bug Report
description: Report a bug or unexpected behavior in OmniRoute
description: Report a bug or unexpected behavior in AgentProxy
title: "fix(): "
labels: ["bug"]
body:
Expand All @@ -13,8 +13,8 @@ body:
- type: input
id: version
attributes:
label: OmniRoute Version
description: "Run `omniroute --version` or check the left sidebar in the dashboard."
label: AgentProxy Version
description: "Run `agentproxy --version` or check the left sidebar in the dashboard."
placeholder: "e.g. 3.7.9"
validations:
required: true
Expand Down Expand Up @@ -80,7 +80,7 @@ body:
id: client-tool
attributes:
label: Client Tool
description: "Which tool are you using OmniRoute with?"
description: "Which tool are you using AgentProxy with?"
placeholder: "e.g. Claude Code, Cursor, Roo Code, OpenClaw, Gemini CLI, cURL"
validations:
required: false
Expand Down
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Feature Request
description: Suggest a new feature or improvement for OmniRoute
description: Suggest a new feature or improvement for AgentProxy
title: "feat(): "
labels: ["enhancement"]
body:
Expand Down Expand Up @@ -52,7 +52,7 @@ body:
id: area
attributes:
label: Area
description: "Which part of OmniRoute does this relate to?"
description: "Which part of AgentProxy does this relate to?"
multiple: true
options:
- Dashboard / UI
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,21 @@ jobs:
echo "- bounded staging: ${stage_elapsed}s"
echo "- npm pack: ${elapsed}s"
} >> "$GITHUB_STEP_SUMMARY"
- name: Verify generated and canonical npm artifact identity (binary-safe)
env:
AGENTPROXY_BUILD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
shell: bash
run: |
set -euo pipefail
python3 -B tests/unit/build/test-agentproxy-artifact-identity.py
python3 -B scripts/check/verify-agentproxy-artifacts.py \
--dir .build/next/standalone \
--dir dist \
--dir "$RUNNER_TEMP/canonical-package-stage" \
--stamp .build/next/standalone/BUILD_SHA \
--stamp dist/BUILD_SHA \
--tar "$RUNNER_TEMP/canonical-package/${{ steps.pack.outputs.tarball }}" \
--build-sha "${AGENTPROXY_BUILD_SHA:0:7}"
- name: Upload canonical npm package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ jobs:
docker run -d --name agentproxy-smoke \
-p 127.0.0.1:20128:20128 \
-p 127.0.0.1:20129:20129 \
-e OMNIROUTE_AUTH_DISABLED=1 \
-e AGENTPROXY_AUTH_DISABLED=1 \
"$image_ref"
for attempt in $(seq 1 120); do
if curl --fail --silent --max-time 2 http://127.0.0.1:20128/readyz >/dev/null \
Expand Down
10 changes: 5 additions & 5 deletions .i18n-state.json
Original file line number Diff line number Diff line change
Expand Up @@ -2022,19 +2022,19 @@
"source_hash": "be11ba39e9795acb6fb4a28772712752a3f736cb0325950d0d624a63637dff0d",
"locales": {}
},
"docs/OMNIROUTE_ALLOCATION_HANDOFF.md": {
"docs/AGENTPROXY_ALLOCATION_HANDOFF.md": {
"source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7",
"locales": {}
},
Comment on lines +2025 to 2028

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug · medium
These state entries reference docs/AGENTPROXY_ALLOCATION_HANDOFF.md and the other renamed docs, but those files are absent from the repository. Any i18n state consumer resolving source paths will treat them as missing/stale, and the allocation handoff entry is already orphaned. Rename/add the corresponding documents in the same change, or keep the old keys until the files exist.

Suggestion:

Suggested change
"docs/AGENTPROXY_ALLOCATION_HANDOFF.md": {
"source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7",
"locales": {}
},
"docs/AGENTPROXY_ALLOCATION_HANDOFF.md": {
"source_hash": "8cd564ba6ac1124ffeb9d814aca1f48474abfb4769831d34f6669dc6297f2ee7",
"locales": {}
}

"docs/OMNIROUTE_PROVIDER_FAILOVER.md": {
"docs/AGENTPROXY_PROVIDER_FAILOVER.md": {
"source_hash": "6fa6e3b466b9a682ef9e9591d03cf4e6c6affc2c02da5d7b7569a142d55a0b43",
"locales": {}
},
"docs/OMNIROUTE_QUOTA_TELEMETRY.md": {
"docs/AGENTPROXY_QUOTA_TELEMETRY.md": {
"source_hash": "6a4983441e4d0f21d61de35841d265d96668153938f633650fbf6bee96d473a4",
"locales": {}
},
"docs/OMNIROUTE_ROUTING_POLICY.md": {
"docs/AGENTPROXY_ROUTING_POLICY.md": {
"source_hash": "0415e6018749665e40b55a5374340126a793e616c1df2b35a3e9c13464014feb",
"locales": {}
},
Expand Down Expand Up @@ -2806,7 +2806,7 @@
}
}
},
"docs/comparison/OMNIROUTE_VS_ALTERNATIVES.md": {
"docs/comparison/AGENTPROXY_VS_ALTERNATIVES.md": {
"source_hash": "5f87f53ffb0c2d95f4d5e5b0a004d4d564ba0aa480a56e018bf7ff05d6ea0cdb",
"locales": {
"pl": {
Expand Down
10 changes: 5 additions & 5 deletions @agentproxy/opencode-plugin-v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ order:
recommended route.
2. **`apiKey` in the plugin options**, when you want a per-project override.
Remember that this puts the key in a config file you may be committing.
3. **`AGENTPROXY_API_KEY` in the environment.** `OMNIROUTE_API_KEY` remains accepted only as a legacy fallback when the canonical variable is unset.
3. **`AGENTPROXY_API_KEY` in the environment.**

If none of the three yields a key, the catalog is empty and the plugin says so
once at startup rather than leaving you with a silent empty model list.
Expand All @@ -84,9 +84,9 @@ explicitly:
}
```

The token can also come from the `OMNIROUTE_MANAGEMENT_API_KEY` environment
The token can also come from the `AGENTPROXY_MANAGEMENT_API_KEY` environment
variable (the option wins when both are set). Resolution order:
`managementReadToken` option, then `OMNIROUTE_MANAGEMENT_API_KEY`, then the
`managementReadToken` option, then `AGENTPROXY_MANAGEMENT_API_KEY`, then the
`apiKey` fallback.

Left unset, `managementReadToken` falls back to `apiKey` for backwards
Expand All @@ -103,8 +103,8 @@ catalog is never a mystery.
| -------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `providerId` | `"agentproxy"` | Provider id and integration id; models publish under `<providerId>/…` |
| `baseURL` | required | AgentProxy gateway root (no `/v1` suffix needed) |
| `apiKey` | connected credential, then `AGENTPROXY_API_KEY` (`OMNIROUTE_API_KEY` legacy fallback) | Chat key for `/v1/*` — see [Credentials](#credentials) |
| `managementReadToken` | option, then `OMNIROUTE_MANAGEMENT_API_KEY`, then `apiKey` | Management key for `/api/*` (combos, providers, enrichment) — usually **not** the same key |
| `apiKey` | connected credential, then `AGENTPROXY_API_KEY` | Chat key for `/v1/*` — see [Credentials](#credentials) |
| `managementReadToken` | option, then `AGENTPROXY_MANAGEMENT_API_KEY`, then `apiKey` | Management key for `/api/*` (combos, providers, enrichment) — usually **not** the same key |
| `displayName` | `"AgentProxy"` | Provider display name |
| `timeoutMs` | `10000` | Per-endpoint fetch timeout (auto-combos use 5s) |
| `modelCacheTtlMs` | `300000` | Catalog cache TTL; disk snapshot warms cold starts |
Expand Down
7 changes: 1 addition & 6 deletions @agentproxy/opencode-plugin-v2/src/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export interface ResolvedApiKey {
}

export const API_KEY_ENV_VAR = "AGENTPROXY_API_KEY";
export const LEGACY_API_KEY_ENV_VAR = "OMNIROUTE_API_KEY";

/**
* `ctx.integration.connection` carries the stored credential. Probing the
Expand Down Expand Up @@ -106,10 +105,6 @@ export async function resolveApiKey(
if (optionKey !== undefined && optionKey.length > 0) return { key: optionKey, origin: "option" };
const fromEnv = process.env[API_KEY_ENV_VAR];
if (fromEnv !== undefined && fromEnv.length > 0) return { key: fromEnv, origin: "env" };
const fromLegacyEnv = process.env[LEGACY_API_KEY_ENV_VAR];
if (fromLegacyEnv !== undefined && fromLegacyEnv.length > 0) {
return { key: fromLegacyEnv, origin: "env" };
}
return { key: "", origin: "missing" };
}

Expand All @@ -122,6 +117,6 @@ export function warnIfMissing(resolved: ResolvedApiKey, integrationID: string, l
log.warn(
`[agentproxy-v2] no API key for "${integrationID}": the catalog will be empty. ` +
`Connect the integration from opencode, set "apiKey" in the plugin options, ` +
`or export ${API_KEY_ENV_VAR} (legacy ${LEGACY_API_KEY_ENV_VAR} remains accepted).`
`or export ${API_KEY_ENV_VAR}.`
);
}
2 changes: 1 addition & 1 deletion @agentproxy/opencode-plugin-v2/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const pluginOptionsSchema = z
export type PluginOptions = z.infer<typeof pluginOptionsSchema>;

/** Environment source for the management token (option wins over this). */
export const MANAGEMENT_TOKEN_ENV_VAR = "OMNIROUTE_MANAGEMENT_API_KEY";
export const MANAGEMENT_TOKEN_ENV_VAR = "AGENTPROXY_MANAGEMENT_API_KEY";

/**
* Resolve the management token: a non-empty option wins, then a non-empty
Expand Down
38 changes: 12 additions & 26 deletions @agentproxy/opencode-plugin-v2/tests/credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,34 +71,20 @@ describe("gateway credential resolution", () => {
assert.deepEqual(env, { key: "from-env", origin: "env" });
});

it("prefers AGENTPROXY_API_KEY over the legacy OMNIROUTE_API_KEY fallback", async () => {
const previousLegacy = process.env.OMNIROUTE_API_KEY;
process.env.OMNIROUTE_API_KEY = "legacy-env";
try {
const { log } = collectingLogger();
const got = await withEnv("agentproxy-env", () =>
resolveApiKey(ctxWith(undefined), "agentproxy", undefined, log)
);
assert.deepEqual(got, { key: "agentproxy-env", origin: "env" });
} finally {
if (previousLegacy === undefined) delete process.env.OMNIROUTE_API_KEY;
else process.env.OMNIROUTE_API_KEY = previousLegacy;
}
it("uses the canonical environment key when the configured option is empty", async () => {
const { log } = collectingLogger();
const got = await withEnv("from-env", () =>
resolveApiKey(ctxWith(undefined), "agentproxy", "", log)
);
assert.deepEqual(got, { key: "from-env", origin: "env" });
});

it("keeps OMNIROUTE_API_KEY as a legacy fallback when the canonical env is absent", async () => {
const previousLegacy = process.env.OMNIROUTE_API_KEY;
process.env.OMNIROUTE_API_KEY = "legacy-env";
try {
const { log } = collectingLogger();
const got = await withEnv(undefined, () =>
resolveApiKey(ctxWith(undefined), "agentproxy", undefined, log)
);
assert.deepEqual(got, { key: "legacy-env", origin: "env" });
} finally {
if (previousLegacy === undefined) delete process.env.OMNIROUTE_API_KEY;
else process.env.OMNIROUTE_API_KEY = previousLegacy;
}
it("rejects an empty canonical environment key as a missing credential", async () => {
const { log } = collectingLogger();
const got = await withEnv("", () =>
resolveApiKey(ctxWith(undefined), "agentproxy", undefined, log)
);
assert.deepEqual(got, { key: "", origin: "missing" });
});

it("reports no key rather than pretending an empty one works", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "../src/cache.js";

function isolateDisk(): { dir: string; restore: () => void } {
const dir = mkdtempSync(join(tmpdir(), "omniroute-disk-atomic-"));
const dir = mkdtempSync(join(tmpdir(), "agentproxy-disk-atomic-"));
const prev = process.env.OPENCODE_DATA_DIR;
process.env.OPENCODE_DATA_DIR = dir;
return {
Expand Down
6 changes: 3 additions & 3 deletions @agentproxy/opencode-plugin-v2/tests/enrichment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function fakeDraft(): { models: Map<string, Record<string, any>>; draft: BetaDra
}

const baseOpts = {
providerId: "omniroute",
providerId: "agentproxy",
baseURL: "https://gw.example.com",
apiKey: "k",
timeoutMs: 1000,
Expand All @@ -70,7 +70,7 @@ describe("catalog enrichment source", () => {
enrichmentFetcher: async () => enrichment,
});
assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 });
const m = models.get("omniroute/cc/m1");
const m = models.get("agentproxy/cc/m1");
assert.ok(m);
assert.equal(m?.name, "Model One");
assert.equal(m?.cost[0].input, 3);
Expand All @@ -93,7 +93,7 @@ describe("catalog enrichment source", () => {
},
});
assert.deepEqual(res, { models: 1, combos: 0, autoCombos: 0 });
assert.ok(models.get("omniroute/cc/m1"));
assert.ok(models.get("agentproxy/cc/m1"));
} finally {
console.warn = origWarn;
}
Expand Down
4 changes: 2 additions & 2 deletions @agentproxy/opencode-plugin-v2/tests/host-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import assert from "node:assert/strict";
import { buildProviderPayload, collectCatalog } from "../src/catalog.js";

const baseOpts = {
providerId: "omniroute",
providerId: "agentproxy",
baseURL: "https://gw.example.com",
apiKey: "k",
timeoutMs: 1000,
Expand Down Expand Up @@ -31,7 +31,7 @@ async function publish() {
describe("stable payload", () => {
it("publishes one provider info with endpoint plus key", async () => {
const { info } = await publish();
assert.equal(info.id, "omniroute");
assert.equal(info.id, "agentproxy");
assert.equal(info.package, "@opencode/ai/providers/openai-compatible");
assert.equal((info.settings as Record<string, unknown>).baseURL, "https://gw.example.com/v1");
assert.equal((info.settings as Record<string, unknown>).apiKey, "k");
Expand Down
14 changes: 7 additions & 7 deletions @agentproxy/opencode-plugin-v2/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe("plugin-v2 entrypoint", () => {
};
try {
await (plugin as unknown as { setup: (ctx: FakeCtx) => Promise<void> }).setup(
fakeCtx({ baseURL: "https://gw.example.com", providerId: "omniroute" }, seen)
fakeCtx({ baseURL: "https://gw.example.com", providerId: "agentproxy" }, seen)
);
} finally {
console.warn = origWarn;
Expand All @@ -78,15 +78,15 @@ describe("plugin-v2 entrypoint", () => {
try {
await (plugin as unknown as { setup: (ctx: FakeCtx) => Promise<void> }).setup(
fakeCtx(
{ baseURL: "https://gw.example.com", providerId: "omniroute", startupDebug: true },
{ baseURL: "https://gw.example.com", providerId: "agentproxy", startupDebug: true },
seen2
)
);
} finally {
console.warn = origWarn;
}
assert.ok(
warns2.some((line) => line.includes("init providerId=omniroute")),
warns2.some((line) => line.includes("init providerId=agentproxy")),
`boot line must show with startupDebug, got: ${JSON.stringify(warns2)}`
);
});
Expand Down Expand Up @@ -140,7 +140,7 @@ describe("plugin-v2 entrypoint", () => {
const integrationCallbacks: Array<(draft: unknown) => unknown> = [];
const providerCallbacks: Array<(editor: unknown) => unknown> = [];
const ctx: FakeCtx = {
options: { baseURL: "https://gw.example.com", providerId: "omniroute" },
options: { baseURL: "https://gw.example.com", providerId: "agentproxy" },
provider: {
transform: (cb: (editor: unknown) => unknown) => {
seen.push({ kind: "provider" });
Expand Down Expand Up @@ -197,11 +197,11 @@ describe("plugin-v2 entrypoint", () => {
},
};
(integrationCallbacks[0] as (draft: FakeIntegrationDraft) => unknown)(draft);
assert.deepEqual(draft.updates, [{ id: "omniroute", name: "AgentProxy" }]);
assert.deepEqual(draft.updates, [{ id: "agentproxy", name: "AgentProxy" }]);
const keyMethod = draft.methods.find((m) => m.method.type === "key");
const envMethod = draft.methods.find((m) => m.method.type === "env");
assert.ok(keyMethod);
assert.equal(keyMethod?.integrationID, "omniroute");
assert.equal(keyMethod?.integrationID, "agentproxy");
assert.ok(envMethod);
assert.deepEqual(envMethod?.method.names, ["AGENTPROXY_API_KEY"]);
assert.ok(!draft.methods.some((m) => m.method.type === "oauth"));
Expand All @@ -212,7 +212,7 @@ describe("plugin-v2 entrypoint", () => {
const { tmpdir } = await import("node:os");
const { join } = await import("node:path");
const { setTimeout: sleep } = await import("node:timers/promises");
const dir = mkdtempSync(join(tmpdir(), "omniroute-lazy-"));
const dir = mkdtempSync(join(tmpdir(), "agentproxy-lazy-"));
const prevDataDir = process.env.OPENCODE_DATA_DIR;
process.env.OPENCODE_DATA_DIR = dir;
let modelsCall = 0;
Expand Down
Loading
Loading