Rust interface
Give every prompt fragment a source. Keep estimates and reported usage distinct.
Use HTMLP's runtime types without writing a prompt file, or connect them to checked file rendering. Your provider adapter sends the request; HTMLP supplies the types and accounting.
Install
[dependencies]
htmlp = { git = "https://github.com/alexmckenley/htmlp", tag = "v0.2.0-alpha.4", features = ["runtime"] }
runtime enables JSON types without a provider SDK, async runtime, networking, or tokenizer tables. Add tokens for Cl100k. See all library features.
Build a request
use htmlp::runtime::{ContentSource, ModelRequest, PromptFragment, Role};
let request = ModelRequest::builder("my-model")
.fragment(PromptFragment::text(
ContentSource::SystemPrompt,
Role::System,
"Review the change.",
))
.fragment(PromptFragment::text(
ContentSource::UserMessage,
Role::User,
"Check the parser.",
))
.max_output_tokens(1024)
.build();
let estimate = request.estimate();
let system_tokens = estimate.tokens_for(&ContentSource::SystemPrompt);
PromptFragment has private fields. Every constructor requires a ContentSource and a Role; ModelRequestBuilder accepts attributed fragments. The type system requires a category, while the caller chooses the correct one. Source and role are independent: repository context can use Role::User.
Attribute context
ContentSource | Use for |
|---|---|
SystemPrompt | Base instructions. |
ContextBlock { block_id } | Named rules, skills, environment, or retrieved context. |
ToolDefinitions | Available tool schemas; builder tools are counted here automatically. |
UserMessage, SessionMessage | User-authored input and session messages. |
AssistantHistory | Replayed assistant output. |
ToolOutput { tool_name } | Results attributed to a named tool. |
SystemReminder { origin }, Hook { hook_name } | Harness-injected reminders and hooks. |
CompactionSummary | Summarized conversation context. |
Names are part of the source key, so two skill IDs have separate accounting. These categories belong to the Rust and JSON API. Markup currently has no category or source attribute, and section IDs do not assign a source or role.
Render a checked file
Author a file with budgets and reasons, then run htmlp sign rules.htmlp. Add tokens alongside runtime for this example:
use htmlp::{Bindings, Cl100k, parse_file, render_checked};
use htmlp::runtime::{ContentSource, PromptFragment, Role};
let document = parse_file("rules.htmlp").expect("valid markup");
let counter = Cl100k::new().expect("tokenizer");
let bindings = Bindings::from([("task".into(), "Review the change.".into())]);
let rendered = render_checked(&document, &bindings, &counter)
.expect("valid signatures, bindings, and budgets");
let fragment = PromptFragment::checked(
ContentSource::SystemPrompt, Role::System, &rendered,
);
Handle diagnostics in your application; expect keeps these examples short. Rendering substitutes values literally and enforces every file, section, and direct-item budget. Missing bindings, invalid signatures, and exceeded budgets return errors before any checked text is exposed.
text()returns the final text;into_text()consumes the checked value.measurement()identifies the tokenizer and complete document count.section(id)selects text only after the entire document passes validation.spans()partitions the text into non-overlapping UTF-8 byte ranges with section ancestry and optional variable names.report()includes overlapping budget measurements. Do not sum parent and child counts.
PromptFragment::checked copies validated text into a fragment. Runtime estimation still uses its explicit heuristic; it does not reuse the file's exact count. Extracting and changing a string does not retain the original validation guarantee.
Track token usage
| Type | Measurement |
|---|---|
TextMeasurement | Complete rendered document count from the chosen tokenizer. |
RequestEstimate | Heuristic request allocation by source, including image reserves. |
ReportedUsage | Provider-reported totals for one call. |
TokenUsage | Optional estimate, optional reported usage, and context window. |
use htmlp::runtime::{ReportedUsage, TokenUsage};
let usage = TokenUsage {
estimate: Some(request.estimate()),
reported: Some(ReportedUsage {
input_tokens: 120,
cache_read_tokens: 80,
output_tokens: 30,
reasoning_tokens: 10,
..Default::default()
}),
context_window_tokens: 128_000,
};
assert_eq!(usage.prompt_tokens(), Some(200));
prompt_tokens() prefers reported input plus cache reads and writes, then falls back to the estimate. It returns None when neither exists. An external harness with unavailable attribution should set estimate: None.
ReportedUsage.input_tokens is uncached input; cache lanes are disjoint. Reasoning tokens are a subset of output, not an extra output charge. Provider adapters normalize their own conventions. Merge cumulative snapshots of the same call with merge_snapshot; sum distinct completed calls with add_call.
ByteEstimator defaults to four UTF-8 payload bytes per token, rounded up once per full source key, plus 8,192 tokens per image. Customize its nonzero bytes_per_token and image_tokens fields, then call estimate(&request). sources() exposes each allocation; total_tokens() derives the sum. These estimates exclude provider framing and do not enforce a context-window limit.
Tools, images, and adapters
PromptFragment::new accepts typed ContentBlock values: text, reasoning, images, tool uses, and tool results. Supply tool-call IDs with ToolUseId::from_string. Add ToolDefinition values through builder.tool(...); schemas use JSON. Set output limits and reasoning preferences with max_output_tokens and thinking_level.
Adapters read request.fragments(), request.tools(), and request settings to build provider-specific messages. bytes_by_source() measures raw payload bytes, including image transport data; it is distinct from token estimation.
Request helpers return derived requests: with_suffix_instruction appends an attributed user instruction and removes tools; with_warm_probe keeps tools, appends a reminder, and sets a one-token output limit; without_image_blocks replaces images with a text note while preserving attribution. Estimate the resulting request before sending it. Cache behavior and provider compatibility remain the adapter's responsibility.
Reference
- Generated runtime API · Parser and rendering API
- Request JSON Schema · Usage JSON Schema
- Plain Markdown reference
- Runnable Rust example
The JSON schemas describe serialized shapes. PromptFragment and ModelRequest are constructed through Rust APIs, not deserialized from JSON. HTMLP does not send model requests or provide native bindings for other languages. Other languages can use the CLI's JSON document output.