Improve consistency and reliability of tools in Stirling Engine (#6855)

# Description of Changes
A few changes to improve things in the engine:
- Changed the PDF to Markdown code to be a real tool in Java, to remove
the need for the `pdf_ingest` code, which looked a bit like an agent but
wasn't behaving as an agent. It's now just covered automatically by the
edit agent.
- Noticed that 0-parameter-endpoints were previously being ignored by
the `tool_models` generator, so some tools which require no params were
being mistakenly excluded.
- Removed tools which currently never succeed like Add Stamp, Cert Sign,
and Overlay, because they require the supporting files to be sent in a
different location in the API call, which we don't currently do.
Ideally, we'd add proper support for this, but we're better off now
removing support for these tools rather than just have them crash. We
can re-add these tools in a future PR properly.
This commit is contained in:
James Brunton
2026-07-07 11:01:18 +00:00
committed by GitHub
parent 1df6a1759c
commit 20204f0ddc
10 changed files with 157 additions and 318 deletions
@@ -21,8 +21,7 @@ public enum AiWorkflowOutcome {
COMPLETED("completed"),
UNSUPPORTED_CAPABILITY("unsupported_capability"),
CANNOT_CONTINUE("cannot_continue"),
GENERATE_FILE("generate_file"),
CONVERT_MARKDOWN("convert_markdown");
GENERATE_FILE("generate_file");
private final String value;
@@ -68,7 +68,6 @@ import tools.jackson.databind.ObjectMapper;
public class AiWorkflowService {
private static final String DOCUMENTS_ENDPOINT = "/api/v1/documents";
private static final String PDF_TO_MARKDOWN_ENDPOINT = "/api/v1/convert/pdf/markdown";
private final CustomPDFDocumentFactory pdfDocumentFactory;
private final AiEngineClient aiEngineClient;
@@ -196,7 +195,6 @@ public class AiWorkflowService {
return switch (response.getOutcome()) {
case NEED_CONTENT -> onNeedContent(response, filesById, request, listener);
case NEED_INGEST -> onNeedIngest(response, filesById, request, listener);
case CONVERT_MARKDOWN -> onConvertMarkdown(response, filesById, listener);
case TOOL_CALL -> onToolCall(response, filesById, listener);
case PLAN -> onPlan(response, filesById, request, listener);
case ANSWER -> onAnswer(response, filesById, request, listener);
@@ -333,72 +331,6 @@ public class AiWorkflowService {
return new WorkflowState.Pending(nextRequest);
}
/**
* Deterministically convert each requested PDF to Markdown via the {@code
* /convert/pdf/markdown} endpoint (backed by {@code PdfMarkdownConverter}) and return the
* {@code .md} file(s) as a completed result. No AI resume — the conversion output is the final
* answer.
*/
private WorkflowState onConvertMarkdown(
AiWorkflowResponse response,
Map<String, MultipartFile> filesById,
ProgressListener listener) {
List<AiFile> filesToConvert = response.getFilesToIngest();
if (filesToConvert == null || filesToConvert.isEmpty()) {
return new WorkflowState.Terminal(
cannotContinue(
"AI engine requested markdown conversion without listing any files."));
}
try {
List<Resource> resultFiles = new ArrayList<>();
List<String> inputNames = new ArrayList<>();
for (int i = 0; i < filesToConvert.size(); i++) {
AiFile file = filesToConvert.get(i);
MultipartFile multipartFile = filesById.get(file.getId());
if (multipartFile == null) {
return new WorkflowState.Terminal(
cannotContinue(
"AI engine requested markdown conversion for unknown file: "
+ file.getName()));
}
listener.onProgress(
AiWorkflowProgressEvent.executingTool(
PDF_TO_MARKDOWN_ENDPOINT, i + 1, filesToConvert.size()));
Resource input = toResource(multipartFile);
PipelineDefinition definition =
new PipelineDefinition(
"convert-markdown",
List.of(new PipelineStep(PDF_TO_MARKDOWN_ENDPOINT, Map.of())),
null);
PolicyExecutionResult result =
policyExecutor.execute(
definition,
PolicyInputs.of(List.of(input)),
PolicyProgressListener.NOOP);
resultFiles.addAll(result.files());
inputNames.add(multipartFile.getOriginalFilename());
}
return new WorkflowState.Terminal(
buildCompletedResponse(null, resultFiles, inputNames, null));
} catch (InternalApiTimeoutException e) {
log.error("PDF to Markdown conversion timed out: {}", e.getMessage());
return new WorkflowState.Terminal(
cannotContinue(toolTimeoutMessage(PDF_TO_MARKDOWN_ENDPOINT, e)));
} catch (Exception e) {
AiWorkflowResponse limit = paygLimitResponseOrNull(e);
if (limit != null) {
log.info(
"AI markdown conversion blocked by downstream entitlement gate ({})",
limit.getErrorCode());
return new WorkflowState.Terminal(limit);
}
log.error("Failed to convert PDF to Markdown: {}", e.getMessage(), e);
return new WorkflowState.Terminal(
cannotContinue(toolFailureMessage(PDF_TO_MARKDOWN_ENDPOINT, e)));
}
}
private Resource toResource(MultipartFile file) throws IOException {
TempFile tempFile = tempFileManager.createManagedTempFile("ai-workflow");
file.transferTo(tempFile.getPath());
@@ -221,34 +221,6 @@ class AiWorkflowServiceMoreTest {
}
}
@Nested
@DisplayName("convert_markdown guards")
class ConvertMarkdownGuards {
@Test
@DisplayName("no files listed yields CANNOT_CONTINUE")
void noFiles() throws IOException {
stubOrchestrator("{\"outcome\":\"convert_markdown\",\"filesToIngest\":[]}");
AiWorkflowResponse result = service.orchestrate(requestFor(pdf("a.pdf", "x"), "to md"));
assertThat(result.getOutcome()).isEqualTo(AiWorkflowOutcome.CANNOT_CONTINUE);
}
@Test
@DisplayName("unknown file id yields CANNOT_CONTINUE")
void unknownFile() throws IOException {
when(fileIdStrategy.idFor(any())).thenReturn("real-id");
stubOrchestrator(
"""
{"outcome":"convert_markdown",
"filesToIngest":[{"id":"other-id","name":"other.pdf"}]}
""");
AiWorkflowResponse result =
service.orchestrate(requestFor(pdf("real.pdf", "x"), "to md"));
assertThat(result.getOutcome()).isEqualTo(AiWorkflowOutcome.CANNOT_CONTINUE);
assertThat(result.getReason()).contains("other.pdf");
}
}
@Nested
@DisplayName("plan guards and errors")
class PlanGuardsAndErrors {
@@ -78,6 +78,7 @@ class AiWorkflowServiceTest {
private static final String SPLIT_ENDPOINT = "/api/v1/general/split-pages";
private static final String MERGE_ENDPOINT = "/api/v1/general/merge-pdfs";
private static final String COMPRESS_ENDPOINT = "/api/v1/misc/compress-pdf";
private static final String MARKDOWN_ENDPOINT = "/api/v1/convert/pdf/markdown";
@Mock private CustomPDFDocumentFactory pdfDocumentFactory;
@Mock private AiEngineClient aiEngineClient;
@@ -440,23 +441,23 @@ class AiWorkflowServiceTest {
}
@Test
void convertMarkdownRunsDeterministicConversionAndReturnsMdFile() throws IOException {
void planWithMarkdownStepReturnsMdFile() throws IOException {
// PDF→Markdown is a normal tool the edit agent emits as a plan step (no bespoke
// outcome); the plan executor runs the converter and returns the .md file.
MockMultipartFile input = pdf("multi-column-test_lorem.pdf", "pdf-bytes");
when(fileIdStrategy.idFor(any())).thenReturn("doc-1");
stubOrchestrator(
"""
{
"outcome":"convert_markdown",
"reason":"PDF to Markdown requested.",
"filesToIngest":[{"id":"doc-1","name":"multi-column-test_lorem.pdf"}]
"outcome":"plan",
"summary":"Convert to Markdown",
"steps":[{"tool":"%s","parameters":{}}]
}
""");
when(toolMetadataService.shouldUnpackZipResponse("/api/v1/convert/pdf/markdown"))
.thenReturn(false);
stubEndpoint(
"/api/v1/convert/pdf/markdown",
pdfResource("# Title", "multi-column-test_lorem.md"));
AtomicInteger ids = stubFileStorage();
"""
.formatted(MARKDOWN_ENDPOINT));
when(toolMetadataService.isMultiInput(anyString())).thenReturn(false);
when(toolMetadataService.shouldUnpackZipResponse(anyString())).thenReturn(false);
stubEndpoint(MARKDOWN_ENDPOINT, pdfResource("# Title", "multi-column-test_lorem.md"));
stubFileStorage();
AiWorkflowResponse result = service.orchestrate(requestFor(input, "convert to markdown"));
@@ -464,8 +465,7 @@ class AiWorkflowServiceTest {
assertEquals(1, result.getResultFiles().size());
// Extension changes (pdf -> md), so the converter's response filename wins.
assertEquals("multi-column-test_lorem.md", result.getResultFiles().get(0).getFileName());
assertEquals(1, ids.get());
verify(internalApiClient, times(1)).post(eq("/api/v1/convert/pdf/markdown"), any());
verify(internalApiClient, times(1)).post(eq(MARKDOWN_ENDPOINT), any());
}
@Test
+32 -4
View File
@@ -59,6 +59,36 @@ class ToolDiscovery:
"/api/v1/convert/",
)
# Endpoints under the allowed prefixes that are NOT edit-agent operations. A listed
# path and everything nested under it is dropped. Several kinds live here:
EXCLUDED_PATHS = (
# 1. Cert-signing family: needs certificate/key files the agent can't supply, plus
# interactive session and hardware-token management. The whole subtree is dropped.
"/api/v1/security/cert-sign",
# 2. Interactive PDF text-editor endpoints, not one-shot operations.
"/api/v1/convert/pdf/text-editor",
"/api/v1/convert/text-editor/pdf",
# 3. Introspection / query endpoints that return metadata, a listing, or a
# verification verdict rather than a transformed document, so they belong to
# the question path, not the edit agent. (decompress is a dev-only stream op.)
"/api/v1/security/get-info-on-pdf",
"/api/v1/security/verify-pdf",
"/api/v1/security/validate-signature",
"/api/v1/misc/list-attachments",
"/api/v1/misc/show-javascript",
"/api/v1/misc/decompress-pdf",
"/api/v1/general/extract-bookmarks",
# 4. Require a secondary file (image, overlay PDF, attachments) on top of the input
# PDF. The agent only ever supplies the input PDF(s), so these can never run.
# (add-stamp / add-watermark stay: their text mode needs no extra file.)
"/api/v1/misc/add-image",
"/api/v1/misc/add-attachments",
"/api/v1/general/overlay-pdfs",
)
def _is_excluded(self, path: str) -> bool:
return any(path == p or path.startswith(p + "/") for p in self.EXCLUDED_PATHS)
def __init__(self, spec: dict[str, Any]):
resource = Resource.from_contents(spec, default_specification=DRAFT202012)
self.resolver = Registry().with_resource("", resource).resolver()
@@ -73,17 +103,15 @@ class ToolDiscovery:
for path, path_item in sorted(self.spec.get("paths", {}).items()):
if "{" in path or not any(path.startswith(p) for p in self.ALLOWED_PATH_PREFIXES):
continue
if self._is_excluded(path):
continue
body_schema = self._get_request_body_schema(path_item) or {}
query_props = self._get_query_parameters(path_item)
body_props = body_schema.get("properties") or {}
# Body properties win on name collision — body is the canonical param source
# for the existing tools; query params are additive.
properties = {**query_props, **body_props}
if not properties:
continue
clean_props = self._filter_properties(properties)
if not clean_props:
continue
enum_name = _deduplicate(_path_to_enum_name(path), used_enum)
class_name = _deduplicate(_path_to_class_name(path), used_class)
+2 -19
View File
@@ -15,7 +15,6 @@ from stirling.agents.pdf_review import PdfReviewAgent
from stirling.agents.user_spec import UserSpecAgent
from stirling.contracts import (
AgentDraftWorkflowResponse,
ConvertMarkdownResponse,
ExtractedTextArtifact,
OrchestratorRequest,
OrchestratorResponse,
@@ -48,7 +47,7 @@ class OrchestratorAgent:
ToolOutput(
self.delegate_pdf_edit,
name="delegate_pdf_edit",
description="Delegate requests for PDF modifications and return the PDF edit result.",
description="Delegate requests to modify or convert PDFs and return the PDF edit result.",
),
ToolOutput(
self.delegate_pdf_question,
@@ -71,13 +70,6 @@ class OrchestratorAgent:
" feedback')."
),
),
ToolOutput(
self.delegate_pdf_ingest,
name="delegate_pdf_ingest",
description=(
"Delegate requests to convert a PDF to Markdown or extract its content as readable text."
),
),
ToolOutput(
self.delegate_pdf_create,
name="delegate_pdf_create",
@@ -98,7 +90,7 @@ class OrchestratorAgent:
system_prompt=(
"You are the top-level orchestrator. "
"Choose exactly one output function that best handles the request. "
"Use delegate_pdf_edit for any requested modification of one or more PDFs. "
"Use delegate_pdf_edit for any request to modify or convert one or more PDFs. "
"Use delegate_pdf_question for questions about the contents of the attached PDFs. "
"Use delegate_user_spec for requests to create or define an agent spec. "
"Use delegate_pdf_review when the user wants the PDF returned with review"
@@ -106,8 +98,6 @@ class OrchestratorAgent:
" 'leave feedback on the PDF'. "
"Use delegate_pdf_create when the user wants to generate a new document from"
" scratch with no input file — invoices, reports, letters, contracts, etc. "
"Use delegate_pdf_ingest for any request to convert a PDF to Markdown "
"or extract its content as readable text. "
"Use unsupported_capability when the user asks about the assistant itself "
"or when none of the other outputs fit; supply a helpful message."
),
@@ -177,13 +167,6 @@ class OrchestratorAgent:
async def _run_agent_draft(self, request: OrchestratorRequest) -> AgentDraftWorkflowResponse:
return await UserSpecAgent(self.runtime).orchestrate(request)
async def delegate_pdf_ingest(self, ctx: RunContext[OrchestratorDeps]) -> ConvertMarkdownResponse:
request = ctx.deps.request
return ConvertMarkdownResponse(
reason="PDF to Markdown requested — Java converts deterministically.",
files_to_ingest=request.files,
)
async def delegate_pdf_review(self, ctx: RunContext[OrchestratorDeps]) -> PdfReviewOrchestrateResponse:
return await self._run_pdf_review(ctx.deps.request)
@@ -13,7 +13,6 @@ from .common import (
AiFile,
ArtifactKind,
ConversationMessage,
ConvertMarkdownResponse,
ExtractedFileText,
GenerateFileResponse,
MathAuditorToolReportArtifact,
@@ -163,7 +162,6 @@ __all__ = [
"NeedContentFileRequest",
"NeedContentResponse",
"NeedIngestResponse",
"ConvertMarkdownResponse",
"NextExecutionAction",
"OrchestratorRequest",
"OrchestratorResponse",
-14
View File
@@ -62,7 +62,6 @@ class WorkflowOutcome(StrEnum):
CANNOT_CONTINUE = "cannot_continue"
UNSUPPORTED_CAPABILITY = "unsupported_capability"
GENERATE_FILE = "generate_file"
CONVERT_MARKDOWN = "convert_markdown"
class ArtifactKind(StrEnum):
@@ -184,19 +183,6 @@ class NeedIngestResponse(ApiModel):
content_types: list[PdfContentType] = Field(default_factory=list)
class ConvertMarkdownResponse(ApiModel):
"""Terminal signal: convert the listed files to Markdown deterministically.
This is a deterministic, non-AI conversion. Java runs the PDF→Markdown converter
(``PdfMarkdownConverter``) on each file and returns the resulting ``.md`` file(s) as a
completed result. There is no resume turn — the conversion output is the final answer.
"""
outcome: Literal[WorkflowOutcome.CONVERT_MARKDOWN] = WorkflowOutcome.CONVERT_MARKDOWN
reason: str
files_to_ingest: list[AiFile]
class ToolOperationStep(ApiModel):
kind: Literal[StepKind.TOOL] = StepKind.TOOL
tool: AnyToolId
@@ -11,7 +11,6 @@ from .common import (
AiFile,
ArtifactKind,
ConversationMessage,
ConvertMarkdownResponse,
ExtractedFileText,
GenerateFileResponse,
NeedContentResponse,
@@ -61,7 +60,6 @@ type OrchestratorResponse = Annotated[
| GenerateFileResponse
| NeedContentResponse
| NeedIngestResponse
| ConvertMarkdownResponse
| AgentDraftResponse
| NextExecutionAction
| UnsupportedCapabilityResponse,
+108 -165
View File
@@ -11,13 +11,6 @@ from pydantic import Field, RootModel, SecretStr
from stirling.models.base import ApiModel
class AddAttachmentsParams(ApiModel):
attachments: list[bytes] = Field(..., description="The image file to be overlaid onto the PDF.")
convert_to_pdf_a3b: bool = Field(
False, description="Convert the resulting PDF to PDF/A-3b format after adding attachments"
)
class AddCommentsParams(ApiModel):
comments: str = Field(
...,
@@ -28,12 +21,6 @@ class AddCommentsParams(ApiModel):
)
class AddImageParams(ApiModel):
every_page: bool = Field(False, description="Whether to overlay the image onto every page of the PDF.")
x: float = Field(0, description="The x-coordinate at which to place the top-left corner of the image.")
y: float = Field(0, description="The y-coordinate at which to place the top-left corner of the image.")
class CustomMargin(StrEnum):
"""
Custom margin: small/medium/large/x-large
@@ -312,50 +299,6 @@ class CbzToPdfParams(ApiModel):
optimize_for_ebook: bool = Field(False, description="Optimize the output PDF for ebook reading using Ghostscript")
class CertType(StrEnum):
"""
The type of the digital certificate. WINDOWS_STORE and PKCS11 are hardware-backed and only available in the desktop app.
"""
pem = "PEM"
pkcs12 = "PKCS12"
pfx = "PFX"
jks = "JKS"
server = "SERVER"
windows_store = "WINDOWS_STORE"
pkcs11 = "PKCS11"
class CertSignParams(ApiModel):
alias: str | None = Field(
None,
description="The alias of the certificate to sign with. Required for WINDOWS_STORE and recommended for PKCS11 tokens holding multiple certificates.",
)
cert_type: CertType = Field(
...,
description="The type of the digital certificate. WINDOWS_STORE and PKCS11 are hardware-backed and only available in the desktop app.",
)
location: str = Field("SPDF", description="The location where the PDF is signed")
name: str = Field("SPDF", description="The name of the signer")
page_number: int = Field(
1,
description="The page number where the signature should be visible. This is required if showSignature is set to true",
)
password: SecretStr | None = Field(
None, description="The password for the keystore / private key, or the token PIN for PKCS11"
)
pkcs11_library_path: str | None = Field(
None,
description="Absolute path to the PKCS#11 driver library (required for PKCS11 type). Must be an allowed driver - a detected one or configured via STIRLING_PKCS11_LIBRARIES.",
)
pkcs11_slot: int | None = Field(
None, description="Optional PKCS#11 slot index. When omitted the first slot with a token is used."
)
reason: str = Field("Signed by SPDF", description="The reason for signing the PDF")
show_logo: bool = Field(True, description="Whether to visually show a signature logo along with the signature")
show_signature: bool = Field(False, description="Whether to visually show the signature in the PDF file")
class LineArtEdgeLevel(IntEnum):
"""
Edge detection strength to use for line art conversion (1-3). This maps to ImageMagick's -edge radius.
@@ -525,6 +468,10 @@ class EmlToPdfParams(ApiModel):
)
class ExtractAttachmentsParams(ApiModel):
pass
class ExtractImageScansParams(ApiModel):
angle_threshold: int = Field(5, description="The angle threshold for the image scan extraction")
border_size: int = Field(1, description="The border size for the image scan extraction")
@@ -547,6 +494,10 @@ class ExtractImagesParams(ApiModel):
format: Format = Field(Format.png, description="The output image format e.g., 'png', 'jpeg', or 'gif'")
class FileToPdfParams(ApiModel):
pass
class FlattenParams(ApiModel):
flatten_only_forms: bool = Field(
False, description="True to flatten only the forms, false to flatten full PDF (Convert page to image)"
@@ -602,6 +553,10 @@ class ImgToPdfParams(ApiModel):
)
class MarkdownToPdfParams(ApiModel):
pass
class SortType(StrEnum):
"""
The type of sorting to be applied on the input files before merging.
@@ -750,41 +705,6 @@ class OcrPdfParams(ApiModel):
sidecar: bool | None = Field(None, description="Include OCR text in a sidecar text file if set to true")
class OverlayMode(StrEnum):
"""
The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts
"""
sequential_overlay = "SequentialOverlay"
interleaved_overlay = "InterleavedOverlay"
fixed_repeat_overlay = "FixedRepeatOverlay"
class OverlayPosition(Enum):
"""
Overlay position 0 is Foregound, 1 is Background
"""
number_0 = 0
number_1 = 1
class OverlayPdfsParams(ApiModel):
counts: list[int] | None = Field(
None,
description="An array of integers specifying the number of times each corresponding overlay file should be applied in the 'FixedRepeatOverlay' mode. This should match the length of the overlayFiles array.",
)
overlay_files: list[bytes] = Field(
...,
description="An array of PDF files to be used as overlays on the base PDF. The order in these files is applied based on the selected mode.",
)
overlay_mode: OverlayMode = Field(
...,
description="The mode of overlaying: 'SequentialOverlay' for sequential application, 'InterleavedOverlay' for round-robin application, 'FixedRepeatOverlay' for fixed repetition based on provided counts",
)
overlay_position: OverlayPosition = Field(..., description="Overlay position 0 is Foregound, 1 is Background")
class PdfToCbrParams(ApiModel):
dpi: int = Field(..., description="The DPI (Dots Per Inch) for rendering PDF pages as images", examples=[150])
@@ -841,6 +761,12 @@ class PdfToEpubParams(ApiModel):
)
class PdfToHtmlParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class ImageFormat(StrEnum):
"""
The output image format
@@ -877,6 +803,12 @@ class PdfToImgParams(ApiModel):
)
class PdfToMarkdownParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class OutputFormat1(StrEnum):
"""
The output format type (PDF/A or PDF/X)
@@ -912,13 +844,11 @@ class PdfToPresentationParams(ApiModel):
output_format: OutputFormat2 = Field(..., description="The output Presentation format")
class PdfToTextEditorParams(ApiModel):
class PdfToSinglePageParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
lightweight: bool = False
class OutputFormat3(StrEnum):
"""
@@ -979,10 +909,10 @@ class PdfToXlsxParams(ApiModel):
)
class Pkcs11CertificatesParams(ApiModel):
library_path: str | None = None
pin: str | None = None
slot: int | None = None
class PdfToXmlParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class CustomMode(StrEnum):
@@ -1061,6 +991,18 @@ class RemoveBlanksParams(ApiModel):
)
class RemoveCertSignParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class RemoveImagePdfParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class RemovePagesParams(ApiModel):
page_numbers: str = Field(
"all",
@@ -1077,6 +1019,12 @@ class RenameAttachmentParams(ApiModel):
new_name: str = Field(..., description="The new name for the attachment")
class RepairParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class HighContrastColorCombination(StrEnum):
"""
If HIGH_CONTRAST_COLOR option selected, then pick the default color option for text and background.
@@ -1237,27 +1185,6 @@ class ScannerEffectParams(ApiModel):
yellowish: bool | None = Field(None, description="Simulate yellowed paper", examples=[False])
class WorkflowType(StrEnum):
signing = "SIGNING"
review = "REVIEW"
approval = "APPROVAL"
class Request(ApiModel):
document_name: str | None = None
due_date: str | None = None
message: str | None = None
owner_email: str | None = None
participant_emails: list[str] | None = None
participant_user_ids: list[int] | None = None
workflow_metadata: str | None = None
workflow_type: WorkflowType | None = None
class SessionsParams(ApiModel):
request: Request | None = None
class SplitBySizeOrCountParams(ApiModel):
split_type: int = Field(
0, description="Determines the type of split: 0 for size, 1 for page count, 2 for document count"
@@ -1360,6 +1287,12 @@ class TimestampPdfParams(ApiModel):
)
class UnlockPdfFormsParams(ApiModel):
"""
Either upload a file or provide a server-side file ID
"""
class Trapped(StrEnum):
"""
The trapped status of the document
@@ -1399,11 +1332,6 @@ class UrlToPdfParams(ApiModel):
url_input: str = Field(..., description="The input URL to be converted to a PDF file")
class ValidateCertificateParams(ApiModel):
cert_type: str | None = None
password: str | None = None
class OutputFormat6(StrEnum):
"""
Target vector format extension
@@ -1462,20 +1390,24 @@ class Model(
| CbzToPdfParams
| EbookToPdfParams
| EmlToPdfParams
| FileToPdfParams
| HtmlToPdfParams
| ImgToPdfParams
| MarkdownToPdfParams
| PdfToCbrParams
| PdfToCbzParams
| PdfToCsvParams
| PdfToEpubParams
| PdfToHtmlParams
| PdfToImgParams
| PdfToMarkdownParams
| PdfToPdfaParams
| PdfToPresentationParams
| PdfToTextParams
| PdfToTextEditorParams
| PdfToVectorParams
| PdfToWordParams
| PdfToXlsxParams
| PdfToXmlParams
| SvgToPdfParams
| UrlToPdfParams
| VectorToPdfParams
@@ -1485,8 +1417,9 @@ class Model(
| EditTextParams
| MergePdfsParams
| MultiPageLayoutParams
| OverlayPdfsParams
| PdfToSinglePageParams
| RearrangePagesParams
| RemoveImagePdfParams
| RemovePagesParams
| RotatePdfParams
| ScalePagesParams
@@ -1495,33 +1428,31 @@ class Model(
| SplitPagesParams
| SplitPdfByChaptersParams
| SplitPdfBySectionsParams
| AddAttachmentsParams
| AddCommentsParams
| AddImageParams
| AddPageNumbersParams
| AddStampParams
| AutoRenameParams
| AutoSplitPdfParams
| CompressPdfParams
| DeleteAttachmentParams
| ExtractAttachmentsParams
| ExtractImageScansParams
| ExtractImagesParams
| FlattenParams
| OcrPdfParams
| RemoveBlanksParams
| RenameAttachmentParams
| RepairParams
| ReplaceInvertPdfParams
| ScannerEffectParams
| UnlockPdfFormsParams
| UpdateMetadataParams
| AddPasswordParams
| AddWatermarkParams
| AutoRedactParams
| CertSignParams
| Pkcs11CertificatesParams
| SessionsParams
| ValidateCertificateParams
| RedactParams
| RedactExecuteParams
| RemoveCertSignParams
| RemovePasswordParams
| SanitizePdfParams
| TimestampPdfParams
@@ -1532,20 +1463,24 @@ class Model(
| CbzToPdfParams
| EbookToPdfParams
| EmlToPdfParams
| FileToPdfParams
| HtmlToPdfParams
| ImgToPdfParams
| MarkdownToPdfParams
| PdfToCbrParams
| PdfToCbzParams
| PdfToCsvParams
| PdfToEpubParams
| PdfToHtmlParams
| PdfToImgParams
| PdfToMarkdownParams
| PdfToPdfaParams
| PdfToPresentationParams
| PdfToTextParams
| PdfToTextEditorParams
| PdfToVectorParams
| PdfToWordParams
| PdfToXlsxParams
| PdfToXmlParams
| SvgToPdfParams
| UrlToPdfParams
| VectorToPdfParams
@@ -1555,8 +1490,9 @@ class Model(
| EditTextParams
| MergePdfsParams
| MultiPageLayoutParams
| OverlayPdfsParams
| PdfToSinglePageParams
| RearrangePagesParams
| RemoveImagePdfParams
| RemovePagesParams
| RotatePdfParams
| ScalePagesParams
@@ -1565,33 +1501,31 @@ class Model(
| SplitPagesParams
| SplitPdfByChaptersParams
| SplitPdfBySectionsParams
| AddAttachmentsParams
| AddCommentsParams
| AddImageParams
| AddPageNumbersParams
| AddStampParams
| AutoRenameParams
| AutoSplitPdfParams
| CompressPdfParams
| DeleteAttachmentParams
| ExtractAttachmentsParams
| ExtractImageScansParams
| ExtractImagesParams
| FlattenParams
| OcrPdfParams
| RemoveBlanksParams
| RenameAttachmentParams
| RepairParams
| ReplaceInvertPdfParams
| ScannerEffectParams
| UnlockPdfFormsParams
| UpdateMetadataParams
| AddPasswordParams
| AddWatermarkParams
| AutoRedactParams
| CertSignParams
| Pkcs11CertificatesParams
| SessionsParams
| ValidateCertificateParams
| RedactParams
| RedactExecuteParams
| RemoveCertSignParams
| RemovePasswordParams
| SanitizePdfParams
| TimestampPdfParams
@@ -1603,20 +1537,24 @@ type ParamToolModel = (
| CbzToPdfParams
| EbookToPdfParams
| EmlToPdfParams
| FileToPdfParams
| HtmlToPdfParams
| ImgToPdfParams
| MarkdownToPdfParams
| PdfToCbrParams
| PdfToCbzParams
| PdfToCsvParams
| PdfToEpubParams
| PdfToHtmlParams
| PdfToImgParams
| PdfToMarkdownParams
| PdfToPdfaParams
| PdfToPresentationParams
| PdfToTextParams
| PdfToTextEditorParams
| PdfToVectorParams
| PdfToWordParams
| PdfToXlsxParams
| PdfToXmlParams
| SvgToPdfParams
| UrlToPdfParams
| VectorToPdfParams
@@ -1626,8 +1564,9 @@ type ParamToolModel = (
| EditTextParams
| MergePdfsParams
| MultiPageLayoutParams
| OverlayPdfsParams
| PdfToSinglePageParams
| RearrangePagesParams
| RemoveImagePdfParams
| RemovePagesParams
| RotatePdfParams
| ScalePagesParams
@@ -1636,33 +1575,31 @@ type ParamToolModel = (
| SplitPagesParams
| SplitPdfByChaptersParams
| SplitPdfBySectionsParams
| AddAttachmentsParams
| AddCommentsParams
| AddImageParams
| AddPageNumbersParams
| AddStampParams
| AutoRenameParams
| AutoSplitPdfParams
| CompressPdfParams
| DeleteAttachmentParams
| ExtractAttachmentsParams
| ExtractImageScansParams
| ExtractImagesParams
| FlattenParams
| OcrPdfParams
| RemoveBlanksParams
| RenameAttachmentParams
| RepairParams
| ReplaceInvertPdfParams
| ScannerEffectParams
| UnlockPdfFormsParams
| UpdateMetadataParams
| AddPasswordParams
| AddWatermarkParams
| AutoRedactParams
| CertSignParams
| Pkcs11CertificatesParams
| SessionsParams
| ValidateCertificateParams
| RedactParams
| RedactExecuteParams
| RemoveCertSignParams
| RemovePasswordParams
| SanitizePdfParams
| TimestampPdfParams
@@ -1675,20 +1612,24 @@ class ToolEndpoint(StrEnum):
CBZ_TO_PDF = "/api/v1/convert/cbz/pdf"
EBOOK_TO_PDF = "/api/v1/convert/ebook/pdf"
EML_TO_PDF = "/api/v1/convert/eml/pdf"
FILE_TO_PDF = "/api/v1/convert/file/pdf"
HTML_TO_PDF = "/api/v1/convert/html/pdf"
IMG_TO_PDF = "/api/v1/convert/img/pdf"
MARKDOWN_TO_PDF = "/api/v1/convert/markdown/pdf"
PDF_TO_CBR = "/api/v1/convert/pdf/cbr"
PDF_TO_CBZ = "/api/v1/convert/pdf/cbz"
PDF_TO_CSV = "/api/v1/convert/pdf/csv"
PDF_TO_EPUB = "/api/v1/convert/pdf/epub"
PDF_TO_HTML = "/api/v1/convert/pdf/html"
PDF_TO_IMG = "/api/v1/convert/pdf/img"
PDF_TO_MARKDOWN = "/api/v1/convert/pdf/markdown"
PDF_TO_PDFA = "/api/v1/convert/pdf/pdfa"
PDF_TO_PRESENTATION = "/api/v1/convert/pdf/presentation"
PDF_TO_TEXT = "/api/v1/convert/pdf/text"
PDF_TO_TEXT_EDITOR = "/api/v1/convert/pdf/text-editor"
PDF_TO_VECTOR = "/api/v1/convert/pdf/vector"
PDF_TO_WORD = "/api/v1/convert/pdf/word"
PDF_TO_XLSX = "/api/v1/convert/pdf/xlsx"
PDF_TO_XML = "/api/v1/convert/pdf/xml"
SVG_TO_PDF = "/api/v1/convert/svg/pdf"
URL_TO_PDF = "/api/v1/convert/url/pdf"
VECTOR_TO_PDF = "/api/v1/convert/vector/pdf"
@@ -1698,8 +1639,9 @@ class ToolEndpoint(StrEnum):
EDIT_TEXT = "/api/v1/general/edit-text"
MERGE_PDFS = "/api/v1/general/merge-pdfs"
MULTI_PAGE_LAYOUT = "/api/v1/general/multi-page-layout"
OVERLAY_PDFS = "/api/v1/general/overlay-pdfs"
PDF_TO_SINGLE_PAGE = "/api/v1/general/pdf-to-single-page"
REARRANGE_PAGES = "/api/v1/general/rearrange-pages"
REMOVE_IMAGE_PDF = "/api/v1/general/remove-image-pdf"
REMOVE_PAGES = "/api/v1/general/remove-pages"
ROTATE_PDF = "/api/v1/general/rotate-pdf"
SCALE_PAGES = "/api/v1/general/scale-pages"
@@ -1708,33 +1650,31 @@ class ToolEndpoint(StrEnum):
SPLIT_PAGES = "/api/v1/general/split-pages"
SPLIT_PDF_BY_CHAPTERS = "/api/v1/general/split-pdf-by-chapters"
SPLIT_PDF_BY_SECTIONS = "/api/v1/general/split-pdf-by-sections"
ADD_ATTACHMENTS = "/api/v1/misc/add-attachments"
ADD_COMMENTS = "/api/v1/misc/add-comments"
ADD_IMAGE = "/api/v1/misc/add-image"
ADD_PAGE_NUMBERS = "/api/v1/misc/add-page-numbers"
ADD_STAMP = "/api/v1/misc/add-stamp"
AUTO_RENAME = "/api/v1/misc/auto-rename"
AUTO_SPLIT_PDF = "/api/v1/misc/auto-split-pdf"
COMPRESS_PDF = "/api/v1/misc/compress-pdf"
DELETE_ATTACHMENT = "/api/v1/misc/delete-attachment"
EXTRACT_ATTACHMENTS = "/api/v1/misc/extract-attachments"
EXTRACT_IMAGE_SCANS = "/api/v1/misc/extract-image-scans"
EXTRACT_IMAGES = "/api/v1/misc/extract-images"
FLATTEN = "/api/v1/misc/flatten"
OCR_PDF = "/api/v1/misc/ocr-pdf"
REMOVE_BLANKS = "/api/v1/misc/remove-blanks"
RENAME_ATTACHMENT = "/api/v1/misc/rename-attachment"
REPAIR = "/api/v1/misc/repair"
REPLACE_INVERT_PDF = "/api/v1/misc/replace-invert-pdf"
SCANNER_EFFECT = "/api/v1/misc/scanner-effect"
UNLOCK_PDF_FORMS = "/api/v1/misc/unlock-pdf-forms"
UPDATE_METADATA = "/api/v1/misc/update-metadata"
ADD_PASSWORD = "/api/v1/security/add-password"
ADD_WATERMARK = "/api/v1/security/add-watermark"
AUTO_REDACT = "/api/v1/security/auto-redact"
CERT_SIGN = "/api/v1/security/cert-sign"
PKCS11_CERTIFICATES = "/api/v1/security/cert-sign/hardware/pkcs11-certificates"
SESSIONS = "/api/v1/security/cert-sign/sessions"
VALIDATE_CERTIFICATE = "/api/v1/security/cert-sign/validate-certificate"
REDACT = "/api/v1/security/redact"
REDACT_EXECUTE = "/api/v1/security/redact-execute"
REMOVE_CERT_SIGN = "/api/v1/security/remove-cert-sign"
REMOVE_PASSWORD = "/api/v1/security/remove-password"
SANITIZE_PDF = "/api/v1/security/sanitize-pdf"
TIMESTAMP_PDF = "/api/v1/security/timestamp-pdf"
@@ -1745,20 +1685,24 @@ OPERATIONS: dict[ToolEndpoint, ParamToolModelType] = {
ToolEndpoint.CBZ_TO_PDF: CbzToPdfParams,
ToolEndpoint.EBOOK_TO_PDF: EbookToPdfParams,
ToolEndpoint.EML_TO_PDF: EmlToPdfParams,
ToolEndpoint.FILE_TO_PDF: FileToPdfParams,
ToolEndpoint.HTML_TO_PDF: HtmlToPdfParams,
ToolEndpoint.IMG_TO_PDF: ImgToPdfParams,
ToolEndpoint.MARKDOWN_TO_PDF: MarkdownToPdfParams,
ToolEndpoint.PDF_TO_CBR: PdfToCbrParams,
ToolEndpoint.PDF_TO_CBZ: PdfToCbzParams,
ToolEndpoint.PDF_TO_CSV: PdfToCsvParams,
ToolEndpoint.PDF_TO_EPUB: PdfToEpubParams,
ToolEndpoint.PDF_TO_HTML: PdfToHtmlParams,
ToolEndpoint.PDF_TO_IMG: PdfToImgParams,
ToolEndpoint.PDF_TO_MARKDOWN: PdfToMarkdownParams,
ToolEndpoint.PDF_TO_PDFA: PdfToPdfaParams,
ToolEndpoint.PDF_TO_PRESENTATION: PdfToPresentationParams,
ToolEndpoint.PDF_TO_TEXT: PdfToTextParams,
ToolEndpoint.PDF_TO_TEXT_EDITOR: PdfToTextEditorParams,
ToolEndpoint.PDF_TO_VECTOR: PdfToVectorParams,
ToolEndpoint.PDF_TO_WORD: PdfToWordParams,
ToolEndpoint.PDF_TO_XLSX: PdfToXlsxParams,
ToolEndpoint.PDF_TO_XML: PdfToXmlParams,
ToolEndpoint.SVG_TO_PDF: SvgToPdfParams,
ToolEndpoint.URL_TO_PDF: UrlToPdfParams,
ToolEndpoint.VECTOR_TO_PDF: VectorToPdfParams,
@@ -1768,8 +1712,9 @@ OPERATIONS: dict[ToolEndpoint, ParamToolModelType] = {
ToolEndpoint.EDIT_TEXT: EditTextParams,
ToolEndpoint.MERGE_PDFS: MergePdfsParams,
ToolEndpoint.MULTI_PAGE_LAYOUT: MultiPageLayoutParams,
ToolEndpoint.OVERLAY_PDFS: OverlayPdfsParams,
ToolEndpoint.PDF_TO_SINGLE_PAGE: PdfToSinglePageParams,
ToolEndpoint.REARRANGE_PAGES: RearrangePagesParams,
ToolEndpoint.REMOVE_IMAGE_PDF: RemoveImagePdfParams,
ToolEndpoint.REMOVE_PAGES: RemovePagesParams,
ToolEndpoint.ROTATE_PDF: RotatePdfParams,
ToolEndpoint.SCALE_PAGES: ScalePagesParams,
@@ -1778,33 +1723,31 @@ OPERATIONS: dict[ToolEndpoint, ParamToolModelType] = {
ToolEndpoint.SPLIT_PAGES: SplitPagesParams,
ToolEndpoint.SPLIT_PDF_BY_CHAPTERS: SplitPdfByChaptersParams,
ToolEndpoint.SPLIT_PDF_BY_SECTIONS: SplitPdfBySectionsParams,
ToolEndpoint.ADD_ATTACHMENTS: AddAttachmentsParams,
ToolEndpoint.ADD_COMMENTS: AddCommentsParams,
ToolEndpoint.ADD_IMAGE: AddImageParams,
ToolEndpoint.ADD_PAGE_NUMBERS: AddPageNumbersParams,
ToolEndpoint.ADD_STAMP: AddStampParams,
ToolEndpoint.AUTO_RENAME: AutoRenameParams,
ToolEndpoint.AUTO_SPLIT_PDF: AutoSplitPdfParams,
ToolEndpoint.COMPRESS_PDF: CompressPdfParams,
ToolEndpoint.DELETE_ATTACHMENT: DeleteAttachmentParams,
ToolEndpoint.EXTRACT_ATTACHMENTS: ExtractAttachmentsParams,
ToolEndpoint.EXTRACT_IMAGE_SCANS: ExtractImageScansParams,
ToolEndpoint.EXTRACT_IMAGES: ExtractImagesParams,
ToolEndpoint.FLATTEN: FlattenParams,
ToolEndpoint.OCR_PDF: OcrPdfParams,
ToolEndpoint.REMOVE_BLANKS: RemoveBlanksParams,
ToolEndpoint.RENAME_ATTACHMENT: RenameAttachmentParams,
ToolEndpoint.REPAIR: RepairParams,
ToolEndpoint.REPLACE_INVERT_PDF: ReplaceInvertPdfParams,
ToolEndpoint.SCANNER_EFFECT: ScannerEffectParams,
ToolEndpoint.UNLOCK_PDF_FORMS: UnlockPdfFormsParams,
ToolEndpoint.UPDATE_METADATA: UpdateMetadataParams,
ToolEndpoint.ADD_PASSWORD: AddPasswordParams,
ToolEndpoint.ADD_WATERMARK: AddWatermarkParams,
ToolEndpoint.AUTO_REDACT: AutoRedactParams,
ToolEndpoint.CERT_SIGN: CertSignParams,
ToolEndpoint.PKCS11_CERTIFICATES: Pkcs11CertificatesParams,
ToolEndpoint.SESSIONS: SessionsParams,
ToolEndpoint.VALIDATE_CERTIFICATE: ValidateCertificateParams,
ToolEndpoint.REDACT: RedactParams,
ToolEndpoint.REDACT_EXECUTE: RedactExecuteParams,
ToolEndpoint.REMOVE_CERT_SIGN: RemoveCertSignParams,
ToolEndpoint.REMOVE_PASSWORD: RemovePasswordParams,
ToolEndpoint.SANITIZE_PDF: SanitizePdfParams,
ToolEndpoint.TIMESTAMP_PDF: TimestampPdfParams,