Use user info proxy (#1963)

<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Switches budget fetch to `https://api.dyad.sh/v1/user/info` and
validates/consumes `usedCredits`, `totalCredits`, and `budgetResetDate`
directly via Zod.
> 
> - **IPC/Pro handlers (`src/ipc/handlers/pro_handlers.ts`)**:
> - **Endpoint**: Update user info URL to
`https://api.dyad.sh/v1/user/info`.
> - **Validation**: Add `zod` schema `UserInfoResponseSchema` to
validate API response.
> - **Data mapping**: Use `usedCredits`, `totalCredits`,
`budgetResetDate`, and `userId` from response directly; remove
conversion logic and old nested `user_info` parsing.
> - **Redaction**: Compute `redactedUserId` from `userId` and return
parsed `UserBudgetInfo`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
da1f192c2cabb2154bd10b69555c27d62fbb6368. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- This is an auto-generated description by cubic. -->
## Summary by cubic
Switched user budget fetch to the new user info proxy and added schema
validation. Uses API-provided credits directly and removes the old
conversion logic.

- **Refactors**
  - Use https://api.dyad.sh/v1/user/info instead of llm-gateway.
- Validate response with a Zod schema (usedCredits, totalCredits,
budgetResetDate, userId).
  - Map fields directly to UserBudgetInfo and remove CONVERSION_RATIO.
  - Keep redacted user ID format (****1234).

- **Dependencies**
- Removed unused html-dom-parser, html-react-parser, and react-property
from the lockfile.

<sup>Written for commit da1f192c2cabb2154bd10b69555c27d62fbb6368.
Summary will update automatically on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-12-15 14:25:55 -08:00
committed by GitHub
parent 9d33f3757d
commit 213def4a67
2 changed files with 25 additions and 48 deletions

37
package-lock.json generated
View File

@@ -12898,37 +12898,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/html-dom-parser": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.1.1.tgz",
"integrity": "sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==",
"license": "MIT",
"dependencies": {
"domhandler": "5.0.3",
"htmlparser2": "10.0.0"
}
},
"node_modules/html-react-parser": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.6.tgz",
"integrity": "sha512-qcpPWLaSvqXi+TndiHbCa+z8qt0tVzjMwFGFBAa41ggC+ZA5BHaMIeMJla9g3VSp4SmiZb9qyQbmbpHYpIfPOg==",
"license": "MIT",
"dependencies": {
"domhandler": "5.0.3",
"html-dom-parser": "5.1.1",
"react-property": "2.0.2",
"style-to-js": "1.1.17"
},
"peerDependencies": {
"@types/react": "0.14 || 15 || 16 || 17 || 18 || 19",
"react": "0.14 || 15 || 16 || 17 || 18 || 19"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/html-to-image": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.13.tgz",
@@ -17952,12 +17921,6 @@
"react": ">=18"
}
},
"node_modules/react-property": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz",
"integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==",
"license": "MIT"
},
"node_modules/react-reconciler": {
"version": "0.33.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz",

View File

@@ -4,12 +4,19 @@ import { createLoggedHandler } from "./safe_handle";
import { readSettings } from "../../main/settings"; // Assuming settings are read this way
import { UserBudgetInfo, UserBudgetInfoSchema } from "../ipc_types";
import { IS_TEST_BUILD } from "../utils/test_utils";
import { z } from "zod";
export const UserInfoResponseSchema = z.object({
usedCredits: z.number(),
totalCredits: z.number(),
budgetResetDate: z.string(), // ISO date string from API
userId: z.string(),
});
export type UserInfoResponse = z.infer<typeof UserInfoResponseSchema>;
const logger = log.scope("pro_handlers");
const handle = createLoggedHandler(logger);
const CONVERSION_RATIO = (10 * 3) / 2;
export function registerProHandlers() {
// This method should try to avoid throwing errors because this is auxiliary
// information and isn't critical to using the app
@@ -36,7 +43,7 @@ export function registerProHandlers() {
return null;
}
const url = "https://llm-gateway.dyad.sh/user/info";
const url = "https://api.dyad.sh/v1/user/info";
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
@@ -57,21 +64,28 @@ export function registerProHandlers() {
return null;
}
const data = await response.json();
const userInfoData = data["user_info"];
const userId = userInfoData["user_id"];
const rawData = await response.json();
// Validate the API response structure
const data = UserInfoResponseSchema.parse(rawData);
// Turn user_abc1234 => "****1234"
// Preserve the last 4 characters so we can correlate bug reports
// with the user.
const redactedUserId =
userId.length > 8 ? "****" + userId.slice(-4) : "<redacted>";
data.userId.length > 8 ? "****" + data.userId.slice(-4) : "<redacted>";
logger.info("Successfully fetched user budget information.");
return UserBudgetInfoSchema.parse({
usedCredits: userInfoData["spend"] * CONVERSION_RATIO,
totalCredits: userInfoData["max_budget"] * CONVERSION_RATIO,
budgetResetDate: new Date(userInfoData["budget_reset_at"]),
// Transform to UserBudgetInfo format
const userBudgetInfo = UserBudgetInfoSchema.parse({
usedCredits: data.usedCredits,
totalCredits: data.totalCredits,
budgetResetDate: new Date(data.budgetResetDate),
redactedUserId: redactedUserId,
});
return userBudgetInfo;
} catch (error: any) {
logger.error(`Error fetching user budget: ${error.message}`, error);
return null;