From 380c1a610af28ddb75cd53100dc8dbcfdf3ec5bd Mon Sep 17 00:00:00 2001 From: Sawyer Date: Mon, 7 Sep 2026 22:34:13 -0700 Subject: [PATCH 1/8] Add codex and xai-oauth to the credential catalog contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both providers authenticate only through a loopback OAuth login (no API-key path), speak the OpenAI Responses protocol, and therefore ride the new openai-responses adapter plugin id end to end: credential-test provider entries, connector descriptors under a new oauth-loopback auth kind, catalog seeds, and the effective-list display names. Codex's config is deliberately probeless — it publishes no live model catalog and has no free auth-check endpoint, so the token exchange that mints the credential is the only proof. seedCatalog now rejects placeholderCredential for these OAuth-only ids with a named PlaceholderCredentialError: a fake secret could never authenticate and would only make the offering look launchable. Codex's offerings carry the host-identity quirks bag its adapter refuses to default. --- packages/connections/src/credential-test.ts | 91 ++++++++- packages/connections/src/descriptor.ts | 13 +- .../connections/test/credential-test.test.ts | 58 ++++++ .../src/effective-list.test.ts | 3 + .../onboarding/src/plant-env-credentials.ts | 5 + packages/seeding/src/catalog-seed-data.ts | 40 ++++ packages/seeding/src/seed.ts | 44 ++++- .../seeding/test/catalog-seed-data.test.ts | 36 ++++ packages/seeding/test/seed.test.ts | 62 ++++++ templates/connectors.test.ts | 92 +++++++++ templates/connectors.ts | 178 ++++++++++++++++++ 11 files changed, 607 insertions(+), 15 deletions(-) diff --git a/packages/connections/src/credential-test.ts b/packages/connections/src/credential-test.ts index 25dfb298ae..501ec7a2fe 100644 --- a/packages/connections/src/credential-test.ts +++ b/packages/connections/src/credential-test.ts @@ -22,6 +22,8 @@ export type SupportedCredentialProvider = | "openai" | "google-genai" | "xai" + | "xai-oauth" + | "codex" | "openrouter" | "opencode-zen" | "groq" @@ -39,6 +41,8 @@ export type SupportedCredentialProvider = * at deploy time they all ride the same OpenAI-compatible wire shape, so * their catalog `plugin` value must be `"openai-compatible"` — never their * own provider id — and `ModelProviderPlugin` gets no wider for them. + * Codex and xai-oauth are the opposite: both speak OpenAI's Responses + * protocol, so they ride `"openai-responses"`. * * This is NOT the same string as the registry key * `byProvider.get(source.provider)` resolves against (CL-6586). For every @@ -55,7 +59,11 @@ export type SupportedCredentialProvider = * field — for an offering whose catalog provider is named `"ollama"`. */ export type AdapterPluginId = - "anthropic" | "openai" | "openai-compatible" | "google-genai"; + | "anthropic" + | "openai" + | "openai-compatible" + | "google-genai" + | "openai-responses"; export type CredentialTestResult = { readonly ok: true } | { readonly ok: false; readonly message: string }; @@ -101,7 +109,29 @@ type ProbeRequest = { readonly body?: string; }; -export type ProviderTestConfig = { +export type ProviderTestConfig = CommonProviderTestConfig & + ( + | { + /** Set for a provider whose credential can never be proven by a + * free network probe — this config exists for the catalog and + * descriptor surfaces only, and `testProviderCredential` returns + * this message as a failed result without dialing anything. */ + readonly probeless: string; + } + | { + /** Builds the free probe request that proves the key without spending a + * token: same auth layer a completion would hit, no generation cost. + * `baseURL` is the resolved origin for this probe — `config.baseURL` + * for every provider except `ollama`, which threads a caller-supplied + * override through here (see `TestProviderCredentialArgs.baseURL`). */ + readonly buildProbeRequest: ( + apiKey: string, + baseURL: string, + ) => ProbeRequest; + } + ); + +type CommonProviderTestConfig = { readonly displayName: string; readonly baseURL: string; readonly adapterPlugin: AdapterPluginId; @@ -109,12 +139,6 @@ export type ProviderTestConfig = { * once the credential is stored, never a claim about how this test * itself validates the key (it never calls this model). */ readonly probeModel: string; - /** Builds the free probe request that proves the key without spending a - * token: same auth layer a completion would hit, no generation cost. - * `baseURL` is the resolved origin for this probe — `config.baseURL` - * for every provider except `ollama`, which threads a caller-supplied - * override through here (see `TestProviderCredentialArgs.baseURL`). */ - readonly buildProbeRequest: (apiKey: string, baseURL: string) => ProbeRequest; /** Whether this status means "the probe proved the key," overriding the * default `response.ok` (2xx) check. Only Opencode Zen's probe needs * this: its empty-body POST can never succeed with a 2xx — the gateway @@ -123,7 +147,8 @@ export type ProviderTestConfig = { readonly isKeyAccepted?: (status: number) => boolean; /** Whether this status/body pair means "the provider rejected the key," * as opposed to a network problem or some other failure — each - * provider maps auth failures to its own status code. */ + * provider maps auth failures to its own status code. Never consulted + * for a `probeless` provider. */ readonly isKeyRejected: (status: number, body: string) => boolean; }; @@ -248,6 +273,47 @@ export const PROVIDER_TEST_CONFIG: Readonly< return parsed.code === "invalid-argument"; }, }, + "xai-oauth": { + displayName: "xAI (Grok OAuth)", + // The grok-cli OAuth proxy, not api.x.ai: tokens minted by this login + // are rejected by the platform API surface, which expects an API key + // (the `xai` entry above). The proxy speaks the Responses protocol at + // its `/responses` path, so this provider rides `openai-responses`. + baseURL: "https://cli-chat-proxy.grok.com/v1", + adapterPlugin: "openai-responses", + // Kept in sync with the proxy's own model list (the upstream xai + // provider's XAI_DEFAULT_MODELS): the proxy rejects model ids its CLI + // does not serve. + probeModel: "grok-4.6", + // The proxy's own list-models route answers 401 to a bad or expired + // OAuth token, so the standard GET probe proves the credential. + buildProbeRequest: (apiKey, _baseURL) => ({ + url: "https://cli-chat-proxy.grok.com/v1/models", + headers: { Authorization: `Bearer ${apiKey}` }, + }), + isKeyRejected: (status) => status === 401, + }, + codex: { + displayName: "Codex", + // OpenAI's consumer authorization server and ChatGPT backend, not the + // platform API-key system (the `openai` entry above) — a Codex login + // yields a ChatGPT-subscription token the platform surface rejects. + baseURL: "https://chatgpt.com/backend-api", + adapterPlugin: "openai-responses", + probeModel: "gpt-5.5", + // Probeless, and deliberately so: Codex publishes no live model + // catalog (`GET /codex/models` does not exist) and has no free + // auth-check endpoint — the cheapest authenticated route is the + // billing-bearing `/codex/responses` itself. The loopback OAuth token + // exchange that mints the credential is the proof a real token was + // obtained; there is nothing a pre-flight fetch could honestly add. + probeless: + "Codex credentials cannot be tested in advance — the ChatGPT " + + "subscription token has no free auth-check endpoint. The OAuth " + + "login that produced it is itself the proof.", + // Never consulted (see `probeless`); required by the config shape. + isKeyRejected: () => false, + }, openrouter: { displayName: "OpenRouter", baseURL: "https://openrouter.ai/api/v1", @@ -468,6 +534,13 @@ export async function testProviderCredential( ): Promise { const config = PROVIDER_TEST_CONFIG[args.provider]; const doFetch = args.fetchImpl ?? fetch; + + // A probeless provider has no honest pre-flight probe (see `codex`): + // report the named reason without dialing anything. + if ("probeless" in config) { + return { ok: false, message: config.probeless }; + } + const probe = config.buildProbeRequest( args.apiKey, args.baseURL ?? config.baseURL, diff --git a/packages/connections/src/descriptor.ts b/packages/connections/src/descriptor.ts index 9dd2831873..e90407e3d6 100644 --- a/packages/connections/src/descriptor.ts +++ b/packages/connections/src/descriptor.ts @@ -6,7 +6,18 @@ import type { CredentialTestResult } from "./credential-test"; export type ConnectorAuthKind = - "oauth-pkce" | "oauth-code" | "api-key" | "webhook-secret"; + | "oauth-pkce" + | "oauth-code" + // A provider whose authorization server only accepts a fixed + // `http://localhost:` redirect URI captured by a local login + // server during the flow (Codex: `localhost:1455`; xai-oauth: + // `127.0.0.1:1456`) — unlike `oauth-pkce`/`oauth-code`, the browser + // never comes back to the hub, so none of `oauth-routes.ts`'s callback + // mechanics apply; the host runs the loopback server itself and only + // the descriptor's `oauth.exchange` is reused. + | "oauth-loopback" + | "api-key" + | "webhook-secret"; /** The result of trading an authorization code for real material — * shared across every OAuth connector regardless of whether the diff --git a/packages/connections/test/credential-test.test.ts b/packages/connections/test/credential-test.test.ts index 0edfe07e59..c0a0832c4b 100644 --- a/packages/connections/test/credential-test.test.ts +++ b/packages/connections/test/credential-test.test.ts @@ -25,6 +25,7 @@ describe("supportedCredentialProviders", () => { .sort(), ).toEqual([ "anthropic", + "codex", "deepseek", "google-genai", "groq", @@ -35,6 +36,7 @@ describe("supportedCredentialProviders", () => { "opencode-zen", "openrouter", "xai", + "xai-oauth", ]); }); }); @@ -76,6 +78,62 @@ describe("providerModelSource", () => { expect(providerModelSource("openai").provider).toBe("openai"); expect(providerModelSource("google-genai").provider).toBe("google-genai"); }); + + test("maps codex and xai-oauth to the openai-responses adapter", () => { + expect(providerModelSource("codex").provider).toBe("openai-responses"); + expect(providerModelSource("codex").model).toBe("gpt-5.5"); + expect(providerModelSource("codex").baseURL).toBe( + "https://chatgpt.com/backend-api", + ); + expect(providerModelSource("xai-oauth").provider).toBe("openai-responses"); + expect(providerModelSource("xai-oauth").baseURL).toBe( + "https://cli-chat-proxy.grok.com/v1", + ); + }); +}); + +describe("codex and xai-oauth probe posture", () => { + test("codex never dials the network and reports why", async () => { + let dialed = false; + const fetchImpl: FetchLike = async () => { + dialed = true; + return new Response("{}", { status: 200 }); + }; + const result = await testProviderCredential({ + provider: "codex", + apiKey: "any", + fetchImpl, + }); + expect(dialed).toBe(false); + expect(result.ok).toBe(false); + if (!result.ok) expect(result.message).toContain("Codex"); + }); + + test("xai-oauth probes its own proxy like any bearer-token provider", async () => { + let url = ""; + const fetchImpl: FetchLike = async (requested, init) => { + url = requested; + expect((init.headers as Headers).get("authorization")).toBe( + "Bearer tok_1", + ); + return new Response(JSON.stringify({ data: [] }), { status: 200 }); + }; + const accepted = await testProviderCredential({ + provider: "xai-oauth", + apiKey: "tok_1", + fetchImpl, + }); + expect(url).toBe("https://cli-chat-proxy.grok.com/v1/models"); + expect(accepted).toEqual({ ok: true }); + + const rejected = await testProviderCredential({ + provider: "xai-oauth", + apiKey: "tok_stale", + fetchImpl: async () => + new Response(JSON.stringify({ error: "expired" }), { status: 401 }), + }); + expect(rejected.ok).toBe(false); + }); }); describe("testProviderCredential", () => { diff --git a/packages/inference-settings/src/effective-list.test.ts b/packages/inference-settings/src/effective-list.test.ts index abe2877f8c..ab8748d4c8 100644 --- a/packages/inference-settings/src/effective-list.test.ts +++ b/packages/inference-settings/src/effective-list.test.ts @@ -20,6 +20,9 @@ describe("providerDisplayName", () => { test("resolves a known provider slug to its own display name", () => { expect(providerDisplayName("ollama")).toBe("Ollama (local)"); expect(providerDisplayName("opencode-zen")).toBe("Opencode Zen"); + // The CL-7510 OAuth-only ids resolve too, not fall back to their slug. + expect(providerDisplayName("codex")).toBe("Codex"); + expect(providerDisplayName("xai-oauth")).toBe("xAI (Grok OAuth)"); }); test("falls back to the raw slug for an unrecognized provider", () => { diff --git a/packages/onboarding/src/plant-env-credentials.ts b/packages/onboarding/src/plant-env-credentials.ts index dc8598cf1a..1a4d2f495d 100644 --- a/packages/onboarding/src/plant-env-credentials.ts +++ b/packages/onboarding/src/plant-env-credentials.ts @@ -57,6 +57,11 @@ export const PROVIDER_ENV_VARS: Readonly< openai: ["OPENAI_API_KEY"], "google-genai": ["GEMINI_API_KEY", "GOOGLE_API_KEY"], xai: ["XAI_API_KEY"], + // The OAuth-only providers (CL-7510) have no env-var API-key path at + // all — a token only exists after its loopback OAuth login, so there is + // no env var an operator could plant and no name here could match. + "xai-oauth": [], + codex: [], openrouter: ["OPENROUTER_API_KEY"], "opencode-zen": ["OPENCODE_ZEN_API_KEY"], groq: ["GROQ_API_KEY"], diff --git a/packages/seeding/src/catalog-seed-data.ts b/packages/seeding/src/catalog-seed-data.ts index fc6a82ef45..3966f686d4 100644 --- a/packages/seeding/src/catalog-seed-data.ts +++ b/packages/seeding/src/catalog-seed-data.ts @@ -35,6 +35,13 @@ export type CatalogProviderSpec = { readonly name: string; readonly plugin: AdapterPluginId; readonly baseURL: string; + /** + * The `quirks` bag stored on every offering this seed creates — data the + * serving adapter needs and cannot default (Codex's host identity: + * `CodexQuirks`, upstream `@corbits/codex-provider`). Absent for every + * provider whose adapter handles its own request shape unmodified. + */ + readonly quirks?: Record; }; export type CatalogProviderSeed = { @@ -100,6 +107,39 @@ export const CATALOG_SEEDS: Readonly< { canonicalName: "grok-code-fast-1", displayName: "Grok Code Fast 1" }, ], }, + "xai-oauth": { + provider: { + name: "xai-oauth", + plugin: "openai-responses", + // The grok-cli OAuth proxy — tokens from this login are rejected by + // api.x.ai, which expects a plain API key (the `xai` seed above). + baseURL: "https://cli-chat-proxy.grok.com/v1", + }, + // Mirrors the proxy's own model list (the upstream xai provider's + // XAI_DEFAULT_MODELS); the proxy rejects any other model id. + models: [ + { canonicalName: "grok-4.6", displayName: "Grok 4.6 (Grok OAuth)" }, + { canonicalName: "grok-4.5", displayName: "Grok 4.5 (Grok OAuth)" }, + { + canonicalName: "grok-composer-2.5-fast", + displayName: "Grok Composer 2.5 Fast (Grok OAuth)", + }, + ], + }, + codex: { + provider: { + name: "codex", + plugin: "openai-responses", + // The ChatGPT subscription backend, not the platform API surface. + baseURL: "https://chatgpt.com/backend-api", + // Codex's adapter validates this host identity bag and refuses to + // default it — there is no honest generic product name. + quirks: { productName: "Codex", environmentTagName: "codex_environment" }, + }, + // Codex publishes no live model catalog, so this curated entry is the + // entire list; the backend serves the Codex CLI's current default. + models: [{ canonicalName: "gpt-5.5", displayName: "GPT-5.5 (Codex)" }], + }, openrouter: { provider: { name: "openrouter", diff --git a/packages/seeding/src/seed.ts b/packages/seeding/src/seed.ts index 6b8339f971..ec7c86ee90 100644 --- a/packages/seeding/src/seed.ts +++ b/packages/seeding/src/seed.ts @@ -2055,6 +2055,25 @@ async function ensureCatalogOffering( // a log line, a screenshot, or a bug report. export const PLACEHOLDER_CATALOG_API_KEY = "placeholder-not-a-real-key"; +/** + * Thrown by `seedCatalog` when a caller asks for a placeholder credential + * against an OAuth-only provider — `codex` and `xai-oauth` have no API-key + * path at all, so a fake `"placeholder-not-a-real-key"` secret could never + * authenticate and would only make the offering look launchable when no + * turn against it can ever succeed. A real token from the provider's own + * OAuth login (or a seeded `apiKey`) is the only way to make these + * launchable. + */ +export class PlaceholderCredentialError extends Error { + override readonly name = "PlaceholderCredentialError"; +} + +/** Providers whose only credential is a real OAuth token — + * `placeholderCredential: true` is a `PlaceholderCredentialError` for + * these, never a planted fake. */ +const OAUTH_ONLY_CATALOG_PROVIDERS: ReadonlySet = + new Set(["codex", "xai-oauth"]); + export type SeedCatalogArgs = { api: ApiCall; cookies: string[]; @@ -2167,6 +2186,17 @@ export async function seedCatalog( const { api, cookies, tenantId, log, provider = "anthropic" } = args; const seed = CATALOG_SEEDS[provider]; + if ( + args.placeholderCredential === true && + OAUTH_ONLY_CATALOG_PROVIDERS.has(provider) + ) { + throw new PlaceholderCredentialError( + `provider "${provider}" has no API-key credential path — a placeholder ` + + `secret could never authenticate. Connect it through its OAuth login ` + + `or seed a real token instead.`, + ); + } + const providerBaseURL = provider === "ollama" ? ollamaOpenAICompatBaseURL(args.baseURLOverride ?? seed.provider.baseURL) @@ -2317,11 +2347,15 @@ export async function seedCatalog( // model's real ceiling (or `undefined` for a provider outside this // mechanism's scope, or a model this catalog has not vetted a ceiling // for), landing on the offering's `quirks` column exactly the way - // `capabilitiesForDeployment` lands on its `capabilities` column. - const quirks = quirksForDeployment({ - providerName: seed.provider.name, - canonicalName: model.canonicalName, - }); + // `capabilitiesForDeployment` lands on its `capabilities` column. A + // provider whose seed declares its own bag (Codex's host identity) + // uses that instead — one quirks source per provider, never both. + const quirks = + seed.provider.quirks ?? + quirksForDeployment({ + providerName: seed.provider.name, + canonicalName: model.canonicalName, + }); await ensureCatalogOffering( api, cookies, diff --git a/packages/seeding/test/catalog-seed-data.test.ts b/packages/seeding/test/catalog-seed-data.test.ts index 489ebfae04..d407bb09e6 100644 --- a/packages/seeding/test/catalog-seed-data.test.ts +++ b/packages/seeding/test/catalog-seed-data.test.ts @@ -35,6 +35,9 @@ describe("CATALOG_SEEDS", () => { // inside the generic 2–5 bound. if (provider === "anthropic" || provider === "openai") continue; if (provider === "google-genai") continue; + // Codex publishes no live model catalog, so its curated entry is + // exactly one model — the bound below assumes a browsable spread. + if (provider === "codex") continue; expect(seed.models.length).toBeGreaterThanOrEqual(2); expect(seed.models.length).toBeLessThanOrEqual(5); } @@ -83,6 +86,39 @@ describe("CATALOG_SEEDS", () => { expect(model.displayName.length).toBeGreaterThan(0); } }); + test("codex and xai-oauth seed behind the openai-responses adapter", () => { + expect(CATALOG_SEEDS.codex.provider).toEqual({ + name: "codex", + plugin: "openai-responses", + baseURL: "https://chatgpt.com/backend-api", + // The Codex adapter validates this host identity and refuses to + // default it — an offering without it could never serve a turn. + quirks: { + productName: "Codex", + environmentTagName: "codex_environment", + }, + }); + expect(CATALOG_SEEDS.codex.models.map((m) => m.canonicalName)).toEqual([ + "gpt-5.5", + ]); + + expect(CATALOG_SEEDS["xai-oauth"].provider).toEqual({ + name: "xai-oauth", + plugin: "openai-responses", + baseURL: "https://cli-chat-proxy.grok.com/v1", + }); + expect( + CATALOG_SEEDS["xai-oauth"].models.map((m) => m.canonicalName), + ).toEqual(["grok-4.6", "grok-4.5", "grok-composer-2.5-fast"]); + expect(CATALOG_SEEDS["xai-oauth"].provider.quirks).toBeUndefined(); + }); + + test("no seed other than codex declares an offering quirks bag", () => { + for (const [provider, seed] of Object.entries(CATALOG_SEEDS)) { + if (provider === "codex") continue; + expect(seed.provider.quirks).toBeUndefined(); + } + }); }); describe("deriveWorkbenchHostInferencePreferences", () => { diff --git a/packages/seeding/test/seed.test.ts b/packages/seeding/test/seed.test.ts index e3691ca3f9..a04ed6669d 100644 --- a/packages/seeding/test/seed.test.ts +++ b/packages/seeding/test/seed.test.ts @@ -14,6 +14,7 @@ import { seedCatalog, ensureProvider, seedTenant, + PlaceholderCredentialError, SETUP_AGENT_ASSET_NAME, type SeedTenantArgs, type WorkflowPusher, @@ -2919,3 +2920,64 @@ describe("seedCatalog", () => { ).rejects.toThrow(HubApiError); }); }); + +describe("seedCatalog placeholderCredential gate", () => { + test("rejects a placeholder credential for an OAuth-only provider with a named error", async () => { + for (const provider of ["codex", "xai-oauth"] as const) { + let caught: unknown; + try { + await seedCatalog({ + api: fakeAPI(() => undefined), + cookies: [], + tenantId: TENANT_ID, + provider, + placeholderCredential: true, + log: () => {}, + }); + } catch (error) { + caught = error; + } + expect(caught).toBeInstanceOf(PlaceholderCredentialError); + expect((caught as Error).message).toContain(provider); + } + }); + + test("a real apiKey still seeds an OAuth-only provider — the gate is placeholder-only", async () => { + // Nothing past the gate runs (the fake API answers nothing), but the + // failure must be the hub's, not the gate's — the gate only exists to + // stop fake credentials, not real ones. + let caught: unknown; + try { + await seedCatalog({ + api: fakeAPI(() => undefined), + cookies: [], + tenantId: TENANT_ID, + provider: "codex", + apiKey: "real-token", + log: () => {}, + }); + } catch (error) { + caught = error; + } + expect(caught).not.toBeInstanceOf(PlaceholderCredentialError); + }); + + test("leaves api-key providers' placeholder behavior untouched", async () => { + let caught: unknown; + try { + await seedCatalog({ + api: fakeAPI(() => undefined), + cookies: [], + tenantId: TENANT_ID, + provider: "xai", + placeholderCredential: true, + log: () => {}, + }); + } catch (error) { + caught = error; + } + // The xai seed proceeds past the gate (and fails later against the + // fake API) — the gate never fires for an api-key provider. + expect(caught).not.toBeInstanceOf(PlaceholderCredentialError); + }); +}); diff --git a/templates/connectors.test.ts b/templates/connectors.test.ts index a4a6b2da1d..6d0fe24923 100644 --- a/templates/connectors.test.ts +++ b/templates/connectors.test.ts @@ -122,6 +122,98 @@ describe("CONNECTOR_REGISTRY", () => { expect(descriptor?.feedsTools).toEqual([]); }); + test("includes Codex and xai-oauth as oauth-loopback connectors, no probe", () => { + for (const id of ["codex", "xai-oauth"]) { + const descriptor = CONNECTOR_REGISTRY[id]; + expect(descriptor?.authKind).toBe("oauth-loopback"); + expect(descriptor?.probe).toBeUndefined(); + expect(descriptor?.oauth).toBeDefined(); + expect(descriptor?.oauth?.usesPKCE).toBe(true); + expect(descriptor?.oauth?.deploysDefaultWorkflows).toBe(true); + expect(descriptor?.feedsTools).toEqual([]); + // The OAuth client ids are the provider CLIs' public identifiers. + expect(descriptor?.oauth?.clientId?.({})).toBeDefined(); + } + }); + + test("pins each loopback provider to its own fixed redirect URI", () => { + const codexUrl = CONNECTOR_REGISTRY["codex"]?.oauth?.buildAuthorizeUrl({ + callbackUrl: "https://bench.example.com/ignored", + state: "s", + codeChallenge: "c", + clientId: "app_1", + }); + expect(codexUrl?.origin).toBe("https://auth.openai.com"); + expect(codexUrl?.searchParams.get("redirect_uri")).toBe( + "http://localhost:1455/auth/callback", + ); + expect(codexUrl?.searchParams.get("originator")).toBe("codex_cli_rs"); + + const xaiUrl = + CONNECTOR_REGISTRY["xai-oauth"]?.oauth?.buildAuthorizeUrl({ + callbackUrl: "https://bench.example.com/ignored", + state: "s", + codeChallenge: "c", + clientId: "client_1", + }); + expect(xaiUrl?.origin).toBe("https://auth.x.ai"); + expect(xaiUrl?.searchParams.get("redirect_uri")).toBe( + "http://127.0.0.1:1456/callback", + ); + expect(xaiUrl?.searchParams.get("scope")).toContain("grok-cli:access"); + }); + + test("exchanges a loopback code for tokens against the provider's own token endpoint", async () => { + const originalFetch = globalThis.fetch; + const requestedURLs: string[] = []; + globalThis.fetch = (async (input: RequestInfo | URL) => { + const url = String(input); + requestedURLs.push(url); + return new Response( + JSON.stringify({ + access_token: "at_1", + refresh_token: "rt_1", + expires_in: 3600, + }), + { status: 200 }, + ); + }) as typeof fetch; + try { + const result = await CONNECTOR_REGISTRY["codex"]?.oauth?.exchange({ + code: "abc", + codeVerifier: "ver", + redirectUri: "http://localhost:1455/auth/callback", + clientId: "app_1", + }); + expect(result).toMatchObject({ + ok: true, + apiKey: "at_1", + refreshToken: "rt_1", + }); + expect(requestedURLs).toEqual(["https://auth.openai.com/oauth/token"]); + expect(requestedURLs[0]).toContain("auth.openai.com"); + } finally { + globalThis.fetch = originalFetch; + } + }); + + test("reports a failed loopback exchange without throwing", async () => { + const originalFetch = globalThis.fetch; + globalThis.fetch = (async () => + new Response("nope", { status: 400 })) as typeof fetch; + try { + const result = await CONNECTOR_REGISTRY["xai-oauth"]?.oauth?.exchange({ + code: "abc", + codeVerifier: "ver", + redirectUri: "http://127.0.0.1:1456/callback", + clientId: "client_1", + }); + expect(result?.ok).toBe(false); + } finally { + globalThis.fetch = originalFetch; + } + }); + test("includes the tool connectors with the right feedsTools", () => { expect(CONNECTOR_REGISTRY["granola"]?.feedsTools).toEqual([ "@corbits/granola-tools", diff --git a/templates/connectors.ts b/templates/connectors.ts index a9de87f7ad..5bd5743b1b 100644 --- a/templates/connectors.ts +++ b/templates/connectors.ts @@ -88,6 +88,8 @@ const INFERENCE_PROVIDER_DOCS_URL: Readonly< openai: "https://platform.openai.com/api-keys", "google-genai": "https://aistudio.google.com/apikey", xai: "https://console.x.ai", + "xai-oauth": "https://x.ai", + codex: "https://developers.openai.com/codex", "opencode-zen": "https://opencode.ai/zen", groq: "https://console.groq.com/keys", deepseek: "https://platform.deepseek.com/api_keys", @@ -131,6 +133,7 @@ function inferenceProviderDescriptors(): Record { const entries: Record = {}; for (const [id, config] of Object.entries(PROVIDER_TEST_CONFIG)) { if (id === "openrouter" || id === "huggingface") continue; + if (id === "codex" || id === "xai-oauth") continue; const providerId = id as SupportedCredentialProvider; // Ollama collects a URL, not a secret — it has no auth layer at all // (see `credential-test.ts`'s own `ollama` config entry). Every @@ -257,6 +260,181 @@ function inferenceProviderDescriptors(): Record { }, }, }; + // The two loopback OAuth providers (CL-7510). Their authorization + // servers only accept the fixed `http://localhost:` redirect URI + // the provider's own CLI registers (`authKind: "oauth-loopback"`), so + // `buildAuthorizeUrl` ignores the hub callback URL the generic + // mechanics would pass and pins the provider's own redirect instead. + // The constants mirror the provider CLIs' public, non-secret values the + // same way `openrouter-connect.ts` mirrors OpenRouter's — until the + // provider packages land (S2), which will replace these literals. + entries["codex"] = { + id: "codex", + displayName: PROVIDER_TEST_CONFIG.codex.displayName, + authKind: "oauth-loopback", + credentialPlugin: "http", + docsUrl: INFERENCE_PROVIDER_DOCS_URL.codex, + feedsTools: [], + oauth: { + authorizeUrl: "https://auth.openai.com/oauth/authorize", + usesPKCE: true, + echoesState: true, + deploysDefaultWorkflows: true, + // The Codex CLI's public client id — not a secret, and the + // authorization server only issues this flow's connector scopes to + // it. + clientId: () => "app_EMoamEEZ73f0CkXaXp7hrann", + buildAuthorizeUrl: ({ state, codeChallenge, clientId }) => { + const url = new URL("https://auth.openai.com/oauth/authorize"); + if (clientId !== undefined) url.searchParams.set("client_id", clientId); + url.searchParams.set("redirect_uri", "http://localhost:1455/auth/callback"); + url.searchParams.set("response_type", "code"); + url.searchParams.set("scope", "openid profile email offline_access"); + url.searchParams.set("state", state); + if (codeChallenge !== undefined) { + url.searchParams.set("code_challenge", codeChallenge); + } + url.searchParams.set("code_challenge_method", "S256"); + // The Codex CLI's authorize request opts into a simplified consent + // screen and identifies the client; the authorization server + // rejects the plain flow for this client without these. + url.searchParams.set("codex_cli_simplified_flow", "true"); + url.searchParams.set("id_token_add_organizations", "true"); + url.searchParams.set("originator", "codex_cli_rs"); + return url; + }, + exchange: async ({ code, codeVerifier, clientId }) => { + try { + const response = await fetch("https://auth.openai.com/oauth/token", { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + signal: AbortSignal.timeout(10_000), + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + code_verifier: codeVerifier ?? "", + client_id: clientId ?? "", + redirect_uri: "http://localhost:1455/auth/callback", + }).toString(), + }); + if (!response.ok) { + return { + ok: false, + message: `Codex rejected the login (status ${response.status})`, + }; + } + const tokens = (await response.json()) as { + access_token?: unknown; + refresh_token?: unknown; + expires_in?: unknown; + }; + if (typeof tokens.access_token !== "string") { + return { ok: false, message: "Codex returned no access token" }; + } + return { + ok: true, + apiKey: tokens.access_token, + ...(typeof tokens.refresh_token === "string" + ? { refreshToken: tokens.refresh_token } + : {}), + ...(typeof tokens.expires_in === "number" + ? { + expiresAt: new Date( + Date.now() + tokens.expires_in * 1000, + ).toISOString(), + } + : {}), + }; + } catch (cause) { + return { + ok: false, + message: `Codex token exchange failed: ${cause instanceof Error ? cause.message : String(cause)}`, + }; + } + }, + }, + }; + entries["xai-oauth"] = { + id: "xai-oauth", + displayName: PROVIDER_TEST_CONFIG["xai-oauth"].displayName, + authKind: "oauth-loopback", + credentialPlugin: "http", + docsUrl: INFERENCE_PROVIDER_DOCS_URL["xai-oauth"], + feedsTools: [], + oauth: { + authorizeUrl: "https://auth.x.ai/oauth2/authorize", + usesPKCE: true, + echoesState: true, + deploysDefaultWorkflows: true, + // The grok CLI's public client id — not a secret. + clientId: () => "b1a00492-073a-47ea-816f-4c329264a828", + buildAuthorizeUrl: ({ state, codeChallenge, clientId }) => { + const url = new URL("https://auth.x.ai/oauth2/authorize"); + if (clientId !== undefined) url.searchParams.set("client_id", clientId); + url.searchParams.set("redirect_uri", "http://127.0.0.1:1456/callback"); + url.searchParams.set("response_type", "code"); + url.searchParams.set( + "scope", + "openid profile email offline_access grok-cli:access api:access", + ); + url.searchParams.set("state", state); + if (codeChallenge !== undefined) { + url.searchParams.set("code_challenge", codeChallenge); + } + url.searchParams.set("code_challenge_method", "S256"); + return url; + }, + exchange: async ({ code, codeVerifier, clientId }) => { + try { + const response = await fetch("https://auth.x.ai/oauth2/token", { + method: "POST", + headers: { "content-type": "application/x-www-form-urlencoded" }, + signal: AbortSignal.timeout(10_000), + body: new URLSearchParams({ + grant_type: "authorization_code", + code, + code_verifier: codeVerifier ?? "", + client_id: clientId ?? "", + redirect_uri: "http://127.0.0.1:1456/callback", + }).toString(), + }); + if (!response.ok) { + return { + ok: false, + message: `xAI rejected the login (status ${response.status})`, + }; + } + const tokens = (await response.json()) as { + access_token?: unknown; + refresh_token?: unknown; + expires_in?: unknown; + }; + if (typeof tokens.access_token !== "string") { + return { ok: false, message: "xAI returned no access token" }; + } + return { + ok: true, + apiKey: tokens.access_token, + ...(typeof tokens.refresh_token === "string" + ? { refreshToken: tokens.refresh_token } + : {}), + ...(typeof tokens.expires_in === "number" + ? { + expiresAt: new Date( + Date.now() + tokens.expires_in * 1000, + ).toISOString(), + } + : {}), + }; + } catch (cause) { + return { + ok: false, + message: `xAI token exchange failed: ${cause instanceof Error ? cause.message : String(cause)}`, + }; + } + }, + }, + }; return entries; } From 832ef0073f7caf659b494f5bff22312dc5e477df Mon Sep 17 00:00:00 2001 From: Sawyer Date: Mon, 7 Sep 2026 22:34:30 -0700 Subject: [PATCH 2/8] Fix stale withOllamaAdapterKey citation in AdapterPluginId docs packages/folded-runs/src/launch.ts no longer exists, and the launch-time provider rewrite it described is gone with it: the launched InferenceSource.provider is resolved straight from the catalog provider row's plugin column at serving time, in vendor/intx/db/src/model-source-resolution.ts's buildSource. Cite that live dispatch site instead. --- packages/connections/src/credential-test.ts | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/packages/connections/src/credential-test.ts b/packages/connections/src/credential-test.ts index 501ec7a2fe..79a2ec3120 100644 --- a/packages/connections/src/credential-test.ts +++ b/packages/connections/src/credential-test.ts @@ -44,19 +44,17 @@ export type SupportedCredentialProvider = * Codex and xai-oauth are the opposite: both speak OpenAI's Responses * protocol, so they ride `"openai-responses"`. * - * This is NOT the same string as the registry key - * `byProvider.get(source.provider)` resolves against (CL-6586). For every - * provider above but Ollama the two happen to be equal, because the - * built-in `"openai-compatible"` adapter is exactly what serves them. Ollama - * is the one exception: it needs `@corbits/ollama-adapter`'s custom factory - * (registered under the key `"ollama"`, `apps/sidecar/src/config.ts`) so an - * offering's `quirks.numCtx` actually reaches `options.num_ctx` — the - * built-in adapter's stricter `quirks` schema rejects that shape outright. - * `packages/folded-runs/src/launch.ts`'s `withOllamaAdapterKey` is the one - * place that correction happens: it leaves `plugin`/`ModelSource.provider` - * as the accurate `"openai-compatible"` wire format and only rewrites the - * launched `InferenceSource.provider` — the actual registry-dispatch - * field — for an offering whose catalog provider is named `"ollama"`. + * This is NOT the same string as the registry key a launched + * `InferenceSource.provider` dispatches against (CL-6586). The launch-time + * dispatch field is resolved from the catalog provider row's own `plugin` + * column at serving time (`vendor/intx/db/src/model-source-resolution.ts`, + * `buildSource`'s `provider: provider.plugin`) — there is no separate + * rewrite step a caller must remember to run. Ollama is the one provider + * whose dispatch key differs from its wire shape: it needs + * `@corbits/ollama-adapter`'s custom factory (registered under the key + * `"ollama"`, `apps/sidecar/src/config.ts`) so an offering's + * `quirks.numCtx` actually reaches `options.num_ctx` — the built-in + * adapter's stricter `quirks` schema rejects that shape outright. */ export type AdapterPluginId = | "anthropic" From df5cae3b867f26bb682503118bb3f7552ea83c23 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Mon, 7 Sep 2026 22:42:48 -0700 Subject: [PATCH 3/8] Fix onboarding provider-card test for the loopback OAuth ids The card list is pinned to supportedCredentialProviders, which now includes codex and xai-oauth; neither can have a key-paste card until the loopback OAuth login is wired, so the pin excludes exactly those two ids. Also applies the formatter to the new connector descriptors and their tests. --- apps/web/test/onboarding.test.tsx | 7 +++++++ templates/connectors.test.ts | 13 ++++++------- templates/connectors.ts | 5 ++++- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/apps/web/test/onboarding.test.tsx b/apps/web/test/onboarding.test.tsx index 15131f8770..2439859d25 100644 --- a/apps/web/test/onboarding.test.tsx +++ b/apps/web/test/onboarding.test.tsx @@ -69,9 +69,16 @@ describe("CREDENTIAL_PROVIDERS", () => { // list, so a provider added or removed there is caught here rather than // drifting silently. test("has one card for every provider the hub can test a credential against", () => { + // The CL-7510 loopback-OAuth ids (`codex`, `xai-oauth`) are + // deliberately absent: their credential only exists after a loopback + // OAuth login, so there is no key-paste card to render until that + // flow is wired — a card here would offer a paste form no key can + // satisfy. + const LOOPBACK_OAUTH_ONLY = new Set(["codex", "xai-oauth"]); expect(CREDENTIAL_PROVIDERS.map((p) => p.id).sort()).toEqual( supportedCredentialProviders() .map((p) => p.id) + .filter((id) => !LOOPBACK_OAUTH_ONLY.has(id)) .sort(), ); }); diff --git a/templates/connectors.test.ts b/templates/connectors.test.ts index 6d0fe24923..04d6faadbb 100644 --- a/templates/connectors.test.ts +++ b/templates/connectors.test.ts @@ -149,13 +149,12 @@ describe("CONNECTOR_REGISTRY", () => { ); expect(codexUrl?.searchParams.get("originator")).toBe("codex_cli_rs"); - const xaiUrl = - CONNECTOR_REGISTRY["xai-oauth"]?.oauth?.buildAuthorizeUrl({ - callbackUrl: "https://bench.example.com/ignored", - state: "s", - codeChallenge: "c", - clientId: "client_1", - }); + const xaiUrl = CONNECTOR_REGISTRY["xai-oauth"]?.oauth?.buildAuthorizeUrl({ + callbackUrl: "https://bench.example.com/ignored", + state: "s", + codeChallenge: "c", + clientId: "client_1", + }); expect(xaiUrl?.origin).toBe("https://auth.x.ai"); expect(xaiUrl?.searchParams.get("redirect_uri")).toBe( "http://127.0.0.1:1456/callback", diff --git a/templates/connectors.ts b/templates/connectors.ts index 5bd5743b1b..7c76814515 100644 --- a/templates/connectors.ts +++ b/templates/connectors.ts @@ -287,7 +287,10 @@ function inferenceProviderDescriptors(): Record { buildAuthorizeUrl: ({ state, codeChallenge, clientId }) => { const url = new URL("https://auth.openai.com/oauth/authorize"); if (clientId !== undefined) url.searchParams.set("client_id", clientId); - url.searchParams.set("redirect_uri", "http://localhost:1455/auth/callback"); + url.searchParams.set( + "redirect_uri", + "http://localhost:1455/auth/callback", + ); url.searchParams.set("response_type", "code"); url.searchParams.set("scope", "openid profile email offline_access"); url.searchParams.set("state", state); From 0b1836423acf2b77f025bcb049ae9e16f04ccdcc Mon Sep 17 00:00:00 2001 From: Sawyer Date: Mon, 7 Sep 2026 23:12:55 -0700 Subject: [PATCH 4/8] Accept openai-responses in the vendored model-provider plugin enum CreateModelProvider and the model_provider.plugin column both rejected the plugin id the codex/xai-oauth seeds carry, so a real-token seedCatalog would 400 at create time and parseModelProviderRow would throw on read. Recorded as a local delta on the vendor/intx/types and vendor/intx/db rows (VENDORED.md delta notes, kill-date tree hashes). The contract test drives the real validator and the real row parser, not the seedCatalog fake API that accepts everything. --- VENDORED.md | 36 +++++++-------- .../test/provider-plugin-contract.test.ts | 45 +++++++++++++++++++ scripts/checks/kill-dates.txt | 4 +- vendor/intx/db/src/schema/catalog.ts | 11 ++++- vendor/intx/types/src/catalog.ts | 3 ++ 5 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 packages/seeding/test/provider-plugin-contract.test.ts diff --git a/VENDORED.md b/VENDORED.md index b5f016f37e..a131cee982 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -22,24 +22,24 @@ never a convenience. ## Ledger -| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | -| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; and a CL-7505 serving-time refresh delta (`model-source-resolution.ts`: `buildSource`/`resolveModelSources`/`resolveInstanceModelSources` accept an optional `servingRefresh` hook, exposing `buildSource`, with a new `credential_needs_reauth` skip so an unrefreshable `oauth_token` credential is never served); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); and a CL-7505 serving-time refresh delta (`run-source-resolution.ts`: `resolveDefinitionSources` accepts and threads an optional `servingRefresh` hook); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; and a CL-7505 serving-time refresh delta (`credential-push.ts`: `pushInstanceSourceUpdate`/`pushSourceUpdates`/`pushSourceUpdatesSubtree` accept and thread an optional `servingRefresh` hook); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | +| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; and a CL-7505 serving-time refresh delta (`model-source-resolution.ts`: `buildSource`/`resolveModelSources`/`resolveInstanceModelSources` accept an optional `servingRefresh` hook, exposing `buildSource`, with a new `credential_needs_reauth` skip so an unrefreshable `oauth_token` credential is never served); one more local delta (CL-7510): `"openai-responses"` added to `schema/catalog.ts`'s `modelProvider.plugin` enum, in lockstep with the `@intx/types` delta; retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); and a CL-7505 serving-time refresh delta (`run-source-resolution.ts`: `resolveDefinitionSources` accepts and threads an optional `servingRefresh` hook); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; and a CL-7505 serving-time refresh delta (`credential-push.ts`: `pushInstanceSourceUpdate`/`pushSourceUpdates`/`pushSourceUpdatesSubtree` accept and thread an optional `servingRefresh` hook); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; one more local delta (CL-7510): `"openai-responses"` added to `catalog.ts`'s `modelProviderPlugins` so the workbench's loopback-OAuth providers (Codex, xai-oauth) can create model-provider rows; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | The pin is `692c3106` (upstream `origin/main`, 2026-09-03), 90 commits past the `v0.3.0` release tag `b5580a02`. npm is still `0.3.0`, so every tree a diff --git a/packages/seeding/test/provider-plugin-contract.test.ts b/packages/seeding/test/provider-plugin-contract.test.ts new file mode 100644 index 0000000000..db3a1ab588 --- /dev/null +++ b/packages/seeding/test/provider-plugin-contract.test.ts @@ -0,0 +1,45 @@ +// The boundary the CL-7510 OAuth-only providers actually cross: the real +// `@intx/types` CreateModelProvider validator (the one the hub's +// model-providers POST route enforces) and `@intx/db`'s +// `parseModelProviderRow` (the one every read applies) must both accept the +// `openai-responses` plugin id the codex/xai-oauth seeds carry — the fake +// API in the seedCatalog tests accepts everything, so this is the only +// test that proves a real-token seed would not 400 at create time or +// throw at read time. +import { describe, expect, test } from "bun:test"; +import { type } from "arktype"; +import { CreateModelProvider, ModelProviderPlugin } from "@intx/types"; +import { parseModelProviderRow } from "../../../vendor/intx/db/src/parse-row"; +import type { modelProvider } from "../../../vendor/intx/db/src/schema"; + +describe("openai-responses plugin id contract", () => { + test("CreateModelProvider accepts a codex-shaped provider body", () => { + const parsed = CreateModelProvider({ + name: "codex", + plugin: "openai-responses", + baseURL: "https://chatgpt.com/backend-api", + credentialId: "cre_1", + }); + expect(!(parsed instanceof type.errors)).toBe(true); + }); + + test("ModelProviderPlugin still rejects an unknown plugin id", () => { + expect( + ModelProviderPlugin("openai-responses-future") instanceof type.errors, + ).toBe(true); + }); + + test("parseModelProviderRow round-trips the new plugin id", () => { + const row = { + id: "mpr_1", + tenantId: "tnt_1", + name: "xai-oauth", + plugin: "openai-responses", + baseURL: "https://cli-chat-proxy.grok.com/v1", + credentialId: "cre_1", + createdAt: new Date(), + updatedAt: new Date(), + } as unknown as typeof modelProvider.$inferSelect; + expect(parseModelProviderRow(row).plugin).toBe("openai-responses"); + }); +}); diff --git a/scripts/checks/kill-dates.txt b/scripts/checks/kill-dates.txt index b41a1c6e66..b5d3766ea9 100644 --- a/scripts/checks/kill-dates.txt +++ b/scripts/checks/kill-dates.txt @@ -15,7 +15,7 @@ # package's VENDORED-FROM delta line in the same change. apps/sidecar | sawyer | 2026-11-03 vendor/intx/agent | sawyer | 2026-11-03 | 9dd132fda8d7aef9d70395de2d289abeb0361416b18a5079f219372df69d81a0 -vendor/intx/db | sawyer | 2026-11-03 | 360b03bd28eaa6c24092db15d63d3445f79d56bd3dbfc2968420d8a27abff57d +vendor/intx/db | sawyer | 2026-11-03 | 5932d23737805aa42a2448ccf2f5126e7b0ef0390ded5ddd5184695a4e1c2868 vendor/intx/harness | sawyer | 2026-11-03 | 399b4e8b3b18e8969e815427625e1bfc6746f31e791fb92cdbc2aaf2c5080dbf vendor/intx/hub-agent | sawyer | 2026-11-03 | 0a24a0cf85cc0483dbeee815d89847440e2fbd1242fdfd992941379a9acc2a4a vendor/intx/hub-api | sawyer | 2026-11-03 | 90aa5cddef828d77b62adb63186dcf82b5465926cafea1ab6b64bca263031b57 @@ -25,7 +25,7 @@ vendor/intx/inference | sawyer | 2026-11-03 | 81e1b31947cd8e6cac4e336d21d8a87ba0 vendor/intx/mail-memory | sawyer | 2026-11-03 | e281498352adbc2face631744ce9a02cb838c60a6c318961022904f0fd05d192 vendor/intx/mailbox | sawyer | 2026-11-03 | 926bfef1b60a4ba7e600252e9ad6e37c17d387a200f79c2cbc0b3ceb612000ea vendor/intx/mime | sawyer | 2026-11-03 | ddf7e626172350c6a447890affc6c746b7938e04ee53af8f1d9c8cffd97d1954 -vendor/intx/types | sawyer | 2026-11-03 | 71bd6493ae6df31abd9be8ff3d2feb79260d2aae3a29a5e275945dc92ea16a12 +vendor/intx/types | sawyer | 2026-11-03 | 5d9738e8b9e6a9193a759272350edb60a1cff09c430763a77d265855117b82c2 vendor/intx/workflow | sawyer | 2026-11-03 | c8f3a7a40d4ef429b58ba913c2faa27a5e7e98baf433dc57f864e12c907eb47a vendor/intx/workflow-deploy | sawyer | 2026-11-03 | 72a4f931ba5c404a091dc46a7ed60f5b0530a63535f7cacacfe31f95c397e473 vendor/intx/workflow-host | sawyer | 2026-11-03 | b9964010920cef377ba4be777c179d414c430c1709e665bbf8c5e4a413e249e9 diff --git a/vendor/intx/db/src/schema/catalog.ts b/vendor/intx/db/src/schema/catalog.ts index d5ae5b307f..77a0cabc1c 100644 --- a/vendor/intx/db/src/schema/catalog.ts +++ b/vendor/intx/db/src/schema/catalog.ts @@ -43,8 +43,17 @@ export const modelProvider = pgTable( .notNull() .references(() => tenant.id, { onDelete: "cascade" }), name: text("name").notNull(), + // Local delta (CL-7510): "openai-responses" — the workbench's + // loopback-OAuth providers (Codex, xai-oauth) ride the Responses + // protocol; must stay in sync with @intx/types' modelProviderPlugins. plugin: text("plugin", { - enum: ["anthropic", "openai", "openai-compatible", "google-genai"], + enum: [ + "anthropic", + "openai", + "openai-compatible", + "google-genai", + "openai-responses", + ], }).notNull(), baseURL: text("base_url").notNull(), // A provider authenticates via exactly one of credential or wallet (the diff --git a/vendor/intx/types/src/catalog.ts b/vendor/intx/types/src/catalog.ts index 47d1c37178..222ccb0592 100644 --- a/vendor/intx/types/src/catalog.ts +++ b/vendor/intx/types/src/catalog.ts @@ -7,6 +7,9 @@ export const modelProviderPlugins = [ "openai", "openai-compatible", "google-genai", + // Local delta (CL-7510): the workbench's loopback-OAuth providers (Codex, + // xai-oauth) speak OpenAI's Responses protocol and ride this plugin id. + "openai-responses", ] as const; export type ModelProviderPlugin = (typeof modelProviderPlugins)[number]; export const ModelProviderPlugin = type From 7e42095ad784b8ba2300de9ec2b7331bce108233 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Mon, 7 Sep 2026 23:13:03 -0700 Subject: [PATCH 5/8] Keep loopback OAuth connectors out of the web redirect flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The oauth start/callback routes redirect any descriptor carrying an oauth config, so codex/xai-oauth would send a browser to their consent pages with a localhost redirect_uri — a dead-end consent — and /oauth-configured reported them as configured. Both routes now refuse oauth-loopback descriptors with a typed loopback_connector error and the configured report omits them; these providers connect through the sidecar lane instead. Also pins the codex descriptor's authorize scope to the upstream CODEX_SCOPES list (including api.connectors.read/ invoke) and drops the probeless config's dead isKeyRejected stub; the xai-oauth probe comment no longer claims live confirmation. --- packages/connections/src/credential-test.ts | 17 +++++------ packages/connections/src/oauth-routes.test.ts | 30 +++++++++++++++++++ packages/connections/src/oauth-routes.ts | 20 +++++++++++++ packages/connections/src/routes.test.ts | 25 ++++++++++++++++ packages/connections/src/routes.ts | 4 +++ templates/connectors.test.ts | 13 ++++++++ templates/connectors.ts | 9 +++++- 7 files changed, 108 insertions(+), 10 deletions(-) diff --git a/packages/connections/src/credential-test.ts b/packages/connections/src/credential-test.ts index 79a2ec3120..659207923c 100644 --- a/packages/connections/src/credential-test.ts +++ b/packages/connections/src/credential-test.ts @@ -126,6 +126,11 @@ export type ProviderTestConfig = CommonProviderTestConfig & apiKey: string, baseURL: string, ) => ProbeRequest; + /** Whether this status/body pair means "the provider rejected the key," + * as opposed to a network problem or some other failure — each + * provider maps auth failures to its own status code. Only probed + * providers carry one; a `probeless` provider never fetches. */ + readonly isKeyRejected: (status: number, body: string) => boolean; } ); @@ -143,11 +148,6 @@ type CommonProviderTestConfig = { * always reaches the missing-`model` validation error after a real key * clears auth — so a 400 there is the success signal, not a 2xx. */ readonly isKeyAccepted?: (status: number) => boolean; - /** Whether this status/body pair means "the provider rejected the key," - * as opposed to a network problem or some other failure — each - * provider maps auth failures to its own status code. Never consulted - * for a `probeless` provider. */ - readonly isKeyRejected: (status: number, body: string) => boolean; }; const ANTHROPIC_VERSION = "2023-06-01"; @@ -283,8 +283,9 @@ export const PROVIDER_TEST_CONFIG: Readonly< // provider's XAI_DEFAULT_MODELS): the proxy rejects model ids its CLI // does not serve. probeModel: "grok-4.6", - // The proxy's own list-models route answers 401 to a bad or expired - // OAuth token, so the standard GET probe proves the credential. + // The proxy's list-models route is expected to answer 401 to a bad or + // expired OAuth token (mirroring the platform surface's behavior) — + // not yet confirmed live against a real login. buildProbeRequest: (apiKey, _baseURL) => ({ url: "https://cli-chat-proxy.grok.com/v1/models", headers: { Authorization: `Bearer ${apiKey}` }, @@ -309,8 +310,6 @@ export const PROVIDER_TEST_CONFIG: Readonly< "Codex credentials cannot be tested in advance — the ChatGPT " + "subscription token has no free auth-check endpoint. The OAuth " + "login that produced it is itself the proof.", - // Never consulted (see `probeless`); required by the config shape. - isKeyRejected: () => false, }, openrouter: { displayName: "OpenRouter", diff --git a/packages/connections/src/oauth-routes.test.ts b/packages/connections/src/oauth-routes.test.ts index 2d113add74..24284ceb56 100644 --- a/packages/connections/src/oauth-routes.test.ts +++ b/packages/connections/src/oauth-routes.test.ts @@ -77,6 +77,11 @@ function asUser(session: { userId: string }): MiddlewareHandler { }; } +function loopbackDescriptor(): ConnectorDescriptor { + const descriptor = fakeDescriptor(); + return { ...descriptor, id: "loopback", authKind: "oauth-loopback" }; +} + function mountAuthenticated( routes: Hono, session: { userId: string } = { userId: "user_1" }, @@ -827,3 +832,28 @@ describe("onConnected hook", () => { ); }); }); + +describe("oauth-loopback descriptors", () => { + test("the web start route refuses them with a typed error, never a redirect", async () => { + const app = connectRoutes({}, { loopback: loopbackDescriptor() }); + const response = await app.request("/api/connections/oauth/loopback/start"); + expect(response.status).toBe(409); + const body = (await response.json()) as { + error: { code: string; message: string }; + }; + expect(body.error.code).toBe("loopback_connector"); + expect(body.error.message).toContain("loopback"); + }); + + test("the callback route refuses them too", async () => { + const app = connectRoutes({}, { loopback: loopbackDescriptor() }); + const response = await app.request( + "/api/connections/oauth/loopback/callback?code=abc", + ); + expect(response.status).toBe(409); + const body = (await response.json()) as { + error: { code: string }; + }; + expect(body.error.code).toBe("loopback_connector"); + }); +}); diff --git a/packages/connections/src/oauth-routes.ts b/packages/connections/src/oauth-routes.ts index 0178a652bb..c804a21132 100644 --- a/packages/connections/src/oauth-routes.ts +++ b/packages/connections/src/oauth-routes.ts @@ -273,6 +273,20 @@ export function createOAuthConnectRoutes( const lastStartByKey = new Map(); + // A loopback OAuth connector's authorization server only accepts a fixed + // `http://localhost:` redirect captured by the host's own login + // server — redirecting a browser there from this web flow would be a + // dead-end consent (CL-7510). These providers connect through the + // sidecar lane instead, so both routes refuse them with a typed error. + function loopbackRefusalBody(connectorId: string) { + return { + error: { + code: "loopback_connector", + message: `${connectorId} connects through a local loopback OAuth login, not this web redirect flow.`, + }, + }; + } + function findOAuthDescriptor(connectorId: string) { const descriptor = registry[connectorId]; if (descriptor === undefined || descriptor.oauth === undefined) { @@ -314,6 +328,9 @@ export function createOAuthConnectRoutes( 404, ); } + if (descriptor.authKind === "oauth-loopback") { + return c.json(loopbackRefusalBody(connectorId), 409); + } const returnPath = sanitizeReturnPath( c.req.query("return"), @@ -427,6 +444,9 @@ export function createOAuthConnectRoutes( 404, ); } + if (descriptor.authKind === "oauth-loopback") { + return c.json(loopbackRefusalBody(connectorId), 409); + } // The cookie is never trusted any more than the query param was at // /start -- it is still attacker-influenced input (this same diff --git a/packages/connections/src/routes.test.ts b/packages/connections/src/routes.test.ts index 8897816811..4310723497 100644 --- a/packages/connections/src/routes.test.ts +++ b/packages/connections/src/routes.test.ts @@ -112,6 +112,24 @@ const FAKE_REGISTRY: Readonly> = { exchange: async () => ({ ok: true, apiKey: "unused" }), }, }, + "loopback-connector": { + id: "loopback-connector", + displayName: "Loopback Connector", + authKind: "oauth-loopback", + credentialPlugin: "http", + docsUrl: "https://example.test/docs", + feedsTools: [], + oauth: { + authorizeUrl: "https://example.test/authorize", + usesPKCE: true, + echoesState: true, + deploysDefaultWorkflows: true, + clientId: () => "public-client-id", + buildAuthorizeUrl: ({ state }) => + new URL(`https://example.test/authorize?state=${state}`), + exchange: async () => ({ ok: true, apiKey: "unused" }), + }, + }, }; function mountAs(routes: Hono): Hono { @@ -214,6 +232,13 @@ describe("GET /oauth-configured", () => { expect(body["accepting-connector"]).toBeUndefined(); expect(body["display-only-connector"]).toBeUndefined(); }); + + test("omits loopback connectors -- they never route through this web redirect flow", async () => { + const app = buildApp(); + const response = await app.request("/oauth-configured"); + const body = (await response.json()) as Record; + expect(body["loopback-connector"]).toBeUndefined(); + }); }); describe("POST /:connectorId/complete", () => { diff --git a/packages/connections/src/routes.ts b/packages/connections/src/routes.ts index 88d2823efc..8fa49114b4 100644 --- a/packages/connections/src/routes.ts +++ b/packages/connections/src/routes.ts @@ -336,6 +336,10 @@ export function createConnectionRoutes( const configured: Record = {}; for (const [id, descriptor] of Object.entries(registry)) { if (descriptor.oauth === undefined) continue; + // A loopback connector is never reachable through this web flow + // (its start/callback routes refuse it), so it is never reported + // as configured here either. + if (descriptor.authKind === "oauth-loopback") continue; const hasClientId = descriptor.oauth.clientId === undefined || descriptor.oauth.clientId(oauthEnv) !== undefined; diff --git a/templates/connectors.test.ts b/templates/connectors.test.ts index 04d6faadbb..1faf90572b 100644 --- a/templates/connectors.test.ts +++ b/templates/connectors.test.ts @@ -148,6 +148,19 @@ describe("CONNECTOR_REGISTRY", () => { "http://localhost:1455/auth/callback", ); expect(codexUrl?.searchParams.get("originator")).toBe("codex_cli_rs"); + // Pinned against the upstream Codex CLI's CODEX_SCOPES list — the + // api.connectors.* scopes are what let the backend serve connector + // tools to this client, and the authorization server issues them only + // to the Codex client id. When the @corbits/codex-provider package + // lands (S2), assert against its exported constant instead. + expect(codexUrl?.searchParams.get("scope")?.split(" ")).toEqual([ + "openid", + "profile", + "email", + "offline_access", + "api.connectors.read", + "api.connectors.invoke", + ]); const xaiUrl = CONNECTOR_REGISTRY["xai-oauth"]?.oauth?.buildAuthorizeUrl({ callbackUrl: "https://bench.example.com/ignored", diff --git a/templates/connectors.ts b/templates/connectors.ts index 7c76814515..948e6c1dec 100644 --- a/templates/connectors.ts +++ b/templates/connectors.ts @@ -292,7 +292,14 @@ function inferenceProviderDescriptors(): Record { "http://localhost:1455/auth/callback", ); url.searchParams.set("response_type", "code"); - url.searchParams.set("scope", "openid profile email offline_access"); + // Exactly the upstream Codex CLI's scope list (CODEX_SCOPES): the + // two api.connectors.* scopes let the backend serve connector + // tools to this client, and the authorization server issues them + // only to the Codex client id. + url.searchParams.set( + "scope", + "openid profile email offline_access api.connectors.read api.connectors.invoke", + ); url.searchParams.set("state", state); if (codeChallenge !== undefined) { url.searchParams.set("code_challenge", codeChallenge); From d2ceb57cce9c88fabdd52bdcac1b56cd49f6a3cb Mon Sep 17 00:00:00 2001 From: Sawyer Date: Mon, 7 Sep 2026 23:31:07 -0700 Subject: [PATCH 6/8] Register the codex and xai-oauth adapters in the sidecar manifest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DEFAULT_ADAPTER_MANIFEST gains codex -> createCodexResponsesAdapter and xai-oauth -> createXaiResponsesAdapter (workspace deps, hoisted at the root like @corbits/ollama-adapter so the vendored manifest loader can import them). Catalog provider rows keep the wire-format plugin "openai-responses"; the launch-resolution seam in @intx/db's buildSource — a recorded local delta on the vendor/intx/db row with its kill-date hash — rewrites only the launched InferenceSource.provider to the adapter registry key, the same correction withOllamaAdapterKey used to perform (the ollama entry in the rewrite table also makes the shipped ollama manifest key actually reachable). Also drops the conflicting noEmit from the ported provider packages' referenced tsconfigs. --- VENDORED.md | 36 ++++----- apps/sidecar/package.json | 2 + apps/sidecar/src/config.ts | 16 +++- apps/sidecar/test/config.test.ts | 27 +++++++ apps/sidecar/tsconfig.json | 6 ++ apps/sidecar/tsconfig.src.json | 6 ++ bun.lock | 4 + package.json | 2 + packages/codex-provider/tsconfig.src.json | 1 - packages/xai-provider/tsconfig.src.json | 1 - scripts/checks/kill-dates.txt | 2 +- .../db/src/model-source-resolution.test.ts | 73 ++++++++++++++++++- vendor/intx/db/src/model-source-resolution.ts | 25 ++++++- 13 files changed, 176 insertions(+), 25 deletions(-) diff --git a/VENDORED.md b/VENDORED.md index a131cee982..4ba4adc088 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -22,24 +22,24 @@ never a convenience. ## Ledger -| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | -| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; and a CL-7505 serving-time refresh delta (`model-source-resolution.ts`: `buildSource`/`resolveModelSources`/`resolveInstanceModelSources` accept an optional `servingRefresh` hook, exposing `buildSource`, with a new `credential_needs_reauth` skip so an unrefreshable `oauth_token` credential is never served); one more local delta (CL-7510): `"openai-responses"` added to `schema/catalog.ts`'s `modelProvider.plugin` enum, in lockstep with the `@intx/types` delta; retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); and a CL-7505 serving-time refresh delta (`run-source-resolution.ts`: `resolveDefinitionSources` accepts and threads an optional `servingRefresh` hook); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; and a CL-7505 serving-time refresh delta (`credential-push.ts`: `pushInstanceSourceUpdate`/`pushSourceUpdates`/`pushSourceUpdatesSubtree` accept and thread an optional `servingRefresh` hook); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; one more local delta (CL-7510): `"openai-responses"` added to `catalog.ts`'s `modelProviderPlugins` so the workbench's loopback-OAuth providers (Codex, xai-oauth) can create model-provider rows; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | +| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; and a CL-7505 serving-time refresh delta (`model-source-resolution.ts`: `buildSource`/`resolveModelSources`/`resolveInstanceModelSources` accept an optional `servingRefresh` hook, exposing `buildSource`, with a new `credential_needs_reauth` skip so an unrefreshable `oauth_token` credential is never served); one more local delta (CL-7510): `"openai-responses"` added to `schema/catalog.ts`'s `modelProvider.plugin` enum, in lockstep with the `@intx/types` delta, and an adapter registry-key dispatch delta in `model-source-resolution.ts` (`buildSource` rewrites the launched `InferenceSource.provider` to the provider's own registry key — `ollama`, `codex`, `xai-oauth` — while the catalog `plugin` column keeps the wire-format id, so the sidecar manifest's custom factories actually resolve); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); and a CL-7505 serving-time refresh delta (`run-source-resolution.ts`: `resolveDefinitionSources` accepts and threads an optional `servingRefresh` hook); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; and a CL-7505 serving-time refresh delta (`credential-push.ts`: `pushInstanceSourceUpdate`/`pushSourceUpdates`/`pushSourceUpdatesSubtree` accept and thread an optional `servingRefresh` hook); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; one more local delta (CL-7510): `"openai-responses"` added to `catalog.ts`'s `modelProviderPlugins` so the workbench's loopback-OAuth providers (Codex, xai-oauth) can create model-provider rows; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | The pin is `692c3106` (upstream `origin/main`, 2026-09-03), 90 commits past the `v0.3.0` release tag `b5580a02`. npm is still `0.3.0`, so every tree a diff --git a/apps/sidecar/package.json b/apps/sidecar/package.json index 3ec85acec7..5ce0a3b537 100644 --- a/apps/sidecar/package.json +++ b/apps/sidecar/package.json @@ -16,9 +16,11 @@ }, "dependencies": { "@corbits/agent-lifecycle": "workspace:*", + "@corbits/codex-provider": "workspace:*", "@corbits/credential-providers": "workspace:*", "@corbits/error-sink": "workspace:*", "@corbits/ollama-adapter": "workspace:*", + "@corbits/xai-provider": "workspace:*", "@intx/agent": "workspace:*", "@intx/authz": "0.3.0", "@intx/crypto": "0.3.0", diff --git a/apps/sidecar/src/config.ts b/apps/sidecar/src/config.ts index d1ecd32993..3ce5a76861 100644 --- a/apps/sidecar/src/config.ts +++ b/apps/sidecar/src/config.ts @@ -17,7 +17,11 @@ import { parseToolRegistries } from "./tool-materialization"; * Ollama deployment's `quirks.numCtx` (see `@corbits/seeding`'s * seed and `./workflow-substrate-factory/context-budget`) actually * reaches Ollama's `options.num_ctx` instead of silently falling back - * to the built-in adapter's defaults. An operator who sets + * to the built-in adapter's defaults, and the CL-7510 loopback-OAuth + * Responses adapters (`codex`, `xai-oauth`) so a launched source — + * rewritten to the provider's own registry key at + * `@intx/db`'s `buildSource` — serves through its own adapter rather + * than the shared wire-format one. An operator who sets * `SIDECAR_ADAPTER_MANIFEST` explicitly gets exactly what they wrote -- * this default never merges with an operator value, only replaces the * unset case. @@ -28,6 +32,16 @@ const DEFAULT_ADAPTER_MANIFEST: AdapterManifest = [ specifier: "@corbits/ollama-adapter", export: "createOllamaAdapter", }, + { + provider: "codex", + specifier: "@corbits/codex-provider", + export: "createCodexResponsesAdapter", + }, + { + provider: "xai-oauth", + specifier: "@corbits/xai-provider", + export: "createXaiResponsesAdapter", + }, ]; const WsURL = type("string").narrow((url, ctx) => { diff --git a/apps/sidecar/test/config.test.ts b/apps/sidecar/test/config.test.ts index c385177776..df326b36d0 100644 --- a/apps/sidecar/test/config.test.ts +++ b/apps/sidecar/test/config.test.ts @@ -15,6 +15,16 @@ const DEFAULT_MANIFEST = [ specifier: "@corbits/ollama-adapter", export: "createOllamaAdapter", }, + { + provider: "codex", + specifier: "@corbits/codex-provider", + export: "createCodexResponsesAdapter", + }, + { + provider: "xai-oauth", + specifier: "@corbits/xai-provider", + export: "createXaiResponsesAdapter", + }, ]; test("parses a complete environment into config", () => { @@ -39,6 +49,23 @@ test("an unset SIDECAR_ADAPTER_MANIFEST defaults to the shipped Ollama adapter, expect(config.adapterManifest).toEqual(DEFAULT_MANIFEST); }); +test("the shipped default manifest registers the loopback-OAuth Responses adapters (CL-7510)", () => { + const config = readSidecarConfig(VALID_ENV); + const byProvider = new Map( + config.adapterManifest.map((entry) => [entry.provider, entry]), + ); + expect(byProvider.get("codex")).toEqual({ + provider: "codex", + specifier: "@corbits/codex-provider", + export: "createCodexResponsesAdapter", + }); + expect(byProvider.get("xai-oauth")).toEqual({ + provider: "xai-oauth", + specifier: "@corbits/xai-provider", + export: "createXaiResponsesAdapter", + }); +}); + test("an operator-set adapter manifest fully replaces the default rather than merging with it", () => { const manifest = [ { diff --git a/apps/sidecar/tsconfig.json b/apps/sidecar/tsconfig.json index 486e36c46d..7ce2ae3a6a 100644 --- a/apps/sidecar/tsconfig.json +++ b/apps/sidecar/tsconfig.json @@ -16,6 +16,9 @@ { "path": "../../packages/agent-lifecycle/tsconfig.src.json" }, + { + "path": "../../packages/codex-provider/tsconfig.src.json" + }, { "path": "../../packages/credential-providers/tsconfig.src.json" }, @@ -25,6 +28,9 @@ { "path": "../../packages/ollama-adapter/tsconfig.src.json" }, + { + "path": "../../packages/xai-provider/tsconfig.src.json" + }, { "path": "../../vendor/intx/agent/tsconfig.src.json" }, diff --git a/apps/sidecar/tsconfig.src.json b/apps/sidecar/tsconfig.src.json index e1cd317710..6163aa3b45 100644 --- a/apps/sidecar/tsconfig.src.json +++ b/apps/sidecar/tsconfig.src.json @@ -13,6 +13,9 @@ { "path": "../../packages/agent-lifecycle/tsconfig.src.json" }, + { + "path": "../../packages/codex-provider/tsconfig.src.json" + }, { "path": "../../packages/credential-providers/tsconfig.src.json" }, @@ -22,6 +25,9 @@ { "path": "../../packages/ollama-adapter/tsconfig.src.json" }, + { + "path": "../../packages/xai-provider/tsconfig.src.json" + }, { "path": "../../vendor/intx/agent/tsconfig.src.json" }, diff --git a/bun.lock b/bun.lock index 3256e3efa5..48e04f0658 100644 --- a/bun.lock +++ b/bun.lock @@ -10,11 +10,13 @@ "devDependencies": { "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#dc435ba749b4378f19e5de772159c2a57e932938", "@corbits/artifacts-hub": "workspace:*", + "@corbits/codex-provider": "workspace:*", "@corbits/connections": "workspace:*", "@corbits/evals": "workspace:*", "@corbits/ollama-adapter": "workspace:*", "@corbits/run-scope": "workspace:*", "@corbits/webhook-triggers": "workspace:*", + "@corbits/xai-provider": "workspace:*", "@eslint/js": "^10.0.0", "@intx/crypto": "workspace:*", "@intx/db": "workspace:*", @@ -110,9 +112,11 @@ }, "dependencies": { "@corbits/agent-lifecycle": "workspace:*", + "@corbits/codex-provider": "workspace:*", "@corbits/credential-providers": "workspace:*", "@corbits/error-sink": "workspace:*", "@corbits/ollama-adapter": "workspace:*", + "@corbits/xai-provider": "workspace:*", "@intx/agent": "workspace:*", "@intx/authz": "0.3.0", "@intx/crypto": "0.3.0", diff --git a/package.json b/package.json index 0acdf99d1d..c0e98fa170 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,10 @@ "devDependencies": { "@corbits/artifacts": "github:corbitsdev/corbits-artifacts#dc435ba749b4378f19e5de772159c2a57e932938", "@corbits/artifacts-hub": "workspace:*", + "@corbits/codex-provider": "workspace:*", "@corbits/evals": "workspace:*", "@corbits/ollama-adapter": "workspace:*", + "@corbits/xai-provider": "workspace:*", "@intx/db": "workspace:*", "@intx/crypto": "workspace:*", "@intx/log": "workspace:*", diff --git a/packages/codex-provider/tsconfig.src.json b/packages/codex-provider/tsconfig.src.json index e74c588ea8..23572def72 100644 --- a/packages/codex-provider/tsconfig.src.json +++ b/packages/codex-provider/tsconfig.src.json @@ -4,7 +4,6 @@ "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"], "compilerOptions": { "types": ["bun"], - "noEmit": true, "composite": true, "emitDeclarationOnly": true, "outDir": "dist", diff --git a/packages/xai-provider/tsconfig.src.json b/packages/xai-provider/tsconfig.src.json index e74c588ea8..23572def72 100644 --- a/packages/xai-provider/tsconfig.src.json +++ b/packages/xai-provider/tsconfig.src.json @@ -4,7 +4,6 @@ "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"], "compilerOptions": { "types": ["bun"], - "noEmit": true, "composite": true, "emitDeclarationOnly": true, "outDir": "dist", diff --git a/scripts/checks/kill-dates.txt b/scripts/checks/kill-dates.txt index b5d3766ea9..fdfaa36a38 100644 --- a/scripts/checks/kill-dates.txt +++ b/scripts/checks/kill-dates.txt @@ -15,7 +15,7 @@ # package's VENDORED-FROM delta line in the same change. apps/sidecar | sawyer | 2026-11-03 vendor/intx/agent | sawyer | 2026-11-03 | 9dd132fda8d7aef9d70395de2d289abeb0361416b18a5079f219372df69d81a0 -vendor/intx/db | sawyer | 2026-11-03 | 5932d23737805aa42a2448ccf2f5126e7b0ef0390ded5ddd5184695a4e1c2868 +vendor/intx/db | sawyer | 2026-11-03 | 2d0f5ddde3a8f9313b7d274fcc9a62d743638cb3ea4078ad4176390bde859136 vendor/intx/harness | sawyer | 2026-11-03 | 399b4e8b3b18e8969e815427625e1bfc6746f31e791fb92cdbc2aaf2c5080dbf vendor/intx/hub-agent | sawyer | 2026-11-03 | 0a24a0cf85cc0483dbeee815d89847440e2fbd1242fdfd992941379a9acc2a4a vendor/intx/hub-api | sawyer | 2026-11-03 | 90aa5cddef828d77b62adb63186dcf82b5465926cafea1ab6b64bca263031b57 diff --git a/vendor/intx/db/src/model-source-resolution.test.ts b/vendor/intx/db/src/model-source-resolution.test.ts index c0a94220b6..ff00fcea1c 100644 --- a/vendor/intx/db/src/model-source-resolution.test.ts +++ b/vendor/intx/db/src/model-source-resolution.test.ts @@ -32,7 +32,7 @@ mock.module("./parse-row", () => ({ parseModelOfferingRow: (row: Record) => ({ ...row, capabilities: [], - quirks: null, + quirks: (row.quirks as unknown) ?? null, }), })); @@ -121,3 +121,74 @@ describe("buildSource servingRefresh seam (CL-7505)", () => { if (built.ok) expect(built.material.secret).toBe("plain:enc:old"); }); }); + +// CL-7510 local delta: provider-name → adapter-registry-key dispatch. The +// catalog `plugin` column stays the wire-format id ("openai-responses" for +// the loopback-OAuth providers, "openai-compatible" for Ollama); only the +// launched InferenceSource.provider is rewritten to the key the sidecar's +// DEFAULT_ADAPTER_MANIFEST registers the custom factory under. +describe("buildSource adapter registry-key dispatch (CL-7510)", () => { + resolvedRows["cred_1"] = row("api_key"); + + test("a codex offering dispatches under the codex registry key with its quirks threaded", async () => { + const codexOffering = { + offering: { + id: "off_codex", + priority: 0, + capabilities: [], + quirks: { + productName: "Codex", + environmentTagName: "codex_environment", + }, + }, + provider: { + id: "prov_codex", + name: "codex", + plugin: "openai-responses", + baseURL: "https://chatgpt.com/backend-api", + credentialId: "cred_1", + walletId: null, + }, + model: { canonicalName: "gpt-5.5" }, + } as unknown as Parameters[2]; + + const built = await buildSource({} as never, "ten_1", codexOffering, cipher); + expect(built.ok).toBe(true); + if (built.ok) { + expect(built.source.provider).toBe("codex"); + expect(built.source.quirks).toEqual({ + productName: "Codex", + environmentTagName: "codex_environment", + }); + expect(built.source.baseURL).toBe("https://chatgpt.com/backend-api"); + } + }); + + test("an xai-oauth offering dispatches under the xai-oauth registry key", async () => { + const xaiOffering = { + offering: { id: "off_xai", priority: 0, capabilities: [] }, + provider: { + id: "prov_xai", + name: "xai-oauth", + plugin: "openai-responses", + baseURL: "https://cli-chat-proxy.grok.com/v1", + credentialId: "cred_1", + walletId: null, + }, + model: { canonicalName: "grok-4.6" }, + } as unknown as Parameters[2]; + + const built = await buildSource({} as never, "ten_1", xaiOffering, cipher); + expect(built.ok).toBe(true); + if (built.ok) expect(built.source.provider).toBe("xai-oauth"); + }); + + test("every other provider keeps its plugin as the dispatch key", async () => { + const built = await buildSource({} as never, "ten_1", offering, cipher); + expect(built.ok).toBe(true); + if (built.ok) { + expect(built.source.provider).toBe("test-plugin"); + expect(built.material.providerKey).toBe("test-plugin"); + } + }); +}); diff --git a/vendor/intx/db/src/model-source-resolution.ts b/vendor/intx/db/src/model-source-resolution.ts index 3060497aa2..2370e5cffd 100644 --- a/vendor/intx/db/src/model-source-resolution.ts +++ b/vendor/intx/db/src/model-source-resolution.ts @@ -20,6 +20,19 @@ import { resolveCredentialById } from "./credential-resolution"; import { parseModelOfferingRow } from "./parse-row"; import { workflowDefinition } from "./schema/workflow-definitions"; +// CL-7510 local delta: providers whose serving adapter is a workbench-side +// custom factory registered under the provider's own name +// (`apps/sidecar/src/config.ts`'s DEFAULT_ADAPTER_MANIFEST). A catalog +// provider row named here launches under that registry key instead of its +// wire-format `plugin`, the same correction `withOllamaAdapterKey` used to +// make at the retired folded-runs launch seam. +const ADAPTER_REGISTRY_KEY_BY_PROVIDER_NAME: Readonly> = + { + ollama: "ollama", + codex: "codex", + "xai-oauth": "xai-oauth", + }; + /** * Why a single offering could not be turned into a launchable source. * `wallet_backed` providers are storable in the catalog but not launchable @@ -257,11 +270,19 @@ export async function buildSource( credentialAad(credential.id, "secret"), ); const parsed = parseModelOfferingRow(offering); + // CL-7510 local delta: adapter registry-key dispatch. The catalog + // `plugin` column stays the wire-format id; a provider whose custom + // factory is registered under its own name (the ollama precedent, + // `apps/sidecar/src/config.ts`'s DEFAULT_ADAPTER_MANIFEST — codex and + // xai-oauth ride the same mechanism) launches under that registry key, + // so an offering's `quirks` reaches the adapter that understands them + // instead of the built-in wire-format adapter. + const registryKey = ADAPTER_REGISTRY_KEY_BY_PROVIDER_NAME[provider.name] ?? provider.plugin; return { ok: true, source: { id: offering.id, - provider: provider.plugin, + provider: registryKey, baseURL: provider.baseURL, credentialId: provider.credentialId, model: model.canonicalName, @@ -270,7 +291,7 @@ export async function buildSource( }, material: { credentialId: provider.credentialId, - providerKey: provider.plugin, + providerKey: registryKey, origin: provider.baseURL, secret, }, From c3ae54a5821c67329edb50465d4a837ab76117ed Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 8 Sep 2026 00:00:26 -0700 Subject: [PATCH 7/8] Source the loopback descriptors from the provider packages' constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inline codex/xai-oauth literals duplicated values the ported provider packages now export and carried a stale 'until the packages land' comment; both descriptors now read the packages' constants subpath (new browser-safe export: constants.ts is pure data, so the web bundle can keep building — the exchange closures stay plain fetch because the oauth-core-backed exchange helpers are node-only). VENDORED.md's vendor/intx/db delta note now names both fields the launch seam rewrites (InferenceSource.provider and the credential material's providerKey). --- VENDORED.md | 36 +++++------ bun.lock | 14 ++++- packages/codex-provider/package.json | 3 +- packages/xai-provider/package.json | 3 +- templates/connectors.ts | 91 ++++++++++++++-------------- templates/package.json | 2 + 6 files changed, 82 insertions(+), 67 deletions(-) diff --git a/VENDORED.md b/VENDORED.md index 4ba4adc088..f399198182 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -22,24 +22,24 @@ never a convenience. ## Ledger -| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | -| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; and a CL-7505 serving-time refresh delta (`model-source-resolution.ts`: `buildSource`/`resolveModelSources`/`resolveInstanceModelSources` accept an optional `servingRefresh` hook, exposing `buildSource`, with a new `credential_needs_reauth` skip so an unrefreshable `oauth_token` credential is never served); one more local delta (CL-7510): `"openai-responses"` added to `schema/catalog.ts`'s `modelProvider.plugin` enum, in lockstep with the `@intx/types` delta, and an adapter registry-key dispatch delta in `model-source-resolution.ts` (`buildSource` rewrites the launched `InferenceSource.provider` to the provider's own registry key — `ollama`, `codex`, `xai-oauth` — while the catalog `plugin` column keeps the wire-format id, so the sidecar manifest's custom factories actually resolve); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); and a CL-7505 serving-time refresh delta (`run-source-resolution.ts`: `resolveDefinitionSources` accepts and threads an optional `servingRefresh` hook); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; and a CL-7505 serving-time refresh delta (`credential-push.ts`: `pushInstanceSourceUpdate`/`pushSourceUpdates`/`pushSourceUpdatesSubtree` accept and thread an optional `servingRefresh` hook); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; one more local delta (CL-7510): `"openai-responses"` added to `catalog.ts`'s `modelProviderPlugins` so the workbench's loopback-OAuth providers (Codex, xai-oauth) can create model-provider rows; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | +| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; and a CL-7505 serving-time refresh delta (`model-source-resolution.ts`: `buildSource`/`resolveModelSources`/`resolveInstanceModelSources` accept an optional `servingRefresh` hook, exposing `buildSource`, with a new `credential_needs_reauth` skip so an unrefreshable `oauth_token` credential is never served); one more local delta (CL-7510): `"openai-responses"` added to `schema/catalog.ts`'s `modelProvider.plugin` enum, in lockstep with the `@intx/types` delta, and an adapter registry-key dispatch delta in `model-source-resolution.ts` (`buildSource` rewrites both the launched `InferenceSource.provider` and the served credential material's `providerKey` to the provider's own registry key — `ollama`, `codex`, `xai-oauth` — while the catalog `plugin` column keeps the wire-format id, so the sidecar manifest's custom factories actually resolve); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); and a CL-7505 serving-time refresh delta (`run-source-resolution.ts`: `resolveDefinitionSources` accepts and threads an optional `servingRefresh` hook); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; and a CL-7505 serving-time refresh delta (`credential-push.ts`: `pushInstanceSourceUpdate`/`pushSourceUpdates`/`pushSourceUpdatesSubtree` accept and thread an optional `servingRefresh` hook); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; one more local delta (CL-7510): `"openai-responses"` added to `catalog.ts`'s `modelProviderPlugins` so the workbench's loopback-OAuth providers (Codex, xai-oauth) can create model-provider rows; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | The pin is `692c3106` (upstream `origin/main`, 2026-09-03), 90 commits past the `v0.3.0` release tag `b5580a02`. npm is still `0.3.0`, so every tree a diff --git a/bun.lock b/bun.lock index 48e04f0658..6fa6780691 100644 --- a/bun.lock +++ b/bun.lock @@ -1531,10 +1531,12 @@ "version": "0.0.1", "dependencies": { "@corbits/code-review": "workspace:*", + "@corbits/codex-provider": "workspace:*", "@corbits/connections": "workspace:*", "@corbits/jimmy-agent": "workspace:*", "@corbits/scout-agent": "workspace:*", "@corbits/workflows": "workspace:*", + "@corbits/xai-provider": "workspace:*", "arktype": "catalog:", "simple-icons": "^16.28.0", }, @@ -3621,10 +3623,12 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@corbits/connections/@corbits/oauth-core": ["@corbits/oauth-core@github:corbitsdev/corbits-oauth-core#e1e69e6", { "dependencies": { "arktype": "2.2.3" } }, "corbitsdev-corbits-oauth-core-e1e69e6", "sha512-wUrD73iVyk/Dtb4yRn3hCh6N8syfsEvAkRz4XYqT0FIB3sYDSMYEWHcDMJx9nq64my/HBOeN1J6PHIOtpjhtPg=="], + "@corbits/inbox/@corbits/mailbox": ["@corbits/mailbox@github:corbitsdev/corbits-mailbox#118a2cd", { "dependencies": { "@hono/standard-validator": "0.2.3", "@standard-community/standard-json": "0.3.5", "@standard-community/standard-openapi": "0.2.9", "arktype": "2.1.29", "hono-openapi": "1.3.1" }, "peerDependencies": { "@intx/log": "^0.2.2", "@intx/mime": "^0.2.2", "@intx/types": "^0.2.2", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0" } }, "corbitsdev-corbits-mailbox-118a2cd", "sha512-jVRl6/IH34JBPVUz7mqWyBm1w7uJxvYAI27gjw7zL1dFO4L+D0E5q/8b+mpwKMNOKKlGXU6UF5pdHnY8ilYx8g=="], "@corbits/mailbox/hono-openapi": ["hono-openapi@1.3.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.11.2", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw=="], + "@corbits/memory-hub/@corbits/memory": ["@corbits/memory@github:corbitsdev/corbits-memory#9e6f213", { "dependencies": { "@intx/agent": "0.2.2", "@intx/authz": "0.2.2", "@intx/hub-api": "0.2.2", "@intx/log": "0.2.2", "@intx/workflow": "0.2.2", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "hono": "^4.9.0", "hono-openapi": "^1.3.1", "postgres": "^3.4.7" } }, "corbitsdev-corbits-memory-9e6f213", "sha512-utnM4ZT2zmslcPXYWAAqxlDNLcpGsXFiTOtj8h7+OXnhCP0Eaw8yl25+yCTyHpvt3jcdeG4h5uFsSj7ou0BZCA=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -3647,6 +3651,10 @@ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.8", "", {}, "sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q=="], + "@workbench/hub/@corbits/mailbox": ["@corbits/mailbox@github:corbitsdev/corbits-mailbox#118a2cd", { "dependencies": { "@hono/standard-validator": "0.2.3", "@standard-community/standard-json": "0.3.5", "@standard-community/standard-openapi": "0.2.9", "arktype": "2.1.29", "hono-openapi": "1.3.1" }, "peerDependencies": { "@intx/log": "^0.2.2", "@intx/mime": "^0.2.2", "@intx/types": "^0.2.2", "drizzle-orm": "^0.45.2", "hono": "^4.12.0", "postgres": "^3.4.0" } }, "corbitsdev-corbits-mailbox-118a2cd", "sha512-jVRl6/IH34JBPVUz7mqWyBm1w7uJxvYAI27gjw7zL1dFO4L+D0E5q/8b+mpwKMNOKKlGXU6UF5pdHnY8ilYx8g=="], + + "@workbench/hub/@corbits/memory": ["@corbits/memory@github:corbitsdev/corbits-memory#9e6f213", { "dependencies": { "@intx/agent": "0.2.2", "@intx/authz": "0.2.2", "@intx/hub-api": "0.2.2", "@intx/log": "0.2.2", "@intx/workflow": "0.2.2", "arktype": "^2.1.29", "drizzle-orm": "^0.45.1", "hono": "^4.9.0", "hono-openapi": "^1.3.1", "postgres": "^3.4.7" } }, "corbitsdev-corbits-memory-9e6f213", "sha512-utnM4ZT2zmslcPXYWAAqxlDNLcpGsXFiTOtj8h7+OXnhCP0Eaw8yl25+yCTyHpvt3jcdeG4h5uFsSj7ou0BZCA=="], + "ajv-formats/ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "better-call/@better-auth/utils": ["@better-auth/utils@0.5.0", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-BL8W4EfIZFwlu0r54m3v1ztjDhu6dDe/amLTm0xybmbZaNgYUqhD3SjpAsnq0q8YD6/ki4iwIgxJNLP/N3TxiA=="], @@ -3701,6 +3709,8 @@ "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "@corbits/inbox/@corbits/mailbox/hono-openapi": ["hono-openapi@1.3.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.11.2", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -3747,6 +3757,8 @@ "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "@workbench/hub/@corbits/mailbox/hono-openapi": ["hono-openapi@1.3.1", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.11.2", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw=="], + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], diff --git a/packages/codex-provider/package.json b/packages/codex-provider/package.json index 9c00997fc8..bec79e38c2 100644 --- a/packages/codex-provider/package.json +++ b/packages/codex-provider/package.json @@ -7,7 +7,8 @@ "type": "module", "sideEffects": false, "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./constants": "./src/constants.ts" }, "scripts": { "typecheck": "tsc --noEmit", diff --git a/packages/xai-provider/package.json b/packages/xai-provider/package.json index 335fa77a9f..ce24db8526 100644 --- a/packages/xai-provider/package.json +++ b/packages/xai-provider/package.json @@ -7,7 +7,8 @@ "type": "module", "sideEffects": false, "exports": { - ".": "./src/index.ts" + ".": "./src/index.ts", + "./constants": "./src/constants.ts" }, "scripts": { "typecheck": "tsc --noEmit", diff --git a/templates/connectors.ts b/templates/connectors.ts index 948e6c1dec..15b95ac099 100644 --- a/templates/connectors.ts +++ b/templates/connectors.ts @@ -64,6 +64,21 @@ import { siRailway, siSentry, } from "simple-icons"; +import { + CODEX_AUTHORIZE_EXTRA_PARAMS, + CODEX_AUTHORIZE_URL, + CODEX_CLIENT_ID, + CODEX_REDIRECT_URI, + CODEX_SCOPES, + CODEX_TOKEN_URL, +} from "@corbits/codex-provider/constants"; +import { + XAI_AUTHORIZE_URL, + XAI_CLIENT_ID, + XAI_REDIRECT_URI, + XAI_SCOPES, + XAI_TOKEN_URL, +} from "@corbits/xai-provider/constants"; import { exchangeCodeForKey, OPENROUTER_AUTH_URL, @@ -265,9 +280,11 @@ function inferenceProviderDescriptors(): Record { // the provider's own CLI registers (`authKind: "oauth-loopback"`), so // `buildAuthorizeUrl` ignores the hub callback URL the generic // mechanics would pass and pins the provider's own redirect instead. - // The constants mirror the provider CLIs' public, non-secret values the - // same way `openrouter-connect.ts` mirrors OpenRouter's — until the - // provider packages land (S2), which will replace these literals. + // Every endpoint, client id, and scope list come from the provider + // packages' own constants subpath (browser-safe, pure data — the + // exchange closures stay plain `fetch` so the registry can ship in the + // web bundle), so the descriptor can never drift from the values the + // adapter's own refresh path uses. entries["codex"] = { id: "codex", displayName: PROVIDER_TEST_CONFIG.codex.displayName, @@ -276,46 +293,32 @@ function inferenceProviderDescriptors(): Record { docsUrl: INFERENCE_PROVIDER_DOCS_URL.codex, feedsTools: [], oauth: { - authorizeUrl: "https://auth.openai.com/oauth/authorize", + authorizeUrl: CODEX_AUTHORIZE_URL, usesPKCE: true, echoesState: true, deploysDefaultWorkflows: true, - // The Codex CLI's public client id — not a secret, and the - // authorization server only issues this flow's connector scopes to - // it. - clientId: () => "app_EMoamEEZ73f0CkXaXp7hrann", + clientId: () => CODEX_CLIENT_ID, buildAuthorizeUrl: ({ state, codeChallenge, clientId }) => { - const url = new URL("https://auth.openai.com/oauth/authorize"); - if (clientId !== undefined) url.searchParams.set("client_id", clientId); - url.searchParams.set( - "redirect_uri", - "http://localhost:1455/auth/callback", - ); + const url = new URL(CODEX_AUTHORIZE_URL); + url.searchParams.set("client_id", clientId ?? CODEX_CLIENT_ID); + url.searchParams.set("redirect_uri", CODEX_REDIRECT_URI); url.searchParams.set("response_type", "code"); - // Exactly the upstream Codex CLI's scope list (CODEX_SCOPES): the - // two api.connectors.* scopes let the backend serve connector - // tools to this client, and the authorization server issues them - // only to the Codex client id. - url.searchParams.set( - "scope", - "openid profile email offline_access api.connectors.read api.connectors.invoke", - ); + url.searchParams.set("scope", CODEX_SCOPES.join(" ")); url.searchParams.set("state", state); if (codeChallenge !== undefined) { url.searchParams.set("code_challenge", codeChallenge); } url.searchParams.set("code_challenge_method", "S256"); - // The Codex CLI's authorize request opts into a simplified consent - // screen and identifies the client; the authorization server - // rejects the plain flow for this client without these. - url.searchParams.set("codex_cli_simplified_flow", "true"); - url.searchParams.set("id_token_add_organizations", "true"); - url.searchParams.set("originator", "codex_cli_rs"); + for (const [name, value] of Object.entries( + CODEX_AUTHORIZE_EXTRA_PARAMS, + )) { + url.searchParams.set(name, value); + } return url; }, - exchange: async ({ code, codeVerifier, clientId }) => { + exchange: async ({ code, codeVerifier }) => { try { - const response = await fetch("https://auth.openai.com/oauth/token", { + const response = await fetch(CODEX_TOKEN_URL, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, signal: AbortSignal.timeout(10_000), @@ -323,8 +326,8 @@ function inferenceProviderDescriptors(): Record { grant_type: "authorization_code", code, code_verifier: codeVerifier ?? "", - client_id: clientId ?? "", - redirect_uri: "http://localhost:1455/auth/callback", + client_id: CODEX_CLIENT_ID, + redirect_uri: CODEX_REDIRECT_URI, }).toString(), }); if (!response.ok) { @@ -372,21 +375,17 @@ function inferenceProviderDescriptors(): Record { docsUrl: INFERENCE_PROVIDER_DOCS_URL["xai-oauth"], feedsTools: [], oauth: { - authorizeUrl: "https://auth.x.ai/oauth2/authorize", + authorizeUrl: XAI_AUTHORIZE_URL, usesPKCE: true, echoesState: true, deploysDefaultWorkflows: true, - // The grok CLI's public client id — not a secret. - clientId: () => "b1a00492-073a-47ea-816f-4c329264a828", + clientId: () => XAI_CLIENT_ID, buildAuthorizeUrl: ({ state, codeChallenge, clientId }) => { - const url = new URL("https://auth.x.ai/oauth2/authorize"); - if (clientId !== undefined) url.searchParams.set("client_id", clientId); - url.searchParams.set("redirect_uri", "http://127.0.0.1:1456/callback"); + const url = new URL(XAI_AUTHORIZE_URL); + url.searchParams.set("client_id", clientId ?? XAI_CLIENT_ID); + url.searchParams.set("redirect_uri", XAI_REDIRECT_URI); url.searchParams.set("response_type", "code"); - url.searchParams.set( - "scope", - "openid profile email offline_access grok-cli:access api:access", - ); + url.searchParams.set("scope", XAI_SCOPES.join(" ")); url.searchParams.set("state", state); if (codeChallenge !== undefined) { url.searchParams.set("code_challenge", codeChallenge); @@ -394,9 +393,9 @@ function inferenceProviderDescriptors(): Record { url.searchParams.set("code_challenge_method", "S256"); return url; }, - exchange: async ({ code, codeVerifier, clientId }) => { + exchange: async ({ code, codeVerifier }) => { try { - const response = await fetch("https://auth.x.ai/oauth2/token", { + const response = await fetch(XAI_TOKEN_URL, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, signal: AbortSignal.timeout(10_000), @@ -404,8 +403,8 @@ function inferenceProviderDescriptors(): Record { grant_type: "authorization_code", code, code_verifier: codeVerifier ?? "", - client_id: clientId ?? "", - redirect_uri: "http://127.0.0.1:1456/callback", + client_id: XAI_CLIENT_ID, + redirect_uri: XAI_REDIRECT_URI, }).toString(), }); if (!response.ok) { diff --git a/templates/package.json b/templates/package.json index 57897da0e0..cc034cc6a5 100644 --- a/templates/package.json +++ b/templates/package.json @@ -18,9 +18,11 @@ "dependencies": { "@corbits/code-review": "workspace:*", "@corbits/connections": "workspace:*", + "@corbits/codex-provider": "workspace:*", "@corbits/jimmy-agent": "workspace:*", "@corbits/scout-agent": "workspace:*", "@corbits/workflows": "workspace:*", + "@corbits/xai-provider": "workspace:*", "arktype": "catalog:", "simple-icons": "^16.28.0" }, From 9a764a38b65f3ea9af3722bb7f4090ae0b798466 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 8 Sep 2026 00:11:08 -0700 Subject: [PATCH 8/8] Enforce the constants subpaths as browser-safe --- scripts/checks/browser-safe-subpaths.ts | 6 ++++++ templates/connectors.test.ts | 18 +++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/scripts/checks/browser-safe-subpaths.ts b/scripts/checks/browser-safe-subpaths.ts index 0ba62e47c8..1313ae3637 100644 --- a/scripts/checks/browser-safe-subpaths.ts +++ b/scripts/checks/browser-safe-subpaths.ts @@ -78,6 +78,12 @@ export const ENTRIES: readonly BrowserSafeEntry[] = [ // wire schema, and the pure lifecycle derivation — the same read // `apps/web`'s workflow detail page (`workflow-detail-api.ts`) needs. { package: "@corbits/workflows", subpath: "./client" }, + // The loopback OAuth descriptors in templates/connectors.ts source + // their client ids, redirect URIs, and scopes from these pure-data + // constant files so the descriptor identity and the adapter refresh + // path cannot drift. Plain data, zero imports — enforced here. + { package: "@corbits/codex-provider", subpath: "./constants" }, + { package: "@corbits/xai-provider", subpath: "./constants" }, ]; const DENYLIST_PATTERNS: readonly RegExp[] = [ diff --git a/templates/connectors.test.ts b/templates/connectors.test.ts index 1faf90572b..de99a37b93 100644 --- a/templates/connectors.test.ts +++ b/templates/connectors.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "bun:test"; +import { CODEX_SCOPES } from "@corbits/codex-provider/constants"; import { connectorDescriptors } from "@corbits/connections/registry"; import { mcpPresetByName, @@ -148,19 +149,14 @@ describe("CONNECTOR_REGISTRY", () => { "http://localhost:1455/auth/callback", ); expect(codexUrl?.searchParams.get("originator")).toBe("codex_cli_rs"); - // Pinned against the upstream Codex CLI's CODEX_SCOPES list — the + // Pinned against the upstream Codex CLI's scope grant — the // api.connectors.* scopes are what let the backend serve connector // tools to this client, and the authorization server issues them only - // to the Codex client id. When the @corbits/codex-provider package - // lands (S2), assert against its exported constant instead. - expect(codexUrl?.searchParams.get("scope")?.split(" ")).toEqual([ - "openid", - "profile", - "email", - "offline_access", - "api.connectors.read", - "api.connectors.invoke", - ]); + // to the Codex client id. Asserted against the exported constant so + // the descriptor cannot drift from the adapter's refresh path. + expect(codexUrl?.searchParams.get("scope")?.split(" ")).toEqual( + CODEX_SCOPES, + ); const xaiUrl = CONNECTOR_REGISTRY["xai-oauth"]?.oauth?.buildAuthorizeUrl({ callbackUrl: "https://bench.example.com/ignored",