-
+
-
{title}
-
-
-
+
{magicRewriteMode ? "✨ Magic Rewrite" : title}
+
+
+
+
+
-
- Changes auto-save on close. Press Escape to close.
+ {magicRewriteMode ? "Back returns to the editor without applying. Apply moves the preview into the editor." : "Changes auto-save on close. Press Escape to close."}
-
- Done
-
+ {magicRewriteMode ? (
+
+
+ Back
+
+
+ Apply
+
+
+ ) : (
+
+ setMagicRewriteMode(true)}
+ className="inline-flex items-center gap-1.5 rounded-lg border border-violet-400/30 bg-violet-500/10 px-3 py-1.5 text-xs font-medium text-violet-200 hover:bg-violet-500/20"
+ title="Open Magic Rewrite"
+ >
+
+ Rewrite
+
+
+ Done
+
+
+ )}
diff --git a/packages/client/src/components/ui/ExpandedTextarea.tsx b/packages/client/src/components/ui/ExpandedTextarea.tsx
index 7a0de2151..28a32719e 100644
--- a/packages/client/src/components/ui/ExpandedTextarea.tsx
+++ b/packages/client/src/components/ui/ExpandedTextarea.tsx
@@ -1,10 +1,11 @@
// ──────────────────────────────────────────────
// Expanded Textarea — Fullscreen editing overlay
// ──────────────────────────────────────────────
-import { useEffect, useRef } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { motion, AnimatePresence } from "framer-motion";
-import { Minimize2 } from "lucide-react";
+import { Minimize2, Sparkles } from "lucide-react";
+import { MagicRewritePanel } from "./MagicRewritePanel";
interface ExpandedTextareaProps {
open: boolean;
@@ -17,22 +18,57 @@ interface ExpandedTextareaProps {
export function ExpandedTextarea({ open, onClose, title, value, onChange, placeholder }: ExpandedTextareaProps) {
const textareaRef = useRef
(null);
+ const [local, setLocal] = useState(value);
+ const [magicRewriteMode, setMagicRewriteMode] = useState(false);
+ const [magicRewriteResult, setMagicRewriteResult] = useState("");
+
+ // Sync parent value into local when prop changes; reset rewrite state on close
+ useEffect(() => {
+ if (!open) {
+ setMagicRewriteMode(false);
+ setMagicRewriteResult("");
+ }
+ setLocal(value);
+ }, [value, open]);
useEffect(() => {
if (!open) return;
const handler = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
+ if (e.key === "Escape" && !magicRewriteMode) onClose();
};
document.addEventListener("keydown", handler);
return () => document.removeEventListener("keydown", handler);
- }, [open, onClose]);
+ }, [open, onClose, magicRewriteMode]);
// Focus textarea when opened
useEffect(() => {
- if (open) {
+ if (open && !magicRewriteMode) {
requestAnimationFrame(() => textareaRef.current?.focus());
}
- }, [open]);
+ }, [open, magicRewriteMode]);
+
+ const handleClose = () => {
+ onChange(local);
+ onClose();
+ };
+
+ const handleMagicRewriteResultChange = useCallback((next: string) => {
+ setMagicRewriteResult(next);
+ }, []);
+
+ const handleMagicRewriteApply = () => {
+ if (!magicRewriteResult) return;
+ setLocal(magicRewriteResult);
+ setMagicRewriteMode(false);
+ setMagicRewriteResult("");
+ onChange(magicRewriteResult);
+ requestAnimationFrame(() => textareaRef.current?.focus());
+ };
+
+ const handleMagicRewriteBack = () => {
+ setMagicRewriteMode(false);
+ setMagicRewriteResult("");
+ };
return createPortal(
@@ -46,11 +82,38 @@ export function ExpandedTextarea({ open, onClose, title, value, onChange, placeh
>
{/* Header */}
-
{title}
+
{magicRewriteMode ? "✨ Magic Rewrite" : title}
- {value.length} characters
+ {!magicRewriteMode && (
+ setMagicRewriteMode(true)}
+ className="flex items-center gap-1.5 rounded-lg border border-violet-400/30 bg-violet-500/10 px-2.5 py-1.5 text-xs font-medium text-violet-200 hover:bg-violet-500/20"
+ title="Open Magic Rewrite"
+ >
+
+ Rewrite
+
+ )}
+ {magicRewriteMode && (
+
+ Back
+
+ )}
+ {magicRewriteMode && (
+
+ Apply
+
+ )}
+ {local.length} characters
@@ -59,15 +122,19 @@ export function ExpandedTextarea({ open, onClose, title, value, onChange, placeh
- {/* Textarea */}
+ {/* Content */}
- onChange(e.target.value)}
- placeholder={placeholder}
- className="h-full w-full resize-none rounded-xl border border-[var(--border)] bg-[var(--secondary)] p-5 text-sm leading-relaxed outline-none transition-colors placeholder:text-[var(--muted-foreground)]/40 focus:border-[var(--primary)]/40 focus:ring-1 focus:ring-[var(--primary)]/20"
- />
+ {magicRewriteMode ? (
+
+ ) : (
+ setLocal(e.target.value)}
+ placeholder={placeholder}
+ className="h-full w-full resize-none rounded-xl border border-[var(--border)] bg-[var(--secondary)] p-5 text-sm leading-relaxed outline-none transition-colors placeholder:text-[var(--muted-foreground)]/40 focus:border-[var(--primary)]/40 focus:ring-1 focus:ring-[var(--primary)]/20"
+ />
+ )}
)}
diff --git a/packages/client/src/components/ui/MagicRewritePanel.tsx b/packages/client/src/components/ui/MagicRewritePanel.tsx
new file mode 100644
index 000000000..2eecccd78
--- /dev/null
+++ b/packages/client/src/components/ui/MagicRewritePanel.tsx
@@ -0,0 +1,245 @@
+// ──────────────────────────────────────────────
+// Magic Rewrite Panel
+// ──────────────────────────────────────────────
+import { useEffect, useMemo, useState } from "react";
+import { Sparkles, Loader2 } from "lucide-react";
+import { api } from "../../lib/api-client";
+
+const PROMPT_KEY = "magic-rewrite-prompt";
+
+type MinimalItem = { id: string; name?: string; title?: string; content?: string; description?: string; data?: unknown };
+type ChatMessage = { role?: string; name?: string; content?: string | { text?: string }; characterId?: string; extra?: unknown };
+
+type RewriteResponse = { text: string };
+type DiffPart = { text: string; changed: boolean };
+type DiffResult =
+ | { skipped: true; before: string; after: string }
+ | { skipped: false; before: DiffPart[]; after: DiffPart[] };
+
+function readActiveChatId(): string | null {
+ try {
+ return window.localStorage.getItem("marinara-active-chat-id");
+ } catch {
+ return null;
+ }
+}
+
+function parseCardData(data: unknown): Record | null {
+ if (!data) return null;
+ if (typeof data === "object") return data as Record;
+ if (typeof data !== "string") return null;
+ try {
+ const parsed = JSON.parse(data) as unknown;
+ return parsed && typeof parsed === "object" ? parsed as Record : null;
+ } catch {
+ return null;
+ }
+}
+
+function labelFor(item: MinimalItem, fallback: string): string {
+ const data = parseCardData(item.data);
+ return item.name || item.title || (typeof data?.name === "string" ? data.name : "") || fallback;
+}
+
+function textFromMessage(message: ChatMessage): string {
+ if (typeof message.content === "string") return message.content;
+ if (message.content && typeof message.content.text === "string") return message.content.text;
+ return "";
+}
+
+function diffWords(before: string, after: string): DiffResult {
+ const beforeWords = before.match(/\S+|\s+/g) ?? [];
+ const afterWords = after.match(/\S+|\s+/g) ?? [];
+ if (beforeWords.length + afterWords.length > 3000) {
+ return { before: before, after: after, skipped: true };
+ }
+
+ const dp = Array.from({ length: beforeWords.length + 1 }, () => new Uint16Array(afterWords.length + 1));
+ for (let i = 1; i <= beforeWords.length; i++) {
+ for (let j = 1; j <= afterWords.length; j++) {
+ dp[i][j] = beforeWords[i - 1] === afterWords[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]);
+ }
+ }
+
+ const deleted = new Uint8Array(beforeWords.length);
+ const added = new Uint8Array(afterWords.length);
+ let i = beforeWords.length;
+ let j = afterWords.length;
+ while (i > 0 || j > 0) {
+ if (i > 0 && j > 0 && beforeWords[i - 1] === afterWords[j - 1]) {
+ i--;
+ j--;
+ } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
+ added[--j] = 1;
+ } else {
+ deleted[--i] = 1;
+ }
+ }
+
+ return {
+ skipped: false,
+ before: beforeWords.map((word, index) => ({ text: word, changed: deleted[index] === 1 })),
+ after: afterWords.map((word, index) => ({ text: word, changed: added[index] === 1 })),
+ };
+}
+
+export function MagicRewritePanel({
+ value,
+ onResultChange,
+}: {
+ value: string;
+ onResultChange: (value: string) => void;
+}) {
+ const [instruction, setInstruction] = useState(() => {
+ try { return window.localStorage.getItem(PROMPT_KEY) ?? ""; } catch { return ""; }
+ });
+ const [characters, setCharacters] = useState([]);
+ const [lorebooks, setLorebooks] = useState([]);
+ const [characterId, setCharacterId] = useState("");
+ const [lorebookId, setLorebookId] = useState("");
+ const [includeChat, setIncludeChat] = useState(false);
+ const [chatLimit, setChatLimit] = useState(20);
+ const [result, setResult] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState("");
+
+ useEffect(() => {
+ const timer = window.setTimeout(() => { try { window.localStorage.setItem(PROMPT_KEY, instruction); } catch { /* noop */ } }, 300);
+ return () => window.clearTimeout(timer);
+ }, [instruction]);
+
+ useEffect(() => {
+ api.get("/characters").then(setCharacters).catch(() => setCharacters([]));
+ api.get("/lorebooks/").then(setLorebooks).catch(() => setLorebooks([]));
+ }, []);
+
+ const diff = useMemo(() => (result ? diffWords(value, result) : null), [value, result]);
+
+ useEffect(() => {
+ onResultChange(result);
+ }, [onResultChange, result]);
+
+ async function buildContext(): Promise {
+ const sections: string[] = [];
+
+ if (characterId) {
+ const character = await api.get>(`/characters/${characterId}`);
+ sections.push(`[CHARACTER CARD]\n${JSON.stringify(character, null, 2)}`);
+ }
+
+ if (includeChat) {
+ const chatId = readActiveChatId();
+ if (chatId) {
+ const messages = await api.get(`/chats/${chatId}/messages?limit=${chatLimit}&skip=0`);
+ const lines = messages
+ .slice(-chatLimit)
+ .map((message) => `${message.name || message.role || "unknown"}: ${textFromMessage(message)}`)
+ .join("\n");
+ sections.push(`[CHAT HISTORY: last ${chatLimit} messages]\n${lines}`);
+ }
+ }
+
+ if (lorebookId) {
+ const entries = await api.get(`/lorebooks/${lorebookId}/entries`);
+ const lorebook = lorebooks.find((item) => item.id === lorebookId);
+ const lines = entries.map((entry, index) => `[${labelFor(entry, `Entry ${index + 1}`)}]\n${entry.content ?? entry.description ?? ""}`).join("\n\n");
+ sections.push(`[LOREBOOK${lorebook ? `: ${labelFor(lorebook, "")}` : ""}]\n${lines}`);
+ }
+
+ return sections.length > 0
+ ? `The following context is provided for reference. Use it to maintain consistency with the character, story, and world.\n\n${sections.join("\n\n---\n\n")}`
+ : "";
+ }
+
+ async function generate() {
+ setLoading(true);
+ setError("");
+ try {
+ const context = await buildContext();
+ const response = await api.post("/magic-rewrite/generate", {
+ text: value,
+ instruction,
+ context,
+ });
+ setResult(response.text ?? "");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Magic Rewrite failed");
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ return (
+
+
+
+
Rewrite instructions
+
setInstruction(event.target.value)}
+ placeholder='e.g. "Make this more vivid and dramatic..."'
+ className="min-h-0 flex-1 resize-none rounded-xl bg-[var(--secondary)] p-3 text-sm ring-1 ring-[var(--border)] focus:outline-none focus:ring-2 focus:ring-violet-500/50"
+ />
+
+
+
+
Included Context
+
+ Character Card
+ setCharacterId(event.target.value)} className="w-full rounded-lg bg-[var(--secondary)] px-2 py-1.5 text-sm text-[var(--foreground)] ring-1 ring-[var(--border)]">
+ — None —
+ {characters.map((character, index) => {labelFor(character, `Character ${index + 1}`)} )}
+
+
+
+
+ Lorebook
+ setLorebookId(event.target.value)} className="w-full rounded-lg bg-[var(--secondary)] px-2 py-1.5 text-sm text-[var(--foreground)] ring-1 ring-[var(--border)]">
+ — None —
+ {lorebooks.map((lorebook, index) => {labelFor(lorebook, `Lorebook ${index + 1}`)} )}
+
+
+
+
+ setIncludeChat(event.target.checked)} />
+ Include Current Chat
+
+
+ Last
+ setChatLimit(Number(event.target.value))} className="flex-1" />
+ {chatLimit} msgs
+
+
+
+ {loading ? : }
+ {loading ? "Rewriting…" : "Generate Rewrite"}
+
+ {error &&
{error}
}
+
+
+
+
+
+
Before
+
+ {diff && !diff.skipped && Array.isArray(diff.before)
+ ? diff.before.map((part, index) => {part.text} )
+ : value}
+
+
+
+
After
+
+ {diff && !diff.skipped && Array.isArray(diff.after)
+ ? diff.after.map((part, index) => {part.text} )
+ : result || "Generated rewrite will appear here."}
+
+
+
+
+ );
+}
diff --git a/packages/server/src/db/migrate.ts b/packages/server/src/db/migrate.ts
index ec23c1b77..7d08cc5b0 100644
--- a/packages/server/src/db/migrate.ts
+++ b/packages/server/src/db/migrate.ts
@@ -583,6 +583,11 @@ const COLUMN_MIGRATIONS: ColumnMigration[] = [
column: "default_for_agents",
definition: "TEXT NOT NULL DEFAULT 'false'",
},
+ {
+ table: "api_connections",
+ column: "default_for_rewrite",
+ definition: "TEXT NOT NULL DEFAULT 'false'",
+ },
{
table: "chats",
column: "folder_id",
diff --git a/packages/server/src/db/schema/connections.ts b/packages/server/src/db/schema/connections.ts
index 5437dd902..62d90dc4d 100644
--- a/packages/server/src/db/schema/connections.ts
+++ b/packages/server/src/db/schema/connections.ts
@@ -37,6 +37,8 @@ export const apiConnections = sqliteTable("api_connections", {
cachingAtDepth: integer("caching_at_depth").notNull().default(5),
/** Whether this connection is the default for all agents (only one allowed) */
defaultForAgents: text("default_for_agents").notNull().default("false"),
+ /** Whether this connection is the default for Magic Rewrite text generation (only one allowed) */
+ defaultForRewrite: text("default_for_rewrite").notNull().default("false"),
/** Model to use for embedding generation (e.g. text-embedding-3-small) */
embeddingModel: text("embedding_model"),
/** Optional: separate base URL for the embedding backend (e.g. a second llama.cpp instance) */
diff --git a/packages/server/src/routes/index.ts b/packages/server/src/routes/index.ts
index d100abb0b..78985dfe2 100644
--- a/packages/server/src/routes/index.ts
+++ b/packages/server/src/routes/index.ts
@@ -50,6 +50,7 @@ import { sidecarRoutes } from "./sidecar.routes.js";
import { ttsRoutes } from "./tts.routes.js";
import { promptOverridesRoutes } from "./prompt-overrides.routes.js";
import { csrfDiagnosticsRoutes } from "./csrf-diagnostics.routes.js";
+import { magicRewriteRoutes } from "./magic-rewrite.routes.js";
export async function registerRoutes(app: FastifyInstance) {
await app.register(chatsRoutes, { prefix: "/api/chats" });
@@ -98,6 +99,7 @@ export async function registerRoutes(app: FastifyInstance) {
await app.register(gameAssetsRoutes, { prefix: "/api/game-assets" });
await app.register(ttsRoutes, { prefix: "/api/tts" });
await app.register(promptOverridesRoutes, { prefix: "/api/prompt-overrides" });
+ await app.register(magicRewriteRoutes, { prefix: "/api/magic-rewrite" });
await app.register(csrfDiagnosticsRoutes, { prefix: "/api/csrf" });
if (process.env.MARINARA_LITE !== "true" && process.env.MARINARA_LITE !== "1") {
await app.register(sidecarRoutes, { prefix: "/api/sidecar" });
diff --git a/packages/server/src/routes/magic-rewrite.routes.ts b/packages/server/src/routes/magic-rewrite.routes.ts
new file mode 100644
index 000000000..0933a9d2c
--- /dev/null
+++ b/packages/server/src/routes/magic-rewrite.routes.ts
@@ -0,0 +1,101 @@
+// ──────────────────────────────────────────────
+// Routes: Magic Rewrite
+// ──────────────────────────────────────────────
+import type { FastifyInstance } from "fastify";
+import { z } from "zod";
+import { PROVIDERS } from "@marinara-engine/shared";
+import { createConnectionsStorage } from "../services/storage/connections.storage.js";
+import { createLLMProvider } from "../services/llm/provider-registry.js";
+import type { ChatMessage } from "../services/llm/base-provider.js";
+import { logger } from "../lib/logger.js";
+
+const magicRewriteSchema = z.object({
+ text: z.string().default(""),
+ instruction: z.string().default(""),
+ context: z.string().optional().default(""),
+});
+
+const REWRITE_SYSTEM_PROMPT = `You are a rewriting assistant for roleplay, fiction, and worldbuilding content.
+Rewrite or generate the requested text according to the user's instructions.
+Use any provided character, chat, and lorebook context only as reference for continuity.
+Return ONLY the rewritten text — no explanations, no markdown fences, no preamble.`;
+
+function resolveBaseUrl(conn: { provider: string; baseUrl: string | null }): string {
+ if (conn.baseUrl) return conn.baseUrl;
+ if (conn.provider === "claude_subscription") return "claude-agent-sdk://local";
+ if (conn.provider === "openai_chatgpt") return "openai-chatgpt://codex-auth";
+ const providerDef = PROVIDERS[conn.provider as keyof typeof PROVIDERS];
+ return providerDef?.defaultBaseUrl ?? "";
+}
+
+export async function magicRewriteRoutes(app: FastifyInstance) {
+ const connections = createConnectionsStorage(app.db);
+
+ app.post("/generate", async (req, reply) => {
+ const result = magicRewriteSchema.safeParse(req.body);
+ if (!result.success) {
+ return reply.status(400).send({ error: "Invalid request", details: result.error.issues });
+ }
+ const input = result.data;
+
+ const defaultRewriteConnection = await connections.getDefaultForRewrite();
+ const fallbackDefault = defaultRewriteConnection ? null : await connections.getDefault();
+ const fallbackAgent = !defaultRewriteConnection && !fallbackDefault ? await connections.getDefaultForAgents() : null;
+ const conn = defaultRewriteConnection
+ ?? (fallbackDefault ? await connections.getWithKey(fallbackDefault.id) : null)
+ ?? fallbackAgent;
+
+ if (!conn) {
+ return reply.status(400).send({ error: "No default language model connection configured" });
+ }
+
+ if (conn.provider === "image_generation") {
+ return reply.status(400).send({ error: "Default connection is an image generation provider" });
+ }
+
+ const baseUrl = resolveBaseUrl(conn);
+ if (!baseUrl) {
+ return reply.status(400).send({ error: "No base URL configured for the default connection" });
+ }
+
+ const hasSourceText = input.text.trim().length > 0;
+ const instruction = input.instruction.trim() || (hasSourceText ? "Improve this text while preserving its meaning." : "Generate suitable content.");
+
+ const userParts = [
+ `Instruction:\n${instruction}`,
+ input.context.trim() ? `Reference context:\n${input.context.trim()}` : null,
+ hasSourceText ? `Text to rewrite:\n${input.text}` : "No source text was provided; generate new content from the instruction and context.",
+ ].filter(Boolean);
+
+ const messages: ChatMessage[] = [
+ { role: "system", content: REWRITE_SYSTEM_PROMPT },
+ { role: "user", content: userParts.join("\n\n---\n\n") },
+ ];
+
+ try {
+ const provider = createLLMProvider(
+ conn.provider,
+ baseUrl,
+ conn.apiKey,
+ conn.maxContext,
+ conn.openrouterProvider,
+ conn.maxTokensOverride,
+ conn.claudeFastMode === "true",
+ );
+
+ const result = await provider.chatComplete(messages, {
+ model: conn.model,
+ temperature: 0.7,
+ maxTokens: 4000,
+ stream: false,
+ enableCaching: conn.enableCaching === "true",
+ cachingAtDepth: conn.cachingAtDepth,
+ });
+
+ return { text: result.content?.trim() ?? "", finishReason: result.finishReason, usage: result.usage ?? null };
+ } catch (error) {
+ logger.error(error, "Magic Rewrite generation failed");
+ return reply.status(500).send({ error: "Magic Rewrite generation failed" });
+ }
+ });
+}
diff --git a/packages/server/src/services/storage/connections.storage.ts b/packages/server/src/services/storage/connections.storage.ts
index a6bb326cc..f7c246906 100644
--- a/packages/server/src/services/storage/connections.storage.ts
+++ b/packages/server/src/services/storage/connections.storage.ts
@@ -41,6 +41,14 @@ export function createConnectionsStorage(db: DB) {
return { ...row, apiKey: decryptApiKey(row.apiKeyEncrypted) };
},
+ /** Get the connection marked as default for Magic Rewrite (with decrypted key). */
+ async getDefaultForRewrite() {
+ const rows = await db.select().from(apiConnections).where(eq(apiConnections.defaultForRewrite, "true"));
+ const row = rows.find((candidate) => candidate.provider !== "image_generation");
+ if (!row) return null;
+ return { ...row, apiKey: decryptApiKey(row.apiKeyEncrypted) };
+ },
+
async create(input: CreateConnectionInput) {
const id = newId();
const timestamp = now();
@@ -67,6 +75,18 @@ export function createConnectionsStorage(db: DB) {
}
}
}
+ // If this is set as default for rewrite, unset other non-image defaults
+ if (input.defaultForRewrite) {
+ const existingDefaults = await db
+ .select()
+ .from(apiConnections)
+ .where(eq(apiConnections.defaultForRewrite, "true"));
+ for (const row of existingDefaults) {
+ if (row.provider !== "image_generation") {
+ await db.update(apiConnections).set({ defaultForRewrite: "false" }).where(eq(apiConnections.id, row.id));
+ }
+ }
+ }
await db.insert(apiConnections).values({
id,
name: input.name,
@@ -78,6 +98,7 @@ export function createConnectionsStorage(db: DB) {
isDefault: String(input.isDefault ?? false),
useForRandom: String(input.useForRandom ?? false),
defaultForAgents: String(input.defaultForAgents ?? false),
+ defaultForRewrite: String(input.defaultForRewrite ?? false),
enableCaching: String(input.enableCaching ?? false),
cachingAtDepth: input.cachingAtDepth ?? 5,
maxParallelJobs: input.maxParallelJobs ?? 1,
@@ -140,6 +161,20 @@ export function createConnectionsStorage(db: DB) {
}
updateFields.defaultForAgents = String(data.defaultForAgents);
}
+ if (data.defaultForRewrite !== undefined) {
+ if (data.defaultForRewrite) {
+ const existingDefaults = await db
+ .select()
+ .from(apiConnections)
+ .where(eq(apiConnections.defaultForRewrite, "true"));
+ for (const row of existingDefaults) {
+ if (row.provider !== "image_generation") {
+ await db.update(apiConnections).set({ defaultForRewrite: "false" }).where(eq(apiConnections.id, row.id));
+ }
+ }
+ }
+ updateFields.defaultForRewrite = String(data.defaultForRewrite);
+ }
if (data.enableCaching !== undefined) {
updateFields.enableCaching = String(data.enableCaching);
}
@@ -203,6 +238,7 @@ export function createConnectionsStorage(db: DB) {
isDefault: "false",
useForRandom: source.useForRandom,
defaultForAgents: "false",
+ defaultForRewrite: "false",
enableCaching: source.enableCaching,
cachingAtDepth: source.cachingAtDepth,
embeddingModel: source.embeddingModel,
diff --git a/packages/shared/src/schemas/connection.schema.ts b/packages/shared/src/schemas/connection.schema.ts
index c2d26f927..3deee3d61 100644
--- a/packages/shared/src/schemas/connection.schema.ts
+++ b/packages/shared/src/schemas/connection.schema.ts
@@ -29,6 +29,7 @@ export const createConnectionSchema = z.object({
isDefault: z.boolean().default(false),
useForRandom: z.boolean().default(false),
defaultForAgents: z.boolean().default(false),
+ defaultForRewrite: z.boolean().default(false),
enableCaching: z.boolean().default(false),
cachingAtDepth: z.number().int().min(0).default(5),
embeddingModel: z.string().default(""),
diff --git a/packages/shared/src/types/connection.ts b/packages/shared/src/types/connection.ts
index 9be3b563f..0cecbcc44 100644
--- a/packages/shared/src/types/connection.ts
+++ b/packages/shared/src/types/connection.ts
@@ -35,6 +35,8 @@ export interface APIConnection {
useForRandom: boolean;
/** Whether this connection is the default for all agents */
defaultForAgents: boolean;
+ /** Whether this connection is the default for Magic Rewrite text generation */
+ defaultForRewrite: boolean;
/** Whether provider-native prompt caching is enabled */
enableCaching: boolean;
/** Conversation message depth for Anthropic cache breakpoints */