+ {part.text} +
+ ); + } + + if (part.type === "reasoning" && part.text.trim()) { + return ( ++ {part.text} +
+ ); + } + + if (part.type === "tool-patch_resume") { + // biome-ignore lint/suspicious/noExplicitAny: AI SDK tool parts have dynamic shapes + const toolPart = part as any; + const state = toolPart.state as string; + const operations = toolPart.input?.operations as { op: string; path: string }[] | undefined; + return
+
+
+ {message.parts.map((part, i) => + part.type === "text" ? {part.text} : null, + )} +
+ ) : ( +` for paragraphs, `
Experienced engineer...
" }` | +| Add experience item | `{ "op": "add", "path": "/sections/experience/items/-", "value": { ...full item object } }` | +| Remove skill at index 2 | `{ "op": "remove", "path": "/sections/skills/items/2" }` | +| Update a specific item field | `{ "op": "replace", "path": "/sections/experience/items/0/company", "value": "New Corp" }` | +| Change template | `{ "op": "replace", "path": "/metadata/template", "value": "bronzor" }` | +| Change primary color | `{ "op": "replace", "path": "/metadata/design/colors/primary", "value": "rgba(37, 99, 235, 1)" }` | +| Hide a section | `{ "op": "replace", "path": "/sections/interests/hidden", "value": true }` | +| Rename a section title | `{ "op": "replace", "path": "/sections/experience/title", "value": "Work History" }` | + +## Current Resume Data + +```json +{{RESUME_DATA}} +``` diff --git a/src/integrations/ai/tools/patch-resume.ts b/src/integrations/ai/tools/patch-resume.ts new file mode 100644 index 000000000..a5ba70aa6 --- /dev/null +++ b/src/integrations/ai/tools/patch-resume.ts @@ -0,0 +1,22 @@ +import type { Operation } from "fast-json-patch"; +import z from "zod"; +import type { ResumeData } from "@/schema/resume/data"; +import { applyResumePatches, jsonPatchOperationSchema } from "@/utils/resume/patch"; + +export const patchResumeInputSchema = z.object({ + operations: z + .array(jsonPatchOperationSchema) + .min(1) + .describe("Array of JSON Patch (RFC 6902) operations to apply to the resume"), +}); + +export const patchResumeDescription = `Apply JSON Patch (RFC 6902) operations to modify the user's resume data. +Use this tool whenever the user asks to change, add, or remove content from their resume. +Always generate the minimal set of operations needed. Prefer "replace" for updates, "add" for new content, "remove" for deletions. +Use the special "-" index to append to arrays (e.g. "/sections/experience/items/-").`; + +export function executePatchResume(resumeData: ResumeData, operations: Operation[]) { + // Validates operations structurally and against the schema; throws on invalid + applyResumePatches(resumeData, operations); + return { success: true as const, appliedOperations: operations }; +} diff --git a/src/integrations/auth/config.ts b/src/integrations/auth/config.ts index 7fb2f3b69..14e78f4bc 100644 --- a/src/integrations/auth/config.ts +++ b/src/integrations/auth/config.ts @@ -2,7 +2,7 @@ import { BetterAuthError } from "@better-auth/core/error"; import { passkey } from "@better-auth/passkey"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { betterAuth } from "better-auth/minimal"; -import { apiKey, type GenericOAuthConfig, genericOAuth, twoFactor } from "better-auth/plugins"; +import { apiKey, type GenericOAuthConfig, genericOAuth, openAPI, twoFactor } from "better-auth/plugins"; import { username } from "better-auth/plugins/username"; import { tanstackStartCookies } from "better-auth/tanstack-start"; import { and, eq, or } from "drizzle-orm"; @@ -211,6 +211,7 @@ const getAuthConfig = () => { }, plugins: [ + openAPI(), apiKey({ enableSessionForAPIKeys: true, rateLimit: { diff --git a/src/integrations/orpc/client.ts b/src/integrations/orpc/client.ts index 26f9c7ded..dc9b1831d 100644 --- a/src/integrations/orpc/client.ts +++ b/src/integrations/orpc/client.ts @@ -1,6 +1,6 @@ -import { createORPCClient } from "@orpc/client"; +import { createORPCClient, onError } from "@orpc/client"; import { RPCLink } from "@orpc/client/fetch"; -import { BatchLinkPlugin } from "@orpc/client/plugins"; +import { BatchLinkPlugin, SimpleCsrfProtectionLinkPlugin } from "@orpc/client/plugins"; import { createRouterClient, type InferRouterInputs, type InferRouterOutputs, type RouterClient } from "@orpc/server"; import { createTanstackQueryUtils } from "@orpc/tanstack-query"; import { createIsomorphicFn } from "@tanstack/react-start"; @@ -11,6 +11,11 @@ import { getLocale } from "@/utils/locale"; export const getORPCClient = createIsomorphicFn() .server((): RouterClient