Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ SIDECAR_CREDENTIAL_ENCRYPTION_KEY=2222222222222222222222222222222222222222222222
# RERANK_MAX_DOC_CHARS=
# RERANK_TIMEOUT_MS=

# Adapters a sidecar loads for providers the stock runtime does not serve.
# A JSON list of {provider, specifier, export}; the specifier is imported at
# boot, so it is operator config only, never tenant or deploy data. These two
# entries are what "Continue with Codex" and "Continue with xAI" need in
# order to run inference once the sign-in has stored its credential.
SIDECAR_ADAPTER_MANIFEST=[{"provider":"codex","specifier":"@corbits/codex-provider","export":"createCodexResponsesAdapter"},{"provider":"xai","specifier":"@corbits/xai-provider","export":"createXaiResponsesAdapter"}]

# Sidecars run as child processes of the hub. Both optional.
# PROCESS_PROVISIONER_SIDECAR_ENTRY=
# PROCESS_PROVISIONER_RUNTIME=
Expand Down
1 change: 1 addition & 0 deletions apps/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@corbits/error-sink": "workspace:*",
"@corbits/mailbox": "github:corbitsdev/corbits-mailbox#65590a85fa143251b3ac20ba2eca92fc35e70e51",
"@corbits/memory": "github:corbitsdev/corbits-memory#e74da20f148a302dff5400915fe504ee2395e913",
"@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core#e97d563b823506d72f83dead478dd9fa9dccfd4c",
"@corbits/url-path": "workspace:*",
"@corbits/webhooks": "github:corbitsdev/webhooks#3f4f83147fd826d3aec5bcac7ef4b0ae7f75c00d",
"@corbits/workflows": "workspace:*",
Expand Down
4 changes: 4 additions & 0 deletions apps/hub/src/provisioners/process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,9 @@ function sidecarEnvFor(
}
const home = process.env["HOME"];
const tmpdir = process.env["TMPDIR"];
// Operator config, not tenant data: without it a sidecar has no adapter
// for a provider the stock runtime does not serve (Codex, xAI).
const adapterManifest = process.env["SIDECAR_ADAPTER_MANIFEST"];
return {
SIDECAR_DATA_DIR: sidecarDataDir,
HUB_WS_URL: args.hubWebSocketUrl,
Expand All @@ -324,6 +327,7 @@ function sidecarEnvFor(
PATH: path,
...(home === undefined ? {} : { HOME: home }),
...(tmpdir === undefined ? {} : { TMPDIR: tmpdir }),
...(adapterManifest === undefined ? {} : { SIDECAR_ADAPTER_MANIFEST: adapterManifest }),
};
}

Expand Down
38 changes: 38 additions & 0 deletions apps/hub/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ import {
mountMailbox,
} from "@corbits/mailbox";
import { createMemory, loadMemoryConfig } from "@corbits/memory";
import { mountOAuthLogin } from "@corbits/oauth-core/hub";
import { CODEX_PROVIDER, codexOAuthConfig, exchangeCodexCode } from "@corbits/codex-provider";
import { XAI_PROVIDER, xaiOAuthConfig, exchangeXaiCode } from "@corbits/xai-provider";
import { createCronTicker, createRunTriggerCronDeliver, mountCron } from "@corbits/cron";
import {
createHubMailboxAuthorizeSender,
Expand Down Expand Up @@ -632,6 +635,41 @@ export async function createHubServer({
app.route("/", memoryApp);
}

{
// "Continue with Codex"/"Continue with xAI": the whole loopback PKCE
// flow runs here, so the verifier and the callback listener never
// leave this process and the browser only learns a credential id.
const oauthLoginApi = new Hono<TenantEnv>();
const requireGrant = createRequireGrant({
grantStore,
conditionRegistry: grantConditionRegistry,
});
mountOAuthLogin(oauthLoginApi, {
db,
cipher: credentialCipher,
requireGrant: requireGrant("credential:*", "create"),
providers: {
[CODEX_PROVIDER]: {
oauthConfig: codexOAuthConfig,
exchange: (code, verifier, now) => exchangeCodexCode(code, verifier, now),
// The Codex backend rejects inference without this header value.
metadata: (tokens) =>
"accountId" in tokens && typeof tokens.accountId === "string"
? { accountId: tokens.accountId }
: {},
},
[XAI_PROVIDER]: {
oauthConfig: xaiOAuthConfig,
exchange: (code, verifier, now) => exchangeXaiCode(code, verifier, now),
},
},
onError: (error, { provider }) => {
reportError(error, { operation: "hub.oauth-login", extra: { provider } });
},
});
app.route(TENANT_PREFIX, oauthLoginApi);
}

await installWebhooks({
app,
db,
Expand Down
Loading
Loading