import { hostedSearchTools } from "std::agents/lib/search"
import { AGENT_SPILL_OVER, llmOptions, withContext } from "std::agents/lib/shared"
import {
  writableFileTools,
  shellTools,
  gitTools,
  githubTools,
  agencyCodeTools,
  memoryTools,
  agencyDocTools,
  planningTools,
  webTools,
  ASK_USER_HINT,
 } from "std::agents/lib/toolkits"
import { guarded } from "std::decorators"
import { setAgentCwd, getAgentCwd } from "std::index"
import { agentSkill } from "std::skills"
import { systemMessage, threadIsNew } from "std::thread"

import { communicationTools } from "./lib/toolkits.agency"

/** @module
@summary Write, edit, and run code to complete a task on disk.

The returned string summarizes the work. Files and other effects are the
deliverable. Use `verifierAgent` afterwards to check the result against the
task, or `std::agents/agency/coding` to generate Agency source as text.

```ts
import { codingAgent } from "std::agents/coding"

node main() {
  const result = codingAgent("Create a CSV report in report.csv")
  print(result)
}
```
*/

static const systemPrompt = """You are an expert software engineer. Solve the task by writing and running real code with your tools.

- The deliverable is a working artifact backed by real tool output, not a description. Actually run and exercise what you build.
- Match the output contract exactly: the requested filename, path, format, and fields. Follow the task's stated whitespace and trailing-newline requirement precisely, whether it asks for one or forbids one. A near-miss fails.
- When an exact check decides success (a numeric tolerance, an exact match, or a hidden test), list the judgment calls you made — each place the task could be read two ways. Check your solution once under each reading. When it passes under all of them, you are done: submit. Re-running a check that already passed adds no information, and every extra minute spent re-verifying a correct answer is worse than submitting it. If you find yourself checking the same property twice, stop and submit.
- If a tool call fails because an argument was malformed or invalid, the environment has not changed — no files moved, no directory disappeared. Fix the argument and retry. Never go searching for "lost" files because one call failed.
- Call `saveDraft` as you go, whenever you finish a meaningful step: the files you have written so far, what you verified, what is left. Your run has a cost and time budget, and if it runs out mid-task the most recent draft is what the caller receives. A run that saved nothing returns nothing.
- For a task with more than two steps, call `todoWrite` first and mark each item done the moment it is finished.
- You have web search. Use it when a task needs current or external information rather than guessing or declining.
- NEVER fabricate output you did not actually produce.

## Avoid safeBash and exec when possible
You have been given safeBash and exec tools. **Avoid using them if at all possible**. These tools should be a last resort: they often need an approval that a human must grant — which slows you down — and that a non-interactive run cannot grant at all. So *always use another tool when possible* and only use safeBash/exec if no other tool will do the job. If a permission is rejected and the task cannot be done without it, do not hunt for workarounds: stop and report exactly which permission is missing.

## Communicating with the user
- Make sure the user is following what you're doing. Use the `whatIAmDoing` tool frequently to tell the user what you're doing.${ASK_USER_HINT}
"""

static const finalizeReminder = "Before finishing, re-read the task's exact output requirements (filename, format, fields, whitespace, trailing newline) and confirm the artifact on disk matches them exactly."

static const skills = agentSkill("coding")

export def buildTools(): any[] {
  """
  Return the coding agent's built-in tools for inspection or extension.
  """
  return [
    setAgentCwd,
    getAgentCwd,
    ...writableFileTools(),
    ...shellTools(),
    ...gitTools(),
    ...githubTools(),
    ...agencyCodeTools(),
    ...memoryTools(),
    ...agencyDocTools(),
    ...planningTools(),
    ...webTools(),
    ...communicationTools(),
    skills,
  ]
}

def solve(
  task: string,
  context: string,
  model: string,
  provider: string,
  extraTools: any[],
): string {
  if (threadIsNew()) {
    systemMessage(systemPrompt)
  }
  const prompt = """
${withContext(task: task, context: context)}

${finalizeReminder}
"""
  return llm(
    prompt,
    llmOptions(
    model: model,
    provider: provider,
    tools: [...buildTools(), ...extraTools, saveDraft],
    spillToolResultsOver: AGENT_SPILL_OVER,
    hostedTools: hostedSearchTools(model, provider),
  ),
  )
}

@guarded(cost: maxCost, time: maxTime)
export def codingAgent(
  task: string,
  context: string = "",
  maxCost: number = $50.00,
  maxTime: number = 30m,
  model: string = "",
  provider: string = "",
  session: string = "",
  extraTools: any[] = [],
): Result<string> {
  """
  Write, edit, and run code to complete a task on disk. Returns a short
  summary; the real output is filesystem side effects.

  @param task - What to accomplish
  @param context - Extra material folded into the prompt, 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: "codingAgent", session: session) {
    return solve(
      task: task,
      context: context,
      model: model,
      provider: provider,
      extraTools: extraTools,
    )
  }
}
