Add custom agent creator, Zread MCP, fix model change context continuity

Features added:
- Custom Agent Creator dialog with AI generation support (up to 30k chars)
- Plus button next to agent selector to create new agents
- Zread MCP Server from Z.AI in marketplace (remote HTTP config)
- Extended MCP config types to support remote/http/sse servers

Bug fixes:
- Filter SDK Z.AI/GLM providers to ensure our custom routing with full message history
- This fixes the issue where changing models mid-chat lost conversationcontext
This commit is contained in:
Gemini AI
2025-12-24 21:34:16 +04:00
Unverified
parent e8c38b0add
commit 3501c20471
4 changed files with 558 additions and 182 deletions

View File

@@ -0,0 +1,327 @@
import { Dialog } from "@kobalte/core/dialog"
import { Bot, Loader2, Sparkles, X } from "lucide-solid"
import { Component, Show, createSignal } from "solid-js"
import { Portal } from "solid-js/web"
import { updateInstanceConfig } from "../stores/instance-config"
import { fetchAgents } from "../stores/sessions"
import { showToastNotification } from "../lib/notifications"
import { getLogger } from "../lib/logger"
const log = getLogger("agent-creator")
const MAX_PROMPT_LENGTH = 30000
interface AgentCreatorDialogProps {
instanceId: string
open: boolean
onClose: () => void
}
const AgentCreatorDialog: Component<AgentCreatorDialogProps> = (props) => {
const [name, setName] = createSignal("")
const [description, setDescription] = createSignal("")
const [prompt, setPrompt] = createSignal("")
const [isGenerating, setIsGenerating] = createSignal(false)
const [isSaving, setIsSaving] = createSignal(false)
const [useAiGeneration, setUseAiGeneration] = createSignal(true)
const resetForm = () => {
setName("")
setDescription("")
setPrompt("")
setIsGenerating(false)
setUseAiGeneration(true)
}
const handleClose = () => {
resetForm()
props.onClose()
}
const generatePromptWithAI = async () => {
if (!name().trim() || !description().trim()) {
showToastNotification({
title: "Missing Information",
message: "Please provide both name and description to generate an agent prompt.",
variant: "warning",
duration: 5000,
})
return
}
setIsGenerating(true)
try {
// Use Z.AI or another endpoint to generate the prompt
const response = await fetch("/api/zai/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: "glm-4.5-flash",
messages: [
{
role: "system",
content: `You are an expert AI agent prompt designer. Generate a comprehensive, detailed system prompt for an AI coding assistant agent based on the user's requirements. The prompt should:
1. Define the agent's role and expertise
2. Specify its capabilities and limitations
3. Include guidelines for code style and best practices
4. Define how it should interact with users
5. Include any domain-specific knowledge relevant to the description
Output ONLY the agent system prompt, no explanations or markdown formatting.`,
},
{
role: "user",
content: `Create a system prompt for an AI coding agent with the following details:
Name: ${name()}
Purpose: ${description()}
Generate a comprehensive system prompt that will make this agent effective at its purpose.`,
},
],
stream: false,
max_tokens: 4096,
}),
})
if (!response.ok) {
throw new Error(`Generation failed: ${response.status}`)
}
const data = await response.json()
const generatedPrompt = data?.choices?.[0]?.message?.content || data?.message?.content || ""
if (generatedPrompt) {
setPrompt(generatedPrompt)
showToastNotification({
title: "Prompt Generated",
message: "AI has generated a system prompt for your agent. Review and edit as needed.",
variant: "success",
duration: 5000,
})
} else {
throw new Error("No prompt content in response")
}
} catch (error) {
log.error("Failed to generate agent prompt", error)
showToastNotification({
title: "Generation Failed",
message: "Could not generate prompt. Please write one manually or check your Z.AI configuration.",
variant: "error",
duration: 8000,
})
} finally {
setIsGenerating(false)
}
}
const handleSave = async () => {
if (!name().trim()) {
showToastNotification({
title: "Name Required",
message: "Please provide a name for the agent.",
variant: "warning",
duration: 5000,
})
return
}
if (!prompt().trim()) {
showToastNotification({
title: "Prompt Required",
message: "Please provide a system prompt for the agent.",
variant: "warning",
duration: 5000,
})
return
}
setIsSaving(true)
try {
await updateInstanceConfig(props.instanceId, (draft) => {
if (!draft.customAgents) {
draft.customAgents = []
}
// Check for duplicate names
const existing = draft.customAgents.findIndex((a) => a.name.toLowerCase() === name().toLowerCase())
if (existing >= 0) {
// Update existing
draft.customAgents[existing] = {
name: name().trim(),
description: description().trim() || undefined,
prompt: prompt().trim(),
}
} else {
// Add new
draft.customAgents.push({
name: name().trim(),
description: description().trim() || undefined,
prompt: prompt().trim(),
})
}
})
// Refresh agents list
await fetchAgents(props.instanceId)
showToastNotification({
title: "Agent Created",
message: `Custom agent "${name()}" has been saved and is ready to use.`,
variant: "success",
duration: 5000,
})
handleClose()
} catch (error) {
log.error("Failed to save custom agent", error)
showToastNotification({
title: "Save Failed",
message: "Could not save the agent. Please try again.",
variant: "error",
duration: 8000,
})
} finally {
setIsSaving(false)
}
}
return (
<Dialog open={props.open} onOpenChange={(open) => !open && handleClose()}>
<Portal>
<Dialog.Overlay class="fixed inset-0 bg-black/60 backdrop-blur-sm z-[9998]" />
<div class="fixed inset-0 flex items-center justify-center z-[9999] p-4">
<Dialog.Content class="bg-zinc-900 border border-zinc-700 rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
{/* Header */}
<div class="flex items-center justify-between p-4 border-b border-zinc-700/50">
<div class="flex items-center gap-3">
<div class="p-2 bg-indigo-500/20 rounded-lg">
<Bot size={20} class="text-indigo-400" />
</div>
<div>
<Dialog.Title class="text-lg font-semibold text-white">Create Custom Agent</Dialog.Title>
<Dialog.Description class="text-xs text-zinc-400">
Define a new AI agent with custom behavior and expertise
</Dialog.Description>
</div>
</div>
<button
onClick={handleClose}
class="p-1.5 text-zinc-400 hover:text-white hover:bg-zinc-700/50 rounded-lg transition-colors"
>
<X size={18} />
</button>
</div>
{/* Content */}
<div class="flex-1 overflow-y-auto p-4 space-y-4">
{/* Name Input */}
<div class="space-y-1.5">
<label class="text-xs font-medium text-zinc-300">Agent Name *</label>
<input
type="text"
value={name()}
onInput={(e) => setName(e.currentTarget.value)}
placeholder="e.g., React Specialist, Python Expert, Code Reviewer..."
class="w-full px-3 py-2 bg-zinc-800 border border-zinc-600 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 transition-colors"
/>
</div>
{/* Description Input */}
<div class="space-y-1.5">
<label class="text-xs font-medium text-zinc-300">Brief Description</label>
<input
type="text"
value={description()}
onInput={(e) => setDescription(e.currentTarget.value)}
placeholder="A few words about what this agent specializes in..."
class="w-full px-3 py-2 bg-zinc-800 border border-zinc-600 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 transition-colors"
/>
</div>
{/* Generation Mode Toggle */}
<div class="flex items-center gap-4 p-3 bg-zinc-800/50 rounded-lg border border-zinc-700/50">
<button
onClick={() => setUseAiGeneration(true)}
class={`flex-1 py-2 px-3 rounded-lg text-sm font-medium transition-all ${useAiGeneration()
? "bg-indigo-500 text-white"
: "text-zinc-400 hover:text-white hover:bg-zinc-700/50"
}`}
>
<Sparkles size={14} class="inline-block mr-1.5" />
AI Generate
</button>
<button
onClick={() => setUseAiGeneration(false)}
class={`flex-1 py-2 px-3 rounded-lg text-sm font-medium transition-all ${!useAiGeneration()
? "bg-indigo-500 text-white"
: "text-zinc-400 hover:text-white hover:bg-zinc-700/50"
}`}
>
Write Manually
</button>
</div>
{/* AI Generation Button */}
<Show when={useAiGeneration()}>
<button
onClick={generatePromptWithAI}
disabled={isGenerating() || !name().trim() || !description().trim()}
class="w-full py-2.5 px-4 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-500 hover:to-indigo-500 text-white rounded-lg font-medium text-sm transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
<Show when={isGenerating()} fallback={<><Sparkles size={16} /> Generate Agent Prompt with AI</>}>
<Loader2 size={16} class="animate-spin" />
Generating...
</Show>
</button>
</Show>
{/* Prompt Textarea */}
<div class="space-y-1.5">
<div class="flex items-center justify-between">
<label class="text-xs font-medium text-zinc-300">System Prompt *</label>
<span class="text-xs text-zinc-500">
{prompt().length.toLocaleString()} / {MAX_PROMPT_LENGTH.toLocaleString()}
</span>
</div>
<textarea
value={prompt()}
onInput={(e) => {
const value = e.currentTarget.value
if (value.length <= MAX_PROMPT_LENGTH) {
setPrompt(value)
}
}}
placeholder="Enter the system prompt that defines this agent's behavior, expertise, and guidelines..."
rows={12}
class="w-full px-3 py-2 bg-zinc-800 border border-zinc-600 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 transition-colors resize-none font-mono text-sm"
/>
</div>
</div>
{/* Footer */}
<div class="flex items-center justify-end gap-3 p-4 border-t border-zinc-700/50 bg-zinc-800/30">
<button
onClick={handleClose}
class="px-4 py-2 text-zinc-400 hover:text-white hover:bg-zinc-700/50 rounded-lg text-sm font-medium transition-colors"
>
Cancel
</button>
<button
onClick={handleSave}
disabled={isSaving() || !name().trim() || !prompt().trim()}
class="px-4 py-2 bg-indigo-500 hover:bg-indigo-400 text-white rounded-lg text-sm font-medium transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
<Show when={isSaving()} fallback={<>Save Agent</>}>
<Loader2 size={14} class="animate-spin" />
Saving...
</Show>
</button>
</div>
</Dialog.Content>
</div>
</Portal>
</Dialog>
)
}
export default AgentCreatorDialog

