Skip to content

Commit 48fab4f

Browse files
committed
feat: add Gemini-powered agent for policy-aware payment intent generation
- Implement off-chain agent using Gemini API - Agent reasons over policy constraints and user intent - Outputs structured PaymentIntent for on-chain execution - Preserves model: AI proposes, contracts enforce
1 parent 8c5e514 commit 48fab4f

File tree

8 files changed

+1285
-5
lines changed

8 files changed

+1285
-5
lines changed

.gitignore

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
# Compiler files
2+
node_modules/
3+
lib/
4+
cache/
5+
out/
6+
dist/
27
cache/
38
out/
49

510
# Ignores development broadcast logs
6-
!/broadcast
7-
/broadcast/*/31337/
8-
/broadcast/**/dry-run/
11+
broadcast/
12+
deployments/
913

1014
# Docs
1115
docs/
1216

1317
# Dotenv file
1418
.env
15-
broadcast/
16-
deployments.json
19+
.env.*
20+
!.env.example
21+
22+
# Misc
23+
.DS_Store

agent/agent.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// import { GoogleGenerativeAI } from "@google/generative-ai";
2+
import { GoogleGenAI } from "@google/genai";
3+
import { PaymentIntentSchema, AgentInput } from "./schema";
4+
import { buildPrompt } from "./prompt";
5+
import "dotenv/config";
6+
7+
export async function proposePayment(input: AgentInput) {
8+
const genAI = new GoogleGenAI({
9+
apiKey: process.env.GEMINI_API_KEY!,
10+
apiVersion: 'v1'
11+
});
12+
const modelName = "gemini-2.5-flash-lite"; // Use 2.0 or 2.5 for stability
13+
14+
const result = await genAI.models.generateContent({
15+
model: modelName,
16+
contents: [{ role: "user", parts: [{ text: buildPrompt(input) }] }],
17+
config: {
18+
// We use 'as any' to tell TypeScript to stay out of the way.
19+
// This allows us to send the snake_case name the API actually requires.
20+
["response_mime_type" as any]: "application/json"
21+
} as any
22+
});
23+
24+
// FIX: Access .text() directly on the result
25+
const text = result?.text;
26+
27+
// Check if the response is actually there
28+
if (!text) {
29+
throw new Error("Gemini returned an empty response. This might be due to safety filters.");
30+
}
31+
32+
let parsed;
33+
try {
34+
const cleanJson = text.replace(/```json/g, "").replace(/```/g, "").trim();
35+
parsed = JSON.parse(cleanJson);
36+
} catch (e) {
37+
throw new Error(`Gemini returned invalid JSON: ${text}`);
38+
}
39+
40+
if (parsed.reject) {
41+
throw new Error(`Agent rejected request: ${parsed.reason}`);
42+
}
43+
44+
return PaymentIntentSchema.parse(parsed);
45+
}

0 commit comments

Comments
 (0)