File size: 4,297 Bytes
b6cd280
 
 
b5e4648
 
801e523
fc99222
b6cd280
b5e4648
b6cd280
b5e4648
 
 
 
 
 
 
 
 
 
 
 
 
 
fc99222
b5e4648
 
 
 
 
 
 
 
 
 
 
 
fc99222
b5e4648
b6cd280
 
b5e4648
 
 
 
fc99222
b5e4648
 
 
 
 
fc99222
 
 
b5e4648
 
 
 
 
 
 
fc99222
b5e4648
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc99222
b5e4648
 
 
 
 
 
 
 
 
 
 
 
 
fc99222
b5e4648
 
 
 
 
 
 
 
 
 
 
 
 
 
fc99222
 
 
 
b5e4648
 
 
b6cd280
 
b5e4648
 
 
b6cd280
b5e4648
 
 
 
b6cd280
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { END, type GraphNode, START, StateGraph } from "@langchain/langgraph";

import { CoderState } from "./state.ts";
import { solidityCoderPrompt, solidityFixPrompt, solidityReviewPrompt } from "./prompts.ts";
import { compileSolidityTool } from "./tools/compile-solidity.ts";
import { createLLM } from "../../config/llm.ts";
import { emitStep } from "../../logger.ts";

const MAX_FIX_ATTEMPTS = 3;

/**
 * Extrai apenas o bloco de c贸digo Solidity de uma resposta do LLM.
 */
function extractSolidityCode(text: string): string {
  const match = text.match(/```(?:solidity)?\s*\n([\s\S]*?)```/);
  if (match) return match[1].trim();
  // Se n茫o tem bloco de c贸digo, assume que a resposta inteira 茅 c贸digo
  return text.trim();
}

/**
 * N贸 1: Gera o smart contract a partir dos requisitos.
 */
const generateContract: GraphNode<typeof CoderState> = async (state) => {
  emitStep({ agent: "coder", step: "gen", status: "running" });
  const llm = createLLM();
  const chain = solidityCoderPrompt.pipe(llm);

  const requirementsText = state.requirements
    .map((r, i) => `${i + 1}. ${r}`)
    .join("\n");

  const result = await chain.invoke({ requirements: requirementsText });
  const code = extractSolidityCode(
    typeof result.content === "string" ? result.content : JSON.stringify(result.content),
  );

  emitStep({ agent: "coder", step: "gen", status: "done" });
  return { contract: code, compilationErrors: [] };
};

/**
 * N贸 2: Compila o contrato e armazena erros (se houver).
 */
const compileContract: GraphNode<typeof CoderState> = async (state) => {
  emitStep({ agent: "coder", step: "compile", status: "running" });
  const result = await compileSolidityTool.invoke({
    sourceCode: state.contract,
    filename: "Contract.sol",
  });

  if (result.errors.length === 0) {
    emitStep({ agent: "coder", step: "compile", status: "done" });
  }
  return { compilationErrors: result.errors };
};

/**
 * N贸 3: Corrige o contrato com base nos erros de compila莽茫o.
 */
const fixContract: GraphNode<typeof CoderState> = async (state) => {
  emitStep({ agent: "coder", step: "compile", status: "running", detail: `fix ${fixAttempts}/${MAX_FIX_ATTEMPTS}` });
  const llm = createLLM();
  const chain = solidityFixPrompt.pipe(llm);

  const errorsText = state.compilationErrors.join("\n\n");

  const result = await chain.invoke({
    contract: state.contract,
    errors: errorsText,
  });

  const code = extractSolidityCode(
    typeof result.content === "string" ? result.content : JSON.stringify(result.content),
  );

  return { contract: code, compilationErrors: [] };
};

/**
 * N贸 4: Revisa o contrato compilado quanto a seguran莽a e boas pr谩ticas.
 */
const reviewContract: GraphNode<typeof CoderState> = async (state) => {
  emitStep({ agent: "coder", step: "review", status: "running" });
  const llm = createLLM();
  const chain = solidityReviewPrompt.pipe(llm);

  const requirementsText = state.requirements.join(", ");

  const result = await chain.invoke({
    requirements: requirementsText,
    contract: state.contract,
  });

  const summary =
    typeof result.content === "string" ? result.content : JSON.stringify(result.content);

  emitStep({ agent: "coder", step: "review", status: "done" });
  return { reviewSummary: summary };
};

/**
 * Roteador: decide se precisa corrigir ou se pode seguir para revis茫o.
 * Controla o n煤mero de tentativas de corre莽茫o.
 */
let fixAttempts = 0;

function shouldFix(state: { compilationErrors: string[] }): "fixContract" | "reviewContract" {
  if (state.compilationErrors.length > 0 && fixAttempts < MAX_FIX_ATTEMPTS) {
    fixAttempts++;
    return "fixContract";
  }
  if (state.compilationErrors.length > 0) {
    emitStep({ agent: "coder", step: "compile", status: "error" });
  }
  fixAttempts = 0;
  return "reviewContract";
}

export const coderAgent = new StateGraph(CoderState)
  .addNode("generateContract", generateContract)
  .addNode("compileContract", compileContract)
  .addNode("fixContract", fixContract)
  .addNode("reviewContract", reviewContract)
  .addEdge(START, "generateContract")
  .addEdge("generateContract", "compileContract")
  .addConditionalEdges("compileContract", shouldFix)
  .addEdge("fixContract", "compileContract")
  .addEdge("reviewContract", END)
  .compile();