View File

@@ -1,9 +1,11 @@
import { Select } from "@kobalte/core/select"
import { For, Show, createEffect, createMemo } from "solid-js"
import { For, Show, createEffect, createMemo, createSignal } from "solid-js"
import { agents, fetchAgents, sessions } from "../stores/sessions"
import { ChevronDown } from "lucide-solid"
import { ChevronDown, Plus } from "lucide-solid"
import type { Agent } from "../types/session"
import { getLogger } from "../lib/logger"
import AgentCreatorDialog from "./agent-creator-dialog"
const log = getLogger("session")
@@ -16,6 +18,7 @@ interface AgentSelectorProps {
export default function AgentSelector(props: AgentSelectorProps) {
const instanceAgents = () => agents().get(props.instanceId) || []
const [showCreator, setShowCreator] = createSignal(false)
const session = createMemo(() => {
const instanceSessions = sessions().get(props.instanceId)
@@ -64,61 +67,82 @@ export default function AgentSelector(props: AgentSelectorProps) {
}
return (
<div class="sidebar-selector">
<Select
value={availableAgents().find((a) => a.name === props.currentAgent)}
onChange={handleChange}
options={availableAgents()}
optionValue="name"
optionTextValue="name"
placeholder="Select agent..."
itemComponent={(itemProps) => (
<Select.Item
item={itemProps.item}
class="selector-option"
>
<div class="flex flex-col flex-1 min-w-0">
<Select.ItemLabel class="selector-option-label flex items-center gap-2">
<span>{itemProps.item.rawValue.name}</span>
<Show when={itemProps.item.rawValue.mode === "subagent"}>
<span class="neutral-badge">subagent</span>
<>
<div class="sidebar-selector flex items-center gap-1">
<Select
value={availableAgents().find((a) => a.name === props.currentAgent)}
onChange={handleChange}
options={availableAgents()}
optionValue="name"
optionTextValue="name"
placeholder="Select agent..."
itemComponent={(itemProps) => (
<Select.Item
item={itemProps.item}
class="selector-option"
>
<div class="flex flex-col flex-1 min-w-0">
<Select.ItemLabel class="selector-option-label flex items-center gap-2">
<span>{itemProps.item.rawValue.name}</span>
<Show when={itemProps.item.rawValue.mode === "subagent"}>
<span class="neutral-badge">subagent</span>
</Show>
<Show when={itemProps.item.rawValue.mode === "custom"}>
<span class="text-[9px] px-1.5 py-0.5 bg-indigo-500/20 text-indigo-400 rounded-full font-medium">custom</span>
</Show>
</Select.ItemLabel>
<Show when={itemProps.item.rawValue.description}>
<Select.ItemDescription class="selector-option-description">
{itemProps.item.rawValue.description.length > 50
? itemProps.item.rawValue.description.slice(0, 50) + "..."
: itemProps.item.rawValue.description}
</Select.ItemDescription>
</Show>
</Select.ItemLabel>
<Show when={itemProps.item.rawValue.description}>
<Select.ItemDescription class="selector-option-description">
{itemProps.item.rawValue.description.length > 50
? itemProps.item.rawValue.description.slice(0, 50) + "..."
: itemProps.item.rawValue.description}
</Select.ItemDescription>
</Show>
</div>
</Select.Item>
)}
>
<Select.Trigger
data-agent-selector
class="selector-trigger"
>
<Select.Value<Agent>>
{(state) => (
<div class="selector-trigger-label">
<span class="selector-trigger-primary">
Agent: {state.selectedOption()?.name ?? "None"}
</span>
</div>
)}
</Select.Value>
<Select.Icon class="selector-trigger-icon">
<ChevronDown class="w-3 h-3" />
</Select.Icon>
</Select.Trigger>
</Select.Item>
)}
>
<Select.Trigger
data-agent-selector
class="selector-trigger"
>
<Select.Value<Agent>>
{(state) => (
<div class="selector-trigger-label">
<span class="selector-trigger-primary">
Agent: {state.selectedOption()?.name ?? "None"}
</span>
</div>
)}
</Select.Value>
<Select.Icon class="selector-trigger-icon">
<ChevronDown class="w-3 h-3" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content class="selector-popover max-h-80 overflow-auto p-1">
<Select.Listbox class="selector-listbox" />
</Select.Content>
</Select.Portal>
</Select>
</div>
<Select.Portal>
<Select.Content class="selector-popover max-h-80 overflow-auto p-1">
<Select.Listbox class="selector-listbox" />
</Select.Content>
</Select.Portal>
</Select>
{/* Add Agent Button */}
<button
onClick={() => setShowCreator(true)}
class="p-1.5 text-zinc-500 hover:text-indigo-400 hover:bg-indigo-500/10 rounded-lg transition-all shrink-0"
title="Create custom agent"
>
<Plus size={14} />
</button>
</div>
{/* Agent Creator Dialog */}
<AgentCreatorDialog
instanceId={props.instanceId}
open={showCreator()}
onClose={() => setShowCreator(false)}
/>
</>
)
}

View File

@@ -10,6 +10,10 @@ type McpServerConfig = {
command?: string
args?: string[]
env?: Record<string, string>
// Remote MCP server support
type?: "remote" | "http" | "sse" | "streamable-http"
url?: string
headers?: Record<string, string>
}
type McpConfig = {
@@ -23,8 +27,10 @@ type McpMarketplaceEntry = {
config: McpServerConfig
tags?: string[]
source?: string
requiresApiKey?: boolean
}
interface McpManagerProps {
instanceId: string
}
@@ -34,6 +40,19 @@ const log = getLogger("mcp-manager")
const MCP_LINKER_RELEASES = "https://github.com/milisp/mcp-linker/releases"
const MCP_LINKER_MARKET = "https://github.com/milisp/mcp-linker"
const MARKETPLACE_ENTRIES: McpMarketplaceEntry[] = [
{
id: "zread",
name: "Zread (Z.AI)",
description: "Search GitHub repos, read code, analyze structure. Powered by Z.AI - requires API key from z.ai/manage-apikey.",
config: {
type: "remote",
url: "https://api.z.ai/api/mcp/zread/mcp",
headers: { "Authorization": "Bearer YOUR_ZAI_API_KEY" }
},
tags: ["github", "code", "search", "z.ai"],
source: "z.ai",
requiresApiKey: true,
},
{
id: "sequential-thinking",
name: "Sequential Thinking",
@@ -76,6 +95,7 @@ const MARKETPLACE_ENTRIES: McpMarketplaceEntry[] = [
},
]
const McpManager: Component<McpManagerProps> = (props) => {
const [config, setConfig] = createSignal<McpConfig>({ mcpServers: {} })
const [isLoading, setIsLoading] = createSignal(false)
@@ -348,61 +368,61 @@ const McpManager: Component<McpManagerProps> = (props) => {
<Dialog.Overlay class="modal-overlay" />
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
<Dialog.Content class="modal-surface w-full max-w-2xl p-5 flex flex-col gap-4">
<div class="flex items-center justify-between">
<div>
<Dialog.Title class="text-sm font-semibold text-white">Configure MCP Server</Dialog.Title>
<Dialog.Description class="text-xs text-zinc-500">
Paste the MCP server config JSON. Use marketplace via MCP Linker for curated servers.
</Dialog.Description>
</div>
<button
class="text-xs px-2 py-1 rounded border border-white/10 text-zinc-400 hover:text-white"
onClick={() => setRawMode((prev) => !prev)}
>
{rawMode() ? "Server Mode" : "Raw Config (JSON)"}
</button>
</div>
<div class="flex items-center justify-between">
<div>
<Dialog.Title class="text-sm font-semibold text-white">Configure MCP Server</Dialog.Title>
<Dialog.Description class="text-xs text-zinc-500">
Paste the MCP server config JSON. Use marketplace via MCP Linker for curated servers.
</Dialog.Description>
</div>
<button
class="text-xs px-2 py-1 rounded border border-white/10 text-zinc-400 hover:text-white"
onClick={() => setRawMode((prev) => !prev)}
>
{rawMode() ? "Server Mode" : "Raw Config (JSON)"}
</button>
</div>
<Show when={!rawMode()}>
<label class="flex flex-col gap-1 text-xs text-zinc-400">
Server Name
<input
value={serverName()}
onInput={(e) => setServerName(e.currentTarget.value)}
class="rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/60"
placeholder="example-server"
/>
</label>
</Show>
<Show when={!rawMode()}>
<label class="flex flex-col gap-1 text-xs text-zinc-400">
Server Name
<input
value={serverName()}
onInput={(e) => setServerName(e.currentTarget.value)}
class="rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 focus:outline-none focus:border-blue-500/60"
placeholder="example-server"
/>
</label>
</Show>
<label class="flex flex-col gap-1 text-xs text-zinc-400">
Config JSON
<textarea
value={serverJson()}
onInput={(e) => setServerJson(e.currentTarget.value)}
class="min-h-[200px] rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 font-mono focus:outline-none focus:border-blue-500/60"
placeholder='{"command":"npx","args":["-y","mcp-server-example"]}'
/>
</label>
<label class="flex flex-col gap-1 text-xs text-zinc-400">
Config JSON
<textarea
value={serverJson()}
onInput={(e) => setServerJson(e.currentTarget.value)}
class="min-h-[200px] rounded-md bg-white/5 border border-white/10 px-3 py-2 text-xs text-zinc-200 font-mono focus:outline-none focus:border-blue-500/60"
placeholder='{"command":"npx","args":["-y","mcp-server-example"]}'
/>
</label>
<div class="flex items-center justify-end gap-2">
<button
onClick={() => {
resetManualForm()
setShowManual(false)
}}
class="px-3 py-1.5 text-xs rounded-md border border-white/10 text-zinc-300 hover:text-white"
>
Cancel
</button>
<button
onClick={handleManualSave}
disabled={saving()}
class="px-3 py-1.5 text-xs rounded-md bg-blue-500/20 border border-blue-500/40 text-blue-200 hover:text-white disabled:opacity-60"
>
{saving() ? "Saving..." : "Confirm"}
</button>
</div>
<div class="flex items-center justify-end gap-2">
<button
onClick={() => {
resetManualForm()
setShowManual(false)
}}
class="px-3 py-1.5 text-xs rounded-md border border-white/10 text-zinc-300 hover:text-white"
>
Cancel
</button>
<button
onClick={handleManualSave}
disabled={saving()}
class="px-3 py-1.5 text-xs rounded-md bg-blue-500/20 border border-blue-500/40 text-blue-200 hover:text-white disabled:opacity-60"
>
{saving() ? "Saving..." : "Confirm"}
</button>
</div>
</Dialog.Content>
</div>
</Dialog.Portal>
@@ -413,83 +433,83 @@ const McpManager: Component<McpManagerProps> = (props) => {
<Dialog.Overlay class="modal-overlay" />
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
<Dialog.Content class="modal-surface w-full max-w-3xl p-5 flex flex-col gap-4">
<div class="flex items-center justify-between">
<div>
<Dialog.Title class="text-sm font-semibold text-white">MCP Marketplace</Dialog.Title>
<Dialog.Description class="text-xs text-zinc-500">
Curated entries inspired by mcp-linker. Install writes to this workspace&apos;s .mcp.json.
</Dialog.Description>
</div>
<button
class="mcp-link-button"
onClick={() => openExternal(MCP_LINKER_MARKET)}
>
Open MCP Linker
</button>
</div>
<div class="flex items-center justify-between">
<div>
<Dialog.Title class="text-sm font-semibold text-white">MCP Marketplace</Dialog.Title>
<Dialog.Description class="text-xs text-zinc-500">
Curated entries inspired by mcp-linker. Install writes to this workspace&apos;s .mcp.json.
</Dialog.Description>
</div>
<button
class="mcp-link-button"
onClick={() => openExternal(MCP_LINKER_MARKET)}
>
Open MCP Linker
</button>
</div>
<div class="mcp-market-search">
<Search size={14} class="text-zinc-500" />
<input
value={marketplaceQuery()}
onInput={(e) => setMarketplaceQuery(e.currentTarget.value)}
placeholder="Search MCP servers..."
class="mcp-market-input"
/>
</div>
<div class="mcp-market-search">
<Search size={14} class="text-zinc-500" />
<input
value={marketplaceQuery()}
onInput={(e) => setMarketplaceQuery(e.currentTarget.value)}
placeholder="Search MCP servers..."
class="mcp-market-input"
/>
</div>
<div class="mcp-market-list">
<Show
when={!marketplaceLoading()}
fallback={<div class="text-[11px] text-zinc-500 italic">Loading marketplace sources...</div>}
>
<For each={filteredMarketplace()}>
{(entry) => (
<div class="mcp-market-card">
<div class="mcp-market-card-info">
<div class="mcp-market-card-title">
{entry.name}
<Show when={entry.source}>
{(source) => <span class="mcp-market-source">{source()}</span>}
</Show>
</div>
<div class="mcp-market-card-desc">{entry.description}</div>
<Show when={entry.tags && entry.tags.length > 0}>
<div class="mcp-market-tags">
<For each={entry.tags}>
{(tag) => <span class="mcp-market-tag">{tag}</span>}
</For>
<div class="mcp-market-list">
<Show
when={!marketplaceLoading()}
fallback={<div class="text-[11px] text-zinc-500 italic">Loading marketplace sources...</div>}
>
<For each={filteredMarketplace()}>
{(entry) => (
<div class="mcp-market-card">
<div class="mcp-market-card-info">
<div class="mcp-market-card-title">
{entry.name}
<Show when={entry.source}>
{(source) => <span class="mcp-market-source">{source()}</span>}
</Show>
</div>
<div class="mcp-market-card-desc">{entry.description}</div>
<Show when={entry.tags && entry.tags.length > 0}>
<div class="mcp-market-tags">
<For each={entry.tags}>
{(tag) => <span class="mcp-market-tag">{tag}</span>}
</For>
</div>
</Show>
</div>
</Show>
</div>
<div class="mcp-market-card-actions">
<button
class="mcp-icon-button"
title="View config"
onClick={() => {
setShowManual(true)
setRawMode(false)
setServerName(entry.id)
setServerJson(JSON.stringify(entry.config, null, 2))
setShowMarketplace(false)
}}
>
<Settings size={14} />
</button>
<button
class="mcp-market-install"
onClick={() => handleMarketplaceInstall(entry)}
disabled={saving()}
>
<Plus size={12} />
Install
</button>
</div>
</div>
)}
</For>
</Show>
</div>
<div class="mcp-market-card-actions">
<button
class="mcp-icon-button"
title="View config"
onClick={() => {
setShowManual(true)
setRawMode(false)
setServerName(entry.id)
setServerJson(JSON.stringify(entry.config, null, 2))
setShowMarketplace(false)
}}
>
<Settings size={14} />
</button>
<button
class="mcp-market-install"
onClick={() => handleMarketplaceInstall(entry)}
disabled={saving()}
>
<Plus size={12} />
Install
</button>
</div>
</div>
)}
</For>
</Show>
</div>
</Dialog.Content>
</div>
</Dialog.Portal>

View File

@@ -746,7 +746,12 @@ async function fetchProviders(instanceId: string): Promise<void> {
})),
}))
const filteredBaseProviders = providerList.filter((provider) => provider.id !== "zai")
// Filter out Z.AI providers from SDK to use our custom routing with full message history
const filteredBaseProviders = providerList.filter((provider) =>
!provider.id.toLowerCase().includes("zai") &&
!provider.id.toLowerCase().includes("z.ai") &&
!provider.id.toLowerCase().includes("glm")
)
const extraProviders = await fetchExtraProviders()
const baseProviders = removeDuplicateProviders(filteredBaseProviders, extraProviders)