Repository: Stirling-Tools/Stirling-PDF
File: frontend/src/core/hooks/tools/shared/useToolOperation.ts (TypeScript)

Code before:
                    (response.headers && response.headers['content-type'] === 'application/pdf')) {
            // Single PDF response (e.g. split with merge option) - add prefix to first original filename
            const filename = `${config.filePrefix}${filesForAPI[0]?.name || 'document.pdf'}`;
            const singleFile = new File([response.data], filename, { type: 'application/pdf' });
            processedFiles = [singleFile];
          } else {
            // Default: assume ZIP response for multi-file endpoints
            // Note: extractZipFiles will check preferences.autoUnzip setting
            processedFiles = await extractZipFiles(response.data);
          }
          // Assume all inputs succeeded together unless server provided an error earlier
          successSourceIds = validFiles.map(f => (f as any).fileId) as any;
          break;
        }

        case ToolType.custom: {
          actions.setStatus('Processing files...');
          const result = await config.customProcessor(params, filesForAPI);

          processedFiles = result.files;
          const consumedAllInputs = result.consumedAllInputs || false;

          // If consumedAllInputs flag is set, mark all inputs as successful
          // (used for operations that combine N inputs into fewer outputs)
          if (consumedAllInputs) {
            successSourceIds = validFiles.map(f => (f as any).fileId) as any;
          } else {
            // Try to map outputs back to inputs by filename (before extension)
            const inputBaseNames = new Map<string, string>();
            for (const f of validFiles) {
              const base = (f.name || '').replace(/\.[^.]+$/, '').toLowerCase();
              inputBaseNames.set(base, (f as any).fileId);
            }
            const mappedSuccess: string[] = [];
            for (const out of processedFiles) {
              const base = (out.name || '').replace(/\.[^.]+$/, '').toLowerCase();
              const id = inputBaseNames.get(base);
              if (id) mappedSuccess.push(id);
            }
            // Fallback to naive alignment if names don't match
            if (mappedSuccess.length === 0) {
              successSourceIds = validFiles.slice(0, processedFiles.length).map(f => (f as any).fileId) as any;
            } else {
              successSourceIds = mappedSuccess as any;
            }
          }
          break;
        }
      }

      // Normalize error flags across tool types: mark failures, clear successes

Review comment (suggestion):
There's a depressing amount of `any`ing going on here. It already was so if it's not easy to fix then I'm cool with them going in, but it's worth a quick check whether they're acutally needed

Code after:
          } else if (response.data.type === 'application/pdf' ||
                    (response.headers && response.headers['content-type'] === 'application/pdf')) {
            // Single PDF response (e.g. split with merge option) - add prefix to first original filename
            const filename = `${config.filePrefix}${filesForAPI[0]?.name || 'document.pdf'}`;
            const singleFile = new File([response.data], filename, { type: 'application/pdf' });
            processedFiles = [singleFile];
          } else {
            // Default: assume ZIP response for multi-file endpoints
            // Note: extractZipFiles will check preferences.autoUnzip setting
            processedFiles = await extractZipFiles(response.data);
          }
          // Assume all inputs succeeded together unless server provided an error earlier
          successSourceIds = validFiles.map(f => f.fileId);
          break;
        }

        case ToolType.custom: {
          actions.setStatus('Processing files...');
          const result = await config.customProcessor(params, filesForAPI);

          processedFiles = result.files;
          const consumedAllInputs = result.consumedAllInputs || false;

          // If consumedAllInputs flag is set, mark all inputs as successful
          // (used for operations that combine N inputs into fewer outputs)
          if (consumedAllInputs) {
            successSourceIds = validFiles.map(f => f.fileId);
          } else {
            // Try to map outputs back to inputs by filename (before extension)
            const inputBaseNames = new Map<string, FileId>();
            for (const f of validFiles) {
              const base = getFilenameWithoutExtension(f.name || '');
              inputBaseNames.set(base, f.fileId);
            }
            const mappedSuccess: FileId[] = [];
            for (const out of processedFiles) {
              const base = getFilenameWithoutExtension(out.name || '');
              const id = inputBaseNames.get(base);
              if (id) mappedSuccess.push(id);
            }
            // Fallback to naive alignment if names don't match
            if (mappedSuccess.length === 0) {
              successSourceIds = validFiles.slice(0, processedFiles.length).map(f => f.fileId);
            } else {
              successSourceIds = mappedSuccess;
            }
          }
          break;
        }
      }