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
6 changes: 6 additions & 0 deletions console/src/api/types/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ export interface ProviderInfo {
api_key: string;
base_url: string;
generate_kwargs: Record<string, unknown>;
/** Custom headers for API requests. */
default_headers: Record<string, string>;
}

export interface ProviderConfigRequest {
api_key?: string;
base_url?: string;
chat_model?: string;
generate_kwargs?: Record<string, unknown>;
default_headers?: Record<string, string>;
}

export interface ModelSlotConfig {
Expand All @@ -55,6 +58,7 @@ export interface CreateCustomProviderRequest {
api_key_prefix?: string;
chat_model?: string;
models?: ModelInfo[];
default_headers?: Record<string, string>;
}

export interface AddModelRequest {
Expand Down Expand Up @@ -126,10 +130,12 @@ export interface TestProviderRequest {
base_url?: string;
chat_model?: string;
generate_kwargs?: Record<string, unknown>;
default_headers?: Record<string, string>;
}

export interface TestModelRequest {
model_id: string;
default_headers?: Record<string, string>;
}

export interface DiscoverModelsResponse {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import { useState, useEffect } from "react";
import { Form, Input, Modal, Select, message } from "@agentscope-ai/design";
import {
Form,
Input,
Modal,
Select,
message,
Button,
} from "@agentscope-ai/design";
import { Space } from "antd";
import {
PlusOutlined,
DeleteOutlined,
DownOutlined,
RightOutlined,
} from "@ant-design/icons";
import api from "../../../../../api";
import { useTranslation } from "react-i18next";

interface HeaderItem {
id: string;
key: string;
value: string;
}

interface CustomProviderModalProps {
open: boolean;
onClose: () => void;
Expand All @@ -17,23 +37,61 @@ export function CustomProviderModal({
const { t } = useTranslation();
const [saving, setSaving] = useState(false);
const [form] = Form.useForm();
const [headers, setHeaders] = useState<HeaderItem[]>([]);
const [advancedOpen, setAdvancedOpen] = useState(false);

useEffect(() => {
if (open) {
form.resetFields();
setHeaders([]);
setAdvancedOpen(false);
}
}, [open, form]);

const addHeader = () => {
// Pre-fill User-Agent for the first header (common for Kimi Coding Plan)
const newHeader: HeaderItem = {
id: crypto.randomUUID(),
key: headers.length === 0 ? "User-Agent" : "",
value: "",
};
setHeaders([...headers, newHeader]);
};

const removeHeader = (index: number) => {
setHeaders(headers.filter((_, i) => i !== index));
};

const updateHeader = (
index: number,
field: keyof Omit<HeaderItem, "id">,
value: string,
) => {
const newHeaders = [...headers];
newHeaders[index][field] = value;
setHeaders(newHeaders);
};

const handleSubmit = async () => {
try {
const values = await form.validateFields();
setSaving(true);

// Convert headers array to object
const defaultHeaders: Record<string, string> = {};
headers.forEach(({ key, value }) => {
if (key.trim()) {
defaultHeaders[key.trim()] = value;
}
});

await api.createCustomProvider({
id: values.id.trim(),
name: values.name.trim(),
default_base_url: values.default_base_url?.trim() || "",
api_key_prefix: values.api_key_prefix?.trim() || "",
chat_model: values.chat_model || "OpenAIChatModel",
default_headers: defaultHeaders,
});
message.success(
t("models.providerCreated", { name: values.name.trim() }),
Expand Down Expand Up @@ -123,6 +181,78 @@ export function CustomProviderModal({
]}
/>
</Form.Item>

{/* Advanced Config - Headers */}
<div style={{ marginTop: 16 }}>
<button
type="button"
onClick={() => setAdvancedOpen(!advancedOpen)}
style={{
background: "none",
border: "none",
padding: 0,
cursor: "pointer",
fontSize: 14,
color: "#666",
display: "flex",
alignItems: "center",
gap: 4,
}}
>
{advancedOpen ? <DownOutlined /> : <RightOutlined />}
{t("models.advancedConfig", "Advanced Config")}
</button>

{advancedOpen && (
<div style={{ marginTop: 12 }}>
<div style={{ marginBottom: 8, fontSize: 14, color: "#333" }}>
{t("models.customHeaders", "Custom Headers")}
</div>
{headers.map((header, index) => (
<Space
key={header.id}
style={{ display: "flex", marginBottom: 8 }}
align="baseline"
>
<Input
placeholder="Header Key (e.g. User-Agent)"
value={header.key}
onChange={(e) => updateHeader(index, "key", e.target.value)}
style={{ width: 200 }}
/>
<Input
placeholder="Header Value"
value={header.value}
onChange={(e) =>
updateHeader(index, "value", e.target.value)
}
style={{ width: 200 }}
/>
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={() => removeHeader(index)}
/>
</Space>
))}
<Button
type="dashed"
onClick={addHeader}
icon={<PlusOutlined />}
size="small"
>
{t("models.addHeader", "Add Header")}
</Button>
<div style={{ marginTop: 8, fontSize: 12, color: "#888" }}>
{t(
"models.headersHint",
"Optional: Add custom HTTP headers for API requests. Example: User-Agent: KimiCLI/0.77",
)}
</div>
</div>
)}
</div>
</Form>
</Modal>
);
Expand Down
Loading
Loading