repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | // ═══════════════════════════════════════════════════════════════ |
| #2 | // AGENT DEPLOYMENT SERVICE - Deploy AI Agents on Cloudflare |
| #3 | // Manages agent deployment lifecycle and delegated signing |
| #4 | // ═══════════════════════════════════════════════════════════════ |
| #5 | |
| #6 | import type { Env, Agent } from '../index'; |
| #7 | |
| #8 | // ───────────────────────────────────────────────── |
| #9 | // TYPES |
| #10 | // ───────────────────────────────────────────────── |
| #11 | |
| #12 | export interface DeployedAgentConfig { |
| #13 | id: string; |
| #14 | agentId: string; |
| #15 | name: string; |
| #16 | description?: string; |
| #17 | status: 'pending' | 'deploying' | 'running' | 'stopped' | 'error'; |
| #18 | walletAddress?: string; |
| #19 | chain: 'solana' | 'solana-devnet'; |
| #20 | publicKey?: string; |
| #21 | delegatedSignerId?: string; |
| #22 | delegatedSignerStatus?: 'pending' | 'active' | 'rejected'; |
| #23 | configuration: { |
| #24 | model: string; |
| #25 | maxTokens: number; |
| #26 | temperature: number; |
| #27 | systemPrompt?: string; |
| #28 | }; |
| #29 | capabilities: string[]; |
| #30 | createdAt: string; |
| #31 | deployedAt?: string; |
| #32 | lastActiveAt?: string; |
| #33 | metadata?: Record<string, unknown>; |
| #34 | } |
| #35 | |
| #36 | export interface DeploymentRequest { |
| #37 | agentId: string; |
| #38 | walletAddress: string; |
| #39 | walletSignerType: 'solana-keypair' | 'evm-passkey'; |
| #40 | configuration?: { |
| #41 | model?: string; |
| #42 | maxTokens?: number; |
| #43 | temperature?: number; |
| #44 | systemPrompt?: string; |
| #45 | }; |
| #46 | capabilities?: string[]; |
| #47 | } |
| #48 | |
| #49 | export interface DelegatedSignerResult { |
| #50 | id: string; |
| #51 | message: string; |
| #52 | targetSignerLocator: string; |
| #53 | alreadyActive: boolean; |
| #54 | } |
| #55 | |
| #56 | // ───────────────────────────────────────────────── |
| #57 | // DEPLOYMENT SERVICE |
| #58 | // ───────────────────────────────────────────────── |
| #59 | |
| #60 | export class DeploymentService { |
| #61 | private crossmintApiKey: string; |
| #62 | private crossmintBaseUrl: string; |
| #63 | |
| #64 | constructor(private env: Env, private db: D1Database) { |
| #65 | this.crossmintApiKey = env.CROSSMINT_SERVERSIDE_API_KEY || ''; |
| #66 | // Determine environment from API key |
| #67 | const isStaging = this.crossmintApiKey.startsWith('sk_staging_'); |
| #68 | this.crossmintBaseUrl = isStaging |
| #69 | ? 'https://staging.crossmint.com/api/2022-06-09' |
| #70 | : 'https://www.crossmint.com/api/2022-06-09'; |
| #71 | } |
| #72 | |
| #73 | // ═══════════════════════════════════════════════════ |
| #74 | // DEPLOYMENT OPERATIONS |
| #75 | // ═══════════════════════════════════════════════════ |
| #76 | |
| #77 | async deployAgent(request: DeploymentRequest, agent: Agent): Promise<{ |
| #78 | deployment: DeployedAgentConfig; |
| #79 | delegatedSigner?: DelegatedSignerResult | null; |
| #80 | }> { |
| #81 | // Generate a unique deployment ID |
| #82 | const deploymentId = `dep_${this.generateId()}`; |
| #83 | |
| #84 | // Create deployment config |
| #85 | const deployment: DeployedAgentConfig = { |
| #86 | id: deploymentId, |
| #87 | agentId: agent.id, |
| #88 | name: agent.name, |
| #89 | description: agent.description || undefined, |
| #90 | status: 'deploying', |
| #91 | walletAddress: request.walletAddress, |
| #92 | chain: agent.chain, |
| #93 | configuration: { |
| #94 | model: request.configuration?.model || 'gpt-4', |
| #95 | maxTokens: request.configuration?.maxTokens || 4096, |
| #96 | temperature: request.configuration?.temperature || 0.7, |
| #97 | systemPrompt: request.configuration?.systemPrompt, |
| #98 | }, |
| #99 | capabilities: request.capabilities || ['transfer', 'swap', 'chat'], |
| #100 | createdAt: new Date().toISOString(), |
| #101 | }; |
| #102 | |
| #103 | // Store deployment in database |
| #104 | await this.db.prepare(` |
| #105 | INSERT INTO agent_deployments ( |
| #106 | id, agent_id, name, description, status, wallet_address, chain, |
| #107 | configuration, capabilities, created_at |
| #108 | ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) |
| #109 | `).bind( |
| #110 | deployment.id, |
| #111 | deployment.agentId, |
| #112 | deployment.name, |
| #113 | deployment.description || null, |
| #114 | deployment.status, |
| #115 | deployment.walletAddress, |
| #116 | deployment.chain, |
| #117 | JSON.stringify(deployment.configuration), |
| #118 | JSON.stringify(deployment.capabilities), |
| #119 | deployment.createdAt |
| #120 | ).run(); |
| #121 | |
| #122 | // Get or create delegated signer |
| #123 | const delegatedSigner = await this.getOrCreateDelegatedSigner( |
| #124 | request.walletAddress, |
| #125 | agent.id, |
| #126 | request.walletSignerType |
| #127 | ); |
| #128 | |
| #129 | // Update deployment with delegated signer info |
| #130 | if (delegatedSigner) { |
| #131 | deployment.delegatedSignerId = delegatedSigner.id; |
| #132 | deployment.delegatedSignerStatus = delegatedSigner.alreadyActive ? 'active' : 'pending'; |
| #133 | |
| #134 | await this.db.prepare(` |
| #135 | UPDATE agent_deployments |
| #136 | SET delegated_signer_id = ?, delegated_signer_status = ?, status = ? |
| #137 | WHERE id = ? |
| #138 | `).bind( |
| #139 | delegatedSigner.id, |
| #140 | deployment.delegatedSignerStatus, |
| #141 | delegatedSigner.alreadyActive ? 'running' : 'pending', |
| #142 | deployment.id |
| #143 | ).run(); |
| #144 | |
| #145 | if (delegatedSigner.alreadyActive) { |
| #146 | deployment.status = 'running'; |
| #147 | deployment.deployedAt = new Date().toISOString(); |
| #148 | } |
| #149 | } |
| #150 | |
| #151 | return { deployment, delegatedSigner }; |
| #152 | } |
| #153 | |
| #154 | async getDeployments(agentId: string): Promise<DeployedAgentConfig[]> { |
| #155 | const result = await this.db.prepare(` |
| #156 | SELECT * FROM agent_deployments WHERE agent_id = ? ORDER BY created_at DESC |
| #157 | `).bind(agentId).all(); |
| #158 | |
| #159 | return (result.results || []).map(row => this.rowToDeployment(row)); |
| #160 | } |
| #161 | |
| #162 | async getAllDeployments(): Promise<DeployedAgentConfig[]> { |
| #163 | const result = await this.db.prepare(` |
| #164 | SELECT * FROM agent_deployments ORDER BY created_at DESC LIMIT 100 |
| #165 | `).all(); |
| #166 | |
| #167 | return (result.results || []).map(row => this.rowToDeployment(row)); |
| #168 | } |
| #169 | |
| #170 | async getDeployment(deploymentId: string): Promise<DeployedAgentConfig | null> { |
| #171 | const result = await this.db.prepare(` |
| #172 | SELECT * FROM agent_deployments WHERE id = ? |
| #173 | `).bind(deploymentId).first(); |
| #174 | |
| #175 | return result ? this.rowToDeployment(result) : null; |
| #176 | } |
| #177 | |
| #178 | async updateDeploymentStatus( |
| #179 | deploymentId: string, |
| #180 | status: DeployedAgentConfig['status'] |
| #181 | ): Promise<void> { |
| #182 | const updates: string[] = ['status = ?']; |
| #183 | const values: unknown[] = [status]; |
| #184 | |
| #185 | if (status === 'running') { |
| #186 | updates.push('deployed_at = ?'); |
| #187 | values.push(new Date().toISOString()); |
| #188 | } |
| #189 | |
| #190 | values.push(deploymentId); |
| #191 | |
| #192 | await this.db.prepare(` |
| #193 | UPDATE agent_deployments SET ${updates.join(', ')} WHERE id = ? |
| #194 | `).bind(...values).run(); |
| #195 | } |
| #196 | |
| #197 | async stopDeployment(deploymentId: string): Promise<void> { |
| #198 | await this.updateDeploymentStatus(deploymentId, 'stopped'); |
| #199 | } |
| #200 | |
| #201 | // ═══════════════════════════════════════════════════ |
| #202 | // DELEGATED SIGNER OPERATIONS |
| #203 | // ═══════════════════════════════════════════════════ |
| #204 | |
| #205 | private async getOrCreateDelegatedSigner( |
| #206 | walletAddress: string, |
| #207 | agentPublicKey: string, |
| #208 | walletSignerType: 'solana-keypair' | 'evm-passkey' |
| #209 | ): Promise<DelegatedSignerResult | null> { |
| #210 | if (!this.crossmintApiKey) { |
| #211 | throw new Error('Crossmint API key not configured'); |
| #212 | } |
| #213 | |
| #214 | const signerLocator = `${walletSignerType}:${agentPublicKey}`; |
| #215 | |
| #216 | try { |
| #217 | // 1. Check if delegated signer already exists |
| #218 | const existingResponse = await fetch( |
| #219 | `${this.crossmintBaseUrl}/wallets/${walletAddress}/signers/${signerLocator}`, |
| #220 | { |
| #221 | method: 'GET', |
| #222 | headers: { |
| #223 | 'X-API-KEY': this.crossmintApiKey, |
| #224 | 'Content-Type': 'application/json', |
| #225 | }, |
| #226 | } |
| #227 | ); |
| #228 | |
| #229 | if (existingResponse.ok) { |
| #230 | const existing = await existingResponse.json(); |
| #231 | const parsed = this.parseDelegatedSignerResponse(existing, walletSignerType === 'evm-passkey'); |
| #232 | |
| #233 | if (parsed.status === 'active' || parsed.status === 'success') { |
| #234 | return { |
| #235 | id: parsed.id, |
| #236 | message: '', |
| #237 | targetSignerLocator: parsed.targetSignerLocator, |
| #238 | alreadyActive: true, |
| #239 | }; |
| #240 | } |
| #241 | |
| #242 | if (parsed.status === 'awaiting-approval') { |
| #243 | return { |
| #244 | id: parsed.id, |
| #245 | message: parsed.message, |
| #246 | targetSignerLocator: parsed.targetSignerLocator, |
| #247 | alreadyActive: false, |
| #248 | }; |
| #249 | } |
| #250 | } |
| #251 | |
| #252 | // 2. Create new delegated signer |
| #253 | const createBody: Record<string, string> = { signer: signerLocator }; |
| #254 | if (walletSignerType === 'evm-passkey') { |
| #255 | createBody.chain = 'base-sepolia'; // Default chain for EVM |
| #256 | } |
| #257 | |
| #258 | const createResponse = await fetch( |
| #259 | `${this.crossmintBaseUrl}/wallets/${walletAddress}/signers`, |
| #260 | { |
| #261 | method: 'POST', |
| #262 | headers: { |
| #263 | 'X-API-KEY': this.crossmintApiKey, |
| #264 | 'Content-Type': 'application/json', |
| #265 | }, |
| #266 | body: JSON.stringify(createBody), |
| #267 | } |
| #268 | ); |
| #269 | |
| #270 | if (!createResponse.ok) { |
| #271 | const errorText = await createResponse.text(); |
| #272 | throw new Error(`Failed to create delegated signer: ${errorText}`); |
| #273 | } |
| #274 | |
| #275 | const created = await createResponse.json(); |
| #276 | const parsed = this.parseDelegatedSignerResponse(created, walletSignerType === 'evm-passkey'); |
| #277 | |
| #278 | return { |
| #279 | id: parsed.id, |
| #280 | message: parsed.message, |
| #281 | targetSignerLocator: parsed.targetSignerLocator, |
| #282 | alreadyActive: false, |
| #283 | }; |
| #284 | } catch (error) { |
| #285 | console.error('Error in getOrCreateDelegatedSigner:', error); |
| #286 | throw error; |
| #287 | } |
| #288 | } |
| #289 | |
| #290 | async submitSignatureApproval( |
| #291 | walletAddress: string, |
| #292 | signatureId: string, |
| #293 | signerLocator: string, |
| #294 | signature: unknown, |
| #295 | metadata?: unknown |
| #296 | ): Promise<{ success: boolean }> { |
| #297 | const response = await fetch( |
| #298 | `${this.crossmintBaseUrl}/wallets/${walletAddress}/signatures/${signatureId}/approvals`, |
| #299 | { |
| #300 | method: 'POST', |
| #301 | headers: { |
| #302 | 'X-API-KEY': this.crossmintApiKey, |
| #303 | 'Content-Type': 'application/json', |
| #304 | }, |
| #305 | body: JSON.stringify({ |
| #306 | approvals: [{ |
| #307 | signer: signerLocator, |
| #308 | metadata, |
| #309 | signature, |
| #310 | }], |
| #311 | }), |
| #312 | } |
| #313 | ); |
| #314 | |
| #315 | if (!response.ok) { |
| #316 | throw new Error('Failed to submit signature approval'); |
| #317 | } |
| #318 | |
| #319 | return { success: true }; |
| #320 | } |
| #321 | |
| #322 | async submitTransactionApproval( |
| #323 | walletAddress: string, |
| #324 | transactionId: string, |
| #325 | signerLocator: string, |
| #326 | signature: string |
| #327 | ): Promise<{ success: boolean }> { |
| #328 | const response = await fetch( |
| #329 | `${this.crossmintBaseUrl}/wallets/${walletAddress}/transactions/${transactionId}/approvals`, |
| #330 | { |
| #331 | method: 'POST', |
| #332 | headers: { |
| #333 | 'X-API-KEY': this.crossmintApiKey, |
| #334 | 'Content-Type': 'application/json', |
| #335 | }, |
| #336 | body: JSON.stringify({ |
| #337 | approvals: [{ |
| #338 | signer: signerLocator, |
| #339 | signature, |
| #340 | }], |
| #341 | }), |
| #342 | } |
| #343 | ); |
| #344 | |
| #345 | if (!response.ok) { |
| #346 | throw new Error('Failed to submit transaction approval'); |
| #347 | } |
| #348 | |
| #349 | return { success: true }; |
| #350 | } |
| #351 | |
| #352 | // ───────────────────────────────────────────────── |
| #353 | // HELPERS |
| #354 | // ───────────────────────────────────────────────── |
| #355 | |
| #356 | private parseDelegatedSignerResponse( |
| #357 | response: unknown, |
| #358 | isEVM: boolean |
| #359 | ): { |
| #360 | message: string; |
| #361 | id: string; |
| #362 | status: string; |
| #363 | targetSignerLocator: string; |
| #364 | } { |
| #365 | const resp = response as Record<string, unknown>; |
| #366 | const target = isEVM |
| #367 | ? Object.values((resp?.chains as Record<string, unknown>) || {})[0] |
| #368 | : resp?.transaction; |
| #369 | |
| #370 | const t = target as Record<string, unknown>; |
| #371 | const approvals = t?.approvals as Record<string, unknown>; |
| #372 | const pending = (approvals?.pending as unknown[]) || []; |
| #373 | const firstPending = pending[0] as Record<string, unknown>; |
| #374 | |
| #375 | return { |
| #376 | message: firstPending?.message as string || '', |
| #377 | id: t?.id as string || '', |
| #378 | status: t?.status as string || '', |
| #379 | targetSignerLocator: firstPending?.signer as string || '', |
| #380 | }; |
| #381 | } |
| #382 | |
| #383 | private rowToDeployment(row: Record<string, unknown>): DeployedAgentConfig { |
| #384 | return { |
| #385 | id: row.id as string, |
| #386 | agentId: row.agent_id as string, |
| #387 | name: row.name as string, |
| #388 | description: row.description as string | undefined, |
| #389 | status: row.status as DeployedAgentConfig['status'], |
| #390 | walletAddress: row.wallet_address as string | undefined, |
| #391 | chain: row.chain as 'solana' | 'solana-devnet', |
| #392 | publicKey: row.public_key as string | undefined, |
| #393 | delegatedSignerId: row.delegated_signer_id as string | undefined, |
| #394 | delegatedSignerStatus: row.delegated_signer_status as 'pending' | 'active' | 'rejected' | undefined, |
| #395 | configuration: JSON.parse(row.configuration as string || '{}'), |
| #396 | capabilities: JSON.parse(row.capabilities as string || '[]'), |
| #397 | createdAt: row.created_at as string, |
| #398 | deployedAt: row.deployed_at as string | undefined, |
| #399 | lastActiveAt: row.last_active_at as string | undefined, |
| #400 | metadata: row.metadata ? JSON.parse(row.metadata as string) : undefined, |
| #401 | }; |
| #402 | } |
| #403 | |
| #404 | private generateId(): string { |
| #405 | const chars = 'abcdef0123456789'; |
| #406 | let result = ''; |
| #407 | for (let i = 0; i < 24; i++) { |
| #408 | result += chars[Math.floor(Math.random() * chars.length)]; |
| #409 | } |
| #410 | return result; |
| #411 | } |
| #412 | } |
| #413 |