///|
priv struct TaskChosen {
file : @document.File
mode : String
transform : Json?
fallback : String?
}
///|
// Pure, bounded greedy state. Root supplies screened candidates; this state
// never opens files, starts async work or estimates a whole by summing parts.
priv struct TaskBudget {
cap : Int32
max_attempts : Int32
render : (Array[TaskChosen]) -> String
count : (String) -> Int32
selected : Array[TaskChosen]
mut attempts : Int32
mut text : String
mut tokens : Int32
}
///|
fn TaskBudget::new(
cap : Int32,
max_attempts : Int32,
required : Array[TaskChosen],
render : (Array[TaskChosen]) -> String,
count : (String) -> Int32,
) -> TaskBudget {
let text = render(required)
let tokens = count(text)
{
cap,
max_attempts,
render,
count,
selected: required.copy(),
attempts: 1,
text,
tokens,
}
}
///|
fn TaskBudget::can_try(self : TaskBudget) -> Bool {
self.attempts < self.max_attempts - 1
}
///|
fn TaskBudget::try_add(self : TaskBudget, candidate : TaskChosen) -> Bool {
if !self.can_try() {
return false
}
let proposed = self.selected.copy()
proposed.push(candidate)
let text = (self.render)(proposed)
let tokens = (self.count)(text)
self.attempts += 1
if tokens > self.cap {
return false
}
self.selected.push(candidate)
self.text = text
self.tokens = tokens
true
}
///|
fn TaskBudget::finish(self : TaskBudget) -> Bool {
// One attempt is always reserved for the exact final returned serialization.
if self.attempts >= self.max_attempts {
return false
}
self.text = (self.render)(self.selected)
self.tokens = (self.count)(self.text)
self.attempts += 1
self.tokens <= self.cap
}