Browse Source

fix: harden prompt type coercion in nanoBanana executor and connectedInputs

- Add defensive type coercion guard for promptText (handles non-string at runtime)
- Sanitize dynamicInputs.prompt to string before including in request payload
- Add final assertion guard before fetch to provide clear error if prompt is not a string
- Ensure text values in connectedInputs are always strings via String() coercion
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
7f7f79aa31
  1. 25
      src/store/execution/nanoBananaExecutor.ts
  2. 4
      src/store/utils/connectedInputs.ts

25
src/store/execution/nanoBananaExecutor.ts

@ -62,6 +62,13 @@ export async function executeNanoBanana(
promptText = connectedText || promptFromDynamic || null; promptText = connectedText || promptFromDynamic || null;
} }
// Defensive: ensure promptText is actually a string at runtime
// (Guards against corrupted node data or race conditions in parallel execution)
if (promptText !== null && typeof promptText !== 'string') {
console.warn('[nanoBanana] promptText was not a string, coercing:', typeof promptText, promptText);
promptText = Array.isArray(promptText) ? (promptText as string[])[0] ?? null : String(promptText);
}
if (!promptText) { if (!promptText) {
updateNodeData(node.id, { updateNodeData(node.id, {
status: "error", status: "error",
@ -80,6 +87,13 @@ export async function executeNanoBanana(
const provider = nodeData.selectedModel?.provider || "gemini"; const provider = nodeData.selectedModel?.provider || "gemini";
const headers = buildGenerateHeaders(provider, providerSettings); const headers = buildGenerateHeaders(provider, providerSettings);
// Sanitize dynamicInputs: coerce prompt to string if it's an array
// (Defensive guard for parallel execution race conditions)
const sanitizedDynamicInputs = { ...dynamicInputs };
if (Array.isArray(sanitizedDynamicInputs.prompt)) {
sanitizedDynamicInputs.prompt = (sanitizedDynamicInputs.prompt as string[])[0] ?? '';
}
const requestPayload = { const requestPayload = {
images, images,
prompt: promptText, prompt: promptText,
@ -89,9 +103,18 @@ export async function executeNanoBanana(
useGoogleSearch: nodeData.useGoogleSearch, useGoogleSearch: nodeData.useGoogleSearch,
selectedModel: nodeData.selectedModel, selectedModel: nodeData.selectedModel,
parameters: nodeData.parameters, parameters: nodeData.parameters,
dynamicInputs, dynamicInputs: sanitizedDynamicInputs,
}; };
// Final guard: assert that prompt is a string before sending to API
// This catches any remaining edge cases and provides a clear error message
if (typeof requestPayload.prompt !== 'string') {
const errorMsg = `Internal error: prompt is ${typeof requestPayload.prompt}, expected string`;
console.error('[nanoBanana]', errorMsg);
updateNodeData(node.id, { status: 'error', error: errorMsg });
throw new Error(errorMsg);
}
try { try {
const response = await fetch("/api/generate", { const response = await fetch("/api/generate", {
method: "POST", method: "POST",

4
src/store/utils/connectedInputs.ts

@ -162,7 +162,9 @@ export function getConnectedInputsPure(
} else if (type === "audio") { } else if (type === "audio") {
audio.push(value); audio.push(value);
} else if (type === "text" || isTextHandle(handleId)) { } else if (type === "text" || isTextHandle(handleId)) {
text = value; // Defensive: ensure text values are always strings
// (Guards against corrupted node data during parallel execution)
text = typeof value === 'string' ? value : String(value);
} else if (isImageHandle(handleId) || !handleId) { } else if (isImageHandle(handleId) || !handleId) {
images.push(value); images.push(value);
} }

Loading…
Cancel
Save