> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lyceum.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Zed

> Configure the Zed editor's agent to use Lyceum inference models

export const LyceumConfig = ({template, filename}) => {
  const EVENT = "lyceum-setup-change";
  const [state, setState] = useState({
    key: "",
    model: "z-ai/glm-5.2"
  });
  const [copied, setCopied] = useState(false);
  useEffect(() => {
    const read = () => {
      const store = window.__lyceumSetup;
      if (store) setState({
        key: store.key,
        model: store.model
      }); else {
        try {
          setState({
            key: localStorage.getItem("lyceum_key") || "",
            model: localStorage.getItem("lyceum_model") || "z-ai/glm-5.2"
          });
        } catch (e) {}
      }
    };
    read();
    window.addEventListener(EVENT, read);
    return () => window.removeEventListener(EVENT, read);
  }, []);
  const source = Array.isArray(template) ? template.join("\n") : template;
  const text = source.split("{{KEY}}").join(state.key || "lk_your_api_key").split("{{MODEL}}").join(state.model).split("{{BASE_URL}}").join("https://api.lyceum.technology/api/v2/external/serverless").split("{{ANTHROPIC_URL}}").join("https://api.lyceum.technology/api/v2/external/claude");
  const copy = () => {
    navigator.clipboard.writeText(text);
    setCopied(true);
    setTimeout(() => setCopied(false), 1500);
  };
  return <div style={{
    border: "1px solid rgba(128,128,128,0.35)",
    borderRadius: "10px",
    margin: "1.2rem 0",
    overflow: "hidden"
  }}>
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    padding: "6px 10px 6px 14px",
    borderBottom: "1px solid rgba(128,128,128,0.25)",
    fontSize: "12px",
    opacity: 0.8
  }}>
        <code>{filename || "config"}</code>
        <button style={{
    padding: "3px 10px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.4)",
    background: "transparent",
    color: "inherit",
    fontSize: "12px",
    cursor: "pointer"
  }} onClick={copy}>
          {copied ? "Copied" : "Copy"}
        </button>
      </div>
      <pre style={{
    margin: 0,
    padding: "14px",
    overflowX: "auto",
    fontSize: "12.5px",
    lineHeight: 1.55
  }}>
        <code>{text}</code>
      </pre>
    </div>;
};

