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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Roomote gitleaks configuration.
# Extends default rules; only add allowlist entries with a concrete reason.

title = "Roomote"

[allowlist]
description = "False positives and intentional public identifiers"

# Public Grok CLI OAuth client id reused by peer tools for SuperGrok device-code
# login. This is not a secret; it is the same client_id published by ecosystem
# agents (Hermes, OpenClaw, CC Switch) for auth.x.ai.
paths = [
'''packages/types/src/xai-subscription\.ts''',
]
regexes = [
'''b1a00492-073a-47ea-816f-4c329264a828''',
]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions apps/api/src/handlers/inference/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@roomote/types';
import {
getFreshChatGptAccessToken,
getFreshXaiAccessToken,
getGitHubCopilotAccessToken,
resolveModelProviderEnvValue,
} from '@roomote/db/server';
Expand All @@ -32,6 +33,8 @@ export type GatewayUpstreamResolution =
* - `api-key`: inject the deployment's static key at the request path.
* - `chatgpt-oauth`: mint a fresh subscription access token, add the
* account-id header, and collapse the request onto the Codex backend.
* - `xai-oauth`: prefer a connected Grok subscription access token, then
* fall back to `XAI_API_KEY`.
*/
export async function resolveGatewayUpstream(
provider: InferenceGatewayProvider,
Expand All @@ -46,6 +49,10 @@ export async function resolveGatewayUpstream(
return resolveGitHubCopilotUpstream(provider, upstreamPath, search);
}

if (provider.authStrategy === 'xai-oauth') {
return resolveXaiUpstream(provider, upstreamPath, search);
}

const [apiKey, upstreamBaseUrl] = await Promise.all([
resolveModelProviderEnvValue(provider.envVarNames),
resolveProviderUpstreamBaseUrl(provider),
Expand Down Expand Up @@ -76,6 +83,49 @@ export async function resolveGatewayUpstream(
};
}

/**
* xAI supports both SuperGrok OAuth and a BYOK API key. Prefer a connected
* subscription (fresh access token) so subscription users never need a key;
* fall back to the deployment key when only that is configured.
*/
async function resolveXaiUpstream(
provider: InferenceGatewayProvider,
upstreamPath: string,
search: string,
): Promise<GatewayUpstreamResolution> {
const [oauthToken, apiKey, upstreamBaseUrl] = await Promise.all([
getFreshXaiAccessToken(),
resolveModelProviderEnvValue(provider.envVarNames),
resolveProviderUpstreamBaseUrl(provider),
]);

const bearer = oauthToken?.access ?? apiKey;

if (!bearer) {
return {
ok: false,
status: 404,
error:
'No connected xAI Grok subscription or XAI_API_KEY is available for this deployment',
};
}

return {
ok: true,
resolved: {
upstreamUrl: `${upstreamBaseUrl}${upstreamPath}${search}`,
headers: provider.authHeader
? {
[provider.authHeader.name]: formatProviderAuthHeaderValue(
provider,
bearer,
),
}
: {},
},
};
}

async function resolveGitHubCopilotUpstream(
provider: InferenceGatewayProvider,
upstreamPath: string,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading