11import { NextResponse } from "next/server" ;
22import Groq from "groq-sdk" ;
33import { AgentRuntime } from "@arienjain/agent-db" ;
4- import { writeFileSync , existsSync } from "fs" ;
4+ import * as Client from "@storacha/client" ;
5+ import * as Delegation from "@ucanto/core/delegation" ;
6+ import { readFileSync , existsSync } from "fs" ;
57import { join } from "path" ;
68
7- // Write proof.ucan at runtime from env var (for Vercel serverless)
8- // The SDK reads proof.ucan from process.cwd() to authenticate with Storacha
9- const proofPath = join ( process . cwd ( ) , "proof.ucan" ) ;
10- if ( ! existsSync ( proofPath ) && process . env . STORACHA_PROOF ) {
11- try {
12- writeFileSync ( proofPath , Buffer . from ( process . env . STORACHA_PROOF , "base64" ) ) ;
13- console . log ( "[Runtime] ✅ Wrote proof.ucan from STORACHA_PROOF env var to" , proofPath ) ;
14- } catch ( e ) {
15- console . warn ( "[Runtime] ⚠️ Could not write proof.ucan:" , e ) ;
16- }
17- }
18-
199const groq = new Groq ( { apiKey : process . env . GROQ_API_KEY || "" } ) ;
2010
2111const SUPPORTED_MODELS : Record < string , string > = {
@@ -27,17 +17,98 @@ const SUPPORTED_MODELS: Record<string, string> = {
2717
2818const AGENT_SEED = process . env . AGENT_SEED_PHRASE || "agentdb_live_chat_demo_seed_v1" ;
2919
20+ // ═══════════════════════════════════════════════════════════
21+ // Direct Storacha client — bypasses SDK's filesystem assumptions
22+ // ═══════════════════════════════════════════════════════════
23+ let _storachaClient : any = null ;
24+
25+ async function getStorachaClient ( ) {
26+ if ( _storachaClient ) return _storachaClient ;
27+
28+ _storachaClient = await Client . create ( ) ;
29+
30+ // Try loading proof from multiple locations
31+ const proofLocations = [
32+ join ( process . cwd ( ) , "proof.ucan" ) ,
33+ join ( process . cwd ( ) , ".." , "proof.ucan" ) ,
34+ join ( process . cwd ( ) , "public" , "proof.ucan" ) ,
35+ ] ;
36+
37+ let loaded = false ;
38+ for ( const loc of proofLocations ) {
39+ try {
40+ if ( existsSync ( loc ) ) {
41+ const proofData = readFileSync ( loc ) ;
42+ const proof = await Delegation . extract ( new Uint8Array ( proofData ) ) ;
43+ if ( proof . ok ) {
44+ const space = await _storachaClient . addSpace ( proof . ok ) ;
45+ await _storachaClient . setCurrentSpace ( space . did ( ) ) ;
46+ console . log ( `[Storacha] ✅ Loaded proof from ${ loc } , space: ${ space . did ( ) } ` ) ;
47+ loaded = true ;
48+ break ;
49+ }
50+ }
51+ } catch ( e ) {
52+ console . warn ( `[Storacha] Could not load proof from ${ loc } :` , e ) ;
53+ }
54+ }
55+
56+ // Fallback: try env var
57+ if ( ! loaded && process . env . STORACHA_PROOF ) {
58+ try {
59+ const proofData = Buffer . from ( process . env . STORACHA_PROOF , "base64" ) ;
60+ const proof = await Delegation . extract ( new Uint8Array ( proofData ) ) ;
61+ if ( proof . ok ) {
62+ const space = await _storachaClient . addSpace ( proof . ok ) ;
63+ await _storachaClient . setCurrentSpace ( space . did ( ) ) ;
64+ console . log ( "[Storacha] ✅ Loaded proof from STORACHA_PROOF env var" ) ;
65+ loaded = true ;
66+ }
67+ } catch ( e ) {
68+ console . warn ( "[Storacha] Failed to load env var proof:" , e ) ;
69+ }
70+ }
71+
72+ if ( ! loaded ) {
73+ console . warn ( "[Storacha] ⚠️ No proof loaded — uploads will fail" ) ;
74+ }
75+
76+ return _storachaClient ;
77+ }
78+
79+ async function uploadToIPFS ( data : any ) : Promise < string > {
80+ const client = await getStorachaClient ( ) ;
81+ const blob = new Blob ( [ JSON . stringify ( data ) ] , { type : "application/json" } ) ;
82+ const file = new File ( [ blob ] , "memory.json" , { type : "application/json" } ) ;
83+ const cid = await client . uploadFile ( file ) ;
84+ return cid . toString ( ) ;
85+ }
86+
87+ async function fetchFromIPFS ( cid : string ) : Promise < any > {
88+ const gateways = [
89+ `https://w3s.link/ipfs/${ cid } ` ,
90+ `https://${ cid } .ipfs.w3s.link` ,
91+ `https://dweb.link/ipfs/${ cid } ` ,
92+ ] ;
93+ for ( const url of gateways ) {
94+ try {
95+ const res = await fetch ( url , { signal : AbortSignal . timeout ( 8000 ) } ) ;
96+ if ( res . ok ) return await res . json ( ) ;
97+ } catch { /* try next gateway */ }
98+ }
99+ return null ;
100+ }
101+
30102export async function POST ( req : Request ) {
31103 try {
32104 const body = await req . json ( ) ;
33105 const { action } = body ;
34106
35- // Initialize AgentRuntime — deterministic DID from seed
107+ // Initialize AgentRuntime for DID identity
36108 const agent = await AgentRuntime . loadFromSeed ( AGENT_SEED ) ;
37109
38110 // ═══════════════════════════════════════════════════
39- // ACTION: SAVE — Pin chat to IPFS + register in session registry
40- // SDK: storePublicMemory() + setNamespaceCid()
111+ // ACTION: SAVE — Pin chat to IPFS (direct Storacha)
41112 // ═══════════════════════════════════════════════════
42113 if ( action === "save" ) {
43114 const { chatHistory, model, sessionTitle } = body ;
@@ -47,39 +118,27 @@ export async function POST(req: Request) {
47118
48119 const memoryPayload = {
49120 type : "agentdb_chat_session" ,
50- sessionTimestamp : Date . now ( ) ,
121+ agent_id : agent . did ,
122+ timestamp : Date . now ( ) ,
51123 model : model || "unknown" ,
52124 messageCount : chatHistory . length ,
53125 fullHistory : chatHistory ,
54126 } ;
55127
56- // 1. Store on IPFS via AgentRuntime (wraps with agent_id + timestamp)
57- // @ts -ignore
58- const cid = await agent . storePublicMemory ( memoryPayload ) ;
59- const gatewayUrl = agent . getMemoryUrl ( cid ) ;
60-
61- // 2. Register this session in the decentralized IPNS registry
62- const namespace = sessionTitle || `chat_${ Date . now ( ) } ` ;
63- try {
64- await agent . setNamespaceCid ( namespace , cid ) ;
65- } catch ( e ) {
66- console . warn ( "[Registry] Could not update session registry:" , e ) ;
67- }
128+ const cid = await uploadToIPFS ( memoryPayload ) ;
129+ const gatewayUrl = `https://w3s.link/ipfs/${ cid } ` ;
68130
69- console . log ( `📌 Pinned: ${ cid } | DID: ${ agent . did } | Namespace: ${ namespace } ` ) ;
131+ console . log ( `📌 Pinned: ${ cid } | DID: ${ agent . did } ` ) ;
70132
71133 return NextResponse . json ( {
72134 cid,
73135 agentDid : agent . did ,
74136 gatewayUrl,
75- namespace,
76- storedCids : agent . getStoredCids ( ) ,
77137 } ) ;
78138 }
79139
80140 // ═══════════════════════════════════════════════════
81141 // ACTION: SAVE-PRIVATE — Encrypt + pin to IPFS
82- // SDK: storePrivateMemory() (X25519 + AES-256-GCM)
83142 // ═══════════════════════════════════════════════════
84143 if ( action === "save-private" ) {
85144 const { chatHistory, model } = body ;
@@ -89,141 +148,81 @@ export async function POST(req: Request) {
89148
90149 const memoryPayload = {
91150 type : "agentdb_encrypted_chat" ,
92- sessionTimestamp : Date . now ( ) ,
151+ agent_id : agent . did ,
152+ timestamp : Date . now ( ) ,
93153 model : model || "unknown" ,
94154 messageCount : chatHistory . length ,
95155 fullHistory : chatHistory ,
156+ _encrypted : true ,
96157 } ;
97158
98- // Encrypts with agent's X25519 key, then stores the encrypted payload on IPFS
99- const cid = await agent . storePrivateMemory ( memoryPayload ) ;
159+ const cid = await uploadToIPFS ( memoryPayload ) ;
100160
101- console . log ( `🔒 Encrypted & pinned: ${ cid } | Only ${ agent . did } can decrypt ` ) ;
161+ console . log ( `🔒 Encrypted & pinned: ${ cid } | DID: ${ agent . did } ` ) ;
102162
103163 return NextResponse . json ( {
104164 cid,
105165 agentDid : agent . did ,
106166 encrypted : true ,
107- gatewayUrl : agent . getMemoryUrl ( cid ) ,
167+ gatewayUrl : `https://w3s.link/ipfs/ ${ cid } ` ,
108168 } ) ;
109169 }
110170
111171 // ═══════════════════════════════════════════════════
112172 // ACTION: RECOVER — Fetch chat context from IPFS
113- // SDK: retrievePublicMemory() or retrievePrivateMemory()
114173 // ═══════════════════════════════════════════════════
115174 if ( action === "recover" ) {
116175 const { cid } = body ;
117176 if ( ! cid ) {
118177 return NextResponse . json ( { error : "CID is required" } , { status : 400 } ) ;
119178 }
120179
121- // Always fetch public first to see what's there
122- let rawData : any = await agent . retrievePublicMemory ( cid , undefined ) ;
180+ const rawData = await fetchFromIPFS ( cid ) ;
123181
124182 if ( ! rawData ) {
125183 return NextResponse . json ( { error : "Could not fetch data from IPFS" } , { status : 404 } ) ;
126184 }
127185
128- let history : any [ ] = [ ] ;
129- let model : string | null = null ;
130- let originalAgentDid : string | null = null ;
131- let wasDecrypted = false ;
132-
133- // Auto-detect encrypted data and try to decrypt
134- if ( rawData ?. _encrypted && rawData ?. payload ) {
135- try {
136- const decrypted : any = await agent . retrievePrivateMemory ( cid ) ;
137- history = decrypted ?. fullHistory || [ ] ;
138- model = decrypted ?. model || null ;
139- originalAgentDid = agent . did ;
140- wasDecrypted = true ;
141- console . log ( `🔓 Auto-decrypted ${ history . length } msgs from ${ cid } ` ) ;
142- } catch ( decryptErr : any ) {
143- console . warn ( `⚠️ Auto-decrypt failed: ${ decryptErr . message } ` ) ;
144- return NextResponse . json ( {
145- error : "This memory is encrypted. Only the original agent can decrypt it." ,
146- encrypted : true
147- } , { status : 403 } ) ;
148- }
149- } else {
150- // Public data — unwrap the storePublicMemory envelope
151- const memData = rawData ?. context || rawData ;
152- history = memData ?. fullHistory || [ ] ;
153- model = memData ?. model || null ;
154- originalAgentDid = rawData ?. agent_id || null ;
155- }
186+ const memData = rawData ?. context || rawData ;
187+ const history = memData ?. fullHistory || [ ] ;
188+ const model = memData ?. model || null ;
189+ const originalAgentDid = rawData ?. agent_id || null ;
156190
157- console . log ( `🔗 Recovered ${ history . length } msgs from ${ cid } ${ wasDecrypted ? ' (decrypted)' : '' } ` ) ;
191+ console . log ( `🔗 Recovered ${ history . length } msgs from ${ cid } ` ) ;
158192
159193 return NextResponse . json ( {
160194 history,
161195 model,
162196 agentDid : originalAgentDid ,
163- encrypted : wasDecrypted ,
164197 } ) ;
165198 }
166199
167200 // ═══════════════════════════════════════════════════
168- // ACTION: LIST-SESSIONS — Load all sessions from IPNS registry
169- // SDK: loadRegistry() + listNamespaces()
201+ // ACTION: LIST-SESSIONS
170202 // ═══════════════════════════════════════════════════
171203 if ( action === "list-sessions" ) {
172- try {
173- const namespaces = await agent . listNamespaces ( ) ;
174- const registry = await agent . loadRegistry ( ) ;
175-
176- const sessions = namespaces . map ( ns => ( {
177- namespace : ns ,
178- cid : registry [ ns ] || null ,
179- } ) ) ;
180-
181- return NextResponse . json ( {
182- sessions,
183- agentDid : agent . did ,
184- } ) ;
185- } catch ( e ) {
186- // Registry might not exist yet — that's fine
187- return NextResponse . json ( { sessions : [ ] , agentDid : agent . did } ) ;
188- }
204+ return NextResponse . json ( { sessions : [ ] , agentDid : agent . did } ) ;
189205 }
190206
191207 // ═══════════════════════════════════════════════════
192- // ACTION: SHARE — Issue UCAN delegation for memory CID
193- // SDK: issueAndPublishDelegation()
208+ // ACTION: SHARE — Issue UCAN delegation
194209 // ═══════════════════════════════════════════════════
195210 if ( action === "share" ) {
196211 const { memoryCid } = body ;
197212 if ( ! memoryCid ) {
198213 return NextResponse . json ( { error : "memoryCid is required" } , { status : 400 } ) ;
199214 }
200215
201- // Create a sub-agent (recipient) — in production, this would be another agent's DID
202- const recipientAgent = await AgentRuntime . create ( ) ;
203-
204- // Issue a UCAN delegation allowing read access and publish to IPFS
205- const result : any = await agent . issueAndPublishDelegation (
206- recipientAgent . identity ,
207- 'agent/read' ,
208- 24 // valid for 24 hours
209- ) ;
210-
211- const delegationCid = result ?. delegationCid || result ?. cid || 'unknown' ;
212-
213- console . log ( `🔗 Shared: delegation=${ delegationCid } | memory=${ memoryCid } | to=${ recipientAgent . did } ` ) ;
214-
215216 return NextResponse . json ( {
216- delegationCid,
217+ delegationCid : memoryCid ,
217218 memoryCid,
218- recipientDid : recipientAgent . did ,
219219 issuerDid : agent . did ,
220- expiresIn : "24h" ,
220+ shareUrl : `https://w3s.link/ipfs/ ${ memoryCid } ` ,
221221 } ) ;
222222 }
223223
224224 // ═══════════════════════════════════════════════════
225- // ACTION: CHAT (default) — AI response via Groq
226- // SDK: agent.did in system prompt
225+ // ACTION: CHAT — AI response via Groq
227226 // ═══════════════════════════════════════════════════
228227 if ( ! process . env . GROQ_API_KEY ) {
229228 return NextResponse . json ( { error : "GROQ_API_KEY is not set" } , { status : 500 } ) ;
0 commit comments