Skip to content

Commit ca11b1e

Browse files
authored
Merge pull request #693 from Merit-Systems/br/x402-settle-later
[Feature] settle in parallel
2 parents 2d5ea14 + a11b30f commit ca11b1e

File tree

3 files changed

+78
-42
lines changed

3 files changed

+78
-42
lines changed

packages/app/server/src/clients/gpt-client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async function makeRequest(useStreaming: boolean = false) {
88
try {
99
// Initialize OpenAI client with custom baseURL
1010
const openai = new OpenAI({
11-
baseURL: 'http://localhost:3070/5b20a7e2-f4eb-4879-b889-dc19148a6b06',
11+
baseURL: 'http://localhost:3070',
1212
apiKey: env.ECHO_API_KEY, // Required by the client but not used with local server
1313
});
1414

packages/app/server/src/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const env = createEnv({
3535
X402RS_FACILITATOR_METHOD_PREFIX: z.string().optional(),
3636
PAYAI_FACILITATOR_BASE_URL: z.string().url().optional(),
3737
PAYAI_FACILITATOR_METHOD_PREFIX: z.string().optional(),
38-
FACILITATOR_REQUEST_TIMEOUT: z.coerce.number().default(20000),
38+
FACILITATOR_REQUEST_TIMEOUT: z.coerce.number().default(60000),
3939

4040
// API Keys - Providers
4141
ECHO_API_KEY: z.string().optional(),

packages/app/server/src/handlers.ts

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { calculateRefundAmount } from 'utils';
55
import { checkBalance } from 'services/BalanceCheckService';
66
import { prisma } from 'server';
77
import { makeProxyPassthroughRequest } from 'services/ProxyPassthroughService';
8-
import logger from 'logger';
8+
import logger, { logMetric } from 'logger';
99
import { ProviderType } from 'providers/ProviderType';
1010
import { settle } from 'handlers/settle';
1111
import { finalize } from 'handlers/finalize';
@@ -24,55 +24,91 @@ export async function handleX402Request({
2424
if (isPassthroughProxyRoute) {
2525
return await makeProxyPassthroughRequest(req, res, provider, headers);
2626
}
27-
const settleResult = await settle(req, res, headers, maxCost);
28-
if (!settleResult) {
29-
return;
30-
}
3127

32-
const { payload, paymentAmountDecimal } = settleResult;
28+
const settlePromise = settle(req, res, headers, maxCost);
3329

34-
try {
35-
const transactionResult = await modelRequestService.executeModelRequest(
36-
req,
37-
res,
38-
headers,
39-
provider,
40-
isStream
41-
);
42-
const transaction = transactionResult.transaction;
43-
if (provider.getType() === ProviderType.OPENAI_VIDEOS) {
44-
await prisma.videoGenerationX402.create({
45-
data: {
46-
videoId: transaction.metadata.providerId,
47-
wallet: payload.authorization.from,
48-
cost: transaction.rawTransactionCost,
49-
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 1),
50-
},
51-
});
52-
}
30+
const modelResultPromise = modelRequestService
31+
.executeModelRequest(req, res, headers, provider, isStream)
32+
.then((data) => ({ success: true as const, data }))
33+
.catch((error) => ({ success: false as const, error: error as Error }));
34+
35+
const [settleResult, modelResult] = await Promise.all([
36+
settlePromise,
37+
modelResultPromise,
38+
]);
39+
40+
// Case 1: Settle failed and model failed
41+
if (!settleResult && !modelResult.success) {
42+
return;
43+
}
5344

45+
// Case 2: Settle failed but model succeeded
46+
if (!settleResult && modelResult.success) {
47+
const { data } = modelResult;
48+
logger.error('Settle failed but model request succeeded', {
49+
provider: provider.getType(),
50+
url: req.url,
51+
metadata: data.transaction.metadata,
52+
});
53+
logMetric('x402_request_settle_failed_model_request_succeeded', 1, {
54+
provider: provider.getType(),
55+
url: req.url,
56+
});
5457
modelRequestService.handleResolveResponse(
5558
res,
5659
isStream,
57-
transactionResult.data
60+
data
5861
);
62+
return;
63+
}
5964

60-
logger.info(
61-
`Creating X402 transaction for app. Metadata: ${JSON.stringify(transaction.metadata)}`
62-
);
63-
const transactionCosts =
64-
await x402AuthenticationService.createX402Transaction(transaction);
65-
66-
await finalize(
67-
paymentAmountDecimal,
68-
transactionCosts.rawTransactionCost,
69-
transactionCosts.totalAppProfit,
70-
transactionCosts.echoProfit,
71-
payload
72-
);
73-
} catch (error) {
65+
// At this point, settleResult is guaranteed to exist
66+
if (!settleResult) {
67+
return;
68+
}
69+
70+
const { payload, paymentAmountDecimal } = settleResult;
71+
72+
// Case 3: Settle succeeded but model failed
73+
if (!modelResult.success) {
7474
await refund(paymentAmountDecimal, payload);
75+
return;
7576
}
77+
78+
// Case 4: Both settle and model succeeded
79+
const transactionResult = modelResult.data;
80+
const transaction = transactionResult.transaction;
81+
82+
if (provider.getType() === ProviderType.OPENAI_VIDEOS) {
83+
await prisma.videoGenerationX402.create({
84+
data: {
85+
videoId: transaction.metadata.providerId,
86+
wallet: payload.authorization.from,
87+
cost: transaction.rawTransactionCost,
88+
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 1),
89+
},
90+
});
91+
}
92+
93+
modelRequestService.handleResolveResponse(
94+
res,
95+
isStream,
96+
transactionResult.data
97+
);
98+
99+
logger.info(
100+
`Creating X402 transaction for app. Metadata: ${JSON.stringify(transaction.metadata)}`
101+
);
102+
const transactionCosts =
103+
await x402AuthenticationService.createX402Transaction(transaction);
104+
105+
await finalize(
106+
paymentAmountDecimal,
107+
transactionCosts.rawTransactionCost,
108+
transactionCosts.totalAppProfit,
109+
transactionCosts.echoProfit,
110+
payload
111+
);
76112
}
77113

78114
export async function handleApiKeyRequest({

0 commit comments

Comments
 (0)