Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const cloudflarePlaygroundProvider: RegistryEntry = {
baseUrl: "https://playground.ai.cloudflare.com",
authType: "none",
authHeader: "none",
toolCalling: "emulated",
models: [
// Frontier/open-weight flagships first.
{ id: "zai-org/glm-5.2", name: "GLM 5.2 (Z.ai)", supportsReasoning: true },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const duckduckgo_webProvider: RegistryEntry = {
baseUrl: "https://duck.ai/duckchat/v1/chat",
authType: "none",
authHeader: "none",
toolCalling: "emulated",
poolConfig: {
minSessions: 2,
maxSessions: 5,
Expand Down
1 change: 1 addition & 0 deletions open-sse/config/providers/registry/opencode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const opencodeProvider: RegistryEntry = {
authHeader: "Authorization",
authPrefix: "Bearer",
passthroughModels: true,
toolCalling: "emulated",
// #15540: the keyless Free tier serves every model below even when the account row
// (apiKey "public") only syncs a subset, so a partial discovery must not reject them.
liveCatalogAuthoritative: false,
Expand Down
1 change: 1 addition & 0 deletions open-sse/config/providers/registry/uncloseai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const uncloseaiProvider: RegistryEntry = {
modelsUrl: "https://hermes.ai.unturf.com/v1/models",
authType: "optional",
authHeader: "bearer",
toolCalling: "emulated",
models: [
// 2026-09-15 refresh — upstream rotated its roster: /v1/models now serves
// exactly one model, and every previously catalogued id (adamo1139/
Expand Down
7 changes: 7 additions & 0 deletions open-sse/config/providers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,13 @@ export interface RegistryEntry {
* OmniRoute accumulates the stream and converts it to a JSON body for the client. (#2081)
*/
forceStream?: boolean;
/**
* Opt-in for providers that use prompt-emulated tool calling (e.g. web-session
* providers like opencode, duckduckgo-web, cloudflare-playground). When set to
* "emulated", the combo tool-calling filter will keep these providers eligible
* even though their registry/capability rows report toolCalling:false.
*/
toolCalling?: "emulated" | boolean;
/**
* Literal API key sent as the bearer token when the request has no real
* credential (synthetic noauth fallback). Lets a primarily-authenticated
Expand Down
57 changes: 57 additions & 0 deletions open-sse/services/autoCombo/emulatedNoAuthAllowlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { NOAUTH_PROVIDERS } from "@/shared/constants/providers";

type NoAuthCatalogEntry = (typeof NOAUTH_PROVIDERS)[keyof typeof NOAUTH_PROVIDERS];

/**
* Auto-discover the no-auth (keyless) providers permitted to enter the
* `auto`/`auto-*` candidate pool: every NOAUTH_PROVIDERS entry that opted into
* prompt-emulated tool calling (`toolCalling: "emulated"` and `noAuth: true`).
*
* Those backends answer without any configuration, and their tool calls are
* executed by the 3rd-party CLI (Claude Code, Codex, Cursor…) — the free
* provider only emits the call markers, which the CLI parses and runs.
*
* Discovered from the catalog, so there is no hardcoded id list to maintain:
* adding `toolCalling: "emulated"` to a NOAUTH_PROVIDERS entry admits it, and
* removing the flag takes it back out. Providers without the flag stay fully
* usable via direct `<alias>/<model>` calls — they are just not auto-routed to.
*
* On this catalog the set is `cloudflare-playground`, `devin-cli-agentic`,
* `duckduckgo-web`, `opencode`, and `uncloseai`. `duckduckgo-web` and
* `devin-cli-agentic` already carried `toolCalling: "emulated"`; `opencode`,
* `cloudflare-playground`, and `uncloseai` opt in the same way. Providers
* without the flag stay out (`aihorde`, `auggie`, `codex-app-server`,
* `veoaifree-web`, `zcode`, and any other).
*
* History (operator decision 2026-07-24, refs #8183/#6453/#7032): the pool used
* to be a hardcoded `{opencode}` allowlist aimed at public-HTTP-egress
* reliability for category/tier and flat-variant `auto/*` pools, not
* `auto/<family>` pools (`bypassAllowlist` in virtualFactory.ts). The owner
* accepted expanding that pool to every no-auth provider with emulated tool
* calling. `duckduckgo-web` is in the pool because it has the flag; `aihorde`
* has no flag and stays out.
*/
function isEmulatedNoAuthProvider(
providerDef: NoAuthCatalogEntry
): providerDef is NoAuthCatalogEntry & { id: string; toolCalling: "emulated" } {
return (
providerDef.noAuth === true &&
"toolCalling" in providerDef &&
providerDef.toolCalling === "emulated" &&
typeof providerDef.id === "string" &&
providerDef.id.length > 0
);
}

/** Ids admitted to category/tier and flat-variant `auto/*` pools. */
export function getEmulatedToolCallingNoAuthProviders(): Set<string> {
const emulated = new Set<string>();
for (const providerDef of Object.values(NOAUTH_PROVIDERS)) {
if (isEmulatedNoAuthProvider(providerDef)) emulated.add(providerDef.id);
}
return emulated;
}

/** Cached at module load; a catalog flag change is visible on the next process start. */
export const EMULATED_TOOL_CALLING_NOAUTH_PROVIDERS: ReadonlySet<string> =
getEmulatedToolCallingNoAuthProviders();
22 changes: 2 additions & 20 deletions open-sse/services/autoCombo/virtualFactory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EMULATED_TOOL_CALLING_NOAUTH_PROVIDERS } from "./emulatedNoAuthAllowlist";
import { AutoComboConfig } from "./engine";
import { MODE_PACKS } from "./modePacks";
import { DEFAULT_WEIGHTS, reliabilityFactor, ScoringWeights } from "./scoring";
Expand Down Expand Up @@ -346,31 +347,12 @@ function hasUsableConnectionCredential(conn: VirtualFactoryConn): boolean {

const SYNTHETIC_NOAUTH_CONNECTION_ID = RESILIENCE_NOAUTH_CONNECTION_ID;

// Allowlist of no-auth (keyless) providers permitted to enter the `auto`/`auto-*`
// candidate pool. Narrowed to the backends verified to answer without any
// configuration on our reference egress (VPS .15): `opencode` returns 200
// there, while duckduckgo-web (429/VQD rate limit),
// aihorde (401, anon key rejected)
// and the others are unreliable. The excluded providers stay fully usable via
// direct `<alias>/<model>` calls — they are just kept OUT of auto-routing until
// re-verified. Re-add an id here to bring it back into every auto/* pool.
//
// Scope (operator decision 2026-07-24, refs #8183/#6453/#7032): this allowlist
// targets public-HTTP-egress reliability for the category/tier and flat-variant
// `auto/*` pools (auto/best-free, auto/coding:fast, ...). It does NOT apply to
// `auto/<family>` pools (auto/glm, auto/zai, ...) — a family combo is an
// identity selector ("whatever genuinely serves GLM"), not a reliability-curated
// pool, so it admits any no-auth backend that genuinely serves the family (e.g.
// auggie, a local CLI subprocess with zero HTTP egress, belongs in auto/glm
// regardless of this list). See the `bypassAllowlist` param below.
const AUTO_COMBO_NOAUTH_ALLOWLIST = new Set<string>(["opencode"]);

function isChatAutoComboNoAuthProvider(
providerDef: NoAuthProviderDefinition,
bypassAllowlist: boolean
): boolean {
if (providerDef.noAuth !== true) return false;
if (!bypassAllowlist && !AUTO_COMBO_NOAUTH_ALLOWLIST.has(providerDef.id)) return false;
if (!bypassAllowlist && !EMULATED_TOOL_CALLING_NOAUTH_PROVIDERS.has(providerDef.id)) return false;
if (!Array.isArray(providerDef.serviceKinds) || providerDef.serviceKinds.length === 0)
return true;
return providerDef.serviceKinds.includes("llm");
Expand Down
3 changes: 3 additions & 0 deletions src/shared/constants/providers/noauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const NOAUTH_PROVIDERS = {
noAuth: true,
hasFree: true,
serviceKinds: ["llm"],
toolCalling: "emulated",
authHint:
"No API key required — OpenCode's free tier can only be used from within OpenCode (client-contract requests).",
freeNote:
Expand Down Expand Up @@ -67,6 +68,7 @@ export const NOAUTH_PROVIDERS = {
noAuth: true,
hasFree: true,
serviceKinds: ["llm"],
toolCalling: "emulated",
freeNote:
"Free — Cloudflare's AI Playground: GLM 5.2, Kimi K2.7 Code, DeepSeek V4 Pro, gpt-oss-120B and 16 more. No account, no API key.",
authHint:
Expand Down Expand Up @@ -163,6 +165,7 @@ export const NOAUTH_PROVIDERS = {
hasFree: true,
passthroughModels: true,
serviceKinds: ["llm"],
toolCalling: "emulated",
authHint:
"No auth required. API accepts any non-empty string as key for identification. If older built-in models return 404, use Available Models → Import from /models or Auto-Sync; verified live model: Lorbus/Qwen3.6-27B-int4-AutoRound.",
freeNote: "Free forever — no signup, no credit card. OpenAI-compatible endpoints.",
Expand Down
68 changes: 49 additions & 19 deletions tests/unit/noauth-autocombo-allowlist.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
/**
* Auto-combo no-auth allowlist — the `auto`/`auto-*` candidate pool must only
* pull in no-auth (keyless) providers verified to work without any credential on
* our reference egress. As of this change that allowlist is narrowed to
* `opencode`: on the reference VPS (.15) it answers 200 with zero configuration.
* The other no-auth providers
* (duckduckgo-web, aihorde) stay OUT of every auto/* pool
* until re-verified — they remain usable via direct `<alias>/<model>` calls, they
* are just not auto-routed to.
* Auto-combo no-auth pool — the `auto`/`auto-*` candidate pool may only pull in
* no-auth (keyless) providers that opted into prompt-emulated tool calling.
*
* Regression guard: AUTO_COMBO_NOAUTH_ALLOWLIST in
* open-sse/services/autoCombo/virtualFactory.ts drives what belongs here.
* The pool is no longer a hand-maintained id list: it is derived at startup from
* NOAUTH_PROVIDERS, taking every entry with `noAuth: true` and
* `toolCalling: "emulated"`. Those providers answer without any credential, and
* their tool calls are executed by the 3rd-party CLI (Claude Code, Codex, Cursor…)
* rather than by the upstream.
*
* Every other keyless provider stays OUT of every auto/* pool — it remains fully
* usable via direct `<alias>/<model>` calls, it is just not auto-routed to.
*
* Owner-accepted set (no feature flag): `cloudflare-playground`,
* `devin-cli-agentic`, `duckduckgo-web`, `opencode`, and `uncloseai`.
* `duckduckgo-web` and `devin-cli-agentic` already carried
* `toolCalling: "emulated"`; the other three opt in the same way.
* `aihorde`, `auggie`, `codex-app-server`, `veoaifree-web`, and `zcode` have no
* flag and stay out. `duckduckgo-web` is in the pool, not excluded.
*
* Historical note: until 2026-07-24 (refs #8183/#6453/#7032) the pool was a
* hardcoded `{opencode}` allowlist. That narrowing is retired.
*
* Regression guard: EMULATED_TOOL_CALLING_NOAUTH_PROVIDERS in
* open-sse/services/autoCombo/emulatedNoAuthAllowlist.ts drives what belongs
* here. Adding `toolCalling: "emulated"` to another NOAUTH_PROVIDERS entry WILL
* change the pool and must update ALLOWED_NOAUTH_PROVIDERS below.
*/
import test from "node:test";
import assert from "node:assert/strict";
Expand Down Expand Up @@ -53,17 +68,31 @@ test.after(async () => {
}
});

const ALLOWED_NOAUTH_PROVIDERS = ["opencode"];
const EXCLUDED_NOAUTH_PROVIDERS = ["duckduckgo-web", "aihorde"];
// Exactly the NOAUTH_PROVIDERS entries with `noAuth: true` + `toolCalling: "emulated"`.
const ALLOWED_NOAUTH_PROVIDERS = [
"cloudflare-playground",
"devin-cli-agentic",
"duckduckgo-web",
"opencode",
"uncloseai",
];
// Keyless providers that never opted in — must stay out of every auto/* pool.
const EXCLUDED_NOAUTH_PROVIDERS = [
"aihorde",
"auggie",
"codex-app-server",
"veoaifree-web",
"zcode",
];

test("fresh install: the allowlisted no-auth providers are present in the auto-combo pool", async () => {
test("fresh install: the emulated-tool-calling no-auth providers are present in the auto-combo pool", async () => {
const combo = await virtualFactory.createVirtualAutoCombo(undefined);

for (const providerId of ALLOWED_NOAUTH_PROVIDERS) {
const models = combo.models.filter((m: { providerId: string }) => m.providerId === providerId);
assert.ok(
models.length >= 1,
`allowlisted no-auth provider "${providerId}" must be present in the fresh-install ` +
`no-auth provider "${providerId}" (toolCalling: "emulated") must be present in the fresh-install ` +
`auto-combo pool. Pool providers: ${JSON.stringify([...new Set(combo.models.map((m: { providerId: string }) => m.providerId))])}`
);
assert.ok(
Expand All @@ -77,16 +106,17 @@ test("fresh install: the allowlisted no-auth providers are present in the auto-c
}
});

test("fresh install: non-allowlisted no-auth providers are EXCLUDED from the auto-combo pool", async () => {
test("fresh install: no-auth providers without the emulated opt-in are EXCLUDED from the auto-combo pool", async () => {
const combo = await virtualFactory.createVirtualAutoCombo(undefined);

for (const providerId of EXCLUDED_NOAUTH_PROVIDERS) {
const present = combo.models.some((m: { providerId: string }) => m.providerId === providerId);
assert.equal(
present,
false,
`no-auth provider "${providerId}" must NOT be auto-routed to (not in allowlist), ` +
`but it appeared in the pool. Pool: ${JSON.stringify(combo.models.map((m: { model: string }) => m.model))}`
`no-auth provider "${providerId}" must NOT be auto-routed to (it carries no ` +
`toolCalling: "emulated" opt-in), but it appeared in the pool. ` +
`Pool: ${JSON.stringify(combo.models.map((m: { model: string }) => m.model))}`
);
assert.ok(
!combo.autoConfig.candidatePool.includes(providerId),
Expand All @@ -95,7 +125,7 @@ test("fresh install: non-allowlisted no-auth providers are EXCLUDED from the aut
}
});

test("fresh install: EVERY synthetic-noauth candidate belongs to an allowlisted provider (no other keyless provider leaks in)", async () => {
test("fresh install: ONLY the emulated-tool-calling providers are synthetic-noauth candidates (no other keyless provider leaks in)", async () => {
const combo = await virtualFactory.createVirtualAutoCombo(undefined);

const noauthProviders = [
Expand All @@ -108,7 +138,7 @@ test("fresh install: EVERY synthetic-noauth candidate belongs to an allowlisted
assert.deepEqual(
noauthProviders,
[...ALLOWED_NOAUTH_PROVIDERS].sort(),
`only the allowlisted providers may be no-auth (connectionId="noauth") candidates on a ` +
`only the emulated-tool-calling providers may be no-auth (connectionId="noauth") candidates on a ` +
`fresh install, but found: ${JSON.stringify(noauthProviders)}`
);
});
42 changes: 31 additions & 11 deletions tests/unit/virtual-auto-combo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ const virtualFactory = await import("../../open-sse/services/autoCombo/virtualFa

type VirtualComboResult = Awaited<ReturnType<typeof virtualFactory.createVirtualAutoCombo>>;

// No-auth pool membership, mirrored from tests/unit/noauth-autocombo-allowlist.test.ts
// (the dedicated guard). Kept explicit here on purpose: adding `toolCalling: "emulated"`
// to a NOAUTH_PROVIDERS entry must show up as a deliberate, reviewed diff in both files.
const ALLOWED_NOAUTH_PROVIDERS = [
"cloudflare-playground",
"devin-cli-agentic",
"duckduckgo-web",
"opencode",
"uncloseai",
];
const EXCLUDED_NOAUTH_PROVIDERS = [
"aihorde",
"auggie",
"codex-app-server",
"veoaifree-web",
"zcode",
];

async function resetStorage() {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
Expand Down Expand Up @@ -277,22 +295,24 @@ test("createVirtualAutoCombo includes no-auth OpenCode Free without provider_con
assert.ok(combo.autoConfig.candidatePool.includes("opencode"));
});

test("createVirtualAutoCombo restricts the no-auth pool to the allowlist", async () => {
// Policy: the no-auth (keyless) auto-combo allowlist is narrowed to `opencode`
// (open-sse/services/autoCombo/virtualFactory.ts::AUTO_COMBO_NOAUTH_ALLOWLIST) —
// the keyless backend verified to work without configuration on our reference
// egress. The others stay usable via direct `<alias>/<model>` calls but must
// NOT be auto-routed to. Dedicated guard:
// tests/unit/noauth-autocombo-allowlist.test.ts.
test("createVirtualAutoCombo restricts the no-auth pool to the emulated-tool-calling providers", async () => {
// Policy: the no-auth (keyless) auto-combo pool is built from the NOAUTH_PROVIDERS
// entries that opted into prompt-emulated tool calling (`toolCalling: "emulated"`),
// discovered at startup by
// open-sse/services/autoCombo/emulatedNoAuthAllowlist.ts::getEmulatedToolCallingNoAuthProviders().
// The admitted set is cloudflare-playground, devin-cli-agentic, duckduckgo-web,
// opencode, and uncloseai — duckduckgo-web is included, not excluded. The others
// stay usable via direct `<alias>/<model>` calls but must NOT be auto-routed to.
// Dedicated guard: tests/unit/noauth-autocombo-allowlist.test.ts.
// contract changed by #15979 (on top of #15839): `excludeTosAvoid` is ON by default and
// uncataloged models now inherit the provider's curated `tos: "avoid"` verdict, so every
// opencode model is ToS-filtered out by default. Opt out explicitly so this test keeps
// asserting the no-auth allowlist, not the ToS filter (covered by
// asserting the no-auth pool membership, not the ToS filter (covered by
// tests/unit/issue-15059-tos-avoid-auto-default.test.ts and tos-provider-alias-15059).
await settingsDb.updateSettings({ excludeTosAvoid: false });
const combo: VirtualComboResult = await virtualFactory.createVirtualAutoCombo("fast");

for (const allowed of ["opencode"]) {
for (const allowed of ALLOWED_NOAUTH_PROVIDERS) {
const models = combo.models.filter((m) => m.providerId === allowed);
assert.ok(models.length >= 1, `${allowed} should have at least one model`);
assert.ok(
Expand All @@ -301,11 +321,11 @@ test("createVirtualAutoCombo restricts the no-auth pool to the allowlist", async
);
}

for (const excluded of ["duckduckgo-web", "aihorde"]) {
for (const excluded of EXCLUDED_NOAUTH_PROVIDERS) {
assert.equal(
combo.models.some((model) => model.providerId === excluded),
false,
`no-auth provider "${excluded}" must be excluded from the auto-combo pool (not in allowlist)`
`no-auth provider "${excluded}" must be excluded from the auto-combo pool (no toolCalling: "emulated" opt-in)`
);
}

Expand Down
Loading