import { z } from "zod"; import { config } from "../config.js"; import { requestWithSessionCredential } from "./credential-refresh.js"; import type { ProductCredential } from "./server-auth-client.js"; import { OperationalTaskClientError } from "./operational-task-client.js"; export const agentMessageSchema = z.object({ message_id: z.string().uuid(), project_id: z.string().uuid(), task_id: z.string().uuid(), run_id: z.string().uuid(), request_id: z.string().uuid(), source_session_id: z.string(), source_owner_id: z.string(), conversation_owner_id: z.string(), target_session_id: z.string().nullable(), direction: z.enum(["to_task", "to_chat"]), in_reply_to: z.string().uuid().nullable(), text: z.string(), status: z.enum(["pending", "enqueued", "delivered", "failed"]), native_message_id: z.string(), failure_detail: z.string().nullable(), created_at: z.string(), updated_at: z.string(), delivered_at: z.string().nullable(), }); export type AgentMessage = z.infer; export const agentMessagePageSchema = z.object({ items: z.array(agentMessageSchema), next_cursor: z.string().nullable() }); export type MessageCredential = string | ProductCredential; /** Business records are Server-owned; credentials never become model arguments. */ export class AgentMessageClient { constructor(private readonly baseUrl = config.TJWATER_SERVER_BASE_URL) {} async request(credential: MessageCredential, path: string, method = "GET", body?: unknown): Promise { const execute = (value: ProductCredential) => fetch(new URL(path, this.baseUrl), { method, signal: AbortSignal.timeout(config.TJWATER_SERVER_TIMEOUT_MS), headers: { Authorization: value.authorization, "X-Project-Id": value.projectId, "Content-Type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const response = typeof credential === "string" ? await requestWithSessionCredential(credential, execute) : await execute(credential); const value: unknown = await response.json(); if (!response.ok) { const problem = z.object({ code: z.string().optional(), message: z.string().optional(), detail: z.unknown().optional() }).passthrough().safeParse(value); const nested = problem.success ? z.object({ code: z.string().optional(), message: z.string().optional() }).safeParse(problem.data.detail) : null; throw new OperationalTaskClientError(response.status, (nested?.success ? nested.data.message : undefined) ?? (problem.success ? problem.data.message : undefined) ?? "Agent message request failed", (nested?.success ? nested.data.code : undefined) ?? (problem.success ? problem.data.code : undefined)); } return value; } async list(credential: MessageCredential, sessionId: string, cursor?: string, limit = 50, pendingOnly = false) { const query = new URLSearchParams({ session_id: sessionId, limit: String(limit) }); if (cursor) query.set("cursor", cursor); if (pendingOnly) query.set("pending_only", "true"); return agentMessagePageSchema.parse(await this.request(credential, `/api/v1/agent-messages?${query}`)); } async get(credential: MessageCredential, id: string) { return agentMessageSchema.parse(await this.request(credential, `/api/v1/agent-messages/${encodeURIComponent(id)}`)); } async send(sessionId: string, input: { request_id: string; task_id: string; run_id: string; message: string }) { return agentMessageSchema.parse(await this.request(sessionId, "/api/v1/agent-messages", "POST", { request_id: input.request_id, task_id: input.task_id, run_id: input.run_id, source_session_id: sessionId, text: input.message, })); } async reply(sessionId: string, input: { request_id: string; message_id: string; message: string }) { return agentMessageSchema.parse(await this.request(sessionId, `/api/v1/agent-messages/${input.message_id}/reply`, "POST", { request_id: input.request_id, source_session_id: sessionId, text: input.message, })); } async receipt(credential: MessageCredential, item: AgentMessage, status: AgentMessage["status"], target: string | null, failure?: string) { return agentMessageSchema.parse(await this.request(credential, `/api/v1/agent-messages/${item.message_id}/delivery`, "POST", { status, target_session_id: target, native_message_id: item.native_message_id, failure_detail: failure ?? null, })); } } export const agentMessageClient = new AgentMessageClient();