Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified artifacts/noodle-1.0.2.zip
Binary file not shown.
14 changes: 7 additions & 7 deletions catalog/catalog.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"generatedAt": "2026-08-09T21:51:52.012Z",
"generatedAt": "2026-08-10T10:41:12.734Z",
"packages": [
{
"manifest": {
Expand Down Expand Up @@ -1057,13 +1057,13 @@
},
{
"path": "server.mjs",
"sha256": "01f3e41a956ac795270a8389c62c515a780c5345e5a9e36e110be3d8eb6fa0bc",
"bytes": 2382557
"sha256": "ed0c9c0b28f5e6360ccf001ed17be7074a4f9b768dc8800d20a38511dfc73eeb",
"bytes": 2387526
},
{
"path": "client.js",
"sha256": "a60bb6487018c27c24d8c556f0b5215d07b5a2baf140dff4c281eef91a13f0c1",
"bytes": 2473078
"sha256": "c1dfcb05055c3ffb3edff526984195ad213be0e1e702babec86ddf70b79fd255",
"bytes": 2475777
},
{
"path": "noodle-klusek.png",
Expand All @@ -1090,8 +1090,8 @@
"iconUrl": "https://raw.githubusercontent.com/Pasta-Devs/Marinara-Agents/main/artwork/agent-covers/noodle.png",
"artifact": {
"url": "https://raw.githubusercontent.com/Pasta-Devs/Marinara-Agents/main/artifacts/noodle-1.0.2.zip",
"sha256": "93a75b33bb81ba43d47fc3749dc0a4fbf7afd26e898dc75b42eb5ce1ae0f6763",
"bytes": 1226046
"sha256": "889f39f8a2d87d990987ad0cd3339ce7024439ed8a6c37a419a25d04ac93e3e0",
"bytes": 1227900
},
"documentationUrl": "https://github.com/Pasta-Devs/Marinara-Agents/blob/main/packages/noodle/README.md"
},
Expand Down
14 changes: 7 additions & 7 deletions catalog/v2/catalog.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"generatedAt": "2026-08-09T21:51:52.012Z",
"generatedAt": "2026-08-10T10:41:12.734Z",
"packages": [
{
"manifest": {
Expand Down Expand Up @@ -1057,13 +1057,13 @@
},
{
"path": "server.mjs",
"sha256": "01f3e41a956ac795270a8389c62c515a780c5345e5a9e36e110be3d8eb6fa0bc",
"bytes": 2382557
"sha256": "ed0c9c0b28f5e6360ccf001ed17be7074a4f9b768dc8800d20a38511dfc73eeb",
"bytes": 2387526
},
{
"path": "client.js",
"sha256": "a60bb6487018c27c24d8c556f0b5215d07b5a2baf140dff4c281eef91a13f0c1",
"bytes": 2473078
"sha256": "c1dfcb05055c3ffb3edff526984195ad213be0e1e702babec86ddf70b79fd255",
"bytes": 2475777
},
{
"path": "noodle-klusek.png",
Expand All @@ -1090,8 +1090,8 @@
"iconUrl": "https://raw.githubusercontent.com/Pasta-Devs/Marinara-Agents/main/artwork/agent-covers/noodle.png",
"artifact": {
"url": "https://raw.githubusercontent.com/Pasta-Devs/Marinara-Agents/main/artifacts/noodle-1.0.2.zip",
"sha256": "93a75b33bb81ba43d47fc3749dc0a4fbf7afd26e898dc75b42eb5ce1ae0f6763",
"bytes": 1226046
"sha256": "889f39f8a2d87d990987ad0cd3339ce7024439ed8a6c37a419a25d04ac93e3e0",
"bytes": 1227900
},
"documentationUrl": "https://github.com/Pasta-Devs/Marinara-Agents/blob/main/packages/noodle/README.md"
},
Expand Down
14 changes: 7 additions & 7 deletions catalog/v3/catalog.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
"generatedAt": "2026-08-09T21:51:52.012Z",
"generatedAt": "2026-08-10T10:41:12.734Z",
"packages": [
{
"manifest": {
Expand Down Expand Up @@ -1057,13 +1057,13 @@
},
{
"path": "server.mjs",
"sha256": "01f3e41a956ac795270a8389c62c515a780c5345e5a9e36e110be3d8eb6fa0bc",
"bytes": 2382557
"sha256": "ed0c9c0b28f5e6360ccf001ed17be7074a4f9b768dc8800d20a38511dfc73eeb",
"bytes": 2387526
},
{
"path": "client.js",
"sha256": "a60bb6487018c27c24d8c556f0b5215d07b5a2baf140dff4c281eef91a13f0c1",
"bytes": 2473078
"sha256": "c1dfcb05055c3ffb3edff526984195ad213be0e1e702babec86ddf70b79fd255",
"bytes": 2475777
},
{
"path": "noodle-klusek.png",
Expand All @@ -1090,8 +1090,8 @@
"iconUrl": "https://raw.githubusercontent.com/Pasta-Devs/Marinara-Agents/main/artwork/agent-covers/noodle.png",
"artifact": {
"url": "https://raw.githubusercontent.com/Pasta-Devs/Marinara-Agents/main/artifacts/noodle-1.0.2.zip",
"sha256": "93a75b33bb81ba43d47fc3749dc0a4fbf7afd26e898dc75b42eb5ce1ae0f6763",
"bytes": 1226046
"sha256": "889f39f8a2d87d990987ad0cd3339ce7024439ed8a6c37a419a25d04ac93e3e0",
"bytes": 1227900
},
"documentationUrl": "https://github.com/Pasta-Devs/Marinara-Agents/blob/main/packages/noodle/README.md"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/noodle/client.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions packages/noodle/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@
},
{
"path": "server.mjs",
"sha256": "01f3e41a956ac795270a8389c62c515a780c5345e5a9e36e110be3d8eb6fa0bc",
"bytes": 2382557
"sha256": "ed0c9c0b28f5e6360ccf001ed17be7074a4f9b768dc8800d20a38511dfc73eeb",
"bytes": 2387526
},
{
"path": "client.js",
"sha256": "a60bb6487018c27c24d8c556f0b5215d07b5a2baf140dff4c281eef91a13f0c1",
"bytes": 2473078
"sha256": "c1dfcb05055c3ffb3edff526984195ad213be0e1e702babec86ddf70b79fd255",
"bytes": 2475777
},
{
"path": "noodle-klusek.png",
Expand Down
442 changes: 228 additions & 214 deletions packages/noodle/server.mjs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import type { DB } from "../../db/connection.js";
import { logger } from "../../lib/logger.js";
import { resolveBaseUrl } from "../generation/connection-base-url.js";
import { resolveIllustratorPromptRuntime } from "../generation/illustrator-prompt-runtime.js";
import { createConnectionsStorage } from "../storage/connections.storage.js";

const MAX_REWRITTEN_PROMPT_LENGTH = 12_000;
const MAX_INSTRUCTIONS_LENGTH = 5_000;

function parseRecord(value: unknown): Record<string, unknown> {
if (value && typeof value === "object" && !Array.isArray(value)) {
return value as Record<string, unknown>;
}
if (typeof value !== "string") return {};
const text = value
.trim()
.replace(/^```(?:json)?\s*/iu, "")
.replace(/\s*```$/u, "");
const start = text.indexOf("{");
const end = text.lastIndexOf("}");
if (start < 0 || end <= start) return {};
try {
const parsed: unknown = JSON.parse(text.slice(start, end + 1));
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
? (parsed as Record<string, unknown>)
: {};
} catch {
return {};
}
}

export async function rewriteNoodleImagePrompt(input: {
db: DB;
prompt: string;
instructions: string;
characterContext?: string;
}): Promise<string | null> {
const instructions = input.instructions.trim().replace(/\s+/g, " ").slice(0, MAX_INSTRUCTIONS_LENGTH);
const prompt = input.prompt.trim().slice(0, MAX_REWRITTEN_PROMPT_LENGTH);
if (!instructions || !prompt) return null;
const characterContext = input.characterContext?.trim().slice(0, 8_000) || "No additional character context was provided.";

try {
const connections = createConnectionsStorage(input.db);
const textConnection =
(await connections.getDefaultForAgents()) ??
(await connections.getFallbackForAgents());
if (!textConnection) return null;

const runtime = await resolveIllustratorPromptRuntime({
chatMetadata: {},
defaultConnection: textConnection,
defaultConnectionId: textConnection.id,
connections,
resolveBaseUrl,
});
const result = await runtime.provider.chatComplete(
[
{
role: "system",
content: [
"You are an image prompt editor.",
"Rewrite the supplied image prompt so it follows the user's image-backend instructions.",
"Preserve the original subject, identity, action, setting, and visual facts unless the instructions explicitly change them.",
"Use the character context to preserve appearance and personality, but do not add characters who are not in the original prompt.",
"Treat the user's instructions as guidance, not text to copy into the image prompt.",
"Return valid JSON only: {\"prompt\":\"provider-ready image prompt\"}.",
].join("\n"),
},
{
role: "user",
content: [
"<original_image_prompt>",
prompt,
"</original_image_prompt>",
"<character_context>",
characterContext,
"</character_context>",
"<image_prompting_instructions>",
instructions,
"</image_prompting_instructions>",
].join("\n"),
},
],
{
model: runtime.model,
...(runtime.suppressModelParameters ? {} : { temperature: 0.3, maxTokens: 2_048 }),
suppressModelParameters: runtime.suppressModelParameters,
enableCaching: runtime.enableCaching,
anthropicExtendedCacheTtl: runtime.anthropicExtendedCacheTtl,
},
);
const parsed = parseRecord(result.content);
const rewritten = typeof parsed.prompt === "string"
? parsed.prompt.trim().slice(0, MAX_REWRITTEN_PROMPT_LENGTH)
: "";
return rewritten || null;
} catch (error) {
logger.warn(error, "[noodle] Image prompt instruction rewrite failed; using the original prompt");
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { createConnectionsStorage } from "../storage/connections.storage.js";
import { createPromptOverridesStorage } from "../storage/prompt-overrides.storage.js";
import { loadPrompt, NOODLE_IMAGE_POST } from "../prompt-overrides/index.js";
import { generateNoodleImageWithRetry } from "./noodle-image-retry.js";
import { rewriteNoodleImagePrompt } from "./noodle-image-prompt-rewrite.js";
import type { ConnectionAdmissionMode } from "../generation/connection-admission.js";
import {
characterAppearanceFromRow,
Expand Down Expand Up @@ -174,8 +175,29 @@ export async function generateNoodlerPostImage(input: {
styleProfiles: imageSettings.styleProfiles,
imageDefaults,
});
const finalPrompt =
const rawFinalPrompt =
input.promptOverride?.prompt.trim() || compiledPrompt.prompt;
const imagePromptInstructions = input.imageConnection.imagePromptInstructions?.trim();
const instructionLine = imagePromptInstructions
? `User image instructions: ${imagePromptInstructions.replace(/\s+/g, " ").slice(0, 5000)}`
: "";
const characterContext = [
characterDescription ? `Appearance:\n${characterDescription}` : "",
characterPersonality ? `Personality:\n${characterPersonality}` : "",
characterImageInstructions ? `Character image preferences:\n${characterImageInstructions}` : "",
].filter(Boolean).join("\n\n");
const rewrittenPrompt = imagePromptInstructions && !input.promptOverride
? await rewriteNoodleImagePrompt({
db: input.db,
prompt: rawFinalPrompt,
instructions: imagePromptInstructions,
characterContext,
})
: null;
const finalPrompt = rewrittenPrompt ??
(instructionLine && !input.promptOverride && !rawFinalPrompt.includes(instructionLine)
? `${rawFinalPrompt}\n${instructionLine}`
: rawFinalPrompt);
const finalNegativePrompt = input.promptOverride
? input.promptOverride.negativePrompt?.trim() || undefined
: compiledPrompt.negativePrompt || undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { createNoodleStorage } from "../storage/noodle.storage.js";
import { createPromptOverridesStorage } from "../storage/prompt-overrides.storage.js";
import { loadPrompt, NOODLE_IMAGE_POST } from "../prompt-overrides/index.js";
import { generateNoodleImageWithRetry } from "./noodle-image-retry.js";
import { rewriteNoodleImagePrompt } from "./noodle-image-prompt-rewrite.js";
import type { ConnectionAdmissionMode } from "../generation/connection-admission.js";
import {
bootstrapVisibleNoodle,
Expand Down Expand Up @@ -249,8 +250,29 @@ export async function generateNoodlePostImage(input: {
styleProfiles: imageSettings.styleProfiles,
imageDefaults,
});
const finalPrompt =
const rawFinalPrompt =
input.promptOverride?.prompt.trim() || compiledPrompt.prompt;
const imagePromptInstructions = input.imageConnection.imagePromptInstructions?.trim();
const instructionLine = imagePromptInstructions
? `User image instructions: ${imagePromptInstructions.replace(/\s+/g, " ").slice(0, 5000)}`
: "";
const characterContext = [
characterDescription ? `Appearance:\n${characterDescription}` : "",
characterPersonality ? `Personality:\n${characterPersonality}` : "",
characterImageInstructions ? `Character image preferences:\n${characterImageInstructions}` : "",
].filter(Boolean).join("\n\n");
const rewrittenPrompt = imagePromptInstructions && !input.promptOverride
? await rewriteNoodleImagePrompt({
db: input.db,
prompt: rawFinalPrompt,
instructions: imagePromptInstructions,
characterContext,
})
: null;
const finalPrompt = rewrittenPrompt ??
(instructionLine && !input.promptOverride && !rawFinalPrompt.includes(instructionLine)
? `${rawFinalPrompt}\n${instructionLine}`
: rawFinalPrompt);
const finalNegativePrompt = input.promptOverride
? input.promptOverride.negativePrompt?.trim() || undefined
: compiledPrompt.negativePrompt || undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const NOODLE_IMAGE_POST: PromptOverrideKeyDef<NoodleImagePostCtx> = {
key: "noodle.imagePost",
label: "Noodle Post Image",
description:
"Template that assembles the final image-generation prompt. Everything this produces is sent to the image model verbatim — no LLM pass runs after it. The default sends the visual idea, appearance notes, and character image habits, without the post text or your Noodle image instructions (those are given to the timeline model instead).",
"Template that assembles the base image-generation prompt. When the selected image connection has image instructions, Marinara first tries to have an agent text model apply them; if rewriting is unavailable, it appends normalized instructions directly. The default sends the visual idea, appearance notes, and character image habits, without the post text or your Noodle image instructions (those are also given to the timeline model).",
variables: [
{ name: "authorName", description: "Display name of the Noodle account posting.", example: "Dottore" },
{
Expand Down
Loading