Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/zai-inference-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@roomote/web': minor
---

Add Z.AI and Z.AI Coding Plan as inference providers with International or China region on connect.
4 changes: 4 additions & 0 deletions .env.production.example
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ OPENROUTER_API_KEY=
# MOONSHOT_API_KEY=
# KIMI_API_KEY=
# MINIMAX_API_KEY=
# ZAI_API_KEY=
# ZAI_REGION=global
# ZAI_CODING_PLAN_API_KEY=
# ZAI_CODING_PLAN_REGION=global
# OPENCODE_API_KEY=

# Compute provider. Use docker for a single-host deployment, or
Expand Down

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

15 changes: 15 additions & 0 deletions apps/api/src/handlers/inference/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,21 @@ async function resolveProviderUpstreamBaseUrl(
);
}

if (provider.regionBaseUrls && provider.region) {
const region =
(await resolveModelProviderEnvValue([provider.region.envVarName])) ??
provider.region.default;

const baseUrl = provider.regionBaseUrls[region];
if (!baseUrl) {
throw new Error(
`${provider.region.envVarName} must be one of ${Object.keys(provider.regionBaseUrls).join(', ')} for ${provider.name}. Received "${region}".`,
);
}

return baseUrl;
}

if (!provider.region) {
return provider.upstreamBaseUrl!;
}
Expand Down
71 changes: 56 additions & 15 deletions apps/web/src/app/(onboarding)/setup/StepInferenceProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { toast } from 'sonner';
import {
CHATGPT_SUBSCRIPTION_PROVIDER_ID,
OPENAI_COMPATIBLE_PROVIDER_ID,
getSetupModelProvider,
type SetupModelProviderId,
type SetupModelStatus,
} from '@roomote/types';
Expand Down Expand Up @@ -133,7 +134,16 @@ export function StepInferenceProvider({
: '',
);
setConnectionName('');
setAdditionalEnvValues({});
const defaults: Record<string, string> = {};
if (selectedProvider) {
for (const field of getSetupModelProvider(selectedProvider)
.additionalEnvFields ?? []) {
if (field.options && field.options.length > 0) {
defaults[field.envVarName] = field.options[0]!.value;
}
}
}
setAdditionalEnvValues(defaults);
setEditingSavedValue(false);
setIsChatGptDialogOpen(false);
setIsGitHubCopilotDialogOpen(false);
Expand Down Expand Up @@ -452,20 +462,51 @@ export function StepInferenceProvider({
{field.label}
{field.required ? '' : ' (optional)'}
</span>
<Input
secret={field.secret}
value={additionalEnvValues[field.envVarName] ?? ''}
onChange={(event) =>
setAdditionalEnvValues((values) => ({
...values,
[field.envVarName]: event.target.value,
}))
}
placeholder={field.placeholder}
disabled={saveModelConfig.isPending}
aria-label={`${field.label} for ${selectedProviderStatus?.label ?? 'provider'}`}
data-1p-ignore
/>
{field.options && field.options.length > 0 ? (
<Select
value={
additionalEnvValues[field.envVarName] ||
field.options[0]?.value ||
''
}
onValueChange={(value) =>
setAdditionalEnvValues((values) => ({
...values,
[field.envVarName]: value,
}))
}
disabled={saveModelConfig.isPending}
>
<SelectTrigger
className="min-w-44"
aria-label={`${field.label} for ${selectedProviderStatus?.label ?? 'provider'}`}
>
<SelectValue placeholder={field.placeholder ?? field.label} />
</SelectTrigger>
<SelectContent>
{field.options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
secret={field.secret}
value={additionalEnvValues[field.envVarName] ?? ''}
onChange={(event) =>
setAdditionalEnvValues((values) => ({
...values,
[field.envVarName]: event.target.value,
}))
}
placeholder={field.placeholder}
disabled={saveModelConfig.isPending}
aria-label={`${field.label} for ${selectedProviderStatus?.label ?? 'provider'}`}
data-1p-ignore
/>
)}
</div>
))}

Expand Down
93 changes: 76 additions & 17 deletions apps/web/src/components/settings/InferenceProviderSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,23 @@ function getInitialAdditionalEnvValues(
(provider.additionalEnvFields ?? []).map((field) => field.envVarName),
);

return Object.fromEntries(
const values = Object.fromEntries(
Object.entries(provider.additionalEnvValues ?? {}).filter(([name]) =>
declaredNames.has(name),
),
);

for (const field of provider.additionalEnvFields ?? []) {
if (
field.options &&
field.options.length > 0 &&
!values[field.envVarName]?.trim()
) {
values[field.envVarName] = field.options[0]!.value;
}
}

return values;
}

function getInitialPrimaryCredential(
Expand All @@ -112,11 +124,23 @@ function getSubmitAdditionalEnvValues(
additionalEnvFields.map((field) => field.envVarName),
);

return Object.fromEntries(
const values = Object.fromEntries(
Object.entries(additionalEnvValues).filter(([name]) =>
declaredNames.has(name),
),
);

for (const field of additionalEnvFields) {
if (
field.options &&
field.options.length > 0 &&
!values[field.envVarName]?.trim()
) {
values[field.envVarName] = field.options[0]!.value;
}
}

return values;
}

function ConnectedProviderRow({
Expand Down Expand Up @@ -485,21 +509,56 @@ function ProviderCredentialsDialog({
{field.label}
{field.required ? '' : ' (optional)'}
</span>
<Input
secret={field.secret}
className={field.secret ? 'font-mono' : undefined}
value={additionalEnvValues[field.envVarName] ?? ''}
onChange={(event) =>
setAdditionalEnvValues((values) => ({
...values,
[field.envVarName]: event.target.value,
}))
}
placeholder={field.placeholder}
disabled={isSaving}
aria-label={`${field.label} for ${selectedProvider.label}`}
data-1p-ignore
/>
{field.options && field.options.length > 0 ? (
<Select
value={
additionalEnvValues[field.envVarName] ||
field.options[0]?.value ||
''
}
onValueChange={(value) =>
setAdditionalEnvValues((values) => ({
...values,
[field.envVarName]: value,
}))
}
disabled={isSaving}
>
<SelectTrigger
aria-label={`${field.label} for ${selectedProvider.label}`}
>
<SelectValue
placeholder={field.placeholder ?? field.label}
/>
</SelectTrigger>
<SelectContent>
{field.options.map((option) => (
<SelectItem
key={option.value}
value={option.value}
>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
secret={field.secret}
className={field.secret ? 'font-mono' : undefined}
value={additionalEnvValues[field.envVarName] ?? ''}
onChange={(event) =>
setAdditionalEnvValues((values) => ({
...values,
[field.envVarName]: event.target.value,
}))
}
placeholder={field.placeholder}
disabled={isSaving}
aria-label={`${field.label} for ${selectedProvider.label}`}
data-1p-ignore
/>
)}
</div>
))}
</>
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/trpc/commands/task-models/index.test.ts

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

4 changes: 4 additions & 0 deletions deploy/compose/docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ x-roomote-inference-env: &roomote-inference-env
MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-}
KIMI_API_KEY: ${KIMI_API_KEY:-}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
ZAI_API_KEY: ${ZAI_API_KEY:-}
ZAI_REGION: ${ZAI_REGION:-}
ZAI_CODING_PLAN_API_KEY: ${ZAI_CODING_PLAN_API_KEY:-}
ZAI_CODING_PLAN_REGION: ${ZAI_CODING_PLAN_REGION:-}
OPENCODE_API_KEY: ${OPENCODE_API_KEY:-}
BASETEN_API_KEY: ${BASETEN_API_KEY:-}
TOGETHER_API_KEY: ${TOGETHER_API_KEY:-}
Expand Down
4 changes: 4 additions & 0 deletions docker-compose.production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ x-roomote-production-env: &roomote-production-env
MOONSHOT_API_KEY: ${MOONSHOT_API_KEY:-}
KIMI_API_KEY: ${KIMI_API_KEY:-}
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
ZAI_API_KEY: ${ZAI_API_KEY:-}
ZAI_REGION: ${ZAI_REGION:-}
ZAI_CODING_PLAN_API_KEY: ${ZAI_CODING_PLAN_API_KEY:-}
ZAI_CODING_PLAN_REGION: ${ZAI_CODING_PLAN_REGION:-}
OPENCODE_API_KEY: ${OPENCODE_API_KEY:-}
BASETEN_API_KEY: ${BASETEN_API_KEY:-}
TOGETHER_API_KEY: ${TOGETHER_API_KEY:-}
Expand Down
Loading
Loading