"use strict"; const WEATHER_TOOL = { name: "get_weather", description: "Get the weather for a location", parameters: { type: "object", properties: { location: { type: "string" }, days: { type: "integer" } }, required: ["location"], }, }; const SAMPLE_REASONING = "The user wants to know the weather, so I need to call get_weather."; const SAMPLES = { chat_completions: pretty({ tools: [{ type: "function", function: WEATHER_TOOL }], messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "What's the weather in Beijing today?" }, { role: "assistant", reasoning_content: SAMPLE_REASONING, tool_calls: [ { id: "call_1", type: "function", function: { name: "get_weather", arguments: JSON.stringify({ location: "Beijing", days: 3 }), }, }, ], }, { role: "tool", tool_call_id: "call_1", content: "Sunny, 25°C" }, { role: "assistant", content: "It's sunny in Beijing today, 25°C." }, { role: "user", content: "Now check and compare the weather in Beijing and Shanghai for the next 3 days." }, ], }), responses: pretty({ instructions: "You are a helpful assistant.", tools: [{ type: "function", ...WEATHER_TOOL }], input: [ { type: "message", role: "user", content: "What's the weather in Beijing today?" }, { type: "reasoning", content: [{ type: "reasoning_text", text: SAMPLE_REASONING }], }, { type: "function_call", call_id: "call_1", name: "get_weather", arguments: JSON.stringify({ location: "Beijing", days: 3 }), }, { type: "function_call_output", call_id: "call_1", output: "Sunny, 25°C" }, { type: "message", role: "assistant", content: "It's sunny in Beijing today, 25°C." }, { type: "message", role: "user", content: "Now check and compare the weather in Beijing and Shanghai for the next 3 days." }, ], }), messages: pretty({ system: "You are a helpful assistant.", tools: [{ name: WEATHER_TOOL.name, description: WEATHER_TOOL.description, input_schema: WEATHER_TOOL.parameters }], messages: [ { role: "user", content: "What's the weather in Beijing today?" }, { role: "assistant", content: [ { type: "thinking", thinking: SAMPLE_REASONING }, { type: "tool_use", id: "toolu_1", name: "get_weather", input: { location: "Beijing", days: 3 } }, ], }, { role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "Sunny, 25°C" }], }, { role: "assistant", content: [{ type: "text", text: "It's sunny in Beijing today, 25°C." }] }, { role: "user", content: "Now check and compare the weather in Beijing and Shanghai for the next 3 days." }, ], }), }; const DEFAULT_FORMAT = "chat_completions"; const EOS = "<|end▁of▁sentence|>"; const OUTPUT_SAMPLE = `The user wants to compare the weather in Beijing and Shanghai for the next three days, so I need forecasts for both cities. I'll check the weather in Beijing and Shanghai for the next three days. <|DSML| calls> <|DSML| invoke name="get_weather"> <|DSML| parameter name="location" string="true">Beijing <|DSML| parameter name="days" string="false">3 <|DSML| invoke name="get_weather"> <|DSML| parameter name="location" string="true">Shanghai <|DSML| parameter name="days" string="false">3 ${EOS}`; const tabs = Array.from(document.querySelectorAll(".tab")); const modeTabs = Array.from(document.querySelectorAll(".mode-tab")); const input = document.getElementById("input"); const output = document.getElementById("output"); const meta = document.getElementById("meta"); const errorBox = document.getElementById("error"); const tip = document.getElementById("tip"); const modelOutput = document.getElementById("model-output"); const modelOutputPreview = document.getElementById("model-output-preview"); const parsedOutput = document.getElementById("parsed-output"); const parseMeta = document.getElementById("parse-meta"); const parseError = document.getElementById("parse-error"); const responseFormat = document.getElementById("response-format"); const state = { mode: "render", format: DEFAULT_FORMAT, drafts: { ...SAMPLES }, renderId: 0, parseId: 0 }; function pretty(value) { return JSON.stringify(value, null, 2); } function pick(value, keys) { return Object.fromEntries(keys.filter((key) => value?.[key] != null).map((key) => [key, value[key]])); } // Select protocol fields explicitly: tool inputs and argument strings are // payloads, so metadata-like keys inside them must remain untouched. function parsedContent(format, response) { switch (format) { case "chat_completions": return { choices: response.choices.map((choice) => { const message = pick(choice.message, ["content", "reasoning_content"]); for (const key of ["content", "reasoning_content"]) { if (message[key] === "") delete message[key]; } if (choice.message.tool_calls?.length) { message.tool_calls = choice.message.tool_calls.map((tool) => ({ type: tool.type, function: pick(tool.function, ["name", "arguments"]), })); } return { message }; }), }; case "responses": return { output: response.output.map((item) => { const content = pick(item, ["type", "name", "namespace", "arguments", "input"]); if (item.content) content.content = item.content.map((part) => pick(part, ["type", "text"])); return content; }), }; case "messages": return { content: response.content.map((block) => pick(block, ["type", "text", "thinking", "name", "input"])), }; default: throw new Error(`Unknown API format: ${format}`); } } function runMode() { if (state.mode === "render") renderPrompt(); else parseOutput(); } function setMode(mode) { state.mode = mode; for (const tab of modeTabs) { const selected = tab.dataset.mode === mode; tab.classList.toggle("active", selected); tab.setAttribute("aria-selected", String(selected)); tab.tabIndex = selected ? 0 : -1; } for (const name of ["render", "parse"]) { document.getElementById(`${name}-panel`).hidden = name !== mode; document.getElementById(`${name}-actions`).hidden = name !== mode; } tip.hidden = true; runMode(); } function setFormat(format) { state.drafts[state.format] = input.value; state.format = format; for (const tab of tabs) { const selected = tab.dataset.format === format; tab.classList.toggle("active", selected); tab.setAttribute("aria-selected", String(selected)); tab.tabIndex = selected ? 0 : -1; if (selected) responseFormat.textContent = tab.textContent; } input.value = state.drafts[format]; state.renderId++; state.parseId++; clearPrompt(); clearParsed(); hideError(); runMode(); } async function renderPrompt() { const id = ++state.renderId; const format = state.format; clearPrompt(); hideError(); meta.textContent = "Rendering…"; output.setAttribute("aria-busy", "true"); try { const body = requestBody(); const payload = await post("/api/render", { format, body }); if (id !== state.renderId) return; render(payload); } catch (err) { if (id !== state.renderId) return; clearPrompt(); showError(err.message); } finally { if (id === state.renderId) output.setAttribute("aria-busy", "false"); } } function requestBody() { try { return JSON.parse(input.value); } catch (err) { throw new Error(`Failed to parse request JSON: ${err.message}`); } } async function post(url, body) { let response; let payload; try { response = await fetch(url, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), }); payload = await response.json(); } catch (err) { throw new Error(`Demo server request failed: ${err.message}`); } if (!response.ok) throw new Error(payload?.error?.message ?? `HTTP ${response.status}`); return payload; } async function parseOutput() { const id = ++state.parseId; const format = state.format; clearParsed(); parseMeta.textContent = "Parsing…"; parsedOutput.setAttribute("aria-busy", "true"); try { const body = requestBody(); const payload = await post("/api/parse", { format, body, output: modelOutput.value, }); if (id !== state.parseId) return; renderModelOutput(payload.segments); parsedOutput.textContent = pretty(parsedContent(format, payload.response)); parseMeta.textContent = ""; } catch (err) { if (id !== state.parseId) return; clearParsed(); parseError.textContent = err.message; parseError.hidden = false; } finally { if (id === state.parseId) parsedOutput.setAttribute("aria-busy", "false"); } } function clearPrompt() { output.replaceChildren(); output.setAttribute("aria-busy", "false"); meta.textContent = ""; tip.hidden = true; } function clearParsed() { parsedOutput.textContent = ""; parsedOutput.setAttribute("aria-busy", "false"); parseMeta.textContent = ""; parseError.hidden = true; } function invalidateParsed() { state.parseId++; clearParsed(); parseMeta.textContent = "Output changed; parse again"; } function render(payload) { const fragment = document.createDocumentFragment(); for (const segment of payload.segments) { fragment.append(renderSegment(segment)); } output.replaceChildren(fragment); meta.textContent = `${payload.prompt.length} characters`; } function syncModelOutputScroll() { modelOutputPreview.scrollTop = modelOutput.scrollTop; modelOutputPreview.scrollLeft = modelOutput.scrollLeft; } function renderModelOutput(segments) { if (segments) { const fragment = document.createDocumentFragment(); for (const segment of segments) fragment.append(renderSegment(segment)); modelOutputPreview.replaceChildren(fragment); } else { modelOutputPreview.textContent = modelOutput.value; } if (modelOutput.value.endsWith("\n")) { modelOutputPreview.append(document.createTextNode("\u200b")); } syncModelOutputScroll(); } function renderSegment(segment) { const span = document.createElement("span"); if (segment.kind === "token") { span.className = "sp"; span.textContent = segment.token; span.dataset.name = segment.name; span.dataset.tip = segment.description; } else { span.className = "tx"; span.textContent = segment.text; } return span; } function positionTip(event) { const margin = 12; tip.style.left = "0px"; tip.style.top = "0px"; const rect = tip.getBoundingClientRect(); let x = event.clientX + margin; let y = event.clientY + margin; if (x + rect.width > window.innerWidth - margin) { x = Math.max(margin, window.innerWidth - margin - rect.width); } if (y + rect.height > window.innerHeight - margin) { y = Math.max(margin, event.clientY - margin - rect.height); } tip.style.left = `${x}px`; tip.style.top = `${y}px`; } function showError(message) { errorBox.textContent = message; errorBox.hidden = false; } function hideError() { errorBox.hidden = true; } function bindTabs(buttons, select) { for (const tab of buttons) { tab.addEventListener("click", () => select(tab)); tab.addEventListener("keydown", (event) => { const index = buttons.indexOf(tab); let next; if (event.key === "ArrowRight") next = (index + 1) % buttons.length; if (event.key === "ArrowLeft") next = (index + buttons.length - 1) % buttons.length; if (event.key === "Home") next = 0; if (event.key === "End") next = buttons.length - 1; if (next === undefined) return; event.preventDefault(); buttons[next].focus(); select(buttons[next]); }); } } bindTabs(tabs, (tab) => setFormat(tab.dataset.format)); bindTabs(modeTabs, (tab) => setMode(tab.dataset.mode)); output.addEventListener("mouseover", (event) => { const token = event.target.closest(".sp"); if (!token) return; tip.textContent = token.dataset.tip; tip.hidden = false; positionTip(event); }); output.addEventListener("mousemove", (event) => { if (!tip.hidden) positionTip(event); }); output.addEventListener("mouseout", (event) => { if (event.target.closest(".sp")) tip.hidden = true; }); document.getElementById("render-prompt").addEventListener("click", renderPrompt); document.getElementById("parse-output").addEventListener("click", parseOutput); input.addEventListener("input", () => { state.drafts[state.format] = input.value; state.renderId++; clearPrompt(); hideError(); meta.textContent = "Request changed; render again"; invalidateParsed(); }); modelOutput.addEventListener("input", () => { renderModelOutput(); invalidateParsed(); }); modelOutput.addEventListener("scroll", syncModelOutputScroll); new ResizeObserver(syncModelOutputScroll).observe(modelOutput); modelOutput.addEventListener("keydown", (event) => { if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { event.preventDefault(); parseOutput(); } }); input.addEventListener("keydown", (event) => { if ((event.metaKey || event.ctrlKey) && event.key === "Enter") { event.preventDefault(); renderPrompt(); } }); input.value = state.drafts[DEFAULT_FORMAT]; modelOutput.value = OUTPUT_SAMPLE; renderModelOutput(); setFormat(DEFAULT_FORMAT);