import { ExpertGuidance, emptyGuidance } from "std::agents/lib/expertGuidance"
import { hostedSearchTools } from "std::agents/lib/search"
import { AGENT_SPILL_OVER, llmOptions, withContext } from "std::agents/lib/shared"
import { webTools, spillTools } from "std::agents/lib/toolkits"
import { guarded } from "std::decorators"
import { agentSkill } from "std::skills"
import { systemMessage, threadIsNew } from "std::thread"

/** @module
@summary Get domain rules and acceptance criteria before solving a task.

Call `expertAgent` before solving a task in an unfamiliar domain. A model
often fails such a task because it does not know a convention of the
domain, such as the rules of a file format. `expertAgent` returns those
rules and an acceptance checklist as `ExpertGuidance`, for a solver or
verifier to use. `guidanceOrEmpty` lets a caller continue if the
consultation fails.
For Agency-language guidance, use `agencyExpertAgent`.

```ts
import { expertAgent } from "std::agents/expert"
import { guidanceOrEmpty, renderGuidance } from "std::agents/lib/expertGuidance"

node main() {
  const guidance = guidanceOrEmpty(expertAgent("Validate a FASTA file"))
  print(renderGuidance(guidance))
}
```
*/

static const skills = agentSkill("expert")

export def buildTools(): any[] {
  """
  Return the expert agent's web lookup and saved-output tools.
  """
  return [...webTools(), ...spillTools(), skills]
}

static const systemPrompt = """
You are a domain expert advising a weaker coding agent that is about to attempt the task below and tends to miss domain-specific conventions.

Identify the technical domain, then return the knowledge it needs:
- domain: a short name for the field (e.g. "molecular biology", "ELF binaries").
- rules: the key conventions, constraints, and facts that apply — especially the ones a non-expert gets wrong. Be concrete: exact numeric ranges, formats, encodings, common pitfalls.
- checklist: concrete acceptance criteria the finished artifact MUST satisfy. For EACH item, state HOW to verify it — the exact command/tool/flags AND the exact input it applies to (which file, which sub-sequence, which bytes). When the task names a ground-truth tool, use it verbatim with its exact flags. Prefer items that can be checked by running a command over ones checked by eye. Be surgical about scope (e.g. "compute Tm on ONLY the annealing portion, not the whole primer").
- ambiguity: if a step allows more than one valid reading — an unclear boundary, an unstated convention, a value the task never pins down — call it out explicitly. When it affects a criterion, require the work to satisfy that criterion under EVERY reasonable reading, not just the first one. A checklist that silently assumes one reading is how a plausible-looking solution fails a hidden test.

Use the web_search tool to confirm specifics when unsure. If the task has no special domain constraints, return empty rules and checklist."""

def consult(
  task: string,
  context: string,
  model: string,
  provider: string,
  extraTools: any[],
): ExpertGuidance {
  if (threadIsNew()) {
    systemMessage(systemPrompt)
  }

  // Return position so a saveDraft the model files mid-consult propagates
  // out as partial ExpertGuidance; an assignment here would drop it.
  return llm(
    "Task:\n${withContext(task: task, context: context)}",
    llmOptions(
    model: model,
    provider: provider,
    tools: buildTools().concat(extraTools).concat([saveDraft]),
    spillToolResultsOver: AGENT_SPILL_OVER,
    hostedTools: hostedSearchTools(model, provider),
  ),
  )
}

@guarded(cost: maxCost, time: maxTime)
export def expertAgent(
  task: string,
  context: string = "",
  maxCost: number = $10.00,
  maxTime: number = 10m,
  model: string = "",
  provider: string = "",
  session: string = "",
  extraTools: any[] = [],
): Result<ExpertGuidance> {
  """
  Consult a domain expert: identify the task's technical domain and return
  the rules and acceptance checklist a solver needs to get it right.

  @param task - The task to advise on
  @param context - Extra material, or ""
  @param maxCost - Cost budget in USD.
  @param maxTime - Time budget in milliseconds.
  @param model - Model override, or "" for the ambient model
  @param provider - Provider for the model override, ignored when model is empty.
  @param session - Session name to share a thread across calls, or "" for isolated
  @param extraTools - Extra tools to offer the LLM, appended to the built-in set
  """

  thread(label: "expertAgent", session: session) {
    return consult(
      task: task,
      context: context,
      model: model,
      provider: provider,
      extraTools: extraTools,
    )
  }
}
