Files
Reactive-Resume/packages/api/src/features/agent/messages.ts
T
Amruth Pillai 57171c6fcb feat(api): add REST endpoints for imports, exports, checks and pagination
Resource-oriented REST aliases cover file imports (PDF text layer, JSON
Resume, Reactive Resume v4/current, LinkedIn), PDF/DOCX/Markdown/JSON
exports for resumes and cover letters, editor content checks, job-term
matching and a public PDF readability checker. Collection endpoints accept
limit/offset with X-Total-Count headers, and PATCH routes cover partial
updates. Existing routes and response shapes stay unchanged.

The OpenAPI spec marks public operations with `security: []`, the REST
surface gets a 40 MiB body limit, and Better Auth publishes its own
OpenAPI schema. Cookie sessions are rejected for cross-origin requests.
DOCX export parses HTML with node-html-parser so it runs on the server,
and PDF line extraction moves to @reactive-resume/import for reuse.
2026-10-01 23:01:11 +02:00

111 lines
3.4 KiB
TypeScript

import { eventIterator } from "@orpc/server";
import z from "zod";
import { protectedProcedure } from "../../context";
import { uiMessageSchema } from "../../dto/agent";
import { aiRequestRateLimit } from "../../middleware/rate-limit";
import { mapAgentEnvironmentError } from "./routing";
import { agentService } from "./service";
export const messagesRouter = {
send: protectedProcedure
.route({
method: "POST",
path: "/agent/messages/send",
tags: ["Agent"],
operationId: "sendAgentMessage",
summary: "Send agent message",
})
.input(
z.object({
threadId: z.string(),
message: uiMessageSchema,
attachmentIds: z.array(z.string().trim().min(1)).max(10).optional(),
// The context chips: leaving one out keeps it out of what's sent.
context: z
.object({
document: z.boolean().optional().describe("Share the open document (on by default)."),
posting: z.boolean().optional().describe("Share the job posting it's for (on by default)."),
applicationId: z.string().trim().min(1).max(255).optional(),
})
.optional(),
}),
)
.use(aiRequestRateLimit)
.use(mapAgentEnvironmentError)
.output(eventIterator(z.string()))
.handler(({ context, input }) =>
agentService.messages.send({
userId: context.user.id,
threadId: input.threadId,
message: input.message,
...(input.attachmentIds ? { attachmentIds: input.attachmentIds } : {}),
...(input.context ? { context: input.context } : {}),
}),
),
stop: protectedProcedure
.route({
method: "POST",
path: "/agent/messages/stop",
tags: ["Agent"],
operationId: "stopAgentMessage",
summary: "Stop active agent run",
})
.input(
z.object({
threadId: z.string(),
// Deprecated and ignored: partial content now persists server-side via the run's
// abort path. Kept in the schema for one release so mid-deploy clients still parse.
partialMessage: uiMessageSchema.optional(),
}),
)
.output(z.void())
.use(mapAgentEnvironmentError)
.handler(({ context, input }) =>
agentService.messages.stop({
userId: context.user.id,
threadId: input.threadId,
}),
),
resume: protectedProcedure
.route({
method: "GET",
path: "/agent/messages/resume",
tags: ["Agent"],
operationId: "resumeAgentMessages",
summary: "Resume agent message stream",
})
.input(z.object({ threadId: z.string() }))
.use(mapAgentEnvironmentError)
.output(eventIterator(z.string()))
.handler(({ context, input }) =>
agentService.messages.resume({ userId: context.user.id, threadId: input.threadId }),
),
setEditStatus: protectedProcedure
.route({
method: "POST",
path: "/agent/messages/edit-status",
tags: ["Agent"],
operationId: "setAgentEditStatus",
summary: "Record what happened to proposed edits",
description:
"Records that proposed edits were accepted, rejected, or are pending again (after an undo), so past conversations show their outcome.",
})
.input(
z.object({
threadId: z.string(),
messageId: z.string(),
toolCallId: z.string(),
edits: z
.array(z.object({ id: z.string(), status: z.enum(["pending", "accepted", "rejected"]) }))
.min(1)
.max(50),
}),
)
.output(z.object({ editsProposed: z.number(), editsAccepted: z.number() }))
.use(mapAgentEnvironmentError)
.handler(({ context, input }) => agentService.messages.setEditStatus({ userId: context.user.id, ...input })),
};