Skip to main content
Glama
by microsoft
prompty.ts7.08 kB
import { ChatCompletionContentPart, ChatCompletionMessageParam, } from "./chattypes" import { splitMarkdown } from "./frontmatter" import { YAMLParse } from "./yaml" import { deleteUndefinedValues } from "./cleaners" import { JSON5Stringify } from "./json5" function promptyFrontmatterToMeta(frontmatter: PromptyFrontmatter): PromptArgs { const { name, description, tags = [], sample, inputs, outputs, model, files, tests, } = frontmatter const { api = "chat", configuration, parameters: modelParameters, } = model ?? {} const parameters: Record<string, JSONSchemaSimpleType> = inputs ? Object.entries(inputs).reduce<Record<string, JSONSchemaSimpleType>>( (acc, [k, v]) => { if (v.type === "list") acc[k] = { type: "array" } else acc[k] = v return acc }, {} ) : undefined if (parameters && sample && typeof sample === "object") for (const p in sample) { const s = sample[p] const pp = parameters[p] if (s !== undefined && pp) pp.default = s } let modelName: string = undefined if (api !== "chat") throw new Error("completion api not supported") if (modelParameters?.n > 1) throw new Error("multi-turn not supported") if (modelParameters?.tools?.length) throw new Error("tools not supported") // resolve model if ( configuration?.type === "azure_openai" || configuration?.type === "azure" ) { if (!configuration.azure_deployment) throw new Error("azure_deployment required") modelName = `azure:${configuration.azure_deployment}` } else if (configuration?.type === "azure_serverless") { modelName = `azure_serverless:${configuration.azure_endpoint}` } else if (configuration?.type === "openai") modelName = `openai:${configuration.type}` const unlisted = tags.includes("unlisted") const meta = deleteUndefinedValues({ model: modelName, title: name, description, files, tests, unlisted: unlisted ? true : undefined, parameters, responseType: outputs ? "json_object" : modelParameters?.response_format?.type, responseSchema: outputs, temperature: modelParameters?.temperature, maxTokens: modelParameters?.max_tokens, topP: modelParameters?.top_p, seed: modelParameters?.seed, } satisfies PromptArgs) return meta } /** * Parses a prompty document from a given filename and text content. * * @param filename - The name of the file being processed. This is used to associate metadata with the document. * @param text - The raw text of the document, including optional frontmatter and content body. * @returns An object representing the parsed prompty document. It includes metadata, parsed frontmatter, remaining content, and extracted messages. * * The parsing process: * - Splits the document into frontmatter and content using a helper function. * - Converts frontmatter content into metadata via a transformation function. * - Processes the content to extract structured message blocks, identified by "system", "user", or "assistant" roles. * - Each message block is trimmed and stored alongside its role for further use. * - Throws an error if improper formatting, such as whitespace before frontmatter markers, is detected. */ export function promptyParse(filename: string, text: string): PromptyDocument { const { frontmatter = "", content = "" } = splitMarkdown(text) if (!frontmatter && /^\s+---/.test(frontmatter)) throw new Error( "Prompty: Frontmatter has invalid whitespace before ---" ) const fm = frontmatter ? YAMLParse(frontmatter) : {} const meta: PromptArgs = fm ? promptyFrontmatterToMeta(fm) : {} if (filename) meta.filename = filename const messages: ChatCompletionMessageParam[] = [] // split const rx = /^\s*(system|user|assistant)\s*:\s*$/gim const lines = content.split(/\r?\n/g) let role: "system" | "user" | "assistant" | undefined = "system" let chunk: string[] = [] const pushMessage = () => { if (role && chunk.length && chunk.some((l) => !!l)) { messages.push({ role, content: chunk.join("\n").trim(), }) } } for (const line of lines) { const m = rx.exec(line) if (m) { // next role starts pushMessage() role = m[1] as "system" | "user" | "assistant" chunk = [] } else { chunk.push(line) } } pushMessage() return { meta, frontmatter: fm, content, messages } } /** * Converts a PromptyDocument into a script compatible with GenAI. * * @param doc - The PromptyDocument containing metadata, content, and messages. * - `meta`: Metadata extracted from the document's frontmatter, such as model, parameters, and other configuration values. * - `messages`: Array of chat messages with roles (system, user, assistant) and respective content. * * Generates a script string by mapping chat roles and content into reusable GenAI script components: * - System messages are represented using `writeText`. * - Assistant messages are processed using `parsers.jinja`. * - User message content is rendered as Jinja templates or other compatible parts (e.g., text, image_url, input_audio). * * Returns a string containing the final generated AI script. */ export function promptyToGenAIScript(doc: PromptyDocument): string { const { messages, meta } = doc const renderJinja = (content: string) => `$\`${content.replace(/`/g, "\\`")}\`${/\{(%|\{)/.test(content) ? `.jinja(env.vars)` : ""}` const renderPart = (c: ChatCompletionContentPart) => c.type === "text" ? renderJinja(c.text) : c.type === "image_url" ? `defImages("${c.image_url}")` : c.type === "input_audio" ? `defAudio("${c.input_audio}")` : `unknown message` let src = `` if (Object.keys(meta).length) { src += `script(${JSON5Stringify(meta, null, 2)})\n\n` } src += messages .map((msg) => { const { role, content } = msg if (role === "assistant") { return `assistant(parsers.jinja(${JSON.stringify(content as string)}, env.vars))` } else if (role === "system") { return `writeText(${JSON.stringify(content as string)}, { role: "system" })` } else { if (typeof content === "string") return renderJinja(content) else if (Array.isArray(content)) return content.map(renderPart).join("\n") else return renderPart(content) } }) .join("\n") return src }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/microsoft/genaiscript'

If you have feedback or need assistance with the MCP directory API, please join our Discord server