Files
gitmost/apps/client/src/features/ai-chat/services/ai-chat-service.ts
claude code agent 227 30c3189220 feat(ai-chat): agent roles (admin-defined persona + optional model)
Reusable, workspace-shared agent roles for the built-in AI chat. A role is
a named persona (system-prompt instructions) + optional model override; a
chat is bound to a role at creation and applies it every turn.

Backend:
- migration 20260620T120000: ai_agent_roles table + ai_chats.role_id
  (FK ON DELETE SET NULL); hand-merged types into db.d.ts/entity.types.ts
  (db.d.ts is hand-curated here, full codegen would clobber it).
- core/ai-chat/roles: CRUD module. list = any workspace member; create/
  update/delete = admin (Manage Settings ability, like ai-settings/mcp).
  All repo queries scoped by workspace_id; soft-delete (deleted_at).
- buildSystemPrompt gains roleInstructions: role REPLACES the persona base
  (admin prompt / DEFAULT_PROMPT) but SAFETY_FRAMEWORK + context are always
  still appended.
- stream(): role resolved from ai_chats.role_id for existing chats (never
  the request body -> no per-turn role swap); body.roleId only on creation.
  Disabled (enabled=false) and soft-deleted roles fall back to universal.
- getChatModel(workspaceId, override): role model_config can swap model id /
  driver; a driver without configured creds throws 503 with a clear message
  naming the driver+role, resolved BEFORE response hijack.

Client:
- new-chat role picker (enabled roles only, default Universal assistant),
  roleId sent only on the first message; role badge (emoji+name) in the chat
  header and conversation list; admin Agent-roles management section in
  Settings -> AI (add/edit/delete, MCP-form pattern).

Tests: ai-chat.prompt.spec (role layering + safety always present, incl.
jailbreak); ai.service.spec (override on unconfigured driver -> 503).

Implements docs/ai-agent-roles-plan.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 06:30:06 +03:00

82 lines
2.5 KiB
TypeScript

import api from "@/lib/api-client";
import { IPagination } from "@/lib/types.ts";
import {
IAiChat,
IAiChatListParams,
IAiChatMessageRow,
IAiChatMessagesParams,
IAiRole,
IAiRoleCreate,
IAiRoleUpdate,
} from "@/features/ai-chat/types/ai-chat.types.ts";
/**
* Per-user AI chat CRUD. The server uses POST for reads (its convention) and
* wraps every (non-stream) response in `{ data }` via the global transform
* interceptor, which the axios client unwraps to the body — so we read `.data`
* (mirroring `comment-service`). The `/ai-chat/stream` endpoint is consumed by
* the AI SDK `useChat` transport directly, not here.
*/
/** List the current user's chats (most recent first, paginated). */
export async function getAiChats(
params: IAiChatListParams,
): Promise<IPagination<IAiChat>> {
const req = await api.post<IPagination<IAiChat>>("/ai-chat/chats", params);
return req.data;
}
/** Fetch a chat's messages (oldest first, paginated). */
export async function getAiChatMessages(
params: IAiChatMessagesParams,
): Promise<IPagination<IAiChatMessageRow>> {
const req = await api.post<IPagination<IAiChatMessageRow>>(
"/ai-chat/messages",
params,
);
return req.data;
}
/** Rename a chat. */
export async function renameAiChat(data: {
chatId: string;
title: string;
}): Promise<void> {
await api.post("/ai-chat/rename", data);
}
/** Soft-delete a chat. */
export async function deleteAiChat(chatId: string): Promise<void> {
await api.post("/ai-chat/delete", { chatId });
}
/**
* Agent roles API (`/ai-chat/roles`). `list` is available to any workspace
* member (for the chat-creation picker); create/update/delete are admin-only
* (the server enforces this). Same `{ data }` unwrap convention as above.
*/
/** List the workspace's agent roles. */
export async function getAiRoles(): Promise<IAiRole[]> {
const req = await api.post<IAiRole[]>("/ai-chat/roles");
return req.data;
}
/** Create a role (admin). */
export async function createAiRole(data: IAiRoleCreate): Promise<IAiRole> {
const req = await api.post<IAiRole>("/ai-chat/roles/create", data);
return req.data;
}
/** Update a role (admin). */
export async function updateAiRole(data: IAiRoleUpdate): Promise<IAiRole> {
const req = await api.post<IAiRole>("/ai-chat/roles/update", data);
return req.data;
}
/** Soft-delete a role (admin). */
export async function deleteAiRole(id: string): Promise<{ success: true }> {
const req = await api.post<{ success: true }>("/ai-chat/roles/delete", { id });
return req.data;
}