///|
/// A malformed code-reading request, rejected before filesystem access.
pub(all) suberror CodeContextError {
InvalidCodeSpec
}
///|
/// Immutable validated request for `read_code_context`.
pub struct CodeContextSpec {
max_tokens : Int32
priv source : String
}
///|
priv struct CodeRequest {
path : String
symbols : Array[String]
ranges : Array[@compression.LineRange]
full : Bool
outline : Bool
dependencies : Bool
}
///|
priv struct CodeDescription {
max_tokens : Int32
max_read_bytes : Int64
requests : Array[CodeRequest]
}
///|
fn code_bool(
fields : Map[String, Json],
key : String,
) -> Bool raise TaskContextError {
match fields.get(key) {
None | Some(False) => false
Some(True) => true
_ => raise InvalidTaskSpec
}
}
///|
fn parse_code_description(
value : Json,
) -> CodeDescription raise TaskContextError {
let fields = task_fields(value, ["maxTokens", "maxReadBytes", "requests"])
let max_tokens = task_integer(
fields.get("maxTokens").unwrap_or(Json::null()),
1,
2147483647,
)
let max_read_bytes = task_integer(
fields.get("maxReadBytes").unwrap_or(Json(16777216)),
1,
16777216,
).to_int64()
guard fields.get("requests") is Some(Array(values)) &&
!values.is_empty() &&
values.length() <= 32 else {
raise InvalidTaskSpec
}
let paths : Set[String] = Set([])
let requests : Array[CodeRequest] = []
for value in values {
let entry = task_fields(value, [
"path", "symbols", "ranges", "full", "outline", "includeDependencies",
])
guard entry.get("path") is Some(String(path)) &&
task_path(path) &&
!paths.contains(path) else {
raise InvalidTaskSpec
}
paths.add(path)
let full = code_bool(entry, "full")
let outline = code_bool(entry, "outline")
let dependencies = code_bool(entry, "includeDependencies")
let symbols : Array[String] = []
if entry.get("symbols") is Some(value) {
guard value is Array(items) && !items.is_empty() && items.length() <= 16 else {
raise InvalidTaskSpec
}
for item in items {
guard item is String(name) &&
!name.is_empty() &&
@utf8.encode(name).length() <= 256 &&
!name.contains("\u0000") &&
!name.contains("\n") &&
!name.contains("\r") &&
!symbols.contains(name) else {
raise InvalidTaskSpec
}
symbols.push(name)
}
}
let ranges : Array[@compression.LineRange] = []
if entry.get("ranges") is Some(value) {
guard value is Array(items) && !items.is_empty() && items.length() <= 16 else {
raise InvalidTaskSpec
}
for item in items {
let pair = task_fields(item, ["startLine", "endLine"])
let start_line = task_integer(
pair.get("startLine").unwrap_or(Json::null()),
1,
2147483647,
)
let end_line = task_integer(
pair.get("endLine").unwrap_or(Json::null()),
start_line,
2147483647,
)
ranges.push({ start_line, end_line, })
}
}
let selectors = (if entry.contains("full") { 1 } else { 0 }) +
(if entry.contains("outline") { 1 } else { 0 }) +
(if symbols.is_empty() { 0 } else { 1 }) +
(if ranges.is_empty() { 0 } else { 1 })
if selectors != 1 ||
(entry.contains("full") && !full) ||
(entry.contains("outline") && !outline) ||
(dependencies && symbols.is_empty()) {
raise InvalidTaskSpec
}
requests.push({ path, symbols, ranges, full, outline, dependencies, })
}
{ max_tokens, max_read_bytes, requests, }
}
///|
/// Each path selects exactly one of `symbols`, inclusive `ranges`, `full:true`,
/// or `outline:true`. `includeDependencies` is available only with symbols.
/// Unknown fields, duplicate paths, and non-canonical relative paths fail.
///
/// ```mbt check
/// test "code context request example" {
/// let spec = @moon_repomix.CodeContextSpec::from_json({
/// "maxTokens": 4000,
/// "requests": [{ "path": "lib/response.js", "symbols": ["res.render"] }],
/// })
/// assert_eq(spec.max_tokens, 4000)
/// }
/// ```
pub fn CodeContextSpec::from_json(
value : Json,
) -> CodeContextSpec raise CodeContextError {
let source = value.stringify()
if @utf8.encode(source).length() > 65536 {
raise InvalidCodeSpec
}
let parsed = parse_code_description(value) catch {
_ => raise InvalidCodeSpec
}
{ max_tokens: parsed.max_tokens, source, }
}