System Prompt Builder — C++ source
Assemble a system prompt from ordered blocks — role, context, constraints, output format — with a live token count, soft-limit warnings, and a shareable URL. 100% client-side.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// system-prompt-builder — C++ port: assemble an ordered list of prompt blocks
// into a markdown-structured system prompt, with pure list operations,
// presets, warnings, and a compact URL codec for shareable state.
//
// Language: C++ (C++17, standard library only)
// Source: CosmoDev polyglot showcase port of the System Prompt Builder
// tool, ported from src/lib/systemPromptBuilder.ts (the canonical
// TypeScript implementation).
// Tool page: https://dev.cosmolabs.org/tools/system-prompt-builder
// License: display source — part of CosmoDev's polyglot tool pages.
//
// Token counting inlines the chars-per-token heuristic from
// src/lib/tokenEstimator.ts (the original imports it); line lengths are
// counted in characters (the original counts UTF-16 code units, which only
// differs for astral-plane text). The URL codec hand-rolls base64url and a
// minimal JSON codec because the standard library ships neither.
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
// One editable section of the system prompt.
struct PromptBlock {
std::string id;
std::string title;
std::string content;
bool enabled = true;
};
// A starter template from the recommended prompt skeleton.
struct PromptPreset {
const char* id;
const char* title;
const char* description;
const char* content;
};
// Assemble + count + lint in one pass — the island's live report.
struct PromptReport {
std::string assembled;
long long tokens = 0;
std::vector<std::string> warnings;
};
// Selects the chars-per-token rate for buildReport ('auto' classifies each
// line by its shape, as src/lib/tokenEstimator.ts does).
enum class ContentType { Prose, Code, Json, Cjk, Auto };
// Fields to patch on one block; nullopt leaves that field untouched.
struct PromptBlockPatch {
std::optional<std::string> title;
std::optional<std::string> content;
std::optional<bool> enabled;
};
// Blocks whose assembled size starts crowding the context on most models.
constexpr int kSystemPromptSoftLimitTokens = 2000;
// True when the encoded form would make an uncomfortably long URL.
constexpr int kMaxEncodedLength = 4000;
// Ordered starter templates — the recommended skeleton of a system prompt.
const std::vector<PromptPreset>& systemPromptPresets() {
static const std::vector<PromptPreset> presets = {
{"role", "Role", "Who the model is and what it optimizes for.",
"You are a senior software engineer. You give correct, concise answers and say so "
"plainly when you are unsure."},
{"context", "Context", "The situation the model is working in.",
"The user is a developer working in a TypeScript codebase. Prefer runnable examples "
"over prose when both work."},
{"constraints", "Constraints", "Hard rules the model must not break.",
"- Never invent library APIs; use only the ones in the provided code.\n"
"- Keep answers under 300 words unless asked for more."},
{"output-format", "Output format", "The exact shape of the answer.",
"Respond with: 1) a one-line summary, 2) a fenced code block, 3) any caveats as "
"bullet points."},
{"examples", "Examples", "Few-shot demonstrations of the desired behavior.",
"Input: reverse \"abc\"\nOutput: \"cba\""},
{"tone", "Tone", "Voice and register.", "Direct and friendly. No filler openers, no apologies."},
{"refusal", "Refusal policy", "How to handle out-of-scope requests.",
"If a request is outside your scope, say so in one sentence and suggest the closest "
"thing you can do."},
{"safety", "Safety", "Guardrails for sensitive content.",
"Refuse requests that could cause harm, and never echo secrets, keys, or credentials "
"back in full."},
};
return presets;
}
// ---- token estimate (tokens figure only, from tokenEstimator.ts) ------------
namespace {
enum class LineType { Prose, Code, Json, Cjk };
double charsPerToken(LineType t) {
switch (t) {
case LineType::Prose: return 4.0;
case LineType::Code: return 3.5;
case LineType::Json: return 3.0;
case LineType::Cjk: return 1.5;
}
return 4.0;
}
// CJK ideographs / kana / Hangul pack roughly one token per 1.5 chars.
bool isCjk(char32_t cp) {
return (cp >= 0x4E00 && cp <= 0x9FFF) || (cp >= 0x3040 && cp <= 0x30FF) ||
(cp >= 0xAC00 && cp <= 0xD82F);
}
bool hasCjk(const std::string& s) {
size_t i = 0;
const size_t n = s.size();
while (i < n) {
const unsigned char b = static_cast<unsigned char>(s[i]);
char32_t cp;
if (b < 0x80) {
cp = b;
i += 1;
} else if ((b & 0xE0) == 0xC0) {
if (i + 1 >= n) return false;
cp = (static_cast<char32_t>(b & 0x1F) << 6) | (s[i + 1] & 0x3F);
i += 2;
} else if ((b & 0xF0) == 0xE0) {
if (i + 2 >= n) return false;
cp = (static_cast<char32_t>(b & 0x0F) << 12) | (static_cast<char32_t>(s[i + 1] & 0x3F) << 6) |
(s[i + 2] & 0x3F);
i += 3;
} else if ((b & 0xF8) == 0xF0) {
if (i + 3 >= n) return false;
cp = (static_cast<char32_t>(b & 0x07) << 18) | (static_cast<char32_t>(s[i + 1] & 0x3F) << 12) |
(static_cast<char32_t>(s[i + 2] & 0x3F) << 6) | (s[i + 3] & 0x3F);
i += 4;
} else {
return false; // invalid UTF-8 lead byte
}
if (isCjk(cp)) return true;
}
return false;
}
} // namespace
// ---- minimal JSON value tree + parser (shared by the codec and probe) -------
namespace jsonmini {
struct Value {
enum class Kind { Null, Bool, Num, Str, Arr, Obj };
Kind kind = Kind::Null;
bool boolean = false;
double num = 0;
std::string str;
std::vector<Value> arr;
std::vector<std::pair<std::string, Value>> obj;
};
class Parser {
public:
explicit Parser(const std::string& text) : s_(text), i_(0) {}
// Parses one value and requires the rest to be whitespace; nullopt on any
// RFC 8259 violation.
static std::optional<Value> parse(const std::string& text) {
Parser p(text);
std::optional<Value> v = p.parseValue();
if (!v) return std::nullopt;
p.skipWs();
if (p.i_ != p.s_.size()) return std::nullopt;
return v;
}
private:
void skipWs() {
while (i_ < s_.size() && (s_[i_] == ' ' || s_[i_] == '\t' || s_[i_] == '\n' || s_[i_] == '\r')) i_++;
}
bool eof() const { return i_ >= s_.size(); }
char peek() const { return s_[i_]; }
bool expectLit(const char* lit) {
size_t n = std::char_traits<char>::length(lit);
if (s_.compare(i_, n, lit) != 0) return false;
i_ += n;
return true;
}
std::optional<Value> parseValue() {
skipWs();
if (eof()) return std::nullopt;
switch (peek()) {
case 'n': return expectLit("null") ? std::optional<Value>(Value{}) : std::nullopt;
case 't': {
if (!expectLit("true")) return std::nullopt;
Value v;
v.kind = Value::Kind::Bool;
v.boolean = true;
return v;
}
case 'f': {
if (!expectLit("false")) return std::nullopt;
Value v;
v.kind = Value::Kind::Bool;
v.boolean = false;
return v;
}
case '"': {
std::optional<std::string> str = parseString();
if (!str) return std::nullopt;
Value v;
v.kind = Value::Kind::Str;
v.str = std::move(*str);
return v;
}
case '[': return parseArray();
case '{': return parseObject();
default: return parseNumber();
}
}
std::optional<std::string> parseString() {
i_++; // opening quote
std::string out;
while (true) {
if (eof()) return std::nullopt;
char c = s_[i_++];
if (c == '"') return out;
if (c != '\\') {
if (static_cast<unsigned char>(c) < 0x20) return std::nullopt;
out.push_back(c); // multi-byte UTF-8 passes through
continue;
}
if (eof()) return std::nullopt;
char esc = s_[i_++];
switch (esc) {
case '"': out.push_back('"'); break;
case '\\': out.push_back('\\'); break;
case '/': out.push_back('/'); break;
case 'b': out.push_back('\b'); break;
case 'f': out.push_back('\f'); break;
case 'n': out.push_back('\n'); break;
case 'r': out.push_back('\r'); break;
case 't': out.push_back('\t'); break;
case 'u': {
std::optional<char32_t> hi = parseHex4();
if (!hi) return std::nullopt;
// Combine a surrogate pair when present; a lone surrogate
// becomes U+FFFD (what a browser's TextDecoder emits).
char32_t cp = *hi;
if (cp >= 0xD800 && cp <= 0xDBFF) {
if (i_ + 1 < s_.size() && s_[i_] == '\\' && s_[i_ + 1] == 'u') {
i_ += 2;
std::optional<char32_t> lo = parseHex4();
if (!lo) return std::nullopt;
cp = (*lo >= 0xDC00 && *lo <= 0xDFFF)
? 0x10000 + ((cp - 0xD800) << 10) + (*lo - 0xDC00)
: 0xFFFD;
} else {
cp = 0xFFFD;
}
} else if (cp >= 0xDC00 && cp <= 0xDFFF) {
cp = 0xFFFD;
}
appendUtf8(out, cp);
break;
}
default: return std::nullopt;
}
}
}
std::optional<char32_t> parseHex4() {
if (i_ + 4 > s_.size()) return std::nullopt;
char32_t v = 0;
for (int k = 0; k < 4; k++) {
char h = s_[i_ + static_cast<size_t>(k)];
v <<= 4;
if (h >= '0' && h <= '9') v |= static_cast<char32_t>(h - '0');
else if (h >= 'a' && h <= 'f') v |= static_cast<char32_t>(h - 'a' + 10);
else if (h >= 'A' && h <= 'F') v |= static_cast<char32_t>(h - 'A' + 10);
else return std::nullopt;
}
i_ += 4;
return v;
}
static void appendUtf8(std::string& out, char32_t cp) {
if (cp < 0x80) {
out.push_back(static_cast<char>(cp));
} else if (cp < 0x800) {
out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else if (cp < 0x10000) {
out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
} else {
out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
}
std::optional<Value> parseNumber() {
const size_t start = i_;
if (!eof() && peek() == '-') i_++;
while (!eof() && peek() >= '0' && peek() <= '9') i_++;
if (!eof() && peek() == '.') {
i_++;
while (!eof() && peek() >= '0' && peek() <= '9') i_++;
}
if (!eof() && (peek() == 'e' || peek() == 'E')) {
i_++;
if (!eof() && (peek() == '+' || peek() == '-')) i_++;
while (!eof() && peek() >= '0' && peek() <= '9') i_++;
}
if (i_ == start) return std::nullopt;
Value v;
v.kind = Value::Kind::Num;
v.num = std::stod(s_.substr(start, i_ - start));
return v;
}
std::optional<Value> parseArray() {
i_++; // '['
Value v;
v.kind = Value::Kind::Arr;
skipWs();
if (!eof() && peek() == ']') {
i_++;
return v;
}
while (true) {
std::optional<Value> item = parseValue();
if (!item) return std::nullopt;
v.arr.push_back(std::move(*item));
skipWs();
if (eof()) return std::nullopt;
if (peek() == ',') { i_++; continue; }
if (peek() == ']') { i_++; return v; }
return std::nullopt;
}
}
std::optional<Value> parseObject() {
i_++; // '{'
Value v;
v.kind = Value::Kind::Obj;
skipWs();
if (!eof() && peek() == '}') {
i_++;
return v;
}
while (true) {
skipWs();
std::optional<std::string> key = parseString();
if (!key) return std::nullopt;
skipWs();
if (eof() || peek() != ':') return std::nullopt;
i_++;
std::optional<Value> value = parseValue();
if (!value) return std::nullopt;
v.obj.emplace_back(std::move(*key), std::move(*value));
skipWs();
if (eof()) return std::nullopt;
if (peek() == ',') { i_++; continue; }
if (peek() == '}') { i_++; return v; }
return std::nullopt;
}
}
const std::string& s_;
size_t i_;
};
} // namespace jsonmini
namespace {
// Classify a single line by its shape. Order: json, cjk, code, prose.
LineType detectLineType(const std::string& line) {
const std::string trimmed = [](std::string t) {
size_t a = t.find_first_not_of(" \t\n\r\v\f");
size_t b = t.find_last_not_of(" \t\n\r\v\f");
return a == std::string::npos ? std::string() : t.substr(a, b - a + 1);
}(line);
// JSON-ish: opens like a JSON fragment AND carries a separator.
const char first = trimmed.empty() ? '\0' : trimmed.front();
if ((first == '{' || first == '}' || first == '[' || first == '"') &&
(line.find(':') != std::string::npos || line.find(',') != std::string::npos)) {
return LineType::Json;
}
if (hasCjk(line)) return LineType::Cjk;
// Code: symbol-dense, or a statement terminator / block opener at EOL.
const size_t chars = line.size();
size_t symbols = 0;
for (char c : line) {
if (std::string_view("{}();=<>[]#").find(c) != std::string_view::npos) symbols++;
}
const double density = chars == 0 ? 0.0 : static_cast<double>(symbols) / static_cast<double>(chars);
if (density > 0.08 || (!trimmed.empty() && (trimmed.back() == ';' || trimmed.back() == '{' ||
trimmed.back() == '}'))) {
return LineType::Code;
}
return LineType::Prose;
}
// Sum of per-line token estimates (excludes chat framing).
long long estimateTokens(const std::string& text, ContentType contentType) {
LineType forced = LineType::Prose;
bool hasForced = true;
switch (contentType) {
case ContentType::Prose: forced = LineType::Prose; break;
case ContentType::Code: forced = LineType::Code; break;
case ContentType::Json: forced = LineType::Json; break;
case ContentType::Cjk: forced = LineType::Cjk; break;
case ContentType::Auto: hasForced = false; break;
}
// AUTO + whole-text JSON: a document that parses as JSON is json all the
// way down.
bool wholeTextJson = false;
{
std::string trimmed = text;
trimmed.erase(0, trimmed.find_first_not_of(" \t\n\r\v\f"));
if (!hasForced && !trimmed.empty()) {
wholeTextJson = jsonmini::Parser::parse(text).has_value();
}
}
long long tokens = 0;
size_t start = 0;
while (start <= text.size()) {
size_t nl = text.find('\n', start);
size_t end = nl == std::string::npos ? text.size() : nl;
std::string line = text.substr(start, end - start);
if (!line.empty() && line.back() == '\r') line.pop_back(); // split on \r?\n
bool blank = line.find_first_not_of(" \t\n\r\v\f") == std::string::npos;
if (!blank) {
LineType t = hasForced ? forced
: (wholeTextJson ? LineType::Json : detectLineType(line));
// max(1, round(len / rate)) — floor(x + 0.5) is JS-style rounding.
double est = std::floor(static_cast<double>(line.size()) / charsPerToken(t) + 0.5);
tokens += static_cast<long long>(est < 1.0 ? 1.0 : est);
}
if (nl == std::string::npos) break;
start = nl + 1;
}
return tokens;
}
// Group integer digits with commas the way toLocaleString('en-US') does.
std::string thousands(long long n) {
std::string digits = std::to_string(n);
std::string out;
const size_t len = digits.size();
for (size_t i = 0; i < len; i++) {
if (i > 0 && (len - i) % 3 == 0 && digits[i - 1] != '-') out.push_back(',');
out.push_back(digits[i]);
}
return out;
}
constexpr const char* kBase64UrlAlphabet =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
// Standard base64 with the URL-safe alphabet and no trailing padding.
std::string toBase64Url(const std::string& data) {
std::string out;
out.reserve(data.size() / 3 * 4 + 4);
for (size_t i = 0; i < data.size(); i += 3) {
const unsigned b0 = static_cast<unsigned char>(data[i]);
const unsigned b1 = i + 1 < data.size() ? static_cast<unsigned char>(data[i + 1]) : 0u;
const unsigned b2 = i + 2 < data.size() ? static_cast<unsigned char>(data[i + 2]) : 0u;
const unsigned n = (b0 << 16) | (b1 << 8) | b2;
out.push_back(kBase64UrlAlphabet[(n >> 18) & 0x3F]);
out.push_back(kBase64UrlAlphabet[(n >> 12) & 0x3F]);
if (i + 1 < data.size()) out.push_back(kBase64UrlAlphabet[(n >> 6) & 0x3F]);
if (i + 2 < data.size()) out.push_back(kBase64UrlAlphabet[n & 0x3F]);
}
return out;
}
int base64UrlVal(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '-') return 62;
if (c == '_') return 63;
return -1;
}
// Inverse of toBase64Url; nullopt on invalid characters or length.
std::optional<std::string> fromBase64Url(const std::string& s) {
if (s.size() % 4 == 1) return std::nullopt;
std::string out;
out.reserve(s.size() / 4 * 3 + 3);
for (size_t i = 0; i < s.size(); i += 4) {
const size_t m = std::min<size_t>(4, s.size() - i);
int v[4] = {0, 0, 0, 0};
for (size_t k = 0; k < m; k++) {
v[k] = base64UrlVal(s[i + k]);
if (v[k] < 0) return std::nullopt;
}
out.push_back(static_cast<char>((v[0] << 2) | (v[1] >> 4)));
if (m > 2) out.push_back(static_cast<char>(((v[1] & 0xF) << 4) | (v[2] >> 2)));
if (m > 3) out.push_back(static_cast<char>(((v[2] & 0x3) << 2) | v[3]));
}
return out;
}
// Append s as a quoted JSON string, escaping exactly like JSON.stringify.
void pushJsonString(std::string& out, const std::string& s) {
out.push_back('"');
for (char c : s) {
switch (c) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\b': out += "\\b"; break;
case '\f': out += "\\f"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
default:
if (static_cast<unsigned char>(c) < 0x20) {
char hex[8];
std::snprintf(hex, sizeof hex, "\\u%04x", c);
out += hex;
} else {
out.push_back(c);
}
}
}
out.push_back('"');
}
std::string trimCopy(const std::string& s) {
size_t a = s.find_first_not_of(" \t\n\r\v\f");
if (a == std::string::npos) return std::string();
size_t b = s.find_last_not_of(" \t\n\r\v\f");
return s.substr(a, b - a + 1);
}
} // namespace
// ---- public API ---------------------------------------------------------------
// Render enabled, non-empty blocks (in order) as one markdown-structured
// prompt (drop the "## Title" headers by passing headers = false).
std::string assemblePrompt(const std::vector<PromptBlock>& blocks, bool headers = true) {
std::vector<std::string> rendered;
for (const PromptBlock& b : blocks) {
std::string content = trimCopy(b.content);
if (!b.enabled || content.empty()) continue;
if (headers) {
std::string title = trimCopy(b.title);
rendered.push_back("## " + (title.empty() ? std::string("Untitled") : title) + "\n" + content);
} else {
rendered.push_back(std::move(content));
}
}
std::string joined;
for (size_t i = 0; i < rendered.size(); i++) {
if (i > 0) joined += "\n\n";
joined += rendered[i];
}
return trimCopy(joined);
}
// Append a block (caller supplies the id so the lib stays pure). The original
// defaults content to "" and enabled to true.
std::vector<PromptBlock> addBlock(std::vector<PromptBlock> blocks, const std::string& id,
const std::string& title, const std::string& content = "",
bool enabled = true) {
blocks.push_back(PromptBlock{id, title, content, enabled});
return blocks;
}
// Patch one block by id; unknown ids leave the list unchanged.
std::vector<PromptBlock> updateBlock(std::vector<PromptBlock> blocks, const std::string& id,
const PromptBlockPatch& patch) {
for (PromptBlock& b : blocks) {
if (b.id != id) continue;
if (patch.title) b.title = *patch.title;
if (patch.content) b.content = *patch.content;
if (patch.enabled) b.enabled = *patch.enabled;
}
return blocks;
}
// Flip one block's enabled flag by id.
std::vector<PromptBlock> toggleBlock(std::vector<PromptBlock> blocks, const std::string& id) {
for (PromptBlock& b : blocks) {
if (b.id == id) b.enabled = !b.enabled;
}
return blocks;
}
// Remove one block by id.
std::vector<PromptBlock> removeBlock(const std::vector<PromptBlock>& blocks, const std::string& id) {
std::vector<PromptBlock> out;
out.reserve(blocks.size());
for (const PromptBlock& b : blocks) {
if (b.id != id) out.push_back(b);
}
return out;
}
// Move a block (no-op when the indexes are out of range or equal — the
// original clamps negatives the same way because they are out of range).
std::vector<PromptBlock> moveBlock(const std::vector<PromptBlock>& blocks, size_t from, size_t to) {
if (from >= blocks.size() || to >= blocks.size() || from == to) return blocks;
std::vector<PromptBlock> next = blocks;
PromptBlock moved = next[from];
next.erase(next.begin() + static_cast<long>(from));
next.insert(next.begin() + static_cast<long>(to), std::move(moved));
return next;
}
// Assemble + count + lint in one pass — the island's live report.
PromptReport buildReport(const std::vector<PromptBlock>& blocks,
ContentType contentType = ContentType::Prose) {
PromptReport report;
report.assembled = assemblePrompt(blocks, true);
report.tokens = report.assembled.empty() ? 0 : estimateTokens(report.assembled, contentType);
if (report.tokens > kSystemPromptSoftLimitTokens) {
report.warnings.push_back(
"Assembled prompt is ~" + thousands(report.tokens) + " tokens — beyond " +
thousands(kSystemPromptSoftLimitTokens) +
" it starts crowding the context window on most models.");
}
bool hasRole = false;
for (const PromptBlock& b : blocks) {
std::string title = trimCopy(b.title);
for (char& c : title) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
if (b.enabled && title == "role") {
hasRole = true;
break;
}
}
if (!blocks.empty() && !hasRole) {
report.warnings.push_back(
"No enabled \"Role\" block — stating who the model is tends to anchor every "
"following instruction.");
}
if (!blocks.empty() && report.assembled.empty()) {
report.warnings.push_back("Every block is disabled or empty — the assembled prompt is empty.");
}
return report;
}
// ---- shareable state codec (URL-safe, compact) -------------------------------
// Triples of [enabled(0/1), title, content] keep URLs far smaller than the
// full object shape; ids are regenerated on decode (they are UI-local).
// Encode blocks to a compact base64url string; "" when blocks are empty.
std::string encodeBlocks(const std::vector<PromptBlock>& blocks) {
if (blocks.empty()) return std::string();
std::string json = "[";
for (size_t i = 0; i < blocks.size(); i++) {
if (i > 0) json.push_back(',');
json.push_back('[');
json.push_back(blocks[i].enabled ? '1' : '0');
json.push_back(',');
pushJsonString(json, blocks[i].title);
json.push_back(',');
pushJsonString(json, blocks[i].content);
json.push_back(']');
}
json.push_back(']');
return toBase64Url(json);
}
// True when the encoded form would make an uncomfortably long URL.
bool encodedTooLong(const std::string& encoded) {
return encoded.size() > static_cast<size_t>(kMaxEncodedLength);
}
// Decode encodeBlocks output; regenerates ids (b1, b2, …). Returns nullopt
// on malformed input — never throws; "" decodes to an empty list.
std::optional<std::vector<PromptBlock>> decodeBlocks(const std::string& encoded) {
if (encoded.empty()) return std::vector<PromptBlock>{};
std::optional<std::string> bytes = fromBase64Url(encoded);
if (!bytes) return std::nullopt;
std::optional<jsonmini::Value> raw = jsonmini::Parser::parse(*bytes);
if (!raw || raw->kind != jsonmini::Value::Kind::Arr) return std::nullopt;
std::vector<PromptBlock> blocks;
blocks.reserve(raw->arr.size());
for (size_t i = 0; i < raw->arr.size(); i++) {
const jsonmini::Value& entry = raw->arr[i];
if (entry.kind != jsonmini::Value::Kind::Arr || entry.arr.size() != 3) return std::nullopt;
const jsonmini::Value& enabled = entry.arr[0];
const jsonmini::Value& title = entry.arr[1];
const jsonmini::Value& content = entry.arr[2];
if (enabled.kind != jsonmini::Value::Kind::Num || title.kind != jsonmini::Value::Kind::Str ||
content.kind != jsonmini::Value::Kind::Str) {
return std::nullopt;
}
blocks.push_back(PromptBlock{"b" + std::to_string(i + 1), title.str, content.str,
enabled.num == 1.0});
}
return blocks;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →