diff --git a/packages/app/server/src/clients/gpt-client.ts b/packages/app/server/src/clients/gpt-client.ts index af887f01f..44ddf2eee 100644 --- a/packages/app/server/src/clients/gpt-client.ts +++ b/packages/app/server/src/clients/gpt-client.ts @@ -8,7 +8,7 @@ async function makeRequest(useStreaming: boolean = false) { try { // Initialize OpenAI client with custom baseURL const openai = new OpenAI({ - baseURL: 'http://localhost:3070/5b20a7e2-f4eb-4879-b889-dc19148a6b06', + baseURL: 'http://localhost:3070', apiKey: env.ECHO_API_KEY, // Required by the client but not used with local server }); diff --git a/packages/app/server/src/env.ts b/packages/app/server/src/env.ts index cc993ae07..da23dd994 100644 --- a/packages/app/server/src/env.ts +++ b/packages/app/server/src/env.ts @@ -35,7 +35,7 @@ export const env = createEnv({ X402RS_FACILITATOR_METHOD_PREFIX: z.string().optional(), PAYAI_FACILITATOR_BASE_URL: z.string().url().optional(), PAYAI_FACILITATOR_METHOD_PREFIX: z.string().optional(), - FACILITATOR_REQUEST_TIMEOUT: z.coerce.number().default(20000), + FACILITATOR_REQUEST_TIMEOUT: z.coerce.number().default(60000), // API Keys - Providers ECHO_API_KEY: z.string().optional(), diff --git a/packages/app/server/src/handlers.ts b/packages/app/server/src/handlers.ts index 70f5657e3..131d35d90 100644 --- a/packages/app/server/src/handlers.ts +++ b/packages/app/server/src/handlers.ts @@ -5,7 +5,7 @@ import { calculateRefundAmount } from 'utils'; import { checkBalance } from 'services/BalanceCheckService'; import { prisma } from 'server'; import { makeProxyPassthroughRequest } from 'services/ProxyPassthroughService'; -import logger from 'logger'; +import logger, { logMetric } from 'logger'; import { ProviderType } from 'providers/ProviderType'; import { settle } from 'handlers/settle'; import { finalize } from 'handlers/finalize'; @@ -24,55 +24,91 @@ export async function handleX402Request({ if (isPassthroughProxyRoute) { return await makeProxyPassthroughRequest(req, res, provider, headers); } - const settleResult = await settle(req, res, headers, maxCost); - if (!settleResult) { - return; - } - const { payload, paymentAmountDecimal } = settleResult; + const settlePromise = settle(req, res, headers, maxCost); - try { - const transactionResult = await modelRequestService.executeModelRequest( - req, - res, - headers, - provider, - isStream - ); - const transaction = transactionResult.transaction; - if (provider.getType() === ProviderType.OPENAI_VIDEOS) { - await prisma.videoGenerationX402.create({ - data: { - videoId: transaction.metadata.providerId, - wallet: payload.authorization.from, - cost: transaction.rawTransactionCost, - expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 1), - }, - }); - } + const modelResultPromise = modelRequestService + .executeModelRequest(req, res, headers, provider, isStream) + .then((data) => ({ success: true as const, data })) + .catch((error) => ({ success: false as const, error: error as Error })); + + const [settleResult, modelResult] = await Promise.all([ + settlePromise, + modelResultPromise, + ]); + + // Case 1: Settle failed and model failed + if (!settleResult && !modelResult.success) { + return; + } + // Case 2: Settle failed but model succeeded + if (!settleResult && modelResult.success) { + const { data } = modelResult; + logger.error('Settle failed but model request succeeded', { + provider: provider.getType(), + url: req.url, + metadata: data.transaction.metadata, + }); + logMetric('x402_request_settle_failed_model_request_succeeded', 1, { + provider: provider.getType(), + url: req.url, + }); modelRequestService.handleResolveResponse( res, isStream, - transactionResult.data + data ); + return; + } - logger.info( - `Creating X402 transaction for app. Metadata: ${JSON.stringify(transaction.metadata)}` - ); - const transactionCosts = - await x402AuthenticationService.createX402Transaction(transaction); - - await finalize( - paymentAmountDecimal, - transactionCosts.rawTransactionCost, - transactionCosts.totalAppProfit, - transactionCosts.echoProfit, - payload - ); - } catch (error) { + // At this point, settleResult is guaranteed to exist + if (!settleResult) { + return; + } + + const { payload, paymentAmountDecimal } = settleResult; + + // Case 3: Settle succeeded but model failed + if (!modelResult.success) { await refund(paymentAmountDecimal, payload); + return; } + + // Case 4: Both settle and model succeeded + const transactionResult = modelResult.data; + const transaction = transactionResult.transaction; + + if (provider.getType() === ProviderType.OPENAI_VIDEOS) { + await prisma.videoGenerationX402.create({ + data: { + videoId: transaction.metadata.providerId, + wallet: payload.authorization.from, + cost: transaction.rawTransactionCost, + expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 1), + }, + }); + } + + modelRequestService.handleResolveResponse( + res, + isStream, + transactionResult.data + ); + + logger.info( + `Creating X402 transaction for app. Metadata: ${JSON.stringify(transaction.metadata)}` + ); + const transactionCosts = + await x402AuthenticationService.createX402Transaction(transaction); + + await finalize( + paymentAmountDecimal, + transactionCosts.rawTransactionCost, + transactionCosts.totalAppProfit, + transactionCosts.echoProfit, + payload + ); } export async function handleApiKeyRequest({