export const LyceumSetup = ({showTest}) => {
  const BASE = "https://api.lyceum.technology/api/v2/external/serverless";
  const EVENT = "lyceum-setup-change";
  const DEFAULT_MODELS = ["z-ai/glm-5.2", "moonshotai/kimi-k2.7-code", "moonshotai/kimi-k3", "moonshotai/kimi-k2.6", "deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-flash-0731", "minimax/minimax-m3"];
  const [state, setState] = useState({
    key: "",
    model: "z-ai/glm-5.2"
  });
  const [models, setModels] = useState(DEFAULT_MODELS);
  const [status, setStatus] = useState(null);
  const [busy, setBusy] = useState(false);
  useEffect(() => {
    const store = window.__lyceumSetup || ({
      key: "",
      model: "z-ai/glm-5.2"
    });
    try {
      const k = localStorage.getItem("lyceum_key");
      const m = localStorage.getItem("lyceum_model");
      if (k) store.key = k;
      if (m) store.model = m;
    } catch (e) {}
    window.__lyceumSetup = store;
    setState({
      key: store.key,
      model: store.model
    });
    if (store.key.indexOf("lk_") === 0) fetchModels(store.key);
  }, []);
  const publish = next => {
    window.__lyceumSetup = next;
    try {
      localStorage.setItem("lyceum_key", next.key);
      localStorage.setItem("lyceum_model", next.model);
    } catch (e) {}
    setState(next);
    window.dispatchEvent(new CustomEvent(EVENT));
  };
  const fetchModels = async key => {
    try {
      const r = await fetch(BASE + "/models", {
        headers: {
          Authorization: "Bearer " + key
        }
      });
      if (!r.ok) return;
      const d = await r.json();
      const list = (d.data || d).map(m => m.id).sort();
      if (list.length) setModels(list);
    } catch (e) {}
  };
  const onKey = e => {
    const key = e.target.value.trim();
    publish({
      key: key,
      model: state.model
    });
    setStatus(null);
    if (key.indexOf("lk_") === 0) fetchModels(key);
  };
  const onModel = e => publish({
    key: state.key,
    model: e.target.value
  });
  const test = async () => {
    if (!state.key) {
      setStatus({
        ok: false,
        text: "Enter an API key first."
      });
      return;
    }
    setBusy(true);
    setStatus(null);
    const started = Date.now();
    try {
      const r = await fetch(BASE + "/chat/completions", {
        method: "POST",
        headers: {
          Authorization: "Bearer " + state.key,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({
          model: state.model,
          messages: [{
            role: "user",
            content: "Reply with a short one-sentence greeting."
          }],
          max_tokens: 512
        })
      });
      const d = await r.json();
      if (!r.ok) {
        setStatus({
          ok: false,
          text: d.detail || d.error && d.error.message || "HTTP " + r.status
        });
      } else {
        const choice = d.choices && d.choices[0];
        const msg = choice && choice.message && choice.message.content || "(empty response)";
        setStatus({
          ok: true,
          text: msg.trim(),
          meta: state.model + " · " + (Date.now() - started) + " ms · " + (d.usage && d.usage.total_tokens || "?") + " tokens"
        });
      }
    } catch (e) {
      setStatus({
        ok: false,
        text: "Request failed: " + e.message
      });
    }
    setBusy(false);
  };
  const field = {
    width: "100%",
    padding: "7px 10px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.4)",
    background: "transparent",
    color: "inherit",
    fontSize: "13px",
    fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace"
  };
  const label = {
    display: "block",
    fontSize: "11px",
    textTransform: "uppercase",
    letterSpacing: "0.06em",
    opacity: 0.65,
    marginBottom: "4px"
  };
  return <div style={{
    border: "1px solid rgba(128,128,128,0.35)",
    borderRadius: "10px",
    padding: "14px",
    margin: "1.2rem 0"
  }}>
      <div style={{
    display: "flex",
    gap: "12px",
    flexWrap: "wrap"
  }}>
        <div style={{
    flex: "2 1 260px"
  }}>
          <span style={label}>Lyceum API key</span>
          <input type="password" style={field} placeholder="lk_your_api_key" value={state.key} onChange={onKey} autoComplete="off" spellCheck="false" />
        </div>
        <div style={{
    flex: "1 1 200px"
  }}>
          <span style={label}>Model</span>
          <select style={field} value={state.model} onChange={onModel}>
            {models.map(m => <option key={m} value={m}>
                {m}
              </option>)}
          </select>
        </div>
        {showTest && <div style={{
    display: "flex",
    alignItems: "flex-end"
  }}>
          <button style={{
    padding: "7px 14px",
    borderRadius: "6px",
    border: "1px solid rgba(128,128,128,0.4)",
    background: "transparent",
    color: "inherit",
    fontSize: "13px",
    cursor: "pointer",
    whiteSpace: "nowrap"
  }} onClick={test} disabled={busy}>
            {busy ? "Testing…" : "Test connection"}
          </button>
        </div>}
      </div>

      <p style={{
    fontSize: "12px",
    opacity: 0.6,
    margin: "10px 0 0"
  }}>
        Your key stays in this browser (local storage) and is sent only to{" "}
        <code>api.lyceum.technology</code>.{" "}
        {showTest ? "Test connection sends one real, billed chat request." : "Every config block on this page fills in automatically."}{" "}
        Don't do this on a shared machine.
      </p>

      {status && <div style={{
    marginTop: "12px",
    padding: "10px 12px",
    borderRadius: "6px",
    border: "1px solid " + (status.ok ? "rgba(34,160,90,0.5)" : "rgba(210,70,70,0.5)"),
    fontSize: "13px"
  }}>
          <strong>{status.ok ? "Connected" : "Failed"}</strong>
          {status.meta && <span style={{
    opacity: 0.6,
    fontSize: "12px"
  }}> ({status.meta})</span>}
          <div style={{
    marginTop: "6px",
    whiteSpace: "pre-wrap"
  }}>{status.text}</div>
        </div>}
    </div>;
};

[Zed](https://zed.dev) has a built-in agent panel that accepts OpenAI-compatible providers.

<LyceumSetup />

## Configure

Open `settings.json` with `cmd-shift-p → zed: open settings`, and add:

<LyceumConfig
  filename="Zed settings.json"
  template={[
'{',
'  "language_models": {',
'    "openai_compatible": {',
'      "lyceum": {',
'        "api_url": "{{BASE_URL}}",',
'        "available_models": [',
'          {',
'            "name": "{{MODEL}}",',
'            "display_name": "Lyceum {{MODEL}}",',
'            "max_tokens": 200000,',
'            "max_output_tokens": 32000,',
'            "capabilities": {',
'              "tools": true,',
'              "images": false,',
'              "parallel_tool_calls": false,',
'              "prompt_cache_key": false',
'            }',
'          }',
'        ]',
'      }',
'    }',
'  }',
'}',
]}
/>

Then set the API key. Zed asks for keys in the UI rather than in `settings.json`. Open the agent panel, choose the **lyceum** provider, and paste your key. Zed also accepts it from the environment:

<LyceumConfig
  filename="shell"
  template={[
'export LYCEUM_API_KEY={{KEY}}',
]}
/>

<Warning>
  Do not put your API key in `settings.json`. Zed's own docs advise against it, and the file is commonly committed to dotfile repos.
</Warning>

## Using it

Open the **Agent Panel** with `Cmd+Shift+A` on macOS or `Ctrl+Shift+A` on Linux. The agent can edit files across the project, run terminal commands, and search the codebase.

For a quick edit in place, select some code and press `Cmd+Enter` on macOS or `Ctrl+Enter` on Linux to use the inline assistant.

## Notes

* **`api_url` is the base**, without `/chat/completions`. Zed appends that itself.
* **`max_tokens` is the context window**, `max_output_tokens` is the reply cap. Values above are safe defaults; check [active models](/docs/inference/active-models) for your model.
* **`tools: true` is required** for the agent panel to edit files.
* Add more entries to `available_models` to switch models from Zed's model picker